Просмотр исходного кода

feat(camera-management): enhance camera management UI and localization support

- Updated English localization file to include missing translations for camera management terms, improving user interface clarity.
- Refactored camera management UI components for better readability and consistency, including search and camera list functionalities.
- Added new fields to camera-related types for improved API request flexibility.
- Enhanced camera editing dialog with additional input fields for comprehensive camera configuration.
yb 1 неделя назад
Родитель
Сommit
ded7e2b805
3 измененных файлов с 460 добавлено и 42 удалено
  1. 21 21
      src/locales/en.json
  2. 10 0
      src/types/index.ts
  3. 429 21
      src/views/lss/index.vue

+ 21 - 21
src/locales/en.json

@@ -2,15 +2,15 @@
   "Cloudflare Stream": "Cloudflare Stream",
   "Cloudflare Stream 配置": "Cloudflare Stream Configuration",
   "IP地址": "IP Address",
-  "LSS": "",
-  "Logo URL": "",
-  "RTSP URL模板": "",
+  "LSS": "LSS",
+  "Logo URL": "Logo URL",
+  "RTSP URL模板": "RTSP URL Template",
   "RTSP 地址": "RTSP URL",
   "RTSP 流": "RTSP Stream",
   "RTSP 流需要通过服务端转换为 HLS/WebRTC 后才能在浏览器播放": "RTSP streams need to be converted to HLS/WebRTC via server before playing in browser",
   "RTSP地址": "RTSP URL",
-  "RTSP端口": "",
-  "Stream SN": "",
+  "RTSP端口": "RTSP Port",
+  "Stream SN": "Stream SN",
   "Stream 测试": "Stream Test",
   "Video ID": "Video ID",
   "button.cancel": "Cancel",
@@ -49,21 +49,21 @@
   "停止": "Stop",
   "全屏": "Fullscreen",
   "全部": "All",
-  "关闭": "",
-  "关闭失败": "",
-  "关闭时间": "",
+  "关闭": "Close",
+  "关闭失败": "Close failed",
+  "关闭时间": "Close time",
   "创建时间": "Created At",
-  "初始化失败": "",
-  "初始化成功": "",
-  "初始化默认数据": "",
+  "初始化失败": "Initialization failed",
+  "初始化成功": "Initialization successful",
+  "初始化默认数据": "Initialization default data",
   "删除": "Delete",
   "删除失败": "Delete failed",
   "删除成功": "Deleted successfully",
   "刷新数据": "Refresh",
-  "功能": "",
-  "协议支持": "",
-  "厂家代码": "",
-  "厂家名称": "",
+  "功能": "Function",
+  "协议支持": "Protocol Support",
+  "厂家代码": "Factory Code",
+  "厂家名称": "Factory Name",
   "原密码": "Old Password",
   "取消": "Cancel",
   "取消选择": "Clear Selection",
@@ -76,7 +76,7 @@
   "启动": "Start",
   "启动失败": "Start failed",
   "启动成功": "Start successful",
-  "启动时间": "",
+  "启动时间": "Start time",
   "启用": "Enabled",
   "启用状态": "Status",
   "命令模板": "Command Template",
@@ -106,7 +106,7 @@
   "批量删除失败": "Batch delete failed",
   "技术支持": "Support",
   "排序": "Sort",
-  "排序号": "",
+  "排序号": "Sort No.",
   "推流方式": "Push Stream Method",
   "推荐通过后端代理调用,避免暴露 Token": "Recommended to call through the backend proxy to avoid exposing the Token",
   "描述": "Description",
@@ -129,8 +129,8 @@
   "操作": "Actions",
   "数据更新时间": "Last Updated",
   "新增": "Add",
-  "新增 LiveStream": "",
-  "新增厂家": "",
+  "新增 LiveStream": "Add LiveStream",
+  "新增厂家": "Add Factory",
   "新增成功": "Added successfully",
   "新增摄像头": "Add Camera",
   "新增机器": "Add Machine",
@@ -143,7 +143,7 @@
   "机器ID": "Machine ID",
   "机器总数": "Total Machines",
   "机器管理": "Machine Management",
