diff --git a/asset-source-address-frontend-integration.md b/asset-source-address-frontend-integration.md new file mode 100644 index 0000000..d689909 --- /dev/null +++ b/asset-source-address-frontend-integration.md @@ -0,0 +1,376 @@ +# 资产设备源地址前端对接文档 + +## 1. 变更说明 + +设备资产新增可选字段 `source_address`,界面名称为“源地址”。该字段适用于所有设备分类,可用于保存 RTSP、HTTP、HTTPS、HLS 等形式的播放源地址。 + +| 属性 | 内容 | +| --- | --- | +| JSON 字段 | `source_address` | +| TypeScript 类型 | `string` | +| 是否必填 | 否 | +| 最大长度 | 1000 个字符 | +| 协议限制 | 无 | +| 数据库字段 | `assets_assets.source_address` | +| 数据库类型 | `varchar(1000)` | + +前端不需要根据设备分类判断是否展示该字段。 + +## 2. 接口公共约定 + +资产接口基础路径: + +```text +/Assets/v1/asset +``` + +请求头: + +```http +Authorization: Bearer +Content-Type: application/json +``` + +统一响应结构: + +```ts +interface ApiResponse { + code: number + message: string + details: T + timeseq: number +} +``` + +`code === 0` 表示请求成功,业务数据位于 `details`。 + +## 3. 前端类型 + +在资产表单类型中增加 `source_address`: + +```ts +export interface AssetForm { + id?: number + asset_name: string + asset_code: string + source_address?: string +} +``` + +当前项目已经在 `src/api/ops/asset.ts` 的 `AssetForm` 中加入该字段。其他以资产对象为基础的详情、列表和批量导入数据可以直接复用该字段。 + +## 4. 创建设备资产 + +### 4.1 接口 + +```http +POST /Assets/v1/asset/create +``` + +### 4.2 请求示例 + +```json +{ + "asset_name": "东门监控摄像头", + "asset_code": "CAMERA-001", + "category_id": 12, + "model": "DS-2CD3T", + "manufacturer": "海康威视", + "serial_number": "SN20260817001", + "source_address": "rtsp://192.168.1.100:554/Streaming/Channels/101", + "status": "in_use" +} +``` + +未填写源地址时可以省略 `source_address`,也可以传空字符串: + +```json +{ + "asset_name": "普通设备", + "asset_code": "DEVICE-001", + "source_address": "" +} +``` + +### 4.3 成功响应 + +```json +{ + "code": 0, + "message": "", + "details": { + "id": 101, + "asset_name": "东门监控摄像头", + "asset_code": "CAMERA-001", + "source_address": "rtsp://192.168.1.100:554/Streaming/Channels/101", + "status": "in_use" + }, + "timeseq": 1786932000000 +} +``` + +### 4.4 调用方式 + +```ts +import { createAsset, type AssetForm } from '@/api/ops/asset' + +const data: AssetForm = { + asset_name: '东门监控摄像头', + asset_code: 'CAMERA-001', + source_address: 'rtsp://192.168.1.100:554/Streaming/Channels/101', + status: 'in_use', +} + +const response = await createAsset(data) +if (response.code !== 0) { + throw new Error(response.message || '创建设备失败') +} +``` + +## 5. 更新设备资产 + +### 5.1 接口 + +```http +PUT /Assets/v1/asset/update +``` + +### 5.2 请求示例 + +```json +{ + "id": 101, + "asset_name": "东门监控摄像头", + "asset_code": "CAMERA-001", + "source_address": "https://video.example.com/live/camera-001.m3u8", + "status": "in_use" +} +``` + +更新接口需要同时传入: + +- `id` +- `asset_name` +- `asset_code` + +`source_address` 仍为选填字段。传入非空值时更新源地址;不传时保留原值。 + +当前后端使用 GORM 结构体更新,空字符串属于零值,因此传入 `source_address: ""` 不会清空已有源地址。前端暂时不要把“传空字符串”作为清空操作。 + +### 5.3 调用方式 + +```ts +import { updateAsset, type AssetForm } from '@/api/ops/asset' + +const data: AssetForm = { + id: 101, + asset_name: '东门监控摄像头', + asset_code: 'CAMERA-001', + source_address: 'https://video.example.com/live/camera-001.m3u8', + status: 'in_use', +} + +const response = await updateAsset(data) +if (response.code !== 0) { + throw new Error(response.message || '更新设备失败') +} +``` + +## 6. 查询设备详情 + +### 6.1 接口 + +```http +GET /Assets/v1/asset/detail/{id} +``` + +### 6.2 成功响应 + +```json +{ + "code": 0, + "message": "", + "details": { + "id": 101, + "asset_name": "东门监控摄像头", + "asset_code": "CAMERA-001", + "source_address": "rtsp://192.168.1.100:554/Streaming/Channels/101", + "status": "in_use" + }, + "timeseq": 1786932000000 +} +``` + +未填写源地址时,`source_address` 返回空字符串。 + +### 6.3 表单回填 + +当前编辑页面使用详情对象整体回填: + +```ts +const response = await fetchAssetDetail(deviceId) +if (response.code === 0) { + form.value = { ...response.details } as AssetForm +} +``` + +`source_address` 会随其他资产字段自动回填,不需要单独请求。 + +## 7. 分页查询设备资产 + +### 7.1 接口 + +```http +POST /Assets/v1/asset/list +``` + +### 7.2 请求示例 + +```json +{ + "page": 1, + "page_size": 20, + "keyword": "", + "status": "in_use" +} +``` + +### 7.3 响应示例 + +```json +{ + "code": 0, + "message": "", + "details": { + "total": 1, + "page": 1, + "page_size": 20, + "data": [ + { + "id": 101, + "asset_name": "东门监控摄像头", + "asset_code": "CAMERA-001", + "source_address": "rtsp://192.168.1.100:554/Streaming/Channels/101" + } + ] + }, + "timeseq": 1786932000000 +} +``` + +以下接口返回的资产对象同样包含 `source_address`: + +```http +GET /Assets/v1/asset/page +GET /Assets/v1/asset/all +GET /Assets/v1/asset/export +``` + +## 8. 批量导入 + +### 8.1 接口 + +```http +POST /Assets/v1/asset/bulk_import +``` + +### 8.2 请求示例 + +```json +{ + "dry_run": false, + "items": [ + { + "asset_name": "西门监控摄像头", + "asset_code": "CAMERA-002", + "source_address": "rtsp://192.168.1.101:554/live", + "status": "in_use" + } + ] +} +``` + +每个 `items` 元素都可以单独填写或省略 `source_address`。 + +## 9. 表单接入 + +### 9.1 默认值 + +```ts +const form = ref({ + asset_name: '', + asset_code: '', + source_address: '', +}) +``` + +### 9.2 表单组件 + +```vue + + + +``` + +该字段不设置 `required` 规则,也不校验具体协议格式。 + +### 9.3 提交 + +新增和编辑直接复用现有资产提交逻辑: + +```ts +const data = { ...form.value } + +if (isEdit.value && deviceId.value) { + await updateAsset({ ...data, id: deviceId.value }) +} else { + await createAsset(data) +} +``` + +## 10. 详情展示 + +```vue + + {{ device?.source_address || '-' }} + +``` + +长地址需要允许换行: + +```css +.source-address { + word-break: break-all; +} +``` + +## 11. 播放功能使用建议 + +获取播放地址时直接读取资产对象的 `source_address`: + +```ts +const sourceAddress = record.source_address?.trim() + +if (!sourceAddress) { + Message.warning('该设备未配置源地址') + return +} +``` + +前端不要自动补全协议、转换大小写或修改地址内容。具体播放器需要支持对应的流媒体协议,例如浏览器不能直接原生播放 RTSP 地址。 + +## 12. 对接检查项 + +- 新增设备时可以不填写源地址。 +- 新增设备时填写源地址后可以正常保存。 +- 编辑设备时能够正确回填源地址。 +- 编辑设备时修改源地址后可以正常保存。 +- 详情接口和详情弹窗能够显示源地址。 +- 列表接口返回的每条资产数据包含 `source_address`。 +- 超长地址能够在详情区域正常换行。 +- 接口失败时优先显示响应中的 `message`。 diff --git a/src/views/ops/pages/datacenter/rack/index.vue b/src/views/ops/pages/datacenter/rack/index.vue index f404fdd..8eca616 100644 --- a/src/views/ops/pages/datacenter/rack/index.vue +++ b/src/views/ops/pages/datacenter/rack/index.vue @@ -60,7 +60,9 @@ :percent="Math.min(Math.max((Number(record.utilization_rate) || 0) / 100, 0), 1)" :size="'small'" :color="(record.utilization_rate || 0) > 80 ? '#f53f3f' : (record.utilization_rate || 0) > 50 ? '#ff7d00' : '#00b42a'" - /> + > + +