前端程序
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=AI实验室
|
||||
VITE_APP_TITLE=
|
||||
|
||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||
VITE_APP_NAMESPACE=vben-web-antd
|
||||
|
||||
@@ -8,8 +8,8 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
name: import.meta.env.VITE_APP_TITLE,
|
||||
// layout: 'sidebar-mixed-nav',
|
||||
name: 'BBIT',
|
||||
// layout: 'header-sidebar-nav',
|
||||
defaultHomePath: '/workspace', // 默认首页路径
|
||||
enablePreferences: false, // 是否启用偏好设置
|
||||
enableRefreshToken: true, // 启动刷新token模式
|
||||
@@ -27,6 +27,9 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
},
|
||||
copyright: {
|
||||
enable: false,
|
||||
companyName: '技术支持:四川主干信息技术有限公司 BBITCN Co.,Ltd',
|
||||
companySiteLink: '',
|
||||
date: '2026',
|
||||
},
|
||||
shortcutKeys: {
|
||||
globalLockScreen: false,
|
||||
@@ -40,6 +43,7 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
name: 'fade-up',
|
||||
},
|
||||
widget: {
|
||||
fullscreen: false,
|
||||
lockScreen: false,
|
||||
notification: false,
|
||||
languageToggle: false,
|
||||
@@ -54,9 +58,13 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
keepAlive: true,
|
||||
},
|
||||
header: {
|
||||
mode: 'static',
|
||||
mode: 'auto',
|
||||
},
|
||||
footer: {
|
||||
enable: false,
|
||||
},
|
||||
logo: {
|
||||
enable: true,
|
||||
}
|
||||
source: 'https://ai.ronsunny.cn:9000/system/default/favicon.ico',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,7 +17,6 @@ import { Button, message, Modal } from 'ant-design-vue';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteLottery, getLotteryList } from '#/api';
|
||||
import * as api from '#/api';
|
||||
import VehicleAlertOverlay from '#/views/sentinel/record/VehicleAlertOverlay.vue';
|
||||
|
||||
import { useColumns } from './data';
|
||||
import Form from './form.vue';
|
||||
@@ -166,11 +165,6 @@ const resetAllItems = async () => {
|
||||
</script>
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<VehicleAlertOverlay
|
||||
:visible="alertState.visible"
|
||||
:content="alertState.content"
|
||||
@acknowledge="acknowledgeAlert"
|
||||
/>
|
||||
|
||||
<FormDrawer @success="onRefresh" />
|
||||
<Grid :table-title="设备列表">
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ const handleAcknowledge = () => {
|
||||
<div class="alert-content">
|
||||
<div class="alert-title">牧安云哨</div>
|
||||
<div class="alert-text">{{ content }}</div>
|
||||
<button class="ack-btn" @click="handleAcknowledge">我已知晓</button>
|
||||
<!-- <button class="ack-btn" @click="handleAcknowledge">我已知晓</button>-->
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
@@ -12,6 +12,7 @@ import EzuikitFlv from 'ezuikit-flv';
|
||||
|
||||
import * as api from '#/api';
|
||||
import { getSentinelRecordList } from '#/api';
|
||||
import VehicleAlertOverlay from '#/views/sentinel/monitor/VehicleAlertOverlay.vue';
|
||||
|
||||
import 'ezuikit-flv/style.css';
|
||||
|
||||
@@ -22,7 +23,7 @@ const videoList = ref([]);
|
||||
const carouselImages = ref([]);
|
||||
|
||||
// ----- 响应式状态 -----
|
||||
const currentTab = ref('monitor'); // 'info' | 'monitor'
|
||||
const currentTab = ref('monitor'); // 'info' | 'monitor' | 'about'
|
||||
const selectedVideoId = ref(null);
|
||||
|
||||
// 右侧记录(初始 10 条),后端逻辑:新连接会先推最近 10 条,这里按你的描述模拟
|
||||
@@ -34,6 +35,11 @@ const calendarStr = ref('');
|
||||
const weekdayStr = ref('');
|
||||
const lunarStr = ref('');
|
||||
|
||||
const alertState = reactive({
|
||||
visible: false,
|
||||
content: '',
|
||||
});
|
||||
|
||||
// 天气(固定数据,12h 刷新位点已留)
|
||||
const weather = reactive({
|
||||
condition: '暂无',
|
||||
@@ -43,31 +49,35 @@ const weather = reactive({
|
||||
function onNewRecord(record) {
|
||||
records.value.unshift(record);
|
||||
|
||||
// 高亮提示
|
||||
nextTick(() => {
|
||||
const el = listInner.value?.children[0];
|
||||
if (el) {
|
||||
el.classList.add('new-record-highlight');
|
||||
// 添加闪烁类
|
||||
el.classList.add('new-record-flash');
|
||||
|
||||
// 1分钟后停止闪烁
|
||||
setTimeout(() => {
|
||||
el.classList.remove('new-record-highlight');
|
||||
el.classList.add('new-record-highlight-removal');
|
||||
setTimeout(() => {
|
||||
el.classList.remove('new-record-highlight-removal');
|
||||
}, 500);
|
||||
}, 1000);
|
||||
el.classList.remove('new-record-flash');
|
||||
}, 60_000); // 60000ms = 1分钟
|
||||
}
|
||||
});
|
||||
|
||||
if (records.value.length > 10) {
|
||||
records.value.pop();
|
||||
}
|
||||
alertState.content = '有车辆即将经过站点,请注意';
|
||||
alertState.visible = true;
|
||||
// 5 秒后隐藏
|
||||
setTimeout(() => {
|
||||
acknowledgeAlert();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 轮播控制
|
||||
const currentIndex = ref(0);
|
||||
|
||||
const autoSwitchInfo = ref(true);
|
||||
const autoSwitchList = ref(true);
|
||||
const autoSwitchList = ref(false);
|
||||
let interval = null;
|
||||
|
||||
const nextSlide = () => {
|
||||
@@ -123,6 +133,9 @@ onMounted(async () => {
|
||||
|
||||
// 切 tab
|
||||
function switchTab(tab) {
|
||||
if (currentTab.value === tab) {
|
||||
return;
|
||||
}
|
||||
currentTab.value = tab;
|
||||
|
||||
if (tab === 'monitor') {
|
||||
@@ -141,7 +154,7 @@ watch(
|
||||
await nextTick(); // 等 DOM
|
||||
interval = setInterval(() => {
|
||||
nextSlide();
|
||||
}, 3000);
|
||||
}, 10_000);
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
}
|
||||
@@ -195,7 +208,19 @@ function destroyPlayers() {
|
||||
});
|
||||
players.value = [];
|
||||
}
|
||||
function lunarDayToChinese(day) {
|
||||
const nStr1 = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
|
||||
const nStr2 = ['初', '十', '廿', '三'];
|
||||
|
||||
if (day === 10) return '初十';
|
||||
if (day === 20) return '二十';
|
||||
if (day === 30) return '三十';
|
||||
|
||||
const a = Math.floor((day - 1) / 10);
|
||||
const b = (day - 1) % 10;
|
||||
|
||||
return nStr2[a] + nStr1[b];
|
||||
}
|
||||
// ---- 时间与农历 ----
|
||||
function updateDateTime() {
|
||||
const d = new Date();
|
||||
@@ -209,7 +234,14 @@ function updateDateTime() {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
}).format(d);
|
||||
lunarStr.value = lunar;
|
||||
|
||||
// 例: "六月3"
|
||||
const match = lunar.match(/(.*?)(\d+)/);
|
||||
|
||||
const month = match[1];
|
||||
const day = Number(match[2]);
|
||||
|
||||
lunarStr.value = month + lunarDayToChinese(day);
|
||||
} catch {
|
||||
lunarStr.value = '农历数据不可用';
|
||||
}
|
||||
@@ -335,6 +367,27 @@ function focusVideo(item) {
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
const previewVisible = ref(false);
|
||||
const previewUrl = ref('');
|
||||
|
||||
function previewImage(url) {
|
||||
previewUrl.value = url;
|
||||
previewVisible.value = true;
|
||||
}
|
||||
function acknowledgeAlert() {
|
||||
alertState.visible = false;
|
||||
}
|
||||
const getPlateClass = (color) => {
|
||||
const map = {
|
||||
0: 'plate-blue',
|
||||
1: 'plate-yellow',
|
||||
2: 'plate-green',
|
||||
3: 'plate-black',
|
||||
4: 'plate-white',
|
||||
};
|
||||
return map[color] || 'plate-blue';
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex h-screen w-screen flex-col bg-slate-50 text-slate-900">
|
||||
@@ -346,58 +399,34 @@ function focusVideo(item) {
|
||||
<img src="../logo.png" alt="logo" class="h-10 w-10 object-contain" />
|
||||
<span class="text-5xl font-extrabold text-sky-800">动监</span>
|
||||
</div>
|
||||
<div class="ml-6 flex flex-col items-start">
|
||||
<span class="text-xl font-medium text-slate-700">
|
||||
<div class="ml-3 flex flex-col items-start">
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- 主标题 -->
|
||||
<span class="text-2xl font-bold tracking-wide text-slate-800">
|
||||
牧安云哨·指定通道畜牧车辆监管预警平台
|
||||
</span>
|
||||
|
||||
<!-- 宁南站标签 -->
|
||||
<span
|
||||
class="mt-1 inline-block rounded-full bg-gradient-to-r from-sky-100 to-sky-200 px-4 py-1 text-sm font-medium text-sky-800 shadow-md"
|
||||
class="inline-flex items-center rounded-full bg-gradient-to-r from-sky-500 to-blue-600 px-4 py-1.5 text-sm font-semibold text-white shadow-lg ring-2 ring-sky-200"
|
||||
>
|
||||
宁南站
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中右 按钮 -->
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="mr-6 flex gap-2">
|
||||
<button
|
||||
class="rounded-md border px-4 py-2"
|
||||
:class="[
|
||||
currentTab === 'info'
|
||||
? 'border-sky-700 bg-sky-700 text-white'
|
||||
: 'border-slate-300 bg-white text-slate-700',
|
||||
]"
|
||||
@click="switchTab('info')"
|
||||
>
|
||||
规章制度
|
||||
</button>
|
||||
<button
|
||||
class="rounded-md border px-4 py-2"
|
||||
:class="[
|
||||
currentTab === 'monitor'
|
||||
? 'border-sky-700 bg-sky-700 text-white'
|
||||
: 'border-slate-300 bg-white text-slate-700',
|
||||
]"
|
||||
@click="switchTab('monitor')"
|
||||
>
|
||||
监控画面
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 时间、农历、星期、天气 -->
|
||||
<div class="flex flex-col items-end text-right">
|
||||
<div class="text-sm text-slate-600">{{ calendarStr }}</div>
|
||||
<div class="text-lg font-medium">{{ timeStr }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">
|
||||
星期{{ weekdayStr }} ·
|
||||
<span class="mr-2">农历{{ lunarStr }}</span> 天气:{{
|
||||
weather.condition
|
||||
}}
|
||||
· 白天 {{ weather.dayTemp }}℃ / 夜间 {{ weather.nightTemp }}℃
|
||||
星期{{ weekdayStr }} · 农历{{ lunarStr }} {{ weather.condition }} ·
|
||||
白天 {{ weather.dayTemp }}℃ / 夜间 {{ weather.nightTemp }}℃
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-slate-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -479,7 +508,10 @@ function focusVideo(item) {
|
||||
</div>
|
||||
</div>
|
||||
<!-- 监控画面:四宫格播放器 -->
|
||||
<div v-else class="grid flex-1 grid-cols-2 grid-rows-2 gap-4">
|
||||
<div
|
||||
v-else-if="currentTab === 'monitor'"
|
||||
class="grid flex-1 grid-cols-2 grid-rows-2 gap-4"
|
||||
>
|
||||
<div
|
||||
v-for="item in videoList"
|
||||
:key="item.id"
|
||||
@@ -504,6 +536,61 @@ function focusVideo(item) {
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex h-full flex-col gap-4">
|
||||
<!-- 系统简介 -->
|
||||
<section
|
||||
class="flex h-[55%] flex-col justify-center rounded-xl border-l-8 border-blue-600 bg-slate-50 p-6"
|
||||
>
|
||||
<h2 class="mb-4 text-2xl font-bold text-blue-700">系统介绍</h2>
|
||||
|
||||
<!-- 主文字(最大) -->
|
||||
<p class="text-lg leading-relaxed text-gray-700">
|
||||
牧安云哨是一套面向畜牧监管部门的智能化车辆检疫系统。系统在关卡布设摄像头,结合人工智能实时分析,实现车牌及车辆信息精准识别,并将动态信息即时推送至数公里外的动物卫生监督检查站,实现高效预警与管控。
|
||||
</p>
|
||||
<p class="mt-3 text-lg leading-relaxed text-gray-700">
|
||||
平台具备完整的检疫信息查询与记录功能,所有数据云端存储,支持统一管理和远程访问。依托人工智能技术,牧安云哨打造智能、实时、可视化的畜牧车辆检疫管理解决方案,为监管部门提供高效、可靠的决策与执法支持。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 公司信息 -->
|
||||
<section
|
||||
class="flex h-[45%] flex-col justify-center rounded-xl border-l-8 border-blue-600 bg-slate-50 p-6"
|
||||
>
|
||||
<h2 class="mb-4 text-xl font-bold text-blue-700">公司信息</h2>
|
||||
|
||||
<!-- 次级文字(略小) -->
|
||||
<p class="text-base text-gray-700">
|
||||
<span class="font-semibold">公司名称:</span
|
||||
>四川主干信息技术有限公司(BBITCN Co.,Ltd)
|
||||
</p>
|
||||
|
||||
<!-- 地址 -->
|
||||
<p class="mt-2 text-base leading-relaxed text-gray-700">
|
||||
<span class="font-semibold">办公地址:</span>
|
||||
四川成都市金牛区蜀西路42号三泰魔方B3-5楼
|
||||
</p>
|
||||
|
||||
<p class="mt-2 text-base leading-relaxed text-gray-700">
|
||||
<span class="font-semibold">研发地址:</span>
|
||||
四川成都市新都区斑竹园双龙田园智慧农业研究中心
|
||||
</p>
|
||||
|
||||
<p class="mt-2 text-base leading-relaxed text-gray-700">
|
||||
<span class="font-semibold">联系电话:</span>4001024304
|
||||
</p>
|
||||
<p class="mt-2 text-base leading-relaxed text-gray-700">
|
||||
<span class="font-semibold">技术联系:</span>18981977117 范先生
|
||||
</p>
|
||||
<p class="mt-2 text-base leading-relaxed text-gray-700">
|
||||
<span class="font-semibold">公司网站:</span>
|
||||
<a
|
||||
href="https://www.bbitcn.com"
|
||||
class="text-blue-600 hover:underline"
|
||||
>www.bbitcn.com</a
|
||||
>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 右侧面板:根据 tab 不同显示不同交互(但都为车辆列表) -->
|
||||
@@ -511,10 +598,13 @@ function focusVideo(item) {
|
||||
class="flex w-[420px] flex-col rounded-lg bg-white p-4 shadow-md"
|
||||
>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h3 class="text-lg font-semibold text-slate-700">实时车辆检测</h3>
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-slate-200 p-3"
|
||||
>
|
||||
<h3 class="flex items-center text-lg font-semibold text-slate-700">
|
||||
车辆实时·云哨监测
|
||||
<!-- 雷达动画 -->
|
||||
<span class="ml-2 radar"></span>
|
||||
</h3>
|
||||
|
||||
<div class="flex items-center justify-between border-slate-200 p-3">
|
||||
<span class="text-sm font-medium text-slate-700">自动滚动</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -524,9 +614,8 @@ function focusVideo(item) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="listWrapper" class="relative flex-1 overflow-hidden">
|
||||
<!-- 滚动区域 -->
|
||||
<div ref="listInner" class="absolute left-0 right-0 top-0">
|
||||
<div ref="listWrapper" class="relative flex-1 overflow-y-auto">
|
||||
<div ref="listInner">
|
||||
<!-- 列表项动画 -->
|
||||
<div
|
||||
v-for="item in records"
|
||||
@@ -540,34 +629,48 @@ function focusVideo(item) {
|
||||
<!-- 左:车身图片 -->
|
||||
<img
|
||||
:src="item.vehicle_image"
|
||||
class="h-20 w-32 rounded border border-slate-200 object-cover"
|
||||
class="h-20 w-full cursor-pointer rounded border border-slate-200 object-cover"
|
||||
@click="previewImage(item.vehicle_image)"
|
||||
/>
|
||||
|
||||
<!-- 右:信息 -->
|
||||
<div class="flex flex-1 flex-col justify-between">
|
||||
<!-- 第一行:车牌 + 车型 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-sm font-semibold text-sky-700">
|
||||
<div
|
||||
v-if="item.license_plate"
|
||||
class="plate"
|
||||
:class="getPlateClass(item.license_plate_color)"
|
||||
@click="previewImage(item.license_plate_image)"
|
||||
>
|
||||
<div class="screw tl"></div>
|
||||
<div class="screw tr"></div>
|
||||
<div class="screw bl"></div>
|
||||
<div class="screw br"></div>
|
||||
|
||||
<span class="plate-text">
|
||||
{{ item.license_plate }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-xs text-slate-400">|</span>
|
||||
<div class="text-xs text-slate-600">
|
||||
{{ item.vehicle_type }}
|
||||
</div>
|
||||
|
||||
<div v-else class="text-sm text-gray-400">暂无车牌信息</div>
|
||||
<!-- <span class="text-xs text-slate-400">|</span>-->
|
||||
<!-- <div class="text-xs text-slate-600">-->
|
||||
<!-- {{ item.vehicle_type }}-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<!-- 第二行:动物 + 部门 -->
|
||||
<div
|
||||
class="mt-1 flex flex-wrap gap-x-4 text-xs text-slate-600"
|
||||
>
|
||||
<div>
|
||||
动物:
|
||||
<span class="font-medium text-slate-700">
|
||||
{{ item.livestock_type || '未知' }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <div>-->
|
||||
<!-- 动物:-->
|
||||
<!-- <span class="font-medium text-slate-700">-->
|
||||
<!-- {{ item.livestock_type || '未知' }}-->
|
||||
<!-- </span>-->
|
||||
<!-- </div>-->
|
||||
<div class="max-w-[180px] truncate">
|
||||
站点:
|
||||
所属站点:
|
||||
<span class="font-medium text-slate-700">
|
||||
{{ item.dept_name }}
|
||||
</span>
|
||||
@@ -582,12 +685,94 @@ function focusVideo(item) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex w-full justify-between">
|
||||
<button
|
||||
class="rounded-md border px-7 py-2"
|
||||
:class="[
|
||||
currentTab === 'monitor'
|
||||
? 'border-sky-700 bg-sky-700 text-white'
|
||||
: 'border-slate-300 bg-white text-slate-700',
|
||||
]"
|
||||
@click="switchTab('monitor')"
|
||||
>
|
||||
监控画面
|
||||
</button>
|
||||
<button
|
||||
class="rounded-md border px-7 py-2"
|
||||
:class="[
|
||||
currentTab === 'info'
|
||||
? 'border-sky-700 bg-sky-700 text-white'
|
||||
: 'border-slate-300 bg-white text-slate-700',
|
||||
]"
|
||||
@click="switchTab('info')"
|
||||
>
|
||||
规章制度
|
||||
</button>
|
||||
<button
|
||||
class="rounded-md border px-7 py-2"
|
||||
:class="[
|
||||
currentTab === 'about'
|
||||
? 'border-sky-700 bg-sky-700 text-white'
|
||||
: 'border-slate-300 bg-white text-slate-700',
|
||||
]"
|
||||
@click="switchTab('about')"
|
||||
>
|
||||
关于我们
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
<!-- 底部技术支持 -->
|
||||
<footer
|
||||
class="flex h-8 items-center justify-center border-t border-slate-200 bg-slate-50 text-xs text-slate-500"
|
||||
>
|
||||
技术支持:四川主干信息技术有限公司 BBITCN Co.,Ltd
|
||||
</footer>
|
||||
<!-- 图片预览弹层 -->
|
||||
<transition name="img-preview">
|
||||
<div
|
||||
v-if="previewVisible"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/70"
|
||||
@click="previewVisible = false"
|
||||
>
|
||||
<img
|
||||
:src="previewUrl"
|
||||
class="preview-img max-h-[90%] max-w-[90%] rounded shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<VehicleAlertOverlay
|
||||
:visible="alertState.visible"
|
||||
:content="alertState.content"
|
||||
@acknowledge="acknowledgeAlert"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
.img-preview-enter-active,
|
||||
.img-preview-leave-active {
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.img-preview-enter-from,
|
||||
.img-preview-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.preview-img {
|
||||
transform: scale(0.7);
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.img-preview-enter-active .preview-img {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.img-preview-leave-active .preview-img {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
/* 额外样式:让右侧列表平滑循环(配合 JS transform) */
|
||||
[ref='listWrapper'] {
|
||||
position: relative;
|
||||
@@ -622,22 +807,179 @@ function focusVideo(item) {
|
||||
.toggle-checkbox:checked::before {
|
||||
transform: translateX(1.25rem); /* 移动到右侧 */
|
||||
}
|
||||
.new-record-highlight {
|
||||
background-color: #e0f2ff; /* 淡蓝色,高级感 */
|
||||
transition: background-color 1.5s ease;
|
||||
|
||||
@keyframes flash-highlight {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
background-color: #fffcd4;
|
||||
}
|
||||
25%,
|
||||
75% {
|
||||
background-color: transparent;
|
||||
}
|
||||
.new-record-highlight {
|
||||
background-color: #e0f2ff;
|
||||
transform: translateY(-10px);
|
||||
opacity: 0.8;
|
||||
transition:
|
||||
all 0.5s ease,
|
||||
background-color 1.5s ease;
|
||||
}
|
||||
|
||||
.new-record-highlight-removal {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
background-color: white;
|
||||
.new-record-flash {
|
||||
animation: flash-highlight 3s ease-in-out infinite;
|
||||
}
|
||||
.plate {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 180px;
|
||||
height: 44px;
|
||||
|
||||
background: linear-gradient(180deg, #1e5ed8, #0f3fa8);
|
||||
border: 3px solid #0a2f80;
|
||||
border-radius: 6px;
|
||||
|
||||
box-shadow:
|
||||
inset 0 2px 3px rgba(255, 255, 255, 0.3),
|
||||
inset 0 -2px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.plate-text {
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
letter-spacing: 3px;
|
||||
font-family: 'Microsoft YaHei', 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
/* 螺丝孔 */
|
||||
.screw {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #cfd6e6;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.tl {
|
||||
top: 4px;
|
||||
left: 16px;
|
||||
}
|
||||
.tr {
|
||||
top: 4px;
|
||||
right: 16px;
|
||||
}
|
||||
.bl {
|
||||
bottom: 4px;
|
||||
left: 16px;
|
||||
}
|
||||
.br {
|
||||
bottom: 4px;
|
||||
right: 16px;
|
||||
}
|
||||
.plate::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
transparent 30%,
|
||||
rgba(255, 255, 255, 0.25) 45%,
|
||||
transparent 60%
|
||||
);
|
||||
border-radius: 6px;
|
||||
}
|
||||
/* 蓝牌(默认) */
|
||||
.plate-blue {
|
||||
background: linear-gradient(180deg, #1e5ed8, #0f3fa8);
|
||||
border-color: #0a2f80;
|
||||
}
|
||||
|
||||
/* 黄牌 */
|
||||
.plate-yellow {
|
||||
background: linear-gradient(180deg, #f5c400, #c89b00);
|
||||
border-color: #a67c00;
|
||||
}
|
||||
|
||||
.plate-yellow .plate-text {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* 绿牌(新能源) */
|
||||
.plate-green {
|
||||
background: linear-gradient(180deg, #3bb54a, #1f8f2d);
|
||||
border-color: #176d22;
|
||||
}
|
||||
|
||||
/* 黑牌(港澳/领事) */
|
||||
.plate-black {
|
||||
background: linear-gradient(180deg, #333, #111);
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
.plate-black .plate-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 白牌(警用/军用) */
|
||||
.plate-white {
|
||||
background: linear-gradient(180deg, #f5f5f5, #dcdcdc);
|
||||
border-color: #bfbfbf;
|
||||
}
|
||||
|
||||
.plate-white .plate-text {
|
||||
color: #000;
|
||||
}
|
||||
.plate-white::after,
|
||||
.plate-yellow::after {
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
transparent 30%,
|
||||
rgba(255, 255, 255, 0.15),
|
||||
transparent 60%
|
||||
);
|
||||
}
|
||||
.radar {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
//border: 1px solid #22c55e; /* 绿色边框 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 扫描扇形 */
|
||||
.radar::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: conic-gradient(
|
||||
rgba(34, 197, 94, 0.8) 0deg,
|
||||
rgba(34, 197, 94, 0.3) 60deg,
|
||||
transparent 120deg
|
||||
);
|
||||
border-radius: 50%;
|
||||
animation: radar-scan 1.5s linear infinite;
|
||||
}
|
||||
|
||||
/* 中心点 */
|
||||
.radar::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
background: #22c55e;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@keyframes radar-scan {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,14 +17,14 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'vehicle_type',
|
||||
label: '车型',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// component: 'Input',
|
||||
// fieldName: 'vehicle_type',
|
||||
// label: '车型',
|
||||
// componentProps: {
|
||||
// allowClear: true,
|
||||
// },
|
||||
// },
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'livestock_type',
|
||||
@@ -71,14 +71,14 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
*/
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'id',
|
||||
label: '编号',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// component: 'Input',
|
||||
// fieldName: 'id',
|
||||
// label: '编号',
|
||||
// componentProps: {
|
||||
// allowClear: true,
|
||||
// },
|
||||
// },
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'license_plate',
|
||||
@@ -87,14 +87,14 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'vehicle_type',
|
||||
label: '车型',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// component: 'Input',
|
||||
// fieldName: 'vehicle_type',
|
||||
// label: '车型',
|
||||
// componentProps: {
|
||||
// allowClear: true,
|
||||
// },
|
||||
// },
|
||||
{
|
||||
component: 'ApiCombobox',
|
||||
fieldName: 'livestock_type',
|
||||
@@ -135,7 +135,14 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const vehicleTypeMap: Record<string, string> = {
|
||||
coupe: '跑车',
|
||||
largevehicle: '大型车辆',
|
||||
sedan: '轿车',
|
||||
suv: 'SUV',
|
||||
truck: '卡车',
|
||||
van: '面包车',
|
||||
};
|
||||
/**
|
||||
* 列表展示
|
||||
* @param onActionClick
|
||||
@@ -159,12 +166,7 @@ export function useColumns<T = SystemUserApi.SystemUser>(
|
||||
{
|
||||
cellRender: { name: 'CellImage' },
|
||||
field: 'license_plate_image',
|
||||
title: '车牌照',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'vehicle_type',
|
||||
title: '车型',
|
||||
title: '正面照',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
@@ -173,16 +175,6 @@ export function useColumns<T = SystemUserApi.SystemUser>(
|
||||
title: '车身照',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'livestock_type',
|
||||
title: '牲畜种类',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'livestock_source',
|
||||
title: '牲畜来源',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'is_inspected',
|
||||
title: '检疫状态',
|
||||
@@ -198,25 +190,41 @@ export function useColumns<T = SystemUserApi.SystemUser>(
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
title: '录入时间',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'updated_at',
|
||||
title: '最后更新时间',
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// field: 'livestock_type',
|
||||
// title: '牲畜种类',
|
||||
// width: 100,
|
||||
// },
|
||||
// {
|
||||
// field: 'vehicle_type',
|
||||
// title: '车型',
|
||||
// width: 150,
|
||||
// formatter: ({ cellValue }) => vehicleTypeMap[cellValue] || cellValue,
|
||||
// },
|
||||
{
|
||||
field: 'dept_name',
|
||||
title: '所属组织',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
width: 300,
|
||||
field: 'livestock_source',
|
||||
title: '牲畜来源',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
title: '录入时间',
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// field: 'remark',
|
||||
// title: '备注',
|
||||
// width: 300,
|
||||
// },
|
||||
{
|
||||
field: 'updated_at',
|
||||
title: '最后更新时间',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
|
||||
@@ -12,7 +12,7 @@ import { onBeforeUnmount, onMounted, reactive, watch } from 'vue';
|
||||
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||
import { Plus } from '@vben/icons';
|
||||
|
||||
import { Button, message, Modal } from 'ant-design-vue';
|
||||
import { Button, message, Modal, notification } from "ant-design-vue";
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
@@ -20,10 +20,11 @@ import {
|
||||
getSentinelRecordList,
|
||||
updateSentinelRecordPatch,
|
||||
} from '#/api';
|
||||
import VehicleAlertOverlay from '#/views/sentinel/record/VehicleAlertOverlay.vue';
|
||||
|
||||
|
||||
import { useColumns, useGridFormSchema } from './data';
|
||||
import Form from './form.vue';
|
||||
import { $t } from "@vben/locales";
|
||||
|
||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: Form,
|
||||
@@ -162,6 +163,7 @@ const wsClient = api.createAutoReconnectWs({
|
||||
path: () => `iot/ws/sentinel_record`,
|
||||
onMessage: (msg) => {
|
||||
if (msg.type === 'vehicle_alert') {
|
||||
// 显示通知
|
||||
handleVehicleAlert(msg.content);
|
||||
}
|
||||
},
|
||||
@@ -176,21 +178,33 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
|
||||
function handleVehicleAlert(content: string) {
|
||||
alertState.content = content;
|
||||
alertState.visible = true;
|
||||
// alertState.content = content;
|
||||
// alertState.visible = true;
|
||||
|
||||
// 刷新列表
|
||||
gridApi.query();
|
||||
|
||||
// message.success({
|
||||
// content: content,
|
||||
// });
|
||||
notification.success({
|
||||
description: content,
|
||||
duration: 10,
|
||||
message: '牧安云哨',
|
||||
});
|
||||
}
|
||||
|
||||
function acknowledgeAlert() {
|
||||
alertState.visible = false;
|
||||
}
|
||||
// function acknowledgeAlert() {
|
||||
// alertState.visible = false;
|
||||
// }
|
||||
</script>
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<VehicleAlertOverlay
|
||||
:visible="alertState.visible"
|
||||
:content="alertState.content"
|
||||
@acknowledge="acknowledgeAlert"
|
||||
/>
|
||||
<!-- <VehicleAlertOverlay-->
|
||||
<!-- :visible="alertState.visible"-->
|
||||
<!-- :content="alertState.content"-->
|
||||
<!-- @acknowledge="acknowledgeAlert"-->
|
||||
<!-- />-->
|
||||
|
||||
<FormDrawer @success="onRefresh" />
|
||||
<Grid :table-title="设备列表">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"welcomeBack": "欢迎回来",
|
||||
"pageTitle": "智能引领·高效驱动的现代化控制平台",
|
||||
"pageDesc": "融合先进技术,打造面向蚕桑行业的高性能人工智能分析平台",
|
||||
"pageDesc": "融合先进技术,打造面向农业的高性能人工智能分析平台",
|
||||
"loginSuccess": "登录成功",
|
||||
"loginSuccessDesc": "欢迎回来",
|
||||
"loginSubtitle": "请输入您的帐户信息",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# push_docker.ps1
|
||||
|
||||
# Set version
|
||||
$env:VERSION = "1.4.6"
|
||||
$env:VERSION = "1.5.2"
|
||||
|
||||
# Docker registry/repository
|
||||
$registry = "ai.ronsunny.cn:13011/bbit_ai/ce_vue"
|
||||
|
||||
Reference in New Issue
Block a user