Files
turing_alert_web/AGENTS.md
2026-06-10 11:46:45 +08:00

10 KiB
Raw Blame History

AGENTS.md

本文档包含针对此 Vue 3 + TypeScript 本地告警管理应用的智能编码代理指南。

开发命令

命令 说明
npm install 安装依赖
npm run dev 启动热重载开发服务器8000端口
npm run build 构建生产版本(包含类型检查)
npm run type-check 仅运行 TypeScript 类型检查
npm run preview 本地预览生产构建

本项目目前未配置测试框架。

项目结构

src/
├── components/      # 可复用 Vue 组件
├── html/           # 页面级 Vue 组件
├── utils/          # 工具函数和 API
│   ├── boxApi.ts               # 主 API 接口
│   ├── useGlobalWebSocket.ts  # WebSocket 管理
│   ├── crossWindowChannel.ts   # 跨窗口通信
│   └── eventBus.ts             # 事件总线mitt
├── router/         # Vue Router 配置
└── stores/        # Pinia 状态管理

代码风格指南

TypeScript

  • 路径别名:@/ 映射到 src/
  • 始终为 props 和数据接口添加类型

Vue 组件

  • 使用 <script setup> 组合式 API
  • 使用 defineProps<T>()defineEmits<T>()

导入顺序

// 1. Vue 导入
import { ref, computed, onMounted } from 'vue'

// 2. 第三方库
import axios from 'axios'
import { ElMessage } from 'element-plus'

// 3. 内部导入(@ 别名)
import { BoxApi } from '@/utils/boxApi'

命名规范

类型 规范 示例
组件 PascalCase CameraRules.vue
变量 camelCase selectedAlerts
常量 UPPER_SNAKE_CASE MAX_COUNT
函数 fetch/handle/format 前缀 fetchEvents

错误处理

  • 异步操作使用 try-catch
  • 使用 ElMessage.error() 显示错误
  • 在 axios 拦截器中处理 401 响应

常用模式

跨窗口通信

import { broadcast, onCrossWindowMessage } from '@/utils/crossWindowChannel'

// 发送
broadcast.sendDialog(data)

// 接收
onCrossWindowMessage((msg) => { /* 处理 */ })

事件总线

import eventBus from '@/utils/eventBus'

eventBus.emit('showDialog', data)
eventBus.on('showDialog', (data) => { /* 处理 */ })

告警列表导出(支持跨页勾选)

// 选中导出selectedAlerts.value 存储勾选的 ID
// 无勾选:导出当前筛选条件下的所有数据

注意事项

  • 这是一个安防摄像头告警管理系统
  • 中文 UI 文本是有意保留的
  • 跨窗口通信使用 BroadcastChannel需同源策略
  • WebSocket 连接地址:ws://192.168.28.32:8080/ws/event
  • API 基础地址:http://192.168.28.32:8000/api/v1

补充说明:

event接口 GET请求/api/v1/event

响应体如下:

{
    "err": {
        "ec": 0,
        "dm": "ok"
    },
    "ret": {
        "count": 1617,
        "next": "http://127.0.0.1:8000/api/v1/events?limit=20&offset=20",
        "previous": null,
        "results": [
            {
                "id": 90908,
                "camera_id": 1,
                "started_at": "2026-04-01T09:30:00Z",
                "ended_at": "2026-04-01T09:30:10Z",
                "mediums": [
                    {
                        "id": 109656,
                        "name": "snapshot",
                        "file": "http://127.0.0.1:8000/media/mediums/2026/04/02/camera1_1775035800_wander_OHtwpT0.jpg",
                        "event_id": 90908
                    }
                ],
                "camera": {
                    "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": {},
                    "rules": [
                        {
                            "id": 1,
                            "unique_id": "1268f3f2-e0c5-47ea-aa1f-5b97b6dfb95d",
                            "camera": 1,
                            "name": "徘徊",
                            "mode": "on",
                            "algo": "wander",
                            "params": {},
                            "params_base": "",
                            "schedule": {},
                            "event_types": {
                                "wander": "人员徘徊"
                            }
                        },
                        {
                            "id": 32,
                            "unique_id": "9c5e2f2d-4d0e-4ca7-97ee-866462664f8e",
                            "camera": 1,
                            "name": "入侵",
                            "mode": "on",
                            "algo": "intrusion",
                            "params": {},
                            "params_base": "",
                            "schedule": {},
                            "event_types": {
                                "intrusion": "入侵"
                            }
                        },
                        {
                            "id": 35,
                            "unique_id": "fdd29eaa-c07a-43c4-ad6c-171d9dfadcf4",
                            "camera": 1,
                            "name": "未穿反光衣",
                            "mode": "on",
                            "algo": "no_reflective_clothing",
                            "params": {},
                            "params_base": "",
                            "schedule": {},
                            "event_types": {
                                "no_reflective_clothing": "未佩戴反光衣"
                            }
                        }
                    ],
                    "should_push": false,
                    "config_params": {},
                    "sampling": false,
                    "note": {},
                    "snapshot": "http://127.0.0.1:8000/media/camera/camera1_snapshot.jpg",
                    "remote_id": -1,
                    "raw_address": "0b8342ec52e62d01dd3273f583d326ec",
                    "ip": "admin:1234qwer@192.168.28.102:554",
                    "port": 8082
                },
                "types": "wander",
                "types_bits": 0,
                "obj_types": {},
                "uuid": "461aeb6e-10f3-4173-9b33-d540ce59511e",
                "status": "pending",
                "remark": null,
                "should_push": false,
                "metadata": {}
            }]

本次修改记录2026年4月

问题描述

部署到服务器后,前端通过 HTTPS 访问 (https://192.168.28.32),但后端返回的 JSON 数据中包含内部地址(如 http://127.0.0.1:8000/media/...),导致:

  1. 图片无法显示 - 告警列表、详情、通道预览图等全部异常
  2. 分页 next/previous URL 异常 - 指向 http://127.0.0.1:8000/...
  3. Channel/CenterTop 摄像头预览图异常 - http://127.0.0.1:8000/media/camera/...

问题原因

后端 Django 在内部请求自己的 API 时,返回的 JSON 数据中使用的是内部地址 127.0.0.1:8000,而不是前端实际访问的服务器地址。

解决方案

boxApi.ts 中添加 normalizeBackendUrls 方法,将后端返回的 URL 统一转换为正确地址。

修改原理

private normalizeBackendUrls(data: any): any {
    // 强制使用 http + 8000 端口
    const baseUrl = `http://${window.location.hostname}:8000`;

    const replaceHost = (url: string | null | undefined): string | null => {
        if (!url) return null;
        return url
            .replace(/https?:\/\/127\.0\.0\.1(?::8000)?/gi, baseUrl)
            .replace(/https?:\/\/localhost(?::8000)?/gi, baseUrl);
    };

    // 处理多种数据格式...
}

转换示例

  • http://127.0.0.1:8000/media/xxx.jpghttp://192.168.28.32:8000/media/xxx.jpg
  • http://localhost:8000/media/xxx.jpghttp://192.168.28.32:8000/media/xxx.jpg

修改的文件

文件 修改内容
src/utils/boxApi.ts 添加 normalizeBackendUrls 方法,在多个 API 接口中调用
src/utils/superboxApi.ts 添加 URL 规范化支持(备用)
src/components/Cameras.vue 修复硬编码 IP 192.168.28.33
vite.config.ts 添加 /media 和 /ws 代理配置

已修复的接口

接口方法 返回数据 处理的字段
getEvents 事件列表 results[].mediums[].file, results[].camera.snapshot
getEventsByParams 事件列表 同上 + next, previous 分页链接
getEventsByUrl 事件列表 同上
getEventById 单个事件 mediums[].file, camera.snapshot
getAllCameras 摄像头列表 [].snapshot
getCameraById 单个摄像头 .snapshot

视频点播WebSocket说明

  • 接口startCameraStream 返回动态端口(如 8094
  • 连接方式:前端直接连接 ws://192.168.28.32:8094/
  • 限制:需要服务器开放对应端口,或配置 Apache 动态代理
  • Vite 代理:仅开发环境有效,部署后需服务器端配置

Vite 开发代理配置

开发环境代理配置(vite.config.ts

proxy: {
    '/api': {
        target: 'http://192.168.28.32:8000', 
        changeOrigin: true,  
    },
    '/media': {
        target: 'http://192.168.28.32:8000',
        changeOrigin: true,
    },
    '/ws': {
        target: 'ws://192.168.28.32:8080',
        ws: true,
    }
}

注意此配置仅开发环境有效部署后需配置服务器端代理Apache/Nginx

API 统一使用说明

  • 项目统一使用 BoxApi 类进行 API 调用
  • SuperboxApi 为遗留代码,未被页面使用
  • 所有页面通过 new BoxApi() 创建实例调用接口

通道视频安全模式观看方式

chrome://flags/#unsafely-treat-insecure-origin-as-secure非安全模式在可开启通知权限

  • 权限开启允许不安全文件