313 lines
10 KiB
Markdown
313 lines
10 KiB
Markdown
# 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>()`
|
||
|
||
### 导入顺序
|
||
```typescript
|
||
// 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 响应
|
||
|
||
## 常用模式
|
||
|
||
### 跨窗口通信
|
||
```typescript
|
||
import { broadcast, onCrossWindowMessage } from '@/utils/crossWindowChannel'
|
||
|
||
// 发送
|
||
broadcast.sendDialog(data)
|
||
|
||
// 接收
|
||
onCrossWindowMessage((msg) => { /* 处理 */ })
|
||
```
|
||
|
||
### 事件总线
|
||
```typescript
|
||
import eventBus from '@/utils/eventBus'
|
||
|
||
eventBus.emit('showDialog', data)
|
||
eventBus.on('showDialog', (data) => { /* 处理 */ })
|
||
```
|
||
|
||
### 告警列表导出(支持跨页勾选)
|
||
```typescript
|
||
// 选中导出: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 统一转换为正确地址。
|
||
|
||
### 修改原理
|
||
|
||
```typescript
|
||
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.jpg` → `http://192.168.28.32:8000/media/xxx.jpg`
|
||
- `http://localhost:8000/media/xxx.jpg` → `http://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`):
|
||
|
||
```typescript
|
||
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(非安全模式在可开启通知权限)
|
||
|
||
- 权限开启允许不安全文件 |