-  "查看": "",
+  "查看": "View",
   "查询": "Search",
   "检测": "Test",
   "检测失败": "Test failed",
@@ -168,7 +168,7 @@
   "直接 URL": "Direct URL",
   "直接 URL 播放": "Direct URL Playback",
   "确定": "Confirm",
-  "确定要初始化默认厂家数据吗?这将添加预设的摄像头厂家信息。": "",
+  "确定要初始化默认厂家数据吗?这将添加预设的摄像头厂家信息。": "Are you sure you want to initialize default factory data? This will add preset camera factory information.",
   "确定要删除厂家": "Are you sure you want to delete factory",
   "确定要删除摄像头": "Are you sure you want to delete camera",
   "确定要删除机器": "Are you sure you want to delete machine",

+ 10 - 0
src/types/index.ts

@@ -175,6 +175,11 @@ export interface CameraAddRequest {
   brand?: string
   capability?: 'switch_only' | 'ptz_enabled'
   machineId?: string
+  lssId?: string
+  model?: string
+  rtspUrl?: string
+  channelNo?: string
+  remark?: string
   channels?: ChannelAddRequest[]
 }
 
@@ -197,6 +202,11 @@ export interface CameraUpdateRequest {
   brand?: string
   capability?: 'switch_only' | 'ptz_enabled'
   machineId?: string
+  lssId?: string
+  model?: string
+  rtspUrl?: string
+  channelNo?: string
+  remark?: string
   enabled?: boolean
   channels?: ChannelUpdateRequest[]
 }

+ 429 - 21
src/views/lss/index.vue

@@ -121,36 +121,160 @@
     <!-- 摄像头列表抽屉 -->
     <el-drawer
       v-model="cameraDrawerVisible"
-      :title="`摄像头列表 - ${currentLss?.lssName || ''}`"
+      :title="`设备列表 - ${currentLss?.lssName || ''}`"
       direction="rtl"
-      size="600px"
+      size="80%"
       destroy-on-close
     >
       <div v-loading="cameraLoading">
-        <el-empty v-if="!cameraLoading && cameraList.length === 0" description="暂无关联摄像头" />
-        <el-table v-else :data="cameraList" stripe size="small">
-          <el-table-column prop="cameraId" label="摄像头 ID" min-width="120" show-overflow-tooltip />
-          <el-table-column prop="name" :label="t('名称')" min-width="120" show-overflow-tooltip />
-          <el-table-column prop="ip" label="IP" min-width="120" />
-          <el-table-column prop="status" :label="t('状态')" min-width="80" align="center">
+        <div class="camera-toolbar">
+          <el-form :model="cameraSearchForm" inline>
+            <el-form-item>
+              <el-input
+                v-model.trim="cameraSearchForm.keyword"
+                placeholder="IP / 设备ID / 名称"
+                clearable
+                style="width: 200px"
+                @keyup.enter="handleCameraSearch"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-select v-model="cameraSearchForm.status" placeholder="状态" clearable style="width: 120px">
+                <el-option label="全部" value="" />
+                <el-option label="在线" value="ONLINE" />
+                <el-option label="离线" value="OFFLINE" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" :icon="Search" @click="handleCameraSearch">查询</el-button>
+              <el-button :icon="RefreshRight" @click="handleCameraReset">重置</el-button>
+            </el-form-item>
+          </el-form>
+          <el-button type="primary" :icon="Plus" @click="handleAddCamera">{{ t('新增') }}</el-button>
+        </div>
+        <el-empty v-if="!cameraLoading && cameraList.length === 0" description="暂无关联设备" />
+        <el-table v-else :data="cameraList" stripe size="small" border>
+          <el-table-column prop="ip" label="本地IP" min-width="110" />
+          <el-table-column prop="cameraId" label="设备ID" min-width="100" show-overflow-tooltip />
+          <el-table-column prop="name" label="名称" min-width="100" show-overflow-tooltip />
+          <el-table-column label="状态(心跳)" min-width="140">
             <template #default="{ row }">
