# local_alert 本地告警页面 ======= ## Project Setup ```sh npm install ``` ### Compile and Hot-Reload for Development ```sh npm run dev ``` ### Type-Check, Compile and Minify for Production ```sh npm run build ``` ## 依赖包 ``` "@jiaminghi/data-view": "^2.10.0", "@kjgl77/datav-vue3": "^1.7.4", "ant-design-vue": "^4.2.6", "axios": "^1.7.3", "dayjs": "^1.11.13", "echarts": "^5.5.1", "element-plus": "^2.7.8", "file-saver": "^2.0.5", "jsmpeg": "^1.0.0", "json2csv": "^5.0.7", "lodash": "^4.17.21", "mitt": "^3.0.1", "papaparse": "^5.4.1", "pinia": "^2.2.6", "vue": "^3.4.29", "vue-router": "^4.4.0" ``` > - @jiaminghi/data-view:DataV > - @kjgl77/datav-vue3/dist/style.css > - ant-design-vue: 没用上 > - axios:异步 > - dayjs:日期截取 > - echarts:仪表盘 > - element-plus: 组件 > - file-saver: 文件加载 > - jsmpeg:视频播放,本地加载静态文件 > - json2csv:表格导出,弃用 > - http-proxy-middleware: 请求跨域 > - lodash: > - mite: Bus线程通信 > - papaparse:表格csv导出 > - pinia:没用上 > - jszip:下载打包 > - qs:静态资源请求 > - vue:框架 > - vue-router:路由 ## 目录结构 ``` │ .gitignore │ env.d.ts │ index.html │ LICENSE │ package-lock.json │ package.json │ README.md │ SScode.md │ tsconfig.app.json │ tsconfig.json │ tsconfig.node.json │ vite.config.ts │ ├─.vscode │ extensions.json │ ├─dist │ │ bg05.png │ │ icon.ico │ │ index.html │ │ login-bg.png │ │ xlogo.png │ │ │ ├─assets │ │ │ └─static │ └─jsmpeg-master │ │ │ └─src │ │ │ └─wasm │ ├─node_modules │ └─src │ App.vue │ main.ts │ ├─assets │ global.css │ viewListStyle.css │ ├─bk │ CenterBottom告警分布.vue │ CenterTop警戒点位.vue │ DataStatics新数据统计.vue │ DataStatistics原始版.vue │ LeftMiddle点位告警数量.vue │ LeftTop告警数据统计.vue │ RightTop类型分布.vue │ StaticData图表简易版.vue │ 告警列表(letf-bottom).vue │ 告警数量分布问题代码.vue │ 时间段内告警类型数量.vue │ 时间段告警数量分布.vue │ 点播页面原版.vue │ 设置页面点播墙组件代码.vue │ ├─components │ │ AlertChart.vue │ │ Cameras.vue │ │ Channel.vue │ │ GlobalDialog.vue │ │ Layout.vue │ │ Settings.vue │ │ Statistics.vue │ │ │ └─Max │ CenterBottom.vue │ CenterTop.vue │ LeftBottom.vue │ LeftMiddle.vue │ LeftTop.vue │ RightTop.vue │ ├─html │ AlertManagement.vue │ CameraRules.vue │ DataStatistics.vue │ Home.vue │ LoginView.vue │ Test.vue │ UserList.vue │ ViewList.vue │ ├─icons │ CameraAll.vue │ CameraOffline.vue │ CameraOnline.vue │ EventAll.vue │ EventClosed.vue │ EventPending.vue │ ├─router │ index.ts │ ├─stores │ globalTimerStore.ts │ ├─types │ CameraData.ts │ RuleData.ts │ └─utils axios-config.ts boxApi.ts eventBus.ts message_channel.js misc.ts superbox.js Superbox.ts superboxApi.ts useGlobalWebSocket.ts ``` ## Src文件说明 ### 目录分解 ``` └─src │ App.vue │ main.ts │ ├─assets │ global.css │ viewListStyle.css │ ├─bk │ CenterBottom告警分布.vue │ CenterTop警戒点位.vue │ DataStatics新数据统计.vue │ DataStatistics原始版.vue │ LeftMiddle点位告警数量.vue │ LeftTop告警数据统计.vue │ RightTop类型分布.vue │ StaticData图表简易版.vue │ 告警列表(letf-bottom).vue │ 告警数量分布问题代码.vue │ 时间段内告警类型数量.vue │ 时间段告警数量分布.vue │ 点播页面原版.vue │ 设置页面点播墙组件代码.vue │ ├─components │ │ AlertChart.vue │ │ Cameras.vue │ │ Channel.vue │ │ GlobalDialog.vue │ │ Layout.vue │ │ Settings.vue │ │ Statistics.vue │ │ │ └─Max │ CenterBottom.vue │ CenterTop.vue │ LeftBottom.vue │ LeftMiddle.vue │ LeftTop.vue │ RightTop.vue │ ├─html │ AlertManagement.vue │ CameraRules.vue │ DataStatistics.vue │ Home.vue │ LoginView.vue │ Test.vue │ UserList.vue │ ViewList.vue │ ├─icons │ CameraAll.vue │ CameraOffline.vue │ CameraOnline.vue │ EventAll.vue │ EventClosed.vue │ EventPending.vue │ ├─router │ index.ts │ ├─stores │ globalTimerStore.ts │ ├─types │ CameraData.ts │ RuleData.ts │ └─utils axios-config.ts boxApi.ts eventBus.ts message_channel.js misc.ts superbox.js Superbox.ts superboxApi.ts useGlobalWebSocket.ts ``` ### 根目录 ``` └─src │ App.vue │ main.ts ├─assets │ ├─bk │ ├─components │ │ │ └─Max │ ├─html │ ├─icons │ ├─router │ ├─stores │ ├─types │ └─utils ``` #### App.vue(关键) - 在index.html中可见 ```
``` > 页面的app为页面主体框架结构,所有组件仿照类似嵌套方式申明和使用,其下囊括页面布局的其他页面和子页面组件 > 相当于整体布局的一个载体(根父组件) ``` ``` > 作为父页面,不可避免需要向其下子组件传递参数,可通过导入vue中的provide方法直接传递多层次的子组件信息,子组件inject获取信息,当然在vue3中数据的变化需要用reactive包裹数据,创建响应式变化。 #### main.ts(关键) > 对于app组件的挂载和改改父组件的依赖使用和申明需要在这里定义使用,当一些新的外部组件引用逻辑正确但是显示异常,可检查是否在根文件中引用 ### 路由(Route) ``` └─src ├─router │ index.ts ``` - 看门狗(to,form,next) ``` import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router' const routes = [ { path: '/', name: '', component: , children: [] } const router = createRouter({ history: createWebHashHistory(), routes }); router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.matched.some(record => record.meta.requiresAuth) && !token) { next({ name: 'Login' }); } else { next(); } }); export default router; ``` ## 登录 - 页面拦截 > 简易拦截,alertToken作为判断条件,拦截对象,当然数据请求需要交互验证。 ``` // 导航守卫,检查登录状态 router.beforeEach((to, from, next) => { const token = localStorage.getItem('alertToken'); // 检查 token 是否存在,作为是否登录的依据 if (to.matched.some(record => record.meta.requiresAuth)) { // 该路由需要认证 if (!token) { // 如果没有 token,重定向到登录页面 next({ path: '/login', query: { redirect: to.fullPath } // 将当前路径传递给登录页面,登录后可以重定向回来 }); } else { // 已登录,继续访问 next(); } } else { // 不需要认证,继续访问 next(); } }); ``` ## 本地告警首页子页面ViewList布局 ![image-20241030162011722](https://gitee.com/gonghao_git/draw-bed/raw/master/img/%E9%A6%96%E9%A1%B5ViewList%E5%B8%83%E5%B1%80-20241030162011722.png) ![image-20241031164625119](https://gitee.com/gonghao_git/draw-bed/raw/master/img/%E5%A4%A7%E5%B1%8F%E9%A1%B5%E9%9D%A2%E5%88%9D%E7%A8%BF-20241031164625119.png) ![image-20251201110453175](https://gitee.com/gonghao_git/draw-bed/raw/master/img/image-20251201110453175.png) ## 告警规则设置 ### 界面接口列表 ``` private readonly apiLogin: string = "/auth/login"; - public async login - (username: string, password: string, cookieLess: boolean = false): Promise private readonly apiLogout: string = "/auth/logout"; - public async logout - (token: string | null = null): Promise private readonly apiAdduser: string = "/auth/adduser"; - public async addUser - (username: string, password: string, email: string = "", token: string | null = null): Promise private readonly apiRmuser: string = "/auth/rmuser"; - public async rmUser - (username: string, token: string | null = null): Promise private readonly apiAllusers: string = "/auth/allusers"; - public async getAllUsers - (token: string | null = null): Promise private readonly apiCameras: string = "/cameras"; - public async getCameras - (limit: number, offset: number, token: string | null = null): Promise private readonly apiEvents: string = "/events"; - public async getEvents - (token: string | null = null, pageSize: number = 20, currentPage: number = 1): Promise - public async getEventsByParams - ( token: string | null = null, pageSize: number = 20, currentPage: number = 1, timeBefore: string | null = null, timeAfter: string | null = null, types: string | null = null, camera_id: number | null = null, status?: 'pending' | 'closed' | null ): Promise - public async setEventStatus - (eventId: number, status: string, remark: string | null = null, token: string | null = null): Promise private readonly apiAlgorithms: string = "/algorithms"; - public async getAlgorithms - (token: string | null = null): Promise private readonly apiResetUser: string = "/auth/resetuser"; - public async resetUser - (username: string, password: string, email: string, token: string | null = null): Promise private readonly apiResetPassword: string = "/auth/reset_password"; - public async resetPassword - (newPassword: string, token: string | null = null): Promise private readonly apiAllCameras: string = "/camera/cameras/get_all"; - public async getAllCameras - (token: string | null = null): Promise private readonly superCamera: string = "/camera/cameras"; - public async getCameraById - (token: string | null = null, cameraId: number): Promise - public async updateCamera - (token: string | null = null, cameraId: number, jsonData: CameraData): Promise - public async startCameraStream - (token: string | null = null, cameraId: number): Promise - public async stopCameraStream - (token: string | null = null, cameraId: number): Promise private readonly superRule: string = "/rules"; - public async updateRule - (token: string | null = null, rules: RuleData[]): Promise private readonly superEvents: string = "/event/events"; - public async delEvents - (token: string | null = null, eventIds: number[]): Promise<{ id: number, success: boolean, message?: string }[]> - public async getEventById - (id: number, token: string | null = null): Promise ``` - 摄像实例 ``` const cameraId = 1; // 示例摄像头 ID const token = "your-auth-token"; const cameraJson = { "id": 1, "name": "421枪机", "uri": "rtsp://admin:1234qwer@192.168.28.102:554/Streaming/Channels/202", "mode": "on", "status": "online", "detect_params": { "threshold": 0.5 }, "default_params": {}, "should_push": false, "config_params": {}, "sampling": false, "note": {}, "snapshot": "http://127.0.0.1:8000/media/cameras/1/snapshot.jpg", "remote_id": -1, "raw_address": "0b8342ec52e62d01dd3273f583d326ec", "ip": "admin:1234qwer@192.168.28.102:554", "port": 8082, "rules": [] // 包含的 rules 会被排除 }; await apiInstance.updateCamera(token, cameraId, cameraJson); ``` - 规则实例 ``` const cameraId = 1; // 示例摄像头 ID const token = "your-auth-token"; const cameraJson = { "id": 1, "name": "421枪机", "uri": "rtsp://admin:1234qwer@192.168.28.102:554/Streaming/Channels/202", "mode": "on", "status": "online", "detect_params": { "threshold": 0.5 }, "default_params": {}, "should_push": false, "config_params": {}, "sampling": false, "note": {}, "snapshot": "http://127.0.0.1:8000/media/cameras/1/snapshot.jpg", "remote_id": -1, "raw_address": "0b8342ec52e62d01dd3273f583d326ec", "ip": "admin:1234qwer@192.168.28.102:554", "port": 8082, "rules": [] // 包含的 rules 会被排除 }; await apiInstance.updateCamera(token, cameraId, cameraJson); ``` ``` 单条camera实例部分内容: { "id": 1, "name": "421枪机", "mode": "on", "rules": [{规则1json数据},{规则2json数据}] } 单条rule实例部分: { "id": 3, "camera": 1, "name": "人员逗留", "mode": "schedule", "algo": "personnel_staying", "params": {}, "params_base": "", "unique_id": "7ce99a51-0ee4-4251-a754-bd897d671303", "event_types": { "personnel_staying": "人员逗留" }, "schedule": { "type": "weekly", "time_slots": [ [ 5, 1415 ] ], "week_day": "Monday" } } ``` - 时间段规则设置 > 添加时间段schedule的设置,组件显示开始时间点和结束时间,数据格式化方式为将代表累计分钟的数字转换为时间点,每60分钟为1小时,数字范围在0到1440,实例[56,65]代表0:56到1:05,组件显示将数字组转换为时间段,请求时将组件时间段显示为数字组。 > > 时间段结束时间点不能大于开始时间点,多时间段添加不能有重合部分。 > > 设置规则,只有在rule.mode为schedule时才可设置时间段,携带参数日期类型和多个时间段数字组合,在未保存切换rule。mode模式时间段不会因为隐藏组件而清空数值,始终显示开始设定好的默认值,当提交时处于时间段模式至少得设置一个时间段数组否则无法保存,若处在schedule情况下保存,携带参数请求,若rule.mode处于on或者off,代表没有时间段设置,那么schedule下的所有参数及时原来有值,也需要伴随请求清空默认值,具体如下: > 1.在rule.mode为schedule模式添加时间段time_slots的组件,添加时间段类型,输入框值默认为daily,不可修改 > > 2.开始时间和结束时间的组件,时间通过分钟数转换为格式化的时间(小时:分钟),有删除按钮可删除临时的时间段设置,使用数字范围(0-1440),单个时间点代表0点到设置的时间点累计的分钟数字 > > 3.时间段的校验,结束时间不能小于开始时间,多时间段之间不能有重叠。 > > 4.如果 rule.mode 为 schedule,则必须包含至少一个时间段,如果 rule.mode 是 on 或 off,清空所有时间段和时间段类型 - 静态数据实例 ``` "camera": { "id": 1, "name": "421枪机", "mode": "on", "status": "online", "rules": [ { "id": 1, "camera": 1, "name": "入侵test2", "mode": "schedule", "algo": "intrude", "schedule": { "type": "daily", "time_slots": [ [ 118, 898 ], [ 0, 60 ] ] }, }, { "id": 3, "camera": 1, "name": "人员逗留", "mode": "schedule", "algo": "personnel_staying", "schedule": { "type": "daily", "time_slots": [ [ 180, 420 ] ] }, } ], "snapshot": "http://192.168.28.33:8000/media/cameras/1/snapshot.jpg", } ``` ## 日志 ### 2024.11.21 - 本地打包 - 服务器页面备份 - 服务器迭代,覆盖 - 版本提交云端 - 更新内容 - 1.弹窗仅图片显示(AlertManagement,LeftBottom,App) - 2.通道点播(Setting,Channel) - 摄像列表 - 搜索/在线状态过滤 - 3.通道规则设置(Channel,CameraRules) - 规则开关 - 时间设置 - 下阶段 - 1.弹窗模式按钮 - 交互式开关(已有,点击响应) - 响应式开关(###) - 2.光影效果 - 声音联动(提示音) - 交互(首页) - 3.用户设定不可更改项 - 测试调整(遗留代码冗余调整) ### 2024.11.22 - 弹窗模式开关 - 交互式开关(点击触发弹窗) - 响应式开关(触发对话框) - 弹窗现实 - 格式化类型 - 格式化时间 - 现实时间 - 弹窗布局调整 - 控制台日志处理(部分) ### 2024.11.26 - 对话框大图显示 - 删除告警按钮 - 主页定时刷新 - 布局滚动查看调整 - 用户删除限制 ### 2024.12.3 - 告警设置 - 消息队列设置 - 声音设置 - 弹窗动画添加 - 弹窗队列添加,错误重试机制(2s*5次)