|
@@ -1,768 +1,1080 @@
|
|
|
<template>
|
|
|
<div class="app-container">
|
|
|
- <el-tabs v-model="activeTab">
|
|
|
- <!-- 摄像机管理标签页 -->
|
|
|
- <el-tab-pane label="摄像机管理" name="cameras">
|
|
|
- <el-form :model="cameraQuery" ref="cameraQueryRef" :inline="true" label-width="98px">
|
|
|
- <el-form-item label="摄像机编号" prop="cameraCode">
|
|
|
- <el-input v-model="cameraQuery.cameraCode" placeholder="请输入摄像机编号" clearable />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="摄像机名称" prop="cameraName">
|
|
|
- <el-input v-model="cameraQuery.cameraName" placeholder="请输入摄像机名称" clearable />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="监控区域" prop="monitorArea">
|
|
|
- <el-input v-model="cameraQuery.monitorArea" placeholder="请输入监控区域" clearable />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="楼栋" prop="building">
|
|
|
- <el-select v-model="cameraQuery.building" placeholder="请选择楼栋" clearable style="width: 180px;">
|
|
|
- <el-option label="A栋" value="A" />
|
|
|
- <el-option label="B栋" value="B" />
|
|
|
- <el-option label="C栋" value="C" />
|
|
|
- <el-option label="D栋" value="D" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="设备状态" prop="deviceStatus">
|
|
|
- <el-select v-model="cameraQuery.deviceStatus" placeholder="请选择状态" clearable style="width: 180px;">
|
|
|
- <el-option label="在线" :value="1" />
|
|
|
- <el-option label="离线" :value="0" />
|
|
|
- <el-option label="故障" :value="2" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button type="primary" icon="Search" @click="getCameraList">搜索</el-button>
|
|
|
- <el-button icon="Refresh" @click="resetCameraQuery">重置</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
-
|
|
|
- <el-table v-loading="cameraLoading" :data="cameraList" stripe border>
|
|
|
- <el-table-column label="摄像机编号" prop="cameraCode" width="150" />
|
|
|
- <el-table-column label="摄像机名称" prop="cameraName" />
|
|
|
- <el-table-column label="监控区域" prop="monitorArea" width="120" />
|
|
|
- <el-table-column label="位置信息" width="150">
|
|
|
- <template #default="scope">
|
|
|
- <div>{{ scope.row.building }}栋-{{ scope.row.floor }}层</div>
|
|
|
- <div class="text-gray-500">{{ scope.row.location_detail }}</div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="IP地址" prop="ipAddress" width="120" />
|
|
|
- <el-table-column label="端口" prop="port" width="80" />
|
|
|
- <el-table-column label="品牌型号" width="120">
|
|
|
- <template #default="scope">
|
|
|
- <div>{{ scope.row.brand }}</div>
|
|
|
- <div class="text-gray-500">{{ scope.row.model }}</div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="分辨率" prop="resolution" width="100" />
|
|
|
- <el-table-column label="设备状态" prop="deviceStatus" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : scope.row.deviceStatus === 2 ? 'warning' : 'danger'">
|
|
|
- {{ getStatusText(scope.row.deviceStatus) }}
|
|
|
- </el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="云台控制" prop="ptzSupport" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <el-tag :type="scope.row.ptzSupport === 1 ? 'success' : 'info'">
|
|
|
- {{ scope.row.ptzSupport === 1 ? '支持' : '不支持' }}
|
|
|
- </el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="录像状态" prop="recordingStatus" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <el-tag :type="scope.row.recordingStatus === 1 ? 'success' : 'danger'">
|
|
|
- {{ scope.row.recordingStatus === 1 ? '录像中' : '未录像' }}
|
|
|
- </el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="操作" width="400" fixed="right">
|
|
|
- <template #default="scope">
|
|
|
- <el-button link type="primary" icon="VideoCamera" @click="handleVideoPreview(scope.row)">实时预览</el-button>
|
|
|
- <el-button link type="success" icon="Setting" @click="handlePtzControl(scope.row)" v-if="scope.row.ptzSupport === 1">云台控制</el-button>
|
|
|
- <el-button link type="warning" icon="View" @click="handleCameraDetail(scope.row)">详情</el-button>
|
|
|
- <el-button link type="info" icon="VideoPlay" @click="handlePlayback(scope.row)">录像回放</el-button>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
-
|
|
|
- <pagination
|
|
|
- v-show="cameraTotal > 0"
|
|
|
- :total="cameraTotal"
|
|
|
- v-model:page="cameraQuery.pageNum"
|
|
|
- v-model:limit="cameraQuery.pageSize"
|
|
|
- @pagination="getCameraList"
|
|
|
- />
|
|
|
- </el-tab-pane>
|
|
|
-
|
|
|
- <!-- 告警联动标签页 -->
|
|
|
- <el-tab-pane label="告警联动" name="linkage">
|
|
|
- <el-form :model="linkageQuery" ref="linkageQueryRef" :inline="true" label-width="98px">
|
|
|
- <el-form-item label="联动规则名称" prop="ruleName">
|
|
|
- <el-input v-model="linkageQuery.ruleName" placeholder="请输入联动规则名称" clearable />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="触发设备" prop="triggerDevice">
|
|
|
- <el-input v-model="linkageQuery.triggerDevice" placeholder="请输入触发设备" clearable />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="联动摄像机" prop="linkageCamera">
|
|
|
- <el-input v-model="linkageQuery.linkageCamera" placeholder="请输入联动摄像机" clearable />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="规则状态" prop="ruleStatus">
|
|
|
- <el-select v-model="linkageQuery.ruleStatus" placeholder="请选择状态" clearable style="width: 180px;">
|
|
|
- <el-option label="启用" :value="1" />
|
|
|
- <el-option label="禁用" :value="0" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button type="primary" icon="Search" @click="getLinkageList">搜索</el-button>
|
|
|
- <el-button icon="Refresh" @click="resetLinkageQuery">重置</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
-
|
|
|
- <el-row :gutter="10" class="mb8">
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- plain
|
|
|
- icon="Plus"
|
|
|
- @click="handleAddLinkage"
|
|
|
- >新增联动规则</el-button>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <el-table v-loading="linkageLoading" :data="linkageList" stripe border>
|
|
|
- <el-table-column label="规则名称" prop="rule_name" />
|
|
|
- <el-table-column label="触发设备" prop="trigger_device_name" />
|
|
|
- <el-table-column label="触发条件" prop="trigger_condition" />
|
|
|
- <el-table-column label="联动摄像机" prop="linkage_camera_name" />
|
|
|
- <el-table-column label="联动动作" prop="linkage_action" >
|
|
|
- <template #default="scope">
|
|
|
- <el-tag>{{ getLinkageActionText(scope.row.linkage_action) }}</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="延迟时间" prop="delayTime" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <span>{{ scope.row.delay_time }}秒</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="规则状态" prop="ruleStatus" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <el-tag :type="scope.row.ruleStatus === 1 ? 'success' : 'danger'">
|
|
|
- {{ scope.row.ruleStatus === 1 ? '启用' : '禁用' }}
|
|
|
+ <!-- 搜索和操作区域 -->
|
|
|
+ <div class="search-container">
|
|
|
+ <el-card shadow="never" class="search-card">
|
|
|
+ <div class="search-form">
|
|
|
+ <el-form :inline="true" :model="searchForm" class="search-form-inline">
|
|
|
+ <el-form-item label="摄像机名称">
|
|
|
+ <el-input
|
|
|
+ v-model="searchForm.name"
|
|
|
+ placeholder="请输入摄像机名称"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ style="width: 200px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+<!-- <el-form-item label="监控点类型">
|
|
|
+ <el-select
|
|
|
+ v-model="searchForm.cameraType"
|
|
|
+ placeholder="请选择类型"
|
|
|
+ clearable
|
|
|
+ style="width: 150px"
|
|
|
+ >
|
|
|
+ <el-option label="全部" value="" />
|
|
|
+ <el-option label="枪机" :value="0" />
|
|
|
+ <el-option label="半球" :value="1" />
|
|
|
+ <el-option label="快球" :value="2" />
|
|
|
+ <el-option label="带云台枪机" :value="3" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>-->
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" icon="Search" @click="handleSearch">
|
|
|
+ 搜索
|
|
|
+ </el-button>
|
|
|
+ <el-button icon="Refresh" @click="resetSearch">
|
|
|
+ 重置
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+<!-- <div class="search-actions">
|
|
|
+ <el-button type="primary" icon="Refresh" @click="getCameraList" class="refresh-btn">
|
|
|
+ <span>刷新列表</span>
|
|
|
+ </el-button>
|
|
|
+ <div class="camera-stats">
|
|
|
+ <span class="stat-item">
|
|
|
+ <i class="el-icon-video-camera"></i>
|
|
|
+ 总计 <span class="stat-number">{{ cameraTotal }}</span> 个摄像机
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>-->
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 摄像机列表 -->
|
|
|
+ <el-card shadow="never" class="table-card">
|
|
|
+ <el-table
|
|
|
+ v-loading="cameraLoading"
|
|
|
+ :data="cameraList"
|
|
|
+ stripe
|
|
|
+ :header-cell-style="{background:'#f5f7fa',color:'#606266'}"
|
|
|
+ class="camera-table"
|
|
|
+ >
|
|
|
+ <el-table-column label="摄像机信息" min-width="300">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="camera-info">
|
|
|
+ <div class="camera-name">{{ scope.row.name }}</div>
|
|
|
+ <div class="camera-code">编号:{{ scope.row.indexCode }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="设备详情" min-width="200">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="device-details">
|
|
|
+ <el-tag
|
|
|
+ :type="getCameraTypeTagType(scope.row.cameraType)"
|
|
|
+ size="small"
|
|
|
+ effect="plain"
|
|
|
+ >
|
|
|
+ {{ getCameraTypeName(scope.row.cameraType) }}
|
|
|
</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="创建时间" prop="create_time" width="150">
|
|
|
- <template #default="scope">
|
|
|
- <span>{{ parseTime(scope.row.create_time) }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="操作" width="250" fixed="right">
|
|
|
- <template #default="scope">
|
|
|
- <el-button link type="primary" icon="Edit" @click="handleEditLinkage(scope.row)">修改</el-button>
|
|
|
- <el-button link type="danger" icon="Delete" @click="handleDeleteLinkage(scope.row)">删除</el-button>
|
|
|
- <el-button link type="warning" icon="Switch" @click="handleToggleLinkage(scope.row)">
|
|
|
- {{ scope.row.ruleStatus === 1 ? '禁用' : '启用' }}
|
|
|
+ <span class="detail-item">通道数 {{ scope.row.chanNum }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="技术参数" min-width="200">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="tech-params">
|
|
|
+ <span class="param-item">
|
|
|
+ <i class="el-icon-connection"></i>
|
|
|
+ {{ getTransTypeName(scope.row.transType) }}
|
|
|
+ </span>
|
|
|
+ <span class="param-item">
|
|
|
+ <i class="el-icon-folder"></i>
|
|
|
+ {{ getRecordLocationName(scope.row.recordLocation) }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="区域位置" min-width="250">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="location-info">
|
|
|
+ <div class="region-name">{{ scope.row.regionName }}</div>
|
|
|
+ <div class="region-path">{{ formatRegionPath(scope.row.regionPathName) }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="创建时间" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <span class="time-text">{{ parseTime(scope.row.createTime) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="操作" width="200" fixed="right">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="action-buttons">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ icon="VideoPlay"
|
|
|
+ @click="handleVideoPreview(scope.row)"
|
|
|
+ class="action-btn preview-btn"
|
|
|
+ >
|
|
|
+ 实时预览
|
|
|
</el-button>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
-
|
|
|
- <pagination
|
|
|
- v-show="linkageTotal > 0"
|
|
|
- :total="linkageTotal"
|
|
|
- v-model:page="linkageQuery.pageNum"
|
|
|
- v-model:limit="linkageQuery.pageSize"
|
|
|
- @pagination="getLinkageList"
|
|
|
- />
|
|
|
- </el-tab-pane>
|
|
|
- </el-tabs>
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ icon="InfoFilled"
|
|
|
+ @click="handleCameraDetail(scope.row)"
|
|
|
+ class="action-btn detail-btn"
|
|
|
+ >
|
|
|
+ 详情
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <pagination
|
|
|
+ v-show="cameraTotal > 0"
|
|
|
+ :total="cameraTotal"
|
|
|
+ v-model:page="cameraQuery.pageNo"
|
|
|
+ v-model:limit="cameraQuery.pageSize"
|
|
|
+ @pagination="getCameraList"
|
|
|
+ class="pagination-wrapper"
|
|
|
+ />
|
|
|
+ </el-card>
|
|
|
|
|
|
<!-- 实时视频预览抽屉 -->
|
|
|
<el-drawer
|
|
|
v-model="videoVisible"
|
|
|
- title="实时视频预览"
|
|
|
+ :title="currentCamera.name"
|
|
|
direction="rtl"
|
|
|
- size="60%"
|
|
|
+ size="70%"
|
|
|
+ class="video-drawer"
|
|
|
>
|
|
|
- <div class="video-container">
|
|
|
- <div class="video-player">
|
|
|
- <video ref="videoPlayer" controls autoplay muted style="width: 100%; height: 400px; background: #000;">
|
|
|
- <source :src="currentVideoUrl" type="application/x-mpegURL">
|
|
|
- 您的浏览器不支持视频播放
|
|
|
- </video>
|
|
|
- </div>
|
|
|
- <div class="video-info">
|
|
|
- <el-descriptions :column="2" border>
|
|
|
- <el-descriptions-item label="摄像机名称">{{ currentCamera.cameraName }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="监控区域">{{ currentCamera.monitorArea }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="位置">{{ currentCamera.building }}栋-{{ currentCamera.floor }}层</el-descriptions-item>
|
|
|
- <el-descriptions-item label="分辨率">{{ currentCamera.resolution }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备状态">
|
|
|
- <el-tag :type="currentCamera.deviceStatus === 1 ? 'success' : 'danger'">
|
|
|
- {{ getStatusText(currentCamera.deviceStatus) }}
|
|
|
- </el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="录像状态">
|
|
|
- <el-tag :type="currentCamera.recordingStatus === 1 ? 'success' : 'danger'">
|
|
|
- {{ currentCamera.recordingStatus === 1 ? '录像中' : '未录像' }}
|
|
|
- </el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
+ <div class="video-wrapper">
|
|
|
+ <!-- 视频播放区域 -->
|
|
|
+ <div class="video-player-container">
|
|
|
+ <div class="video-header">
|
|
|
+ <h3 class="video-title">
|
|
|
+ <i class="el-icon-video-camera"></i>
|
|
|
+ 实时画面
|
|
|
+ </h3>
|
|
|
+ <el-button type="success" size="small" icon="Camera" @click="takeSnapshot">
|
|
|
+ 抓拍
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="video-player">
|
|
|
+ <iframe
|
|
|
+ v-if="currentVideoUrl"
|
|
|
+ :src="currentVideoUrl"
|
|
|
+ class="video-iframe"
|
|
|
+ allowfullscreen
|
|
|
+ ></iframe>
|
|
|
+ <div v-else class="video-placeholder">
|
|
|
+ <i class="el-icon-video-camera"></i>
|
|
|
+ <p>正在加载视频流...</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="video-controls">
|
|
|
- <el-button type="primary" @click="startRecording" v-if="currentCamera.recordingStatus === 0">开始录像</el-button>
|
|
|
- <el-button type="danger" @click="stopRecording" v-if="currentCamera.recordingStatus === 1">停止录像</el-button>
|
|
|
- <el-button type="success" @click="takeSnapshot">抓拍</el-button>
|
|
|
- <el-button type="warning" @click="handlePtzControl(currentCamera)" v-if="currentCamera.ptzSupport === 1">云台控制</el-button>
|
|
|
+
|
|
|
+ <!-- 摄像机信息展示 -->
|
|
|
+ <div class="camera-details-panel">
|
|
|
+ <h4 class="panel-title">摄像机信息</h4>
|
|
|
+ <div class="info-grid">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">摄像机名称</span>
|
|
|
+ <span class="info-value">{{ currentCamera.name }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">设备编号</span>
|
|
|
+ <span class="info-value">{{ currentCamera.indexCode }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">监控点类型</span>
|
|
|
+ <span class="info-value">
|
|
|
+ <el-tag
|
|
|
+ :type="getCameraTypeTagType(currentCamera.cameraType)"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ {{ getCameraTypeName(currentCamera.cameraType) }}
|
|
|
+ </el-tag>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">通道数</span>
|
|
|
+ <span class="info-value">{{ currentCamera.chanNum }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">传输协议</span>
|
|
|
+ <span class="info-value">{{ getTransTypeName(currentCamera.transType) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">录像位置</span>
|
|
|
+ <span class="info-value">{{ getRecordLocationName(currentCamera.recordLocation) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-drawer>
|
|
|
|
|
|
- <!-- 云台控制抽屉 -->
|
|
|
+ <!-- 摄像机详情抽屉 -->
|
|
|
<el-drawer
|
|
|
- v-model="ptzVisible"
|
|
|
- title="云台控制"
|
|
|
+ v-model="detailVisible"
|
|
|
+ title="摄像机详细信息"
|
|
|
direction="rtl"
|
|
|
- size="40%"
|
|
|
+ size="50%"
|
|
|
+ class="detail-drawer"
|
|
|
>
|
|
|
- <div class="ptz-container">
|
|
|
- <div class="ptz-direction">
|
|
|
- <h4>方向控制</h4>
|
|
|
- <div class="direction-grid">
|
|
|
- <div class="direction-row">
|
|
|
- <el-button @click="ptzControl('UP')" icon="ArrowUp" class="direction-btn">上</el-button>
|
|
|
+ <div class="detail-wrapper">
|
|
|
+ <div class="detail-section">
|
|
|
+ <h4 class="section-title">基本信息</h4>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">摄像机名称</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.name }}</span>
|
|
|
</div>
|
|
|
- <div class="direction-row">
|
|
|
- <el-button @click="ptzControl('LEFT')" icon="ArrowLeft" class="direction-btn">左</el-button>
|
|
|
- <el-button @click="ptzControl('STOP')" icon="Close" class="direction-btn stop-btn">停止</el-button>
|
|
|
- <el-button @click="ptzControl('RIGHT')" icon="ArrowRight" class="direction-btn">右</el-button>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">摄像机编号</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.indexCode }}</span>
|
|
|
</div>
|
|
|
- <div class="direction-row">
|
|
|
- <el-button @click="ptzControl('DOWN')" icon="ArrowDown" class="direction-btn">下</el-button>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">外部编号</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.externalIndexCode || '无' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">资源类型</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.resourceType }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="ptz-zoom">
|
|
|
- <h4>变焦控制</h4>
|
|
|
- <div class="zoom-controls">
|
|
|
- <el-button @click="ptzControl('ZOOM_IN')" icon="ZoomIn">放大</el-button>
|
|
|
- <el-button @click="ptzControl('ZOOM_OUT')" icon="ZoomOut">缩小</el-button>
|
|
|
+ <div class="detail-section">
|
|
|
+ <h4 class="section-title">技术参数</h4>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">监控点类型</span>
|
|
|
+ <span class="detail-value">
|
|
|
+ <el-tag
|
|
|
+ :type="getCameraTypeTagType(currentCamera.cameraType)"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ {{ getCameraTypeName(currentCamera.cameraType) }}
|
|
|
+ </el-tag>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">通道类型</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.channelType }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">传输类型</span>
|
|
|
+ <span class="detail-value">{{ getTransTypeName(currentCamera.transType) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">协议类型</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.treatyType }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">解码标签</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.decodeTag }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">录像位置</span>
|
|
|
+ <span class="detail-value">{{ getRecordLocationName(currentCamera.recordLocation) }}</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="ptz-focus">
|
|
|
- <h4>聚焦控制</h4>
|
|
|
- <div class="focus-controls">
|
|
|
- <el-button @click="ptzControl('FOCUS_NEAR')" icon="Minus">近焦</el-button>
|
|
|
- <el-button @click="ptzControl('FOCUS_FAR')" icon="Plus">远焦</el-button>
|
|
|
- <el-button @click="ptzControl('AUTO_FOCUS')" icon="Aim">自动聚焦</el-button>
|
|
|
+ <div class="detail-section">
|
|
|
+ <h4 class="section-title">位置信息</h4>
|
|
|
+ <div class="detail-grid">
|
|
|
+<!-- <div class="detail-item full-width">
|
|
|
+ <span class="detail-label">安装位置</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.installLocation || '未设置' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">经度</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.longitude || '未设置' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">纬度</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.latitude || '未设置' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">海拔</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.elevation || '未设置' }}</span>
|
|
|
+ </div>-->
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">所属区域</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.regionName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">区域路径</span>
|
|
|
+ <span class="detail-value">{{ formatRegionPath(currentCamera.regionPathName) }}</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="ptz-preset">
|
|
|
- <h4>预置位控制</h4>
|
|
|
- <el-form :model="presetForm" :inline="true">
|
|
|
- <el-form-item label="预置位">
|
|
|
- <el-select v-model="presetForm.presetId" placeholder="选择预置位">
|
|
|
- <el-option
|
|
|
- v-for="preset in presetList"
|
|
|
- :key="preset.id"
|
|
|
- :label="preset.name"
|
|
|
- :value="preset.id"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button @click="gotoPreset">转到</el-button>
|
|
|
- <el-button @click="setPreset">设置</el-button>
|
|
|
- <el-button @click="deletePreset">删除</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
+ <div class="detail-section">
|
|
|
+ <h4 class="section-title">功能能力</h4>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item full-width">
|
|
|
+ <span class="detail-label">支持功能</span>
|
|
|
+ <span class="detail-value">{{ formatCapabilities(currentCamera.capability) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="ptz-speed">
|
|
|
- <h4>控制速度</h4>
|
|
|
- <el-slider v-model="ptzSpeed" :min="1" :max="10" show-input />
|
|
|
+ <div class="detail-section">
|
|
|
+ <h4 class="section-title">其他信息</h4>
|
|
|
+ <div class="detail-grid">
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">父级编号</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.parentIndexCode }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">级联编码</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.cascadeCode || '无' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">区域编号</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.regionIndexCode }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">排序号</span>
|
|
|
+ <span class="detail-value">{{ currentCamera.disOrder }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">创建时间</span>
|
|
|
+ <span class="detail-value">{{ parseTime(currentCamera.createTime) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">更新时间</span>
|
|
|
+ <span class="detail-value">{{ parseTime(currentCamera.updateTime) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-drawer>
|
|
|
-
|
|
|
- <!-- 摄像机详情抽屉 -->
|
|
|
- <el-drawer
|
|
|
- v-model="detailVisible"
|
|
|
- title="摄像机详情"
|
|
|
- direction="rtl"
|
|
|
- size="50%"
|
|
|
- >
|
|
|
- <el-descriptions :column="2" border>
|
|
|
- <el-descriptions-item label="摄像机编号">{{ currentCamera.cameraCode }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="摄像机名称">{{ currentCamera.cameraName }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="监控区域">{{ currentCamera.monitorArea }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="位置">{{ currentCamera.building }}栋-{{ currentCamera.floor }}层-{{ currentCamera.locationDetail }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="IP地址">{{ currentCamera.ipAddress }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="端口">{{ currentCamera.port }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="品牌">{{ currentCamera.brand }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="型号">{{ currentCamera.model }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="分辨率">{{ currentCamera.resolution }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="帧率">{{ currentCamera.frameRate }} fps</el-descriptions-item>
|
|
|
- <el-descriptions-item label="云台支持">
|
|
|
- <el-tag :type="currentCamera.ptzSupport === 1 ? 'success' : 'info'">
|
|
|
- {{ currentCamera.ptzSupport === 1 ? '支持' : '不支持' }}
|
|
|
- </el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="红外夜视">
|
|
|
- <el-tag :type="currentCamera.infraredSupport === 1 ? 'success' : 'info'">
|
|
|
- {{ currentCamera.infraredSupport === 1 ? '支持' : '不支持' }}
|
|
|
- </el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备状态">
|
|
|
- <el-tag :type="currentCamera.deviceStatus === 1 ? 'success' : currentCamera.deviceStatus === 2 ? 'warning' : 'danger'">
|
|
|
- {{ getStatusText(currentCamera.deviceStatus) }}
|
|
|
- </el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="录像状态">
|
|
|
- <el-tag :type="currentCamera.recordingStatus === 1 ? 'success' : 'danger'">
|
|
|
- {{ currentCamera.recordingStatus === 1 ? '录像中' : '未录像' }}
|
|
|
- </el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="存储路径">{{ currentCamera.storagePath }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="最后在线时间">{{ parseTime(currentCamera.lastOnlineTime) }}</el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
- </el-drawer>
|
|
|
-
|
|
|
- <!-- 联动规则配置对话框 -->
|
|
|
- <el-dialog v-model="linkageDialogVisible" :title="linkageDialogTitle" width="600px">
|
|
|
- <el-form :model="linkageForm" :rules="linkageRules" ref="linkageFormRef" label-width="120px">
|
|
|
- <el-form-item label="规则名称" prop="ruleName">
|
|
|
- <el-input v-model="linkageForm.ruleName" placeholder="请输入联动规则名称" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="触发设备" prop="triggerDeviceCode">
|
|
|
- <el-select v-model="linkageForm.triggerDeviceCode" placeholder="请选择触发设备" filterable>
|
|
|
- <el-option
|
|
|
- v-for="device in triggerDeviceList"
|
|
|
- :key="device.code"
|
|
|
- :label="device.name"
|
|
|
- :value="device.code"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="触发条件" prop="triggerCondition">
|
|
|
- <el-select v-model="linkageForm.triggerCondition" placeholder="请选择触发条件">
|
|
|
- <el-option label="设备报警" value="deviceAlarm" />
|
|
|
- <el-option label="入侵检测" value="intrusionDetection" />
|
|
|
- <el-option label="门禁异常" value="accessAbnormal" />
|
|
|
- <el-option label="火灾报警" value="fireAlarm" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="联动摄像机" prop="linkageCameraCode">
|
|
|
- <el-select v-model="linkageForm.linkageCameraCode" placeholder="请选择联动摄像机" filterable>
|
|
|
- <el-option
|
|
|
- v-for="camera in cameraList"
|
|
|
- :key="camera.cameraCode"
|
|
|
- :label="camera.cameraName"
|
|
|
- :value="camera.cameraCode"
|
|
|
- />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="联动动作" prop="linkageAction">
|
|
|
- <el-select v-model="linkageForm.linkageAction" placeholder="请选择联动动作">
|
|
|
- <el-option label="开始录像" value="start_recording" />
|
|
|
- <el-option label="转到预置位" value="goto_preset" />
|
|
|
- <el-option label="自动跟踪" value="auto_tracking" />
|
|
|
- <el-option label="抓拍图片" value="capture_image" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="延迟时间" prop="delayTime">
|
|
|
- <el-input-number v-model="linkageForm.delayTime" :min="0" :max="300" controls-position="right" />
|
|
|
- <span style="margin-left: 8px;">秒</span>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="规则状态" prop="ruleStatus">
|
|
|
- <el-radio-group v-model="linkageForm.ruleStatus">
|
|
|
- <el-radio :label="1">启用</el-radio>
|
|
|
- <el-radio :label="0">禁用</el-radio>
|
|
|
- </el-radio-group>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- <template #footer>
|
|
|
- <span class="dialog-footer">
|
|
|
- <el-button @click="linkageDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" @click="submitLinkageForm">确定</el-button>
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, reactive, watch, onMounted } from 'vue'
|
|
|
-import { listCameras, getCameraStream, ptzControl as ptzControlApi, recordingControl, takeCameraSnapshot } from '@/api/subsystem/video'
|
|
|
-import { listLinkageRules, addLinkageRule, updateLinkageRule, deleteLinkageRule, toggleLinkageRule } from '@/api/subsystem/video'
|
|
|
+import { ref, reactive, onMounted } from 'vue'
|
|
|
+import axios from 'axios'
|
|
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
-const activeTab = ref('cameras')
|
|
|
|
|
|
-// 监听activeTab的变化
|
|
|
-watch(activeTab, (newVal) => {
|
|
|
- if (newVal === 'linkage') {
|
|
|
- getLinkageList()
|
|
|
- }
|
|
|
+// 搜索表单
|
|
|
+const searchForm = reactive({
|
|
|
+ name: '',
|
|
|
+ cameraType: ''
|
|
|
})
|
|
|
|
|
|
// 摄像机查询相关
|
|
|
const cameraQuery = reactive({
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 10,
|
|
|
- cameraCode: null,
|
|
|
- cameraName: null,
|
|
|
- monitorArea: null,
|
|
|
- building: null,
|
|
|
- deviceStatus: null
|
|
|
+ pageNo: 1,
|
|
|
+ pageSize: 10
|
|
|
})
|
|
|
const cameraList = ref([])
|
|
|
const cameraTotal = ref(0)
|
|
|
const cameraLoading = ref(false)
|
|
|
|
|
|
-// 联动规则查询相关
|
|
|
-const linkageQuery = reactive({
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 10,
|
|
|
- ruleName: null,
|
|
|
- triggerDevice: null,
|
|
|
- linkageCamera: null,
|
|
|
- ruleStatus: null
|
|
|
-})
|
|
|
-const linkageList = ref([])
|
|
|
-const linkageTotal = ref(0)
|
|
|
-const linkageLoading = ref(false)
|
|
|
-
|
|
|
// 视频预览相关
|
|
|
const videoVisible = ref(false)
|
|
|
const currentCamera = ref({})
|
|
|
const currentVideoUrl = ref('')
|
|
|
|
|
|
-// 云台控制相关
|
|
|
-const ptzVisible = ref(false)
|
|
|
-const ptzSpeed = ref(5)
|
|
|
-const presetForm = ref({
|
|
|
- presetId: null
|
|
|
-})
|
|
|
-const presetList = ref([
|
|
|
- { id: 1, name: '预置位1' },
|
|
|
- { id: 2, name: '预置位2' },
|
|
|
- { id: 3, name: '预置位3' }
|
|
|
-])
|
|
|
-
|
|
|
// 详情相关
|
|
|
const detailVisible = ref(false)
|
|
|
|
|
|
-// 联动规则配置相关
|
|
|
-const linkageDialogVisible = ref(false)
|
|
|
-const linkageDialogTitle = ref('')
|
|
|
-const linkageForm = ref({
|
|
|
- id: null,
|
|
|
- ruleName: '',
|
|
|
- triggerDeviceCode: '',
|
|
|
- triggerCondition: '',
|
|
|
- linkageCameraCode: '',
|
|
|
- linkageAction: '',
|
|
|
- delayTime: 0,
|
|
|
- ruleStatus: 1
|
|
|
-})
|
|
|
-const linkageRules = {
|
|
|
- ruleName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
|
|
- triggerDeviceCode: [{ required: true, message: '请选择触发设备', trigger: 'change' }],
|
|
|
- triggerCondition: [{ required: true, message: '请选择触发条件', trigger: 'change' }],
|
|
|
- linkageCameraCode: [{ required: true, message: '请选择联动摄像机', trigger: 'change' }],
|
|
|
- linkageAction: [{ required: true, message: '请选择联动动作', trigger: 'change' }]
|
|
|
-}
|
|
|
-
|
|
|
-// 触发设备列表(模拟数据,实际应从其他子系统获取)
|
|
|
-const triggerDeviceList = ref([
|
|
|
- { code: 'ALARM001', name: '火灾报警器001' },
|
|
|
- { code: 'DOOR001', name: '门禁001' },
|
|
|
- { code: 'MOTION001', name: '红外探测器001' }
|
|
|
-])
|
|
|
-
|
|
|
-// 设备状态文本
|
|
|
-const getStatusText = (status) => {
|
|
|
- const texts = ['离线', '在线', '故障']
|
|
|
- return texts[status] || '未知'
|
|
|
-}
|
|
|
-
|
|
|
-// 联动动作文本
|
|
|
-const getLinkageActionText = (action) => {
|
|
|
- const texts = {
|
|
|
- 'start_recording': '开始录像',
|
|
|
- 'goto_preset': '转到预置位',
|
|
|
- 'auto_tracking': '自动跟踪',
|
|
|
- 'capture_image': '抓拍图片'
|
|
|
- }
|
|
|
- return texts[action] || '未知'
|
|
|
-}
|
|
|
-
|
|
|
// 查询摄像机列表
|
|
|
function getCameraList() {
|
|
|
cameraLoading.value = true
|
|
|
+
|
|
|
const params = {
|
|
|
- ...cameraQuery,
|
|
|
- pageNum: cameraQuery.pageNum,
|
|
|
+ pageNo: cameraQuery.pageNo,
|
|
|
pageSize: cameraQuery.pageSize
|
|
|
}
|
|
|
- listCameras(params).then(response => {
|
|
|
- cameraList.value = response.rows
|
|
|
- cameraTotal.value = response.total
|
|
|
+
|
|
|
+ // 添加搜索条件
|
|
|
+ if (searchForm.name) {
|
|
|
+ params.name = searchForm.name
|
|
|
+ }
|
|
|
+ if (searchForm.cameraType !== '') {
|
|
|
+ params.cameraType = searchForm.cameraType
|
|
|
+ }
|
|
|
+
|
|
|
+ axios.get(`http://${__LOCAL_API__}/Hikvision/getCameraSearch`, {
|
|
|
+ params: params
|
|
|
+ }).then(response => {
|
|
|
+ if (response.data.code === 200) {
|
|
|
+ cameraList.value = response.data.data.list
|
|
|
+ cameraTotal.value = response.data.data.total
|
|
|
+ }
|
|
|
cameraLoading.value = false
|
|
|
+ }).catch(error => {
|
|
|
+ console.error('获取摄像机列表失败:', error)
|
|
|
+ cameraLoading.value = false
|
|
|
+ proxy.$modal.msgError('获取摄像机列表失败')
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-// 重置摄像机查询
|
|
|
-function resetCameraQuery() {
|
|
|
- proxy.resetForm('cameraQueryRef')
|
|
|
- cameraQuery.pageNum = 1
|
|
|
+// 搜索
|
|
|
+function handleSearch() {
|
|
|
+ cameraQuery.pageNo = 1
|
|
|
getCameraList()
|
|
|
}
|
|
|
|
|
|
-// 查询联动规则列表
|
|
|
-function getLinkageList() {
|
|
|
- linkageLoading.value = true
|
|
|
- const params = {
|
|
|
- ...linkageQuery,
|
|
|
- pageNum: linkageQuery.pageNum,
|
|
|
- pageSize: linkageQuery.pageSize
|
|
|
- }
|
|
|
- listLinkageRules(params).then(response => {
|
|
|
- linkageList.value = response.rows
|
|
|
- linkageTotal.value = response.total
|
|
|
- linkageLoading.value = false
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-// 重置联动规则查询
|
|
|
-function resetLinkageQuery() {
|
|
|
- proxy.resetForm('linkageQueryRef')
|
|
|
- linkageQuery.pageNum = 1
|
|
|
- getLinkageList()
|
|
|
+// 重置搜索
|
|
|
+function resetSearch() {
|
|
|
+ searchForm.name = ''
|
|
|
+ searchForm.cameraType = ''
|
|
|
+ handleSearch()
|
|
|
}
|
|
|
|
|
|
// 实时视频预览
|
|
|
function handleVideoPreview(row) {
|
|
|
currentCamera.value = row
|
|
|
videoVisible.value = true
|
|
|
-
|
|
|
- // 获取视频流地址
|
|
|
- getCameraStream(row.camera_code).then(response => {
|
|
|
- currentVideoUrl.value = response.data.streamUrl
|
|
|
- })
|
|
|
+ // 使用indexCode作为摄像机标识
|
|
|
+ currentVideoUrl.value = `http://${__LOCAL_API__}/Hikvision/getMonitoring?cameraIndexCode=${row.indexCode}`
|
|
|
}
|
|
|
|
|
|
-// 云台控制
|
|
|
-function handlePtzControl(row) {
|
|
|
+// 查看摄像机详情
|
|
|
+function handleCameraDetail(row) {
|
|
|
currentCamera.value = row
|
|
|
- ptzVisible.value = true
|
|
|
+ detailVisible.value = true
|
|
|
}
|
|
|
|
|
|
-// 云台方向控制
|
|
|
-function ptzControl(direction) {
|
|
|
- const params = {
|
|
|
- cameraCode: currentCamera.value.cameraCode,
|
|
|
- command: direction,
|
|
|
- speed: ptzSpeed.value
|
|
|
+// 抓拍
|
|
|
+function takeSnapshot() {
|
|
|
+ proxy.$modal.msgSuccess('抓拍成功,图片已保存')
|
|
|
+}
|
|
|
+
|
|
|
+// 时间格式化
|
|
|
+function parseTime(time) {
|
|
|
+ if (!time) return ''
|
|
|
+ const date = new Date(time)
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ const hours = String(date.getHours()).padStart(2, '0')
|
|
|
+ const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
|
+ return `${year}-${month}-${day} ${hours}:${minutes}`
|
|
|
+}
|
|
|
+
|
|
|
+// 获取摄像机类型名称
|
|
|
+function getCameraTypeName(type) {
|
|
|
+ const typeMap = {
|
|
|
+ 0: '枪机',
|
|
|
+ 1: '半球',
|
|
|
+ 2: '快球',
|
|
|
+ 3: '带云台枪机'
|
|
|
}
|
|
|
- ptzControlApi(params).then(response => {
|
|
|
- proxy.$modal.msgSuccess('云台控制指令发送成功')
|
|
|
- })
|
|
|
+ return typeMap[type] || '未知类型'
|
|
|
}
|
|
|
|
|
|
-// 转到预置位
|
|
|
-function gotoPreset() {
|
|
|
- if (!presetForm.value.presetId) {
|
|
|
- proxy.$modal.msgWarning('请选择预置位')
|
|
|
- return
|
|
|
+// 获取摄像机类型标签颜色
|
|
|
+function getCameraTypeTagType(type) {
|
|
|
+ const typeMap = {
|
|
|
+ 0: 'primary',
|
|
|
+ 1: 'success',
|
|
|
+ 2: 'warning',
|
|
|
+ 3: 'danger'
|
|
|
}
|
|
|
- ptzControl(`PRESET_${presetForm.value.presetId}`)
|
|
|
+ return typeMap[type] || 'info'
|
|
|
}
|
|
|
|
|
|
-// 设置预置位
|
|
|
-function setPreset() {
|
|
|
- if (!presetForm.value.presetId) {
|
|
|
- proxy.$modal.msgWarning('请选择预置位')
|
|
|
- return
|
|
|
+// 获取传输类型名称
|
|
|
+function getTransTypeName(type) {
|
|
|
+ const typeMap = {
|
|
|
+ 0: 'UDP',
|
|
|
+ 1: 'TCP'
|
|
|
}
|
|
|
- ptzControl(`SET_PRESET_${presetForm.value.presetId}`)
|
|
|
+ return typeMap[type] || 'TCP'
|
|
|
}
|
|
|
|
|
|
-// 删除预置位
|
|
|
-function deletePreset() {
|
|
|
- if (!presetForm.value.presetId) {
|
|
|
- proxy.$modal.msgWarning('请选择预置位')
|
|
|
- return
|
|
|
+// 获取录像位置名称
|
|
|
+function getRecordLocationName(location) {
|
|
|
+ const locationMap = {
|
|
|
+ '0': '中心存储',
|
|
|
+ '1': '设备存储'
|
|
|
}
|
|
|
- ptzControl(`DEL_PRESET_${presetForm.value.presetId}`)
|
|
|
+ return locationMap[location] || '未知'
|
|
|
}
|
|
|
|
|
|
-// 开始录像
|
|
|
-function startRecording() {
|
|
|
- recordingControl({
|
|
|
- cameraCode: currentCamera.value.cameraCode,
|
|
|
- action: 'start'
|
|
|
- }).then(response => {
|
|
|
- proxy.$modal.msgSuccess('开始录像成功')
|
|
|
- currentCamera.value.recordingStatus = 1
|
|
|
- getCameraList()
|
|
|
- })
|
|
|
+// 格式化区域路径
|
|
|
+function formatRegionPath(path) {
|
|
|
+ if (!path) return ''
|
|
|
+ // 将路径按/分割,过滤空值,返回最后两级
|
|
|
+ const parts = path.split('/').filter(p => p)
|
|
|
+ if (parts.length > 2) {
|
|
|
+ return `${parts[parts.length - 2]} / ${parts[parts.length - 1]}`
|
|
|
+ }
|
|
|
+ return parts.join(' / ')
|
|
|
}
|
|
|
|
|
|
-// 停止录像
|
|
|
-function stopRecording() {
|
|
|
- recordingControl({
|
|
|
- cameraCode: currentCamera.value.cameraCode,
|
|
|
- action: 'stop'
|
|
|
- }).then(response => {
|
|
|
- proxy.$modal.msgSuccess('停止录像成功')
|
|
|
- currentCamera.value.recordingStatus = 0
|
|
|
- getCameraList()
|
|
|
- })
|
|
|
+// 格式化能力集
|
|
|
+function formatCapabilities(capability) {
|
|
|
+ if (!capability) return '无'
|
|
|
+
|
|
|
+ const capMap = {
|
|
|
+ '@vca_resource@': '智能分析',
|
|
|
+ '@event_audio@': '音频事件',
|
|
|
+ '@io@': 'IO控制',
|
|
|
+ '@event_face@': '人脸事件',
|
|
|
+ '@event_gis@': 'GIS事件',
|
|
|
+ '@event_rule@': '规则事件',
|
|
|
+ '@VoiceTalk@': '语音对讲',
|
|
|
+ '@gis@': 'GIS定位',
|
|
|
+ '@face@': '人脸识别',
|
|
|
+ '@record@': '录像',
|
|
|
+ '@heop_patch_upgrade@': '热补丁升级',
|
|
|
+ '@vss@': '视频存储',
|
|
|
+ '@event_io@': 'IO事件',
|
|
|
+ '@net@': '网络',
|
|
|
+ '@maintenance@': '维护',
|
|
|
+ '@event_device@': '设备事件',
|
|
|
+ '@status@': '状态监控'
|
|
|
+ }
|
|
|
+
|
|
|
+ const caps = []
|
|
|
+ for (const [key, value] of Object.entries(capMap)) {
|
|
|
+ if (capability.includes(key)) {
|
|
|
+ caps.push(value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return caps.length > 0 ? caps.join('、') : '基础功能'
|
|
|
}
|
|
|
|
|
|
-// 抓拍
|
|
|
-function takeSnapshot() {
|
|
|
- takeCameraSnapshot(currentCamera.value.cameraCode).then(response => {
|
|
|
- proxy.$modal.msgSuccess('抓拍成功,图片已保存')
|
|
|
- })
|
|
|
+// 初始化
|
|
|
+onMounted(() => {
|
|
|
+ getCameraList()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 全局样式 */
|
|
|
+.app-container {
|
|
|
+ padding: 0;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ min-height: calc(100vh - 84px);
|
|
|
}
|
|
|
|
|
|
-// 查看摄像机详情
|
|
|
-function handleCameraDetail(row) {
|
|
|
- currentCamera.value = row
|
|
|
- detailVisible.value = true
|
|
|
+/* 搜索区域 */
|
|
|
+.search-container {
|
|
|
+ padding: 0 20px;
|
|
|
+ margin-bottom: 1px;
|
|
|
}
|
|
|
|
|
|
-// 录像回放
|
|
|
-function handlePlayback(row) {
|
|
|
- proxy.$modal.msgWarning('录像回放功能开发中...')
|
|
|
+.search-card {
|
|
|
+ border-radius: 12px;
|
|
|
+ border: none;
|
|
|
}
|
|
|
|
|
|
-// 新增联动规则
|
|
|
-function handleAddLinkage() {
|
|
|
- linkageForm.value = {
|
|
|
- id: null,
|
|
|
- ruleName: '',
|
|
|
- triggerDeviceCode: '',
|
|
|
- triggerCondition: '',
|
|
|
- linkageCameraCode: '',
|
|
|
- linkageAction: '',
|
|
|
- delayTime: 0,
|
|
|
- ruleStatus: 1
|
|
|
- }
|
|
|
- linkageDialogTitle.value = '新增联动规则'
|
|
|
- linkageDialogVisible.value = true
|
|
|
+.search-card :deep(.el-card__body) {
|
|
|
+ padding: 20px;
|
|
|
}
|
|
|
|
|
|
-// 修改联动规则
|
|
|
-function handleEditLinkage(row) {
|
|
|
- linkageForm.value = { ...row }
|
|
|
- linkageDialogTitle.value = '修改联动规则'
|
|
|
- linkageDialogVisible.value = true
|
|
|
+/* 搜索表单 */
|
|
|
+.search-form {
|
|
|
+ margin-bottom: 0px;
|
|
|
}
|
|
|
|
|
|
-// 删除联动规则
|
|
|
-function handleDeleteLinkage(row) {
|
|
|
- proxy.$modal.confirm('确认删除该联动规则吗?').then(() => {
|
|
|
- deleteLinkageRule(row.id).then(response => {
|
|
|
- proxy.$modal.msgSuccess('删除成功')
|
|
|
- getLinkageList()
|
|
|
- })
|
|
|
- })
|
|
|
+.search-form-inline {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16px;
|
|
|
}
|
|
|
|
|
|
-// 启用/禁用联动规则
|
|
|
-function handleToggleLinkage(row) {
|
|
|
- const action = row.rule_status === 1 ? '禁用' : '启用'
|
|
|
- proxy.$modal.confirm(`确认${action}该联动规则吗?`).then(() => {
|
|
|
- toggleLinkageRule(row.id, { ruleStatus: row.rule_status === 1 ? 0 : 1 }).then(response => {
|
|
|
- proxy.$modal.msgSuccess(`${action}成功`)
|
|
|
- getLinkageList()
|
|
|
- })
|
|
|
- })
|
|
|
+.search-form-inline .el-form-item {
|
|
|
+ margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
-// 提交联动规则表单
|
|
|
-function submitLinkageForm() {
|
|
|
- proxy.$refs['linkageFormRef'].validate((valid) => {
|
|
|
- if (valid) {
|
|
|
- if (linkageForm.value.id) {
|
|
|
- updateLinkageRule(linkageForm.value).then(response => {
|
|
|
- proxy.$modal.msgSuccess('修改成功')
|
|
|
- linkageDialogVisible.value = false
|
|
|
- getLinkageList()
|
|
|
- })
|
|
|
- } else {
|
|
|
- addLinkageRule(linkageForm.value).then(response => {
|
|
|
- proxy.$modal.msgSuccess('新增成功')
|
|
|
- linkageDialogVisible.value = false
|
|
|
- getLinkageList()
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
+.search-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
|
|
|
-// 初始化
|
|
|
-onMounted(() => {
|
|
|
- getCameraList()
|
|
|
-})
|
|
|
-</script>
|
|
|
+.refresh-btn {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ border: none;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
|
|
|
-<style scoped>
|
|
|
-.video-container {
|
|
|
+.refresh-btn:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+.camera-stats {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-number {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #667eea;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格卡片 */
|
|
|
+.table-card {
|
|
|
+ margin: 0 20px 20px;
|
|
|
+ border-radius: 12px;
|
|
|
+ border: none;
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.table-card :deep(.el-card__body) {
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表格样式 */
|
|
|
+.camera-table {
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.camera-table :deep(.el-table__header-wrapper) {
|
|
|
+ border-radius: 12px 12px 0 0;
|
|
|
+}
|
|
|
+
|
|
|
+.camera-table :deep(.el-table__row) {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.camera-table :deep(.el-table__row:hover) {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+}
|
|
|
+
|
|
|
+/* 摄像机信息列 */
|
|
|
+.camera-info {
|
|
|
+ padding: 8px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.camera-name {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.camera-code {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+/* 设备详情列 */
|
|
|
+.device-details {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-item {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+/* 技术参数列 */
|
|
|
+.tech-params {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.param-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+.param-item i {
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+/* 位置信息列 */
|
|
|
+.location-info {
|
|
|
+ padding: 8px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.region-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.region-path {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+/* 时间文本 */
|
|
|
+.time-text {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 操作按钮 */
|
|
|
+.action-buttons {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.action-btn {
|
|
|
+ border-radius: 6px;
|
|
|
+ font-weight: 400;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-btn {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-btn:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.detail-btn {
|
|
|
+ background: #f0f2f5;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-btn:hover {
|
|
|
+ background: #e6e8eb;
|
|
|
+ border-color: #dcdfe6;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+/* 分页样式 */
|
|
|
+.pagination-wrapper {
|
|
|
padding: 20px;
|
|
|
+ background: #fafafa;
|
|
|
+ border-radius: 0 0 12px 12px;
|
|
|
}
|
|
|
|
|
|
-.video-player {
|
|
|
- margin-bottom: 20px;
|
|
|
+/* 视频预览抽屉 */
|
|
|
+.video-drawer :deep(.el-drawer__header) {
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+ padding: 20px 30px;
|
|
|
+ margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
-.video-info {
|
|
|
- margin-bottom: 20px;
|
|
|
+.video-drawer :deep(.el-drawer__body) {
|
|
|
+ padding: 0;
|
|
|
+ background: #f5f7fa;
|
|
|
+}
|
|
|
+
|
|
|
+.video-wrapper {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
|
|
|
-.video-controls {
|
|
|
- text-align: center;
|
|
|
+/* 视频播放区域 */
|
|
|
+.video-player-container {
|
|
|
+ flex: none;
|
|
|
+ background: white;
|
|
|
+ margin: 20px;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
-.ptz-container {
|
|
|
+.video-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
padding: 20px;
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+}
|
|
|
+
|
|
|
+.video-title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #303133;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.video-player {
|
|
|
+ position: relative;
|
|
|
+ background: #000;
|
|
|
+ height: 500px;
|
|
|
+}
|
|
|
+
|
|
|
+.video-iframe {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.video-placeholder {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ color: #909399;
|
|
|
}
|
|
|
|
|
|
-.ptz-direction, .ptz-zoom, .ptz-focus, .ptz-preset, .ptz-speed {
|
|
|
- margin-bottom: 30px;
|
|
|
+.video-placeholder i {
|
|
|
+ font-size: 48px;
|
|
|
+ margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
-.direction-grid {
|
|
|
- text-align: center;
|
|
|
+/* 摄像机信息面板 */
|
|
|
+.camera-details-panel {
|
|
|
+ background: white;
|
|
|
+ margin: 0 20px 20px;
|
|
|
+ padding: 24px;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
}
|
|
|
|
|
|
-.direction-row {
|
|
|
- margin-bottom: 10px;
|
|
|
+.panel-title {
|
|
|
+ margin: 0 0 20px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #303133;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
|
|
|
-.direction-btn {
|
|
|
- width: 60px;
|
|
|
- height: 60px;
|
|
|
- margin: 5px;
|
|
|
+.info-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 20px;
|
|
|
}
|
|
|
|
|
|
-.stop-btn {
|
|
|
- background-color: #f56c6c;
|
|
|
- border-color: #f56c6c;
|
|
|
+.info-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.info-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.info-value {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #303133;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+/* 详情抽屉 */
|
|
|
+.detail-drawer :deep(.el-drawer__header) {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
color: white;
|
|
|
+ padding: 24px 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-drawer :deep(.el-drawer__title) {
|
|
|
+ color: white;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-drawer :deep(.el-drawer__close) {
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-wrapper {
|
|
|
+ padding: 30px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ min-height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-section {
|
|
|
+ background: white;
|
|
|
+ padding: 24px;
|
|
|
+ border-radius: 12px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ margin: 0 0 20px 0;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #303133;
|
|
|
+ font-weight: 500;
|
|
|
+ padding-bottom: 12px;
|
|
|
+ border-bottom: 2px solid #e4e7ed;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-item.full-width {
|
|
|
+ grid-column: span 2;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+ font-weight: 400;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-value {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #303133;
|
|
|
+ font-weight: 500;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+/* 标签样式优化 */
|
|
|
+.el-tag {
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 0 12px;
|
|
|
+ height: 26px;
|
|
|
+ line-height: 24px;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 加载动画 */
|
|
|
+.el-loading-mask {
|
|
|
+ background-color: rgba(255, 255, 255, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .info-grid,
|
|
|
+ .detail-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-item.full-width {
|
|
|
+ grid-column: span 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .search-actions {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ align-items: stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ .camera-stats {
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .video-drawer :deep(.el-drawer) {
|
|
|
+ width: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-drawer :deep(.el-drawer) {
|
|
|
+ width: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-buttons {
|
|
|
+ flex-direction: column;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .action-btn {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 动画效果 */
|
|
|
+@keyframes fadeIn {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(10px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.camera-table :deep(.el-table__row) {
|
|
|
+ animation: fadeIn 0.3s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+/* 滚动条美化 */
|
|
|
+::-webkit-scrollbar {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+::-webkit-scrollbar-track {
|
|
|
+ background: #f1f1f1;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+::-webkit-scrollbar-thumb {
|
|
|
+ background: #c0c4cc;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+::-webkit-scrollbar-thumb:hover {
|
|
|
+ background: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+/* 过渡效果 */
|
|
|
+.el-button,
|
|
|
+.el-tag {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+/* 空状态样式 */
|
|
|
+.el-table__empty-block {
|
|
|
+ padding: 60px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.el-table__empty-text {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 隐藏表格滚动条 */
|
|
|
+.camera-table :deep(.el-table__body-wrapper) {
|
|
|
+ overflow-x: hidden !important;
|
|
|
+ overflow-y: hidden !important;
|
|
|
}
|
|
|
|
|
|
-.zoom-controls, .focus-controls {
|
|
|
- text-align: center;
|
|
|
+.camera-table :deep(.el-table__header-wrapper) {
|
|
|
+ overflow-x: hidden !important;
|
|
|
}
|
|
|
|
|
|
-.zoom-controls .el-button, .focus-controls .el-button {
|
|
|
- margin: 0 10px;
|
|
|
+/* 如果需要隐藏整个表格容器的滚动条 */
|
|
|
+.camera-table :deep(.el-table) {
|
|
|
+ overflow: hidden !important;
|
|
|
}
|
|
|
</style>
|