-              <el-tag :type="row.status === 'ONLINE' ? 'success' : 'danger'" size="small">
-                {{ row.status === 'ONLINE' ? '在线' : '离线' }}
-              </el-tag>
+              <span :class="['status-text', row.status === 'ONLINE' ? 'status-active' : 'status-dead']">
+                {{ formatCameraStatus(row) }}
+              </span>
             </template>
           </el-table-column>
-          <el-table-column prop="enabled" :label="t('启用')" min-width="70" align="center">
+          <el-table-column label="参数配置" min-width="80" align="center">
             <template #default="{ row }">
-              <el-tag :type="row.enabled ? 'success' : 'info'" size="small">
-                {{ row.enabled ? '是' : '否' }}
-              </el-tag>
+              <el-button type="primary" link @click="handleViewConfig(row)">查看</el-button>
+            </template>
+          </el-table-column>
+          <el-table-column label="运行参数" min-width="80" align="center">
+            <template #default="{ row }">
+              <el-button type="primary" link @click="handleViewRunParams(row)">查看</el-button>
+            </template>
+          </el-table-column>
+          <el-table-column prop="brand" label="厂商" min-width="90">
+            <template #default="{ row }">
+              {{ formatBrand(row.brand) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="model" label="型号" min-width="130" show-overflow-tooltip />
+          <el-table-column label="添加时间" min-width="140">
+            <template #default="{ row }">
+              {{ formatTime(row.createdAt) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="设备控制" min-width="100" align="center" fixed="right">
+            <template #default="{ row }">
+              <el-button type="primary" link :icon="Edit" @click="handleEditCamera(row)" />
+              <el-button type="danger" link :icon="Delete" @click="handleDeleteCamera(row)" />
             </template>
           </el-table-column>
         </el-table>
-        <div v-if="cameraList.length > 0" class="camera-count">共 {{ cameraList.length }} 个摄像头</div>
+        <div v-if="cameraList.length > 0" class="camera-count">共 {{ cameraList.length }} 个设备</div>
       </div>
     </el-drawer>
 
+    <!-- 摄像头编辑弹窗 -->
+    <el-dialog
+      v-model="cameraDialogVisible"
+      :title="isEditCamera ? '编辑摄像头' : '绑定摄像头'"
+      width="500px"
+      :close-on-click-modal="false"
+    >
+      <el-form ref="cameraFormRef" :model="cameraForm" :rules="cameraRules" label-width="100px">
+        <!-- 新增时显示摄像头选择下拉 -->
+        <el-form-item v-if="!isEditCamera" label="选择摄像头" prop="selectedCameraId">
+          <el-select
+            v-model="cameraForm.selectedCameraId"
+            placeholder="请选择摄像头"
+            style="width: 100%"
+            filterable
+            @change="handleCameraSelect"
+          >
+            <el-option
+              v-for="cam in availableCameras"
+              :key="cam.id"
+              :label="`${cam.ip} - ${cam.cameraId}`"
+              :value="cam.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="IP 地址" prop="ip">
+          <el-input v-model="cameraForm.ip" disabled placeholder="IP 地址" />
+        </el-form-item>
+        <el-form-item label="摄像头 ID" prop="cameraId">
+          <el-input v-model="cameraForm.cameraId" disabled placeholder="摄像头 ID" />
+        </el-form-item>
+        <el-form-item label="名称" prop="name">
+          <el-input v-model="cameraForm.name" placeholder="请输入名称" />
+        </el-form-item>
+        <el-form-item label="端口" prop="port">
+          <el-input-number v-model="cameraForm.port" :min="1" :max="65535" style="width: 100%" />
+        </el-form-item>
+        <el-form-item label="用户名" prop="username">
+          <el-input v-model="cameraForm.username" placeholder="请输入用户名" />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input v-model="cameraForm.password" type="password" show-password placeholder="请输入密码" />
+        </el-form-item>
+        <el-form-item label="品牌" prop="brand">
+          <el-select v-model="cameraForm.brand" placeholder="请选择品牌" style="width: 100%">
+            <el-option label="海康威视" value="hikvision" />
+            <el-option label="大华" value="dahua" />
+            <el-option label="宇视" value="uniview" />
+            <el-option label="其他" value="other" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="能力" prop="capability">
+          <el-select v-model="cameraForm.capability" placeholder="请选择能力" style="width: 100%">
+            <el-option label="仅切换" value="switch_only" />
+            <el-option label="支持 PTZ" value="ptz_enabled" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="RTSP 地址" prop="rtspUrl">
+          <el-input v-model="cameraForm.rtspUrl" placeholder="请输入 RTSP 地址" />
+        </el-form-item>
+        <el-form-item label="型号" prop="model">
+          <el-input v-model="cameraForm.model" placeholder="请输入型号" />
+        </el-form-item>
+        <el-form-item label="通道号" prop="channelNo">
+          <el-input v-model="cameraForm.channelNo" placeholder="请输入通道号" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="cameraForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
+        </el-form-item>
+        <el-form-item label="启用状态" prop="enabled">
+          <el-switch v-model="cameraForm.enabled" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="cameraDialogVisible = false">{{ t('取消') }}</el-button>
+        <el-button type="primary" :loading="cameraSubmitting" @click="handleSubmitCamera">{{ t('确定') }}</el-button>
+      </template>
+    </el-dialog>
+
     <!-- 分页 -->
     <div class="pagination-container">
       <el-pagination
@@ -168,12 +292,26 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted } from 'vue'
-import { Search, RefreshRight, Delete, View, Edit, VideoCamera } from '@element-plus/icons-vue'
+import { ref, reactive, onMounted, computed } from 'vue'
+import { Search, RefreshRight, Delete, View, Edit, VideoCamera, Plus } from '@element-plus/icons-vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { listLssNodes, deleteLssNode, setLssNodeEnabled } from '@/api/lss'
-import { adminListCameras } from '@/api/camera'
-import type { LssNodeDTO, LssNodeStatus, LssNodeListRequest, CameraInfoDTO } from '@/types'
+import {
+  adminListCameras,
+  adminAddCamera,
+  adminUpdateCamera,
+  adminDeleteCamera,
+  adminListAllCameras
+} from '@/api/camera'
+import type {
+  LssNodeDTO,
+  LssNodeStatus,
+  LssNodeListRequest,
+  CameraInfoDTO,
+  CameraAddRequest,
+  CameraUpdateRequest
+} from '@/types'
+import type { FormInstance, FormRules } from 'element-plus'
 import dayjs from 'dayjs'
 import { useI18n } from 'vue-i18n'
 
@@ -217,6 +355,38 @@ function formatTime(time: string | undefined): string {
   return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
 }
 
+// 格式化摄像头状态
+function formatCameraStatus(row: CameraInfoDTO): string {
+  if (row.status === 'ONLINE') {
+    return `active [${formatTime(row.updatedAt)}]`
+  } else {
+    return `dead (离线)`
+  }
+}
+
+// 格式化品牌
+function formatBrand(brand: string | undefined): string {
+  const brandMap: Record<string, string> = {
+    hikvision: 'HIKVISION',
+    dahua: 'DAHUA',
+    uniview: 'UNIVIEW',
+    other: '其他'
+  }
+  return brand ? brandMap[brand] || brand.toUpperCase() : '-'
+}
+
+// 查看参数配置
+function handleViewConfig(row: CameraInfoDTO) {
+  ElMessage.info(`查看 ${row.name} 的参数配置`)
+  // TODO: 打开参数配置弹窗
+}
+
+// 查看运行参数
+function handleViewRunParams(row: CameraInfoDTO) {
+  ElMessage.info(`查看 ${row.name} 的运行参数`)
+  // TODO: 打开运行参数弹窗
+}
+
 const loading = ref(false)
 const lssList = ref<(LssNodeDTO & { _switching?: boolean })[]>([])
 const tableRef = ref()
@@ -230,6 +400,44 @@ const cameraDrawerVisible = ref(false)
 const cameraLoading = ref(false)
 const cameraList = ref<CameraInfoDTO[]>([])
 
+// 摄像头搜索表单
+const cameraSearchForm = reactive({
+  keyword: '',
+  status: '' as 'ONLINE' | 'OFFLINE' | ''
+})
+
+// 摄像头编辑弹窗状态
+const cameraDialogVisible = ref(false)
+const cameraFormRef = ref<FormInstance>()
+const isEditCamera = ref(false)
+const cameraSubmitting = ref(false)
+const currentCamera = ref<CameraInfoDTO | null>(null)
+const availableCameras = ref<CameraInfoDTO[]>([])
+
+// 摄像头表单
+const cameraForm = reactive({
+  selectedCameraId: null as number | null,
+  cameraId: '',
+  name: '',
+  ip: '',
+  port: 80,
+  username: '',
+  password: '',
+  brand: '',
+  capability: 'switch_only' as 'switch_only' | 'ptz_enabled',
+  rtspUrl: '',
+  model: '',
+  channelNo: '',
+  remark: '',
+  enabled: true
+})
+
+// 摄像头表单验证规则(动态)
+const cameraRules = computed<FormRules>(() => ({
+  selectedCameraId: isEditCamera.value ? [] : [{ required: true, message: '请选择摄像头', trigger: 'change' }],
+  name: [{ required: false, message: '请输入名称', trigger: 'blur' }]
+}))
+
 // 排序状态
 const sortState = reactive<{
   sortBy: string
@@ -334,12 +542,26 @@ function handleEdit(row: LssNodeDTO) {
 
 async function handleCameraList(row: LssNodeDTO) {
   currentLss.value = row
+  cameraSearchForm.keyword = ''
+  cameraSearchForm.status = ''
   cameraDrawerVisible.value = true
+  await loadCameraList()
+}
+
+async function loadCameraList() {
+  if (!currentLss.value) return
   cameraLoading.value = true
   cameraList.value = []
 
   try {
-    const res = await adminListCameras({ lssId: row.lssId })
+    const params: any = { lssId: currentLss.value.lssId }
+    if (cameraSearchForm.keyword) {
+      params.keyword = cameraSearchForm.keyword
+    }
+    if (cameraSearchForm.status) {
+      params.status = cameraSearchForm.status
+    }
+    const res = await adminListCameras(params)
     if (res.success && res.data) {
       cameraList.value = res.data.list || []
     } else {
@@ -353,6 +575,162 @@ async function handleCameraList(row: LssNodeDTO) {
   }
 }
 
+function handleCameraSearch() {
+  loadCameraList()
+}
+
+function handleCameraReset() {
+  cameraSearchForm.keyword = ''
+  cameraSearchForm.status = ''
+  loadCameraList()
+}
+
+function resetCameraForm() {
+  cameraForm.selectedCameraId = null
+  cameraForm.cameraId = ''
+  cameraForm.name = ''
+  cameraForm.ip = ''
+  cameraForm.port = 80
+  cameraForm.username = ''
+  cameraForm.password = ''
+  cameraForm.brand = ''
+  cameraForm.capability = 'switch_only'
+  cameraForm.rtspUrl = ''
+  cameraForm.model = ''
+  cameraForm.channelNo = ''
+  cameraForm.remark = ''
+  cameraForm.enabled = true
+  cameraFormRef.value?.clearValidate()
+}
+
+async function loadAvailableCameras() {
+  try {
+    // 获取所有摄像头(不传 lssId 获取未绑定的)
+    const res = await adminListAllCameras()
+    if (res.success && res.data) {
+      // 过滤掉已绑定到当前 LSS 的摄像头
+      availableCameras.value = res.data.filter((cam) => !cam.lssId || cam.lssId !== currentLss.value?.lssId)
+    }
+  } catch (error) {
+    console.error('获取可用摄像头列表失败', error)
+  }
+}
+
+function handleCameraSelect(cameraId: number) {
+  const camera = availableCameras.value.find((cam) => cam.id === cameraId)
+  if (camera) {
+    cameraForm.cameraId = camera.cameraId
+    cameraForm.ip = camera.ip
+    cameraForm.name = camera.name || ''
+    cameraForm.port = camera.port || 80
+    cameraForm.username = camera.username || ''
+    cameraForm.brand = camera.brand || ''
+    cameraForm.capability = camera.capability || 'switch_only'
+    cameraForm.rtspUrl = camera.rtspUrl || ''
+    cameraForm.model = camera.model || ''
+    cameraForm.channelNo = camera.channelNo || ''
+    cameraForm.remark = camera.remark || ''
+    cameraForm.enabled = camera.enabled
+    currentCamera.value = camera
+  }
+}
+
+async function handleAddCamera() {
+  isEditCamera.value = false
+  currentCamera.value = null
+  resetCameraForm()
+  await loadAvailableCameras()
+  cameraDialogVisible.value = true
+}
+
+function handleEditCamera(row: CameraInfoDTO) {
+  isEditCamera.value = true
+  currentCamera.value = row
+  cameraForm.selectedCameraId = row.id
+  cameraForm.cameraId = row.cameraId
+  cameraForm.name = row.name
+  cameraForm.ip = row.ip
+  cameraForm.port = row.port || 80
+  cameraForm.username = row.username || ''
+  cameraForm.password = ''
+  cameraForm.brand = row.brand || ''
+  cameraForm.capability = row.capability || 'switch_only'
+  cameraForm.rtspUrl = row.rtspUrl || ''
+  cameraForm.model = row.model || ''
+  cameraForm.channelNo = row.channelNo || ''
+  cameraForm.remark = row.remark || ''
+  cameraForm.enabled = row.enabled
+  cameraDialogVisible.value = true
+}
+
+async function handleSubmitCamera() {
+  if (!cameraFormRef.value) return
+
+  await cameraFormRef.value.validate(async (valid) => {
+    if (!valid) return
+
+    cameraSubmitting.value = true
+    try {
+      if (!currentCamera.value) {
+        ElMessage.error('请选择摄像头')
+        return
+      }
+
+      // 无论新增还是编辑,都是更新摄像头信息(绑定 lssId)
+      const data: CameraUpdateRequest = {
+        id: currentCamera.value.id,
+        name: cameraForm.name,
+        port: cameraForm.port,
+        username: cameraForm.username,
+        brand: cameraForm.brand,
+        capability: cameraForm.capability,
+        lssId: currentLss.value?.lssId,
+        rtspUrl: cameraForm.rtspUrl,
+        model: cameraForm.model,
+        channelNo: cameraForm.channelNo,
+        remark: cameraForm.remark,
+        enabled: cameraForm.enabled
+      }
+      if (cameraForm.password) {
+        data.password = cameraForm.password
+      }
+      const res = await adminUpdateCamera(data)
+      if (res.success) {
+        ElMessage.success(isEditCamera.value ? '更新成功' : '绑定成功')
+        cameraDialogVisible.value = false
+        loadCameraList()
+      } else {
+        ElMessage.error(res.errMessage || '操作失败')
+      }
+    } catch (error) {
+      console.error('保存摄像头失败', error)
+      ElMessage.error('操作失败')
+    } finally {
+      cameraSubmitting.value = false
+    }
+  })
+}
+
+async function handleDeleteCamera(row: CameraInfoDTO) {
+  try {
+    await ElMessageBox.confirm(`确定要删除摄像头 "${row.name}" 吗?`, '提示', {
+      type: 'warning'
+    })
+    const res = await adminDeleteCamera(row.id)
+    if (res.success) {
+      ElMessage.success('删除成功')
+      loadCameraList()
+    } else {
+      ElMessage.error(res.errMessage || '删除失败')
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除摄像头失败', error)
+      ElMessage.error('删除失败')
+    }
+  }
+}
+
 async function handleToggleEnabled(row: LssNodeDTO & { _switching?: boolean }, enabled: boolean) {
   row._switching = true
   try {
@@ -462,6 +840,20 @@ onMounted(() => {
   }
 }
 
+.camera-toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 16px;
+
+  :deep(.el-form) {
+    .el-form-item {
+      margin-bottom: 0;
+      margin-right: 12px;
+    }
+  }
+}
+
 .camera-count {
   margin-top: 16px;
   text-align: right;
@@ -469,6 +861,22 @@ onMounted(() => {
   font-size: 14px;
 }
 
+.status-text {
+  font-size: 12px;
+}
+
+.status-active {
+  color: #67c23a;
+}
+
+.status-hold {
+  color: #e6a23c;
+}
+
+.status-dead {
+  color: #f56c6c;
+}
+
 // 抽屉样式
 :deep(.el-drawer) {
   .el-drawer__header {