前端程序

This commit is contained in:
BBIT-Kai
2026-03-26 17:54:22 +08:00
parent 8592af3111
commit de256fb5e0
9 changed files with 538 additions and 172 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# 应用标题 # 应用标题
VITE_APP_TITLE=AI实验室 VITE_APP_TITLE=
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 # 应用命名空间,用于缓存、store等功能的前缀,确保隔离
VITE_APP_NAMESPACE=vben-web-antd VITE_APP_NAMESPACE=vben-web-antd
+13 -5
View File
@@ -8,8 +8,8 @@ import { defineOverridesPreferences } from '@vben/preferences';
export const overridesPreferences = defineOverridesPreferences({ export const overridesPreferences = defineOverridesPreferences({
// overrides // overrides
app: { app: {
name: import.meta.env.VITE_APP_TITLE, name: 'BBIT',
// layout: 'sidebar-mixed-nav', // layout: 'header-sidebar-nav',
defaultHomePath: '/workspace', // 默认首页路径 defaultHomePath: '/workspace', // 默认首页路径
enablePreferences: false, // 是否启用偏好设置 enablePreferences: false, // 是否启用偏好设置
enableRefreshToken: true, // 启动刷新token模式 enableRefreshToken: true, // 启动刷新token模式
@@ -27,6 +27,9 @@ export const overridesPreferences = defineOverridesPreferences({
}, },
copyright: { copyright: {
enable: false, enable: false,
companyName: '技术支持:四川主干信息技术有限公司 BBITCN Co.,Ltd',
companySiteLink: '',
date: '2026',
}, },
shortcutKeys: { shortcutKeys: {
globalLockScreen: false, globalLockScreen: false,
@@ -40,6 +43,7 @@ export const overridesPreferences = defineOverridesPreferences({
name: 'fade-up', name: 'fade-up',
}, },
widget: { widget: {
fullscreen: false,
lockScreen: false, lockScreen: false,
notification: false, notification: false,
languageToggle: false, languageToggle: false,
@@ -54,9 +58,13 @@ export const overridesPreferences = defineOverridesPreferences({
keepAlive: true, keepAlive: true,
}, },
header: { header: {
mode: 'static', mode: 'auto',
}, },
logo:{ footer: {
enable: false,
},
logo: {
enable: true, 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 { useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteLottery, getLotteryList } from '#/api'; import { deleteLottery, getLotteryList } from '#/api';
import * as api from '#/api'; import * as api from '#/api';
import VehicleAlertOverlay from '#/views/sentinel/record/VehicleAlertOverlay.vue';
import { useColumns } from './data'; import { useColumns } from './data';
import Form from './form.vue'; import Form from './form.vue';
@@ -166,11 +165,6 @@ const resetAllItems = async () => {
</script> </script>
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<VehicleAlertOverlay
:visible="alertState.visible"
:content="alertState.content"
@acknowledge="acknowledgeAlert"
/>
<FormDrawer @success="onRefresh" /> <FormDrawer @success="onRefresh" />
<Grid :table-title="设备列表"> <Grid :table-title="设备列表">
@@ -19,7 +19,7 @@ const handleAcknowledge = () => {
<div class="alert-content"> <div class="alert-content">
<div class="alert-title">牧安云哨</div> <div class="alert-title">牧安云哨</div>
<div class="alert-text">{{ content }}</div> <div class="alert-text">{{ content }}</div>
<button class="ack-btn" @click="handleAcknowledge">我已知晓</button> <!-- <button class="ack-btn" @click="handleAcknowledge">我已知晓</button>-->
</div> </div>
</div> </div>
</teleport> </teleport>
@@ -12,6 +12,7 @@ import EzuikitFlv from 'ezuikit-flv';
import * as api from '#/api'; import * as api from '#/api';
import { getSentinelRecordList } from '#/api'; import { getSentinelRecordList } from '#/api';
import VehicleAlertOverlay from '#/views/sentinel/monitor/VehicleAlertOverlay.vue';
import 'ezuikit-flv/style.css'; import 'ezuikit-flv/style.css';
@@ -22,7 +23,7 @@ const videoList = ref([]);
const carouselImages = ref([]); const carouselImages = ref([]);
// ----- 响应式状态 ----- // ----- 响应式状态 -----
const currentTab = ref('monitor'); // 'info' | 'monitor' const currentTab = ref('monitor'); // 'info' | 'monitor' | 'about'
const selectedVideoId = ref(null); const selectedVideoId = ref(null);
// 右侧记录(初始 10 条),后端逻辑:新连接会先推最近 10 条,这里按你的描述模拟 // 右侧记录(初始 10 条),后端逻辑:新连接会先推最近 10 条,这里按你的描述模拟
@@ -34,6 +35,11 @@ const calendarStr = ref('');
const weekdayStr = ref(''); const weekdayStr = ref('');
const lunarStr = ref(''); const lunarStr = ref('');
const alertState = reactive({
visible: false,
content: '',
});
// 天气(固定数据,12h 刷新位点已留) // 天气(固定数据,12h 刷新位点已留)
const weather = reactive({ const weather = reactive({
condition: '暂无', condition: '暂无',
@@ -43,31 +49,35 @@ const weather = reactive({
function onNewRecord(record) { function onNewRecord(record) {
records.value.unshift(record); records.value.unshift(record);
// 高亮提示
nextTick(() => { nextTick(() => {
const el = listInner.value?.children[0]; const el = listInner.value?.children[0];
if (el) { if (el) {
el.classList.add('new-record-highlight'); // 添加闪烁类
el.classList.add('new-record-flash');
// 1分钟后停止闪烁
setTimeout(() => { setTimeout(() => {
el.classList.remove('new-record-highlight'); el.classList.remove('new-record-flash');
el.classList.add('new-record-highlight-removal'); }, 60_000); // 60000ms = 1分钟
setTimeout(() => {
el.classList.remove('new-record-highlight-removal');
}, 500);
}, 1000);
} }
}); });
if (records.value.length > 10) { if (records.value.length > 10) {
records.value.pop(); records.value.pop();
} }
alertState.content = '有车辆即将经过站点,请注意';
alertState.visible = true;
// 5 秒后隐藏
setTimeout(() => {
acknowledgeAlert();
}, 3000);
} }
// 轮播控制 // 轮播控制
const currentIndex = ref(0); const currentIndex = ref(0);
const autoSwitchInfo = ref(true); const autoSwitchInfo = ref(true);
const autoSwitchList = ref(true); const autoSwitchList = ref(false);
let interval = null; let interval = null;
const nextSlide = () => { const nextSlide = () => {
@@ -123,6 +133,9 @@ onMounted(async () => {
// 切 tab // 切 tab
function switchTab(tab) { function switchTab(tab) {
if (currentTab.value === tab) {
return;
}
currentTab.value = tab; currentTab.value = tab;
if (tab === 'monitor') { if (tab === 'monitor') {
@@ -141,7 +154,7 @@ watch(
await nextTick(); // 等 DOM await nextTick(); // 等 DOM
interval = setInterval(() => { interval = setInterval(() => {
nextSlide(); nextSlide();
}, 3000); }, 10_000);
} else { } else {
clearInterval(interval); clearInterval(interval);
} }
@@ -179,7 +192,7 @@ function initPlayers() {
hasAudio: false, hasAudio: false,
useMSE: true, useMSE: true,
autoPlay: true, autoPlay: true,
muted:true, muted: true,
debug: false, debug: false,
}); });
players.value.push({ id: item.id, player }); players.value.push({ id: item.id, player });
@@ -195,7 +208,19 @@ function destroyPlayers() {
}); });
players.value = []; 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() { function updateDateTime() {
const d = new Date(); const d = new Date();
@@ -209,7 +234,14 @@ function updateDateTime() {
month: 'long', month: 'long',
day: 'numeric', day: 'numeric',
}).format(d); }).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 { } catch {
lunarStr.value = '农历数据不可用'; lunarStr.value = '农历数据不可用';
} }
@@ -335,6 +367,27 @@ function focusVideo(item) {
}, 3000); }, 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> </script>
<template> <template>
<div class="flex h-screen w-screen flex-col bg-slate-50 text-slate-900"> <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" /> <img src="../logo.png" alt="logo" class="h-10 w-10 object-contain" />
<span class="text-5xl font-extrabold text-sky-800">动监</span> <span class="text-5xl font-extrabold text-sky-800">动监</span>
</div> </div>
<div class="ml-6 flex flex-col items-start"> <div class="ml-3 flex flex-col items-start">
<span class="text-xl font-medium text-slate-700"> <div class="flex items-center gap-3">
牧安云哨·指定通道畜牧车辆监管预警平台 <!-- 主标题 -->
</span> <span class="text-2xl font-bold tracking-wide text-slate-800">
牧安云哨·指定通道畜牧车辆监管预警平台
</span>
<!-- 宁南站标签 --> <!-- 宁南站标签 -->
<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> </span>
</div>
</div> </div>
</div> </div>
<!-- 中右 按钮 --> <!-- 中右 按钮 -->
<div class="flex items-center gap-4"> <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="flex flex-col items-end text-right">
<div class="text-sm text-slate-600">{{ calendarStr }}</div> <div class="text-sm text-slate-600">{{ calendarStr }}</div>
<div class="text-lg font-medium">{{ timeStr }}</div> <div class="text-lg font-medium">{{ timeStr }}</div>
<div class="mt-1 text-xs text-slate-500"> <div class="mt-1 text-xs text-slate-500">
星期{{ weekdayStr }} · 星期{{ weekdayStr }} · 农历{{ lunarStr }} {{ weather.condition }} ·
<span class="mr-2">农历{{ lunarStr }}</span> 天气{{ 白天 {{ weather.dayTemp }} / 夜间 {{ weather.nightTemp }}
weather.condition
}}
· 白天 {{ weather.dayTemp }} / 夜间 {{ weather.nightTemp }}
</div> </div>
<div class="mt-1 text-xs text-slate-500"></div>
</div> </div>
</div> </div>
</header> </header>
@@ -479,7 +508,10 @@ function focusVideo(item) {
</div> </div>
</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 <div
v-for="item in videoList" v-for="item in videoList"
:key="item.id" :key="item.id"
@@ -504,6 +536,61 @@ function focusVideo(item) {
></div> ></div>
</div> </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> </section>
<!-- 右侧面板根据 tab 不同显示不同交互但都为车辆列表 --> <!-- 右侧面板根据 tab 不同显示不同交互但都为车辆列表 -->
@@ -511,10 +598,13 @@ function focusVideo(item) {
class="flex w-[420px] flex-col rounded-lg bg-white p-4 shadow-md" class="flex w-[420px] flex-col rounded-lg bg-white p-4 shadow-md"
> >
<div class="mb-3 flex items-center justify-between"> <div class="mb-3 flex items-center justify-between">
<h3 class="text-lg font-semibold text-slate-700">实时车辆检测</h3> <h3 class="flex items-center text-lg font-semibold text-slate-700">
<div 车辆实时·云哨监测
class="flex items-center justify-between border-b border-slate-200 p-3" <!-- 雷达动画 -->
> <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> <span class="text-sm font-medium text-slate-700">自动滚动</span>
<input <input
type="checkbox" type="checkbox"
@@ -524,9 +614,8 @@ function focusVideo(item) {
</div> </div>
</div> </div>
<div ref="listWrapper" class="relative flex-1 overflow-hidden"> <div ref="listWrapper" class="relative flex-1 overflow-y-auto">
<!-- 滚动区域 --> <div ref="listInner">
<div ref="listInner" class="absolute left-0 right-0 top-0">
<!-- 列表项动画 --> <!-- 列表项动画 -->
<div <div
v-for="item in records" v-for="item in records"
@@ -540,34 +629,48 @@ function focusVideo(item) {
<!-- 车身图片 --> <!-- 车身图片 -->
<img <img
:src="item.vehicle_image" :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 flex-1 flex-col justify-between">
<!-- 第一行车牌 + 车型 --> <!-- 第一行车牌 + 车型 -->
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="text-sm font-semibold text-sky-700"> <div
{{ item.license_plate }} v-if="item.license_plate"
</div> class="plate"
<span class="text-xs text-slate-400">|</span> :class="getPlateClass(item.license_plate_color)"
<div class="text-xs text-slate-600"> @click="previewImage(item.license_plate_image)"
{{ item.vehicle_type }} >
<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> </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>
<!-- 第二行动物 + 部门 --> <!-- 第二行动物 + 部门 -->
<div <div
class="mt-1 flex flex-wrap gap-x-4 text-xs text-slate-600" class="mt-1 flex flex-wrap gap-x-4 text-xs text-slate-600"
> >
<div> <!-- <div>-->
动物 <!-- 动物-->
<span class="font-medium text-slate-700"> <!-- <span class="font-medium text-slate-700">-->
{{ item.livestock_type || '未知' }} <!-- {{ item.livestock_type || '未知' }}-->
</span> <!-- </span>-->
</div> <!-- </div>-->
<div class="max-w-[180px] truncate"> <div class="max-w-[180px] truncate">
站点 所属站点
<span class="font-medium text-slate-700"> <span class="font-medium text-slate-700">
{{ item.dept_name }} {{ item.dept_name }}
</span> </span>
@@ -582,12 +685,94 @@ function focusVideo(item) {
</div> </div>
</div> </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> </aside>
</div> </div>
</main> </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> </div>
</template> </template>
<style> <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 */ /* 额外样式:让右侧列表平滑循环(配合 JS transform */
[ref='listWrapper'] { [ref='listWrapper'] {
position: relative; position: relative;
@@ -622,22 +807,179 @@ function focusVideo(item) {
.toggle-checkbox:checked::before { .toggle-checkbox:checked::before {
transform: translateX(1.25rem); /* 移动到右侧 */ transform: translateX(1.25rem); /* 移动到右侧 */
} }
.new-record-highlight {
background-color: #e0f2ff; /* 淡蓝色,高级感 */ @keyframes flash-highlight {
transition: background-color 1.5s ease; 0%,
} 50%,
.new-record-highlight { 100% {
background-color: #e0f2ff; background-color: #fffcd4;
transform: translateY(-10px); }
opacity: 0.8; 25%,
transition: 75% {
all 0.5s ease, background-color: transparent;
background-color 1.5s ease; }
} }
.new-record-highlight-removal { .new-record-flash {
transform: translateY(0); animation: flash-highlight 3s ease-in-out infinite;
opacity: 1; }
background-color: white; .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> </style>
@@ -17,14 +17,14 @@ export function useFormSchema(): VbenFormSchema[] {
allowClear: true, allowClear: true,
}, },
}, },
{ // {
component: 'Input', // component: 'Input',
fieldName: 'vehicle_type', // fieldName: 'vehicle_type',
label: '车型', // label: '车型',
componentProps: { // componentProps: {
allowClear: true, // allowClear: true,
}, // },
}, // },
{ {
component: 'Input', component: 'Input',
fieldName: 'livestock_type', fieldName: 'livestock_type',
@@ -71,14 +71,14 @@ export function useFormSchema(): VbenFormSchema[] {
*/ */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
return [ return [
{ // {
component: 'Input', // component: 'Input',
fieldName: 'id', // fieldName: 'id',
label: '编号', // label: '编号',
componentProps: { // componentProps: {
allowClear: true, // allowClear: true,
}, // },
}, // },
{ {
component: 'Input', component: 'Input',
fieldName: 'license_plate', fieldName: 'license_plate',
@@ -87,14 +87,14 @@ export function useGridFormSchema(): VbenFormSchema[] {
allowClear: true, allowClear: true,
}, },
}, },
{ // {
component: 'Input', // component: 'Input',
fieldName: 'vehicle_type', // fieldName: 'vehicle_type',
label: '车型', // label: '车型',
componentProps: { // componentProps: {
allowClear: true, // allowClear: true,
}, // },
}, // },
{ {
component: 'ApiCombobox', component: 'ApiCombobox',
fieldName: 'livestock_type', 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 * @param onActionClick
@@ -159,12 +166,7 @@ export function useColumns<T = SystemUserApi.SystemUser>(
{ {
cellRender: { name: 'CellImage' }, cellRender: { name: 'CellImage' },
field: 'license_plate_image', field: 'license_plate_image',
title: '车牌照', title: '正面照',
width: 150,
},
{
field: 'vehicle_type',
title: '车型',
width: 150, width: 150,
}, },
{ {
@@ -173,16 +175,6 @@ export function useColumns<T = SystemUserApi.SystemUser>(
title: '车身照', title: '车身照',
width: 150, width: 150,
}, },
{
field: 'livestock_type',
title: '牲畜种类',
width: 100,
},
{
field: 'livestock_source',
title: '牲畜来源',
width: 200,
},
{ {
field: 'is_inspected', field: 'is_inspected',
title: '检疫状态', title: '检疫状态',
@@ -198,25 +190,41 @@ export function useColumns<T = SystemUserApi.SystemUser>(
}, },
}, },
}, },
{ // {
field: 'created_at', // field: 'livestock_type',
title: '录入时间', // title: '牲畜种类',
width: 150, // width: 100,
}, // },
{ // {
field: 'updated_at', // field: 'vehicle_type',
title: '最后更新时间', // title: '车型',
width: 150, // width: 150,
}, // formatter: ({ cellValue }) => vehicleTypeMap[cellValue] || cellValue,
// },
{ {
field: 'dept_name', field: 'dept_name',
title: '所属组织', title: '所属组织',
width: 300, width: 300,
}, },
{ {
field: 'remark', field: 'livestock_source',
title: '备注', title: '牲畜来源',
width: 300, width: 150,
},
{
field: 'created_at',
title: '录入时间',
width: 150,
},
// {
// field: 'remark',
// title: '备注',
// width: 300,
// },
{
field: 'updated_at',
title: '最后更新时间',
width: 150,
}, },
{ {
align: 'center', align: 'center',
@@ -12,7 +12,7 @@ import { onBeforeUnmount, onMounted, reactive, watch } from 'vue';
import { Page, useVbenDrawer } from '@vben/common-ui'; import { Page, useVbenDrawer } from '@vben/common-ui';
import { Plus } from '@vben/icons'; 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 { useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
@@ -20,10 +20,11 @@ import {
getSentinelRecordList, getSentinelRecordList,
updateSentinelRecordPatch, updateSentinelRecordPatch,
} from '#/api'; } from '#/api';
import VehicleAlertOverlay from '#/views/sentinel/record/VehicleAlertOverlay.vue';
import { useColumns, useGridFormSchema } from './data'; import { useColumns, useGridFormSchema } from './data';
import Form from './form.vue'; import Form from './form.vue';
import { $t } from "@vben/locales";
const [FormDrawer, formDrawerApi] = useVbenDrawer({ const [FormDrawer, formDrawerApi] = useVbenDrawer({
connectedComponent: Form, connectedComponent: Form,
@@ -162,6 +163,7 @@ const wsClient = api.createAutoReconnectWs({
path: () => `iot/ws/sentinel_record`, path: () => `iot/ws/sentinel_record`,
onMessage: (msg) => { onMessage: (msg) => {
if (msg.type === 'vehicle_alert') { if (msg.type === 'vehicle_alert') {
// 显示通知
handleVehicleAlert(msg.content); handleVehicleAlert(msg.content);
} }
}, },
@@ -176,21 +178,33 @@ onBeforeUnmount(() => {
}); });
function handleVehicleAlert(content: string) { function handleVehicleAlert(content: string) {
alertState.content = content; // alertState.content = content;
alertState.visible = true; // alertState.visible = true;
// 刷新列表
gridApi.query();
// message.success({
// content: content,
// });
notification.success({
description: content,
duration: 10,
message: '牧安云哨',
});
} }
function acknowledgeAlert() { // function acknowledgeAlert() {
alertState.visible = false; // alertState.visible = false;
} // }
</script> </script>
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<VehicleAlertOverlay <!-- <VehicleAlertOverlay-->
:visible="alertState.visible" <!-- :visible="alertState.visible"-->
:content="alertState.content" <!-- :content="alertState.content"-->
@acknowledge="acknowledgeAlert" <!-- @acknowledge="acknowledgeAlert"-->
/> <!-- />-->
<FormDrawer @success="onRefresh" /> <FormDrawer @success="onRefresh" />
<Grid :table-title="设备列表"> <Grid :table-title="设备列表">
@@ -1,7 +1,7 @@
{ {
"welcomeBack": "欢迎回来", "welcomeBack": "欢迎回来",
"pageTitle": "智能引领·高效驱动的现代化控制平台", "pageTitle": "智能引领·高效驱动的现代化控制平台",
"pageDesc": "融合先进技术,打造面向蚕桑行业的高性能人工智能分析平台", "pageDesc": "融合先进技术,打造面向业的高性能人工智能分析平台",
"loginSuccess": "登录成功", "loginSuccess": "登录成功",
"loginSuccessDesc": "欢迎回来", "loginSuccessDesc": "欢迎回来",
"loginSubtitle": "请输入您的帐户信息", "loginSubtitle": "请输入您的帐户信息",
+1 -1
View File
@@ -1,7 +1,7 @@
# push_docker.ps1 # push_docker.ps1
# Set version # Set version
$env:VERSION = "1.4.6" $env:VERSION = "1.5.2"
# Docker registry/repository # Docker registry/repository
$registry = "ai.ronsunny.cn:13011/bbit_ai/ce_vue" $registry = "ai.ronsunny.cn:13011/bbit_ai/ce_vue"