|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="div-container">
|
|
|
|
|
|
<div class="sidebar">
|
|
|
|
|
|
<!-- 左侧树型列表-->
|
|
|
|
|
|
<el-card class="box-card" shadow="never">
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<span>资源列表</span>
|
|
|
|
|
|
<el-button link type="primary" @click="fetchData">刷新</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<el-input v-model="filterText" placeholder="输入关键字过滤" prefix-icon="Search" style="margin-bottom: 15px" />
|
|
|
|
|
|
<el-tree
|
|
|
|
|
|
ref="treeRef"
|
|
|
|
|
|
:data="treeData"
|
|
|
|
|
|
:props="defaultProps"
|
|
|
|
|
|
:filter-node-method="filterNode"
|
|
|
|
|
|
node-key="id"
|
|
|
|
|
|
default-expand-all
|
|
|
|
|
|
highlight-current
|
|
|
|
|
|
@node-click="handleNodeClick"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #default="{ node, data }">
|
|
|
|
|
|
<span class="custom-tree-node">
|
|
|
|
|
|
<el-icon v-if="data.type === 0"><OfficeBuilding /></el-icon>
|
|
|
|
|
|
<div v-else-if="data.type===1">
|
|
|
|
|
|
<el-icon ><School /></el-icon>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-icon v-else-if="data.type === 2"><VideoCamera /></el-icon>
|
|
|
|
|
|
<span class="node-label" :class="{ 'ehv-text': data.type === 'STATION_EHV' }">
|
|
|
|
|
|
{{ node.label }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div v-if="data.type===1" style="margin-left: 5px;">
|
|
|
|
|
|
<el-button link type="primary" @click="showComputeRoom(data)">机房平面图</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-tree>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="main-content">
|
|
|
|
|
|
<div v-if="viewMode === 'list'" class="box-card">
|
|
|
|
|
|
<!-- NVR查询表单-->
|
|
|
|
|
|
<el-form
|
|
|
|
|
|
:inline="true"
|
|
|
|
|
|
:model="NvrFormData"
|
|
|
|
|
|
@submit.prevent
|
|
|
|
|
|
class="QueryForm">
|
|
|
|
|
|
<el-form-item label="设备名称:">
|
|
|
|
|
|
<el-input v-model="NvrFormData.name" placeholder="请输入名称" clearable/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="状态" >
|
|
|
|
|
|
<el-select v-model="NvrFormData.status" placeholder="请选择" style="width: 210px;" clearable>
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="item in optionsStore.getDictOptions('status').map(it=>{
|
|
|
|
|
|
return {
|
|
|
|
|
|
... it,
|
|
|
|
|
|
id: Number(it.id.substring(6))
|
|
|
|
|
|
}
|
|
|
|
|
|
})"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
:label="item.codeName"
|
|
|
|
|
|
:value="Number(item.id)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item >
|
|
|
|
|
|
<el-button type="primary" class="queryBtn" @click="queryNvr">查询</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item >
|
|
|
|
|
|
<el-button type="primary" :icon="Plus" style="width: 100px;" @click="openAddNvrDialog">新增设备</el-button>
|
|
|
|
|
|
<el-button type="danger" :icon="Delete" style="width: 100px;" @click="deleteNvrsBatch">批量删除</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<!-- NVR表格-->
|
|
|
|
|
|
<el-table :data="tableData"
|
|
|
|
|
|
style="width: 100%;padding: 10px"
|
|
|
|
|
|
stripe height="calc(100vh - 200px)"
|
|
|
|
|
|
@selection-change="handleSelectionChange">
|
|
|
|
|
|
<el-table-column type="selection" width="55"/>
|
|
|
|
|
|
<el-table-column prop="id" label="NVR ID" width="120" />
|
|
|
|
|
|
<el-table-column prop="name" label="设备名称" min-width="180" />
|
|
|
|
|
|
<el-table-column prop="ip" label="IP地址" width="140"/>
|
|
|
|
|
|
<el-table-column prop="port" label="端口号" width="140"/>
|
|
|
|
|
|
<el-table-column prop="account" label="账号" width="140"/>
|
|
|
|
|
|
<el-table-column prop="password" label="密码" width="140"/>
|
|
|
|
|
|
<el-table-column prop="driver" label="类型" width="100" align="center">
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<el-tag v-if="scope.row.driver === 0" type="success" effect="dark">海康</el-tag>
|
|
|
|
|
|
<el-tag v-if="scope.row.driver === 1" type="success">大华</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="status" label="状态" width="100" align="center">
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<el-tag v-if="scope.row.status === 1" type="success" effect="dark">在线</el-tag>
|
|
|
|
|
|
<el-tag v-if="scope.row.status === 0" type="danger">离线</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="createTime" label="创建时间" width="140"/>
|
|
|
|
|
|
<el-table-column label="操作" width="320" fixed="right" align="center">
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<el-button size="small" icon="Refresh" type="primary" @click="handleRefresh(scope.row)" >
|
|
|
|
|
|
同步 </el-button>
|
|
|
|
|
|
<!-- <el-button size="small" icon="Refresh" type="primary" @click="refreshChannel(scope.row)" :loading="scope.row.isLoading">-->
|
|
|
|
|
|
<!-- 刷新 </el-button>-->
|
|
|
|
|
|
<el-button size="small" icon="VideoCamera" type="primary" @click="showChannelList(scope.row)"> 通道 </el-button>
|
|
|
|
|
|
<el-button size="small" icon="EditPen" type="primary" @click="openNvrChangeDialog(scope.row)"> 编辑 </el-button>
|
|
|
|
|
|
<el-button size="small" type="danger" icon="Delete" @click="deleteByIdNvr(scope.row.id)"> 删除 </el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
<DataSync
|
|
|
|
|
|
:percentage="refreshObj.percentage"
|
|
|
|
|
|
:is-syncing="refreshObj.isSyncing"
|
|
|
|
|
|
:is-success="refreshObj.isSuccess"
|
|
|
|
|
|
:status="refreshObj.status"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分页组件 -->
|
|
|
|
|
|
<div class="pagination-container" >
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
|
v-model:current-page="PageParams.pageNum"
|
|
|
|
|
|
v-model:page-size="PageParams.pageSize"
|
|
|
|
|
|
:page-sizes="[10, 20, 30, 40]"
|
|
|
|
|
|
:total="PageParams.total"
|
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- NVR新增或者编辑弹框-->
|
|
|
|
|
|
<el-dialog v-model="deviceDialogObj.deviceVisible"
|
|
|
|
|
|
@close="handleCancel"
|
|
|
|
|
|
:title="deviceDialogObj.title"
|
|
|
|
|
|
draggable
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
width="500">
|
|
|
|
|
|
<el-form :model="deviceDialogObj.deviceForm"
|
|
|
|
|
|
ref="addOrUpdateFormRef"
|
|
|
|
|
|
:rules="addOrUpdateRules">
|
|
|
|
|
|
<el-form-item label="设备名称" prop="name">
|
|
|
|
|
|
<el-input v-model="deviceDialogObj.deviceForm.name" clearable />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="设备账号" prop="account">
|
|
|
|
|
|
<el-input v-model="deviceDialogObj.deviceForm.account" clearable/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="设备密码" prop="password">
|
|
|
|
|
|
<el-input type="password" show-password v-model="deviceDialogObj.deviceForm.password" clearable/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="设备IP" prop="ip">
|
|
|
|
|
|
<el-input v-model="deviceDialogObj.deviceForm.ip" clearable/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="设备端口" prop="port">
|
|
|
|
|
|
<el-input v-model="deviceDialogObj.deviceForm.port" clearable/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="设备类型" prop="driver">
|
|
|
|
|
|
<el-select v-model="deviceDialogObj.deviceForm.driver" clearable>
|
|
|
|
|
|
<el-option v-for="item in optionsStore.getDictOptions('device_category').map(it=>{
|
|
|
|
|
|
return {
|
|
|
|
|
|
... it,
|
|
|
|
|
|
id: Number(it.id.substring(6))
|
|
|
|
|
|
}
|
|
|
|
|
|
})"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
:label="item.codeName"
|
|
|
|
|
|
:value="item.id"/>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="设备所在变电站" prop="stationId">
|
|
|
|
|
|
<el-select placeholder="请选择变电站" style="width: 240px"
|
|
|
|
|
|
v-model="deviceDialogObj.deviceForm.stationId"
|
|
|
|
|
|
:options="subStationList"
|
|
|
|
|
|
:props="StationProps"
|
|
|
|
|
|
>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<div class="form-actions">
|
|
|
|
|
|
<el-button @click="handleCancel">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="handleSave">保存</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-card v-else-if="viewMode === 'detail'" class="box-card">
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<div style="display: flex; align-items: center">
|
|
|
|
|
|
<el-button link icon="ArrowLeft" @click="backToList" style="margin-right: 10px; font-size: 16px">
|
|
|
|
|
|
返回列表
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-divider direction="vertical" />
|
|
|
|
|
|
<el-tag size="small" style="margin-left: 10px" :type="currentNvrDetail?.status === 1 ? 'success' : 'info'">
|
|
|
|
|
|
{{ currentNvrDetail?.status=== 1 ? '设备在线' : '设备离线' }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<div class="detail-container" >
|
|
|
|
|
|
<!-- 通道查询表单 -->
|
|
|
|
|
|
<el-form :model="channelQuery"
|
|
|
|
|
|
:inline="true"
|
|
|
|
|
|
ref="channelQueryRef"
|
|
|
|
|
|
@submit.prevent>
|
|
|
|
|
|
<el-form-item label="名称" prop="name" placeholder="请选择">
|
|
|
|
|
|
<el-input v-model="channelQuery.name" placeholder="请输入名称" clearable />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="云台类型" prop="type">
|
|
|
|
|
|
<el-select v-model="channelQuery.type" placeholder="请选择" style="width: 210px;" clearable>
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="item in optionsStore.getDictOptions('camera_type').map(it=>{
|
|
|
|
|
|
return {
|
|
|
|
|
|
... it,
|
|
|
|
|
|
id: Number(it.id.substring(6))
|
|
|
|
|
|
}
|
|
|
|
|
|
})"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
:label="item.codeName"
|
|
|
|
|
|
:value="Number(item.id)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item >
|
|
|
|
|
|
<el-button type="primary" class="queryBtn" @click="queryCamera">查询</el-button>
|
|
|
|
|
|
<el-button type="warning" icon="Refresh" @click="refreshCamera(currentNvrDetail)" plain :loading="refreshLoading">同步通道</el-button>
|
|
|
|
|
|
<el-button type="danger" icon="Delete" @click="handleBatchDel" plain :loading="deleteBatchLoading">批量删除</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<!-- 通道列表 -->
|
|
|
|
|
|
<el-table :data="channelData"
|
|
|
|
|
|
height="350"
|
|
|
|
|
|
style="width: 100%" @selection-change="handleBatchChannel"
|
|
|
|
|
|
:row-key="(row: any)=>row.id"
|
|
|
|
|
|
size="small" >
|
|
|
|
|
|
<el-table-column type="selection" width="20"/>
|
|
|
|
|
|
<el-table-column prop="name" label="名称" width="280" align="center"/>
|
|
|
|
|
|
<el-table-column prop="cameraNo" label="摄像机编号" width="100" align="center"/>
|
|
|
|
|
|
<el-table-column prop="channelId" label="通道号" width="180" align="center" />
|
|
|
|
|
|
<el-table-column prop="ipAddress" label="IP地址" width="180" align="center" />
|
|
|
|
|
|
<el-table-column prop="algInfo" label="算法配置" width="180" align="center" >
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<div class="alg-info-container">
|
|
|
|
|
|
<div v-if="row.algInfo && row.algInfo.length > 0">
|
|
|
|
|
|
<div v-for="(item,index) in row.algInfo" :key="index" class="alg-tag-item">
|
|
|
|
|
|
<el-tag>{{ALGORITHM_MAP[item]}}</el-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="no-alg-info">
|
|
|
|
|
|
<span>无</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="enableInspection" label="抓图对比" width="180" align="center" >
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-tag type="primary" v-if="row.enableInspection==1">开启</el-tag>
|
|
|
|
|
|
<el-tag type="danger" v-else-if="row.enableInspection==0">关闭</el-tag>
|
|
|
|
|
|
<span v-else>其他</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="installLocation" label="位置信息" width="180" align="center" >
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<span v-if="row.installLocation==''">无</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="videoType" label="视频类型" width="180" align="center" >
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<span v-if="row.videoType==1">可见光</span>
|
|
|
|
|
|
<span v-else-if="row.videoType==2">热成像</span>
|
|
|
|
|
|
<span v-else>其他</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="type" label="云台类型" width="180" align="center" >
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<span v-if="row.type==0">枪机</span>
|
|
|
|
|
|
<span v-else-if="row.type==1">球体</span>
|
|
|
|
|
|
<span v-else-if="row.type==2">云台</span>
|
|
|
|
|
|
<span v-else>其他</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="channelDriver" label="通道类型" width="180" align="center" >
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<span v-if="row.channelDriver==0">海康</span>
|
|
|
|
|
|
<span v-else-if="row.channelDriver==1">大华</span>
|
|
|
|
|
|
<span v-else>其他</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="status" label="状态" width="100">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-tag type="success" v-if="row.status==1">在线</el-tag>
|
|
|
|
|
|
<el-tag type="danger" v-else-if="row.status==0">离线</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="操作" fixed="right" width="250">
|
|
|
|
|
|
<template #default="{row}">
|
|
|
|
|
|
|
|
|
|
|
|
<el-button size="small" type="primary" link @click="openPlayDialog(row)" :loading="playLoading">预览</el-button>
|
|
|
|
|
|
<el-button size="small" type="primary" link @click="openCameraEditDialog(row)"> 编辑 </el-button>
|
|
|
|
|
|
<el-button size="small" type="warning" link @click="handleEditDel(row)">删除</el-button>
|
|
|
|
|
|
<el-dropdown
|
|
|
|
|
|
trigger="hover"
|
|
|
|
|
|
@command="(command:string) => handleCommand(command, row)"
|
|
|
|
|
|
style="margin-left: 12px; vertical-align: middle;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-button link type="primary" size="small">
|
|
|
|
|
|
更多 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
|
|
|
|
<template #dropdown>
|
|
|
|
|
|
<el-dropdown-menu>
|
|
|
|
|
|
<el-dropdown-item command="control">控制</el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item command="blur">模糊检测</el-dropdown-item>
|
|
|
|
|
|
<el-dropdown-item command="algo" divided>配置算法</el-dropdown-item>
|
|
|
|
|
|
</el-dropdown-menu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dropdown>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
<!-- 通道编辑弹框-->
|
|
|
|
|
|
<el-dialog v-model="editCameraDialog.editVisible" title="编辑" :destroy-on-close="true" draggable>
|
|
|
|
|
|
<el-form
|
|
|
|
|
|
ref="ruleFormRef"
|
|
|
|
|
|
label-width="140px"
|
|
|
|
|
|
label-suffix=" :"
|
|
|
|
|
|
:rules="rules"
|
|
|
|
|
|
:model="editCameraDialog.cameraForm"
|
|
|
|
|
|
@submit.enter.prevent="handleSubmit"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-form-item label="摄像机名称" prop="name">
|
|
|
|
|
|
<el-input v-model="editCameraDialog.cameraForm.name" placeholder="请填写名称" clearable />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="摄像机编号" prop="cameraNo">
|
|
|
|
|
|
<el-input v-model="editCameraDialog.cameraForm.cameraNo" placeholder="请填写编号" clearable />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="安装位置" prop="installLocation">
|
|
|
|
|
|
<el-input v-model="editCameraDialog.cameraForm.installLocation" placeholder="请填写安装位置" clearable />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="摄像机类型" prop="type">
|
|
|
|
|
|
<el-select v-model="editCameraDialog.cameraForm.type" clearable placeholder="请选择摄像机区分类型">
|
|
|
|
|
|
<el-option v-for="item in cameraType" :key="item.id" :label="item.codeName" :value="item.id" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="摄像机品牌" prop="channelDriver">
|
|
|
|
|
|
<el-select v-model="editCameraDialog.cameraForm.channelDriver" clearable placeholder="请选择摄像机品牌">
|
|
|
|
|
|
<el-option v-for="item in cameraProduct" :key="item.id" :label="item.codeName" :value="item.id" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="抓图对比功能" prop="enableInspection">
|
|
|
|
|
|
<el-select v-model="editCameraDialog.cameraForm.enableInspection" clearable placeholder="请选择">
|
|
|
|
|
|
<el-option v-for="item in inspectionType" :key="item.id" :label="item.codeName" :value="item.id" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="handleEditClose"> 取消 </el-button>
|
|
|
|
|
|
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 算法编辑的组件-->
|
|
|
|
|
|
<AlgConfigForm :camera-row="algDialogObj.cameraRow"
|
|
|
|
|
|
:title="algDialogObj.title"
|
|
|
|
|
|
@handleVisibleUpdate="handleVisibleUpdate"
|
|
|
|
|
|
@handleSubmit="handleAlgSubmit"
|
|
|
|
|
|
:alg-visible="algDialogObj.algVisible"
|
|
|
|
|
|
:alg-form="algDialogObj.algForm"/>
|
|
|
|
|
|
<!-- 分页组件-->
|
|
|
|
|
|
<div class="pagination-container" style="margin-top: 10px;">
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
|
v-model:current-page="channelPage.pageNum"
|
|
|
|
|
|
v-model:page-size="channelPage.pageSize"
|
|
|
|
|
|
:page-sizes="[10, 20, 30, 40]"
|
|
|
|
|
|
:total="channelPage.total"
|
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 视频预览的弹框-->
|
|
|
|
|
|
<el-dialog v-model="playDialogObj.playVisible"
|
|
|
|
|
|
:title="playDialogObj.title"
|
|
|
|
|
|
:before-close="handleClosePlay"
|
|
|
|
|
|
:destroy-on-close="true"
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="video-wrapper"
|
|
|
|
|
|
>
|
|
|
|
|
|
<VideoPlayer :camera-id="playDialogObj.id"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 控制摄像头的弹框 -->
|
|
|
|
|
|
<el-dialog v-model="controlDialogObj.controlVisible"
|
|
|
|
|
|
:title="controlDialogObj.title"
|
|
|
|
|
|
:before-close="handleClose"
|
|
|
|
|
|
align-center
|
|
|
|
|
|
:destroy-on-close="true"
|
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
|
>
|
|
|
|
|
|
<CameraControl
|
|
|
|
|
|
:camera-id="controlDialogObj.id"
|
|
|
|
|
|
:curCamera="controlDialogObj.curCamera"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 模糊检测的弹框-->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="blurDialogObj.blurVisible"
|
|
|
|
|
|
:title="blurDialogObj.title"
|
|
|
|
|
|
width="500"
|
|
|
|
|
|
:before-close="handelCancelBlur"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-form label-width="120px" label-suffix=" :"
|
|
|
|
|
|
ref="blurFormRef"
|
|
|
|
|
|
:rules="{
|
|
|
|
|
|
url: [
|
|
|
|
|
|
{
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
message: '请填写上报地址',
|
|
|
|
|
|
trigger: 'blur'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}"
|
|
|
|
|
|
:model="blurDialogObj.burDetectionVO">
|
|
|
|
|
|
<el-form-item label="开启模糊检测" >
|
|
|
|
|
|
<el-switch v-model="blurDialogObj.burDetectionVO.enable"
|
|
|
|
|
|
:active-value="1"
|
|
|
|
|
|
:inactive-value="0"></el-switch>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="上报地址" v-if="blurDialogObj.burDetectionVO.enable===1" prop="url">
|
|
|
|
|
|
<el-input v-model="blurDialogObj.burDetectionVO.url" placeholder="请填写上报地址"></el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
|
<el-button @click="handelCancelBlur">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="handelSubmitBlur">
|
|
|
|
|
|
确定
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 机房平面图列表组件-->
|
|
|
|
|
|
<compute-room-model-list ref="computeRoomRef"/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
|
|
|
|
|
|
import {onBeforeUnmount, onMounted, reactive, ref, watch} from 'vue';
|
|
|
|
|
|
// 引入需要的图标,增加了 ArrowLeft, Refresh, Document
|
|
|
|
|
|
import {ArrowDown, Delete, OfficeBuilding, Plus, School, VideoCamera} from '@element-plus/icons-vue';
|
|
|
|
|
|
import {getTreeData} from "@/api/modules/monitor/Tree.js";
|
|
|
|
|
|
import type {TreeNode} from "@/api/types/monitor/Tree";
|
|
|
|
|
|
import {createNvrApi, getNvrListApi, listByNode, removeNvrApi, updateNvrApi} from "@/api/modules/monitor/Nvr";
|
|
|
|
|
|
import type {NvrForm, NvrQuery, NvrRow} from "@/api/types/monitor/nvr";
|
|
|
|
|
|
import {type ElForm, ElMessage, ElMessageBox, type TreeInstance} from "element-plus";
|
|
|
|
|
|
import {getCameraListApi, removeCameraApi, updateCameraApi} from "@/api/modules/monitor/Camera";
|
|
|
|
|
|
import type {CameraForm, CameraQuery, CameraRow} from "@/api/types/monitor/camera";
|
|
|
|
|
|
import {deviceLogin, refresh} from "@/api/modules/monitor/device";
|
|
|
|
|
|
import CameraControl from "@/views/sysmonitortree/sysMonitorTree/components/CameraControl.vue";
|
|
|
|
|
|
import VideoPlayer from "@/views/sysmonitortree/sysMonitorTree/components/VideoPlayer.vue";
|
|
|
|
|
|
import {useDictOptions} from "@/hooks/useDictOptions";
|
|
|
|
|
|
import {deepClone} from "@/utils/clone";
|
|
|
|
|
|
import {useOptionsStore} from "@/stores/modules/options";
|
|
|
|
|
|
import {getAllSubstationVO} from "@/api/modules/monitor/substation";
|
|
|
|
|
|
import AlgConfigForm from "@/views/sysmonitortree/sysMonitorTree/components/AlgConfigForm.vue";
|
|
|
|
|
|
import {handleAlgTask} from "@/api/modules/monitor/channel";
|
|
|
|
|
|
import {getAlgorithmTaskByCameraId} from "@/api/modules/monitor/algorithmTask";
|
|
|
|
|
|
import type {AlgorithmTaskVO, AlgTaskConfigDto} from "@/api/types/edgebox/EdgeBox";
|
|
|
|
|
|
import DataSync from "@/views/sysmonitortree/sysMonitorTree/components/DataSync.vue";
|
|
|
|
|
|
import {fa} from "element-plus/es/locale";
|
|
|
|
|
|
import ComputeRoomModelList from "@/views/sysmonitortree/sysMonitorTree/components/ComputeRoomModelList.vue";
|
|
|
|
|
|
import {getListApi} from "@/api/modules/computerRoom/computerRoom";
|
|
|
|
|
|
import type {BlurDetectionUpdateDTO, BlurDetectionVO} from "@/api/types/blurDetection/BlurDetection";
|
|
|
|
|
|
import {addOrUpdateBlurDetectionApi, getBlurDetectionByCameraIdApi} from "@/api/modules/blurDetection/BlurDetection";
|
|
|
|
|
|
const refreshLoading=ref(false);
|
|
|
|
|
|
const playLoading = ref(false);
|
|
|
|
|
|
const filterText = ref('');
|
|
|
|
|
|
const treeRef = ref<TreeInstance>();
|
|
|
|
|
|
const treeData = ref<TreeNode[]>([]);
|
|
|
|
|
|
const tableData = ref<NvrRow[]>([]);
|
|
|
|
|
|
const currentSelectNode = ref<TreeNode>();
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const channelData=ref<CameraRow[]>([]);
|
|
|
|
|
|
const ruleFormRef=ref();
|
|
|
|
|
|
const optionsStore = useOptionsStore();
|
|
|
|
|
|
const addOrUpdateFormRef=ref();
|
|
|
|
|
|
const editCameraDialog=reactive({
|
|
|
|
|
|
editVisible: false,
|
|
|
|
|
|
cameraForm: {} as CameraForm
|
|
|
|
|
|
})
|
|
|
|
|
|
const addOrUpdateRules = reactive({
|
|
|
|
|
|
name: [
|
|
|
|
|
|
{ required: true, message: '请输入设备名称', trigger: 'blur' },
|
|
|
|
|
|
],
|
|
|
|
|
|
account: [
|
|
|
|
|
|
{ required: true, message: '请输入账号', trigger: 'blur' },
|
|
|
|
|
|
],
|
|
|
|
|
|
password: [
|
|
|
|
|
|
{ required: true, message: '请输入密码', trigger: 'blur' },
|
|
|
|
|
|
],
|
|
|
|
|
|
ip: [
|
|
|
|
|
|
{ required: true, message: '请输入IP地址', trigger: 'blur' },
|
|
|
|
|
|
],
|
|
|
|
|
|
port: [
|
|
|
|
|
|
{ required: true, message: '请输入端口号', trigger: 'blur' },
|
|
|
|
|
|
],
|
|
|
|
|
|
driver: [
|
|
|
|
|
|
{ required: true, message: '请输入NVR的类型', trigger: 'blur' },
|
|
|
|
|
|
],
|
|
|
|
|
|
manufacturer: [
|
|
|
|
|
|
{ required: true, message: '请输入设备厂商', trigger: 'blur' },
|
|
|
|
|
|
],
|
|
|
|
|
|
stationId: [
|
|
|
|
|
|
{ required: true, message: '请输入区域', trigger: 'blur' },
|
|
|
|
|
|
],
|
|
|
|
|
|
});
|
|
|
|
|
|
const NvrFormData=reactive<NvrQuery>({});
|
|
|
|
|
|
const channelQuery=reactive<CameraQuery>({});
|
|
|
|
|
|
const rules = reactive({
|
|
|
|
|
|
name: [{ required: true, message: '请填写名称' }],
|
|
|
|
|
|
cameraNo: [{ required: true, message: '请填写摄像头编号' }],
|
|
|
|
|
|
type: [{ required: true, message: '请填写类型' }],
|
|
|
|
|
|
channelDriver: [{ required: true, message: '请填写品牌' }],
|
|
|
|
|
|
});
|
|
|
|
|
|
const computeRoomRef=ref();
|
|
|
|
|
|
// 查看机房平面图
|
|
|
|
|
|
const showComputeRoom=(data:any)=>{
|
|
|
|
|
|
console.log(data)
|
|
|
|
|
|
const params={
|
|
|
|
|
|
data,
|
|
|
|
|
|
getListApi
|
|
|
|
|
|
}
|
|
|
|
|
|
computeRoomRef.value.acceptParams(params)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 算法的映射
|
|
|
|
|
|
const ALGORITHM_MAP: Record<number, string> = {
|
|
|
|
|
|
8: "明烟明火检测",
|
|
|
|
|
|
52: "人脸识别",
|
|
|
|
|
|
58: "脸部抓拍",
|
|
|
|
|
|
1:"安全帽检测"
|
|
|
|
|
|
};
|
|
|
|
|
|
const handleCommand = (command: string, row: any) => {
|
|
|
|
|
|
switch (command) {
|
|
|
|
|
|
case 'control':
|
|
|
|
|
|
openControlDialog(row);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'blur':
|
|
|
|
|
|
openBlurDialog(row);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'algo':
|
|
|
|
|
|
openAlgDialog(row);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 处理算法提交
|
|
|
|
|
|
const handleAlgSubmit=async (algForm:AlgorithmTaskVO)=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (algForm.algInfo.length>3) {
|
|
|
|
|
|
ElMessage.warning("最多只能同时配置三个算法");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const res=await handleAlgTask({
|
|
|
|
|
|
id: algForm.id,
|
|
|
|
|
|
url: algForm.url,
|
|
|
|
|
|
AlgInfo: algForm.algInfo,
|
|
|
|
|
|
TaskDesc: algForm.taskDesc,
|
|
|
|
|
|
UserData: algForm.userData,
|
|
|
|
|
|
cameraId: algDialogObj.cameraRow.id,
|
|
|
|
|
|
});
|
|
|
|
|
|
if(res.code==="0000")
|
|
|
|
|
|
{
|
|
|
|
|
|
handleVisibleUpdate();
|
|
|
|
|
|
await getChannelPage();
|
|
|
|
|
|
ElMessage.success('配置成功');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('配置算法失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 关闭配置算法的弹框
|
|
|
|
|
|
const handleVisibleUpdate=()=>{
|
|
|
|
|
|
algDialogObj.algVisible=false;
|
|
|
|
|
|
algDialogObj.algForm={};
|
|
|
|
|
|
}
|
|
|
|
|
|
// 配置算法的弹框对象
|
|
|
|
|
|
const algDialogObj=reactive({
|
|
|
|
|
|
algVisible: false,
|
|
|
|
|
|
title: '配置算法',
|
|
|
|
|
|
algForm:{} as AlgorithmTaskVO,
|
|
|
|
|
|
cameraRow:{} as CameraRow,
|
|
|
|
|
|
});
|
|
|
|
|
|
// 打开配置算法的弹框
|
|
|
|
|
|
const openAlgDialog=async (data:any)=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取当前摄像头配置的算法信息
|
|
|
|
|
|
const res=await getAlgorithmTaskByCameraId({
|
|
|
|
|
|
cameraId: data.id
|
|
|
|
|
|
})
|
|
|
|
|
|
algDialogObj.algForm=res.data || {};
|
|
|
|
|
|
algDialogObj.algVisible=true;
|
|
|
|
|
|
algDialogObj.cameraRow=data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取算法配置失败:', error);
|
|
|
|
|
|
ElMessage.error('获取算法配置失败,请重试');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// NVR条件查询
|
|
|
|
|
|
const queryNvr=async ()=>{
|
|
|
|
|
|
const res=await getNvrListApi(NvrFormData);
|
|
|
|
|
|
tableData.value=res.data.rows;
|
|
|
|
|
|
PageParams.pageNum=res.data.current;
|
|
|
|
|
|
PageParams.pageSize=res.data.limit;
|
|
|
|
|
|
PageParams.total=res.data.total;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 打开新增NVR的弹框
|
|
|
|
|
|
const openAddNvrDialog=()=>{
|
|
|
|
|
|
deviceDialogObj.deviceVisible=true;
|
|
|
|
|
|
deviceDialogObj.title='新增设备';
|
|
|
|
|
|
}
|
|
|
|
|
|
// 新增或者修改设备的弹框
|
|
|
|
|
|
const deviceDialogObj = reactive({
|
|
|
|
|
|
deviceVisible: false,
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
deviceForm:{} as NvrForm,
|
|
|
|
|
|
id: -1
|
|
|
|
|
|
});
|
|
|
|
|
|
interface SubStation {
|
|
|
|
|
|
id:number;
|
|
|
|
|
|
name:string
|
|
|
|
|
|
}
|
|
|
|
|
|
// 打开编辑NVR的弹框
|
|
|
|
|
|
const openNvrChangeDialog=(data:NvrRow)=>{
|
|
|
|
|
|
deviceDialogObj.deviceVisible=true;
|
|
|
|
|
|
deviceDialogObj.title='修改设备';
|
|
|
|
|
|
deviceDialogObj.deviceForm=deepClone(data);
|
|
|
|
|
|
console.log('编辑',deviceDialogObj.deviceForm)
|
|
|
|
|
|
deviceDialogObj.id=data.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 保存NVR
|
|
|
|
|
|
const handleSave=async ()=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 表单验证
|
|
|
|
|
|
await addOrUpdateFormRef.value.validate();
|
|
|
|
|
|
if(deviceDialogObj.title=='新增设备')
|
|
|
|
|
|
{
|
|
|
|
|
|
// 执行上传并等待结果
|
|
|
|
|
|
const res=await createNvrApi(deviceDialogObj.deviceForm);
|
|
|
|
|
|
if(res.code=="0000")
|
|
|
|
|
|
{
|
|
|
|
|
|
// 重置表单数据,关闭弹窗
|
|
|
|
|
|
handleCancel();
|
|
|
|
|
|
//调用后端,进行一个设备的登录
|
|
|
|
|
|
const result=await deviceLogin({
|
|
|
|
|
|
id:res.data
|
|
|
|
|
|
})
|
|
|
|
|
|
console.log('登录信息',result);
|
|
|
|
|
|
// 刷新数据
|
|
|
|
|
|
await fetchTableData({});
|
|
|
|
|
|
ElMessage.success('新增成功');
|
|
|
|
|
|
}else {
|
|
|
|
|
|
ElMessage.warning("新增失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
}else {
|
|
|
|
|
|
const res=await updateNvrApi(deviceDialogObj.deviceForm);
|
|
|
|
|
|
if(res.code=="0000")
|
|
|
|
|
|
{
|
|
|
|
|
|
// 重置表单数据,关闭弹窗
|
|
|
|
|
|
handleCancel();
|
|
|
|
|
|
// 刷新数据
|
|
|
|
|
|
await fetchTableData({});
|
|
|
|
|
|
ElMessage.success('修改成功');
|
|
|
|
|
|
}else {
|
|
|
|
|
|
ElMessage.warning("修改失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.log('表单验证失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 记录NVR的Id集合
|
|
|
|
|
|
const ids=[] as number[];
|
|
|
|
|
|
// 表格多选点击事件
|
|
|
|
|
|
const handleSelectionChange=(data:NvrRow[])=>{
|
|
|
|
|
|
ids.length=0;
|
|
|
|
|
|
for(let i=0;i<data.length;i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ids.push(data[i].id || 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 批量删除NVR
|
|
|
|
|
|
const deleteNvrsBatch=async ()=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
if(ids.length === 0) {
|
|
|
|
|
|
ElMessage.warning('请选择要删除的设备');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const res=await removeNvrApi({
|
|
|
|
|
|
ids
|
|
|
|
|
|
});
|
|
|
|
|
|
if(res.code==="0000")
|
|
|
|
|
|
{
|
|
|
|
|
|
if(tableData.value.length==1 && PageParams.pageNum > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
PageParams.pageNum-=1;
|
|
|
|
|
|
}
|
|
|
|
|
|
await fetchTableData({});
|
|
|
|
|
|
ElMessage.success('批量删除成功');
|
|
|
|
|
|
}else {
|
|
|
|
|
|
ElMessage.error('批量删除失败: ' + (res.message || '未知错误'));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('批量删除NVR失败:', error);
|
|
|
|
|
|
ElMessage.error('批量删除失败,请重试');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 根据id删除NVR
|
|
|
|
|
|
const deleteByIdNvr=async (id: number)=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
'确定要删除该 NVR 设备吗?',
|
|
|
|
|
|
'删除确认',
|
|
|
|
|
|
{
|
|
|
|
|
|
confirmButtonText: '确定删除',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning',
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
const res=await removeNvrApi({
|
|
|
|
|
|
ids: [id]
|
|
|
|
|
|
});
|
|
|
|
|
|
if(res.code==="0000")
|
|
|
|
|
|
{
|
|
|
|
|
|
if(tableData.value.length==1 && PageParams.pageNum > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
PageParams.pageNum-=1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 刷新数据
|
|
|
|
|
|
await fetchTableData({});
|
|
|
|
|
|
ElMessage.success('删除成功');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error === 'cancel') {
|
|
|
|
|
|
ElMessage.info('已取消删除');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('删除NVR失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const subStationList=ref<SubStation[]>([]);
|
|
|
|
|
|
const StationProps={
|
|
|
|
|
|
label: 'name',
|
|
|
|
|
|
value: 'id'
|
|
|
|
|
|
}
|
|
|
|
|
|
// 获取所有的区域
|
|
|
|
|
|
const getAllSubstationVOs= async ()=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res=await getAllSubstationVO();
|
|
|
|
|
|
if(res.code=='0000')
|
|
|
|
|
|
{
|
|
|
|
|
|
subStationList.value=res.data;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('获取区域列表失败:', res.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取区域列表失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const openCameraEditDialog=(row:CameraRow)=>{
|
|
|
|
|
|
console.log(row);
|
|
|
|
|
|
editCameraDialog.editVisible=true;
|
|
|
|
|
|
editCameraDialog.cameraForm=deepClone(row) as CameraForm;
|
|
|
|
|
|
console.log(editCameraDialog.cameraForm);
|
|
|
|
|
|
}
|
|
|
|
|
|
const inspectionType = useDictOptions('inspection_type').value.map(item => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
id: Number(item.id.substring(6))
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
const cameraProduct = useDictOptions('camera_driver').value.map(item => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
id: Number(item.id.substring(6))
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
const cameraType = useDictOptions('camera_type').value.map(item => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
id: Number(item.id.substring(6))
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmit=async ()=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ruleFormRef.value.validate();
|
|
|
|
|
|
const res=await updateCameraApi(editCameraDialog.cameraForm);
|
|
|
|
|
|
if(res.code==='0000')
|
|
|
|
|
|
{
|
|
|
|
|
|
ElMessage.success("修改成功");
|
|
|
|
|
|
// 刷新数据
|
|
|
|
|
|
await getChannelPage();
|
|
|
|
|
|
// 关闭弹框
|
|
|
|
|
|
editCameraDialog.editVisible=false;
|
|
|
|
|
|
}else {
|
|
|
|
|
|
ElMessage.success("修改失败");
|
|
|
|
|
|
// 重置数据
|
|
|
|
|
|
ruleFormRef.value.resetFields();
|
|
|
|
|
|
}
|
|
|
|
|
|
}catch(error)
|
|
|
|
|
|
{
|
|
|
|
|
|
console.log('表单验证失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
// 取消
|
|
|
|
|
|
const handleEditClose=()=>{
|
|
|
|
|
|
editCameraDialog.editVisible=false;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
const cameraIds=[] as number[];
|
|
|
|
|
|
const deleteBatchLoading = ref(false);
|
|
|
|
|
|
// 表格多选点击事件
|
|
|
|
|
|
const handleBatchChannel=(data: CameraRow[])=>{
|
|
|
|
|
|
ids.length=0;
|
|
|
|
|
|
for(let i=0;i<data.length;i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
cameraIds.push(data[i].id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 批量删除摄像头
|
|
|
|
|
|
const handleBatchDel = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (cameraIds.length === 0) {
|
|
|
|
|
|
ElMessage.warning('请选择要删除的摄像机');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
await ElMessageBox.confirm('确定要删除这些摄像机吗?', '删除确认', {
|
|
|
|
|
|
confirmButtonText: '确定删除',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning',
|
|
|
|
|
|
});
|
|
|
|
|
|
deleteBatchLoading.value = true;
|
|
|
|
|
|
await removeCameraApi({ ids: cameraIds });
|
|
|
|
|
|
ElMessage.success('删除成功');
|
|
|
|
|
|
deleteBatchLoading.value = false;
|
|
|
|
|
|
await getChannelPage();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 5. 捕获取消操作或接口报错
|
|
|
|
|
|
if (error === 'cancel') {
|
|
|
|
|
|
// 用户点击了取消按钮
|
|
|
|
|
|
ElMessage.info('已取消删除');
|
|
|
|
|
|
deleteBatchLoading.value = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
deleteBatchLoading.value = false;
|
|
|
|
|
|
console.log(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const handleEditDel = async (data: CameraRow) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm('确定要删除该摄像机吗?', '删除确认', {
|
|
|
|
|
|
confirmButtonText: '确定删除',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning',
|
|
|
|
|
|
});
|
|
|
|
|
|
await removeCameraApi({ ids: [data.id] });
|
|
|
|
|
|
ElMessage.success('删除成功');
|
|
|
|
|
|
await getChannelPage();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 5. 捕获取消操作或接口报错
|
|
|
|
|
|
if (error === 'cancel') {
|
|
|
|
|
|
// 用户点击了取消按钮
|
|
|
|
|
|
ElMessage.info('已取消删除');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 接口层面的报错(如果你的 axios 拦截器没有抛出全局提示,可以在这里处理)
|
|
|
|
|
|
console.error('删除失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 查询摄像机
|
|
|
|
|
|
const queryCamera= async()=>{
|
|
|
|
|
|
const res=await getCameraListApi({
|
|
|
|
|
|
...channelQuery,
|
|
|
|
|
|
nvrId:NvrId.value,
|
|
|
|
|
|
limit: channelPage.pageSize,
|
|
|
|
|
|
page: channelPage.pageNum
|
|
|
|
|
|
});
|
|
|
|
|
|
channelData.value=res.data.rows;
|
|
|
|
|
|
channelPage.pageNum=res.data.current;
|
|
|
|
|
|
channelPage.pageSize=res.data.limit;
|
|
|
|
|
|
channelPage.total=res.data.total;
|
|
|
|
|
|
}
|
|
|
|
|
|
const handleCancel=()=>{
|
|
|
|
|
|
deviceDialogObj.deviceVisible=false;
|
|
|
|
|
|
deviceDialogObj.deviceForm={};
|
|
|
|
|
|
}
|
|
|
|
|
|
// 新增:视图模式控制
|
|
|
|
|
|
const viewMode = ref('list');
|
|
|
|
|
|
const currentNvrDetail = ref<NvrRow>();
|
|
|
|
|
|
const defaultProps = {
|
|
|
|
|
|
children: 'children',
|
|
|
|
|
|
label: 'name'
|
|
|
|
|
|
};
|
|
|
|
|
|
// 分页查询参数
|
|
|
|
|
|
const PageParams = reactive({
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
|
total: 0
|
|
|
|
|
|
});
|
|
|
|
|
|
// 通道分页参数
|
|
|
|
|
|
const channelPage=reactive({
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
|
total: 0
|
|
|
|
|
|
})
|
|
|
|
|
|
// 视频控制对象
|
|
|
|
|
|
const controlDialogObj=reactive({
|
|
|
|
|
|
controlVisible:false,
|
|
|
|
|
|
title:'',
|
|
|
|
|
|
id: -1,
|
|
|
|
|
|
curCamera:{}
|
|
|
|
|
|
})
|
|
|
|
|
|
// 模糊检测对象
|
|
|
|
|
|
const blurDialogObj=reactive({
|
|
|
|
|
|
blurVisible:false,
|
|
|
|
|
|
title:'',
|
|
|
|
|
|
burDetectionVO: {
|
|
|
|
|
|
enable: 0,
|
|
|
|
|
|
} as BlurDetectionUpdateDTO
|
|
|
|
|
|
})
|
|
|
|
|
|
// 视频播放对象
|
|
|
|
|
|
const playDialogObj=reactive({
|
|
|
|
|
|
playVisible: false,
|
|
|
|
|
|
title:'',
|
|
|
|
|
|
id: -1
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 视频播放
|
|
|
|
|
|
const openPlayDialog=(data:CameraRow) =>{
|
|
|
|
|
|
if(data.status==0)
|
|
|
|
|
|
{
|
|
|
|
|
|
ElMessage.warning('摄像头不在线');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
playDialogObj.playVisible=true;
|
|
|
|
|
|
playDialogObj.title= '视频预览';
|
|
|
|
|
|
if (data.channelId !== undefined) {
|
|
|
|
|
|
playDialogObj.id = data.id;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn('channelId 不存在于当前数据中');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const blurFormRef=ref();
|
|
|
|
|
|
// 提交模糊检测弹框
|
|
|
|
|
|
const handelSubmitBlur=async ()=>{
|
|
|
|
|
|
const res=await addOrUpdateBlurDetectionApi(blurDialogObj.burDetectionVO);
|
|
|
|
|
|
if(res.code=='0000')
|
|
|
|
|
|
{
|
|
|
|
|
|
ElMessage.success('提交成功');
|
|
|
|
|
|
blurDialogObj.blurVisible=false;
|
|
|
|
|
|
}else {
|
|
|
|
|
|
ElMessage.success('提交失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 模糊检测弹框
|
|
|
|
|
|
const openBlurDialog=async (data:CameraRow)=>{
|
|
|
|
|
|
if(data.status==0)
|
|
|
|
|
|
{
|
|
|
|
|
|
ElMessage.warning('摄像头不在线');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
blurDialogObj.blurVisible=true;
|
|
|
|
|
|
blurDialogObj.title='模糊检测';
|
|
|
|
|
|
if (data.id !== undefined) {
|
|
|
|
|
|
const res=await getBlurDetectionByCameraIdApi({
|
|
|
|
|
|
id: data.id
|
|
|
|
|
|
});
|
|
|
|
|
|
if(res.code=='0000' && res.data)
|
|
|
|
|
|
{
|
|
|
|
|
|
blurDialogObj.burDetectionVO=res.data;
|
|
|
|
|
|
console.log('进行赋值', blurDialogObj.burDetectionVO)
|
|
|
|
|
|
}else {
|
|
|
|
|
|
blurDialogObj.burDetectionVO.cameraId=data.id;
|
|
|
|
|
|
blurDialogObj.burDetectionVO.cameraName=data.name ?? '';
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('当前弹框对象', blurDialogObj.burDetectionVO)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn('channelId 不存在于当前数据中');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 关闭模糊检测弹框
|
|
|
|
|
|
const handelCancelBlur=()=>{
|
|
|
|
|
|
blurFormRef.value.resetFields();
|
|
|
|
|
|
blurDialogObj.burDetectionVO={};
|
|
|
|
|
|
blurDialogObj.blurVisible=false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 视频控制
|
|
|
|
|
|
const openControlDialog=(data:CameraRow)=>{
|
|
|
|
|
|
if(data.status==0)
|
|
|
|
|
|
{
|
|
|
|
|
|
ElMessage.warning('摄像头不在线');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
controlDialogObj.controlVisible=true;
|
|
|
|
|
|
controlDialogObj.title="摄像头控制"
|
|
|
|
|
|
if (data.id !== undefined) {
|
|
|
|
|
|
controlDialogObj.id = data.id;
|
|
|
|
|
|
controlDialogObj.curCamera=data;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn('channelId 不存在于当前数据中');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const handleClosePlay= async ()=>{
|
|
|
|
|
|
playDialogObj.playVisible=false
|
|
|
|
|
|
}
|
|
|
|
|
|
const handleClose=async ()=>{
|
|
|
|
|
|
controlDialogObj.controlVisible=false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 记录NVR的id
|
|
|
|
|
|
const NvrId=ref();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取通道列表
|
|
|
|
|
|
const showChannelList= async (data:NvrRow)=>{
|
|
|
|
|
|
viewMode.value='detail';
|
|
|
|
|
|
currentNvrDetail.value=data;
|
|
|
|
|
|
console.log(currentNvrDetail.value)
|
|
|
|
|
|
// 根据NVRid获取通道列表
|
|
|
|
|
|
const res=await getCameraListApi({
|
|
|
|
|
|
nvrId:data.id,
|
|
|
|
|
|
limit: channelPage.pageSize,
|
|
|
|
|
|
page: channelPage.pageNum
|
|
|
|
|
|
})
|
|
|
|
|
|
NvrId.value=data.id;
|
|
|
|
|
|
channelData.value=res.data.rows;
|
|
|
|
|
|
channelPage.pageNum=res.data.current;
|
|
|
|
|
|
channelPage.pageSize=res.data.limit;
|
|
|
|
|
|
channelPage.total=res.data.total;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 分页获取通道列表
|
|
|
|
|
|
const getChannelPage=async () =>{
|
|
|
|
|
|
const res=await getCameraListApi({
|
|
|
|
|
|
nvrId:NvrId.value,
|
|
|
|
|
|
limit: channelPage.pageSize,
|
|
|
|
|
|
page: channelPage.pageNum
|
|
|
|
|
|
})
|
|
|
|
|
|
channelData.value=res.data.rows;
|
|
|
|
|
|
channelPage.pageNum=res.data.current;
|
|
|
|
|
|
channelPage.pageSize=res.data.limit;
|
|
|
|
|
|
channelPage.total=res.data.total;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 点击节点
|
|
|
|
|
|
const handleNodeClick =async (data:TreeNode) => {
|
|
|
|
|
|
currentSelectNode.value = data;
|
|
|
|
|
|
if(data.type===3) return;
|
|
|
|
|
|
// 获取该节点下的所有NVR列表
|
|
|
|
|
|
viewMode.value = 'list';
|
|
|
|
|
|
PageParams.pageNum = 1;
|
|
|
|
|
|
if (data.type === 2) {
|
|
|
|
|
|
// 如果点击的是Nvr节点,直接展示这个NVR的列表
|
|
|
|
|
|
const res=await getNvrListApi({
|
|
|
|
|
|
id: data.id
|
|
|
|
|
|
});
|
|
|
|
|
|
tableData.value = res.data.rows;
|
|
|
|
|
|
PageParams.pageNum=res.data.current;
|
|
|
|
|
|
PageParams.pageSize=res.data.limit;
|
|
|
|
|
|
PageParams.total=res.data.total;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 获取该节点下的所有NVR列表
|
|
|
|
|
|
viewMode.value = 'list';
|
|
|
|
|
|
const res=await listByNode({
|
|
|
|
|
|
nodeId: data.id,
|
|
|
|
|
|
type: data.type,
|
|
|
|
|
|
...PageParams
|
|
|
|
|
|
})
|
|
|
|
|
|
tableData.value = res.data.rows;
|
|
|
|
|
|
PageParams.pageNum=res.data.current;
|
|
|
|
|
|
PageParams.pageSize=res.data.limit;
|
|
|
|
|
|
PageParams.total=res.data.total;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 2. 请求后端分页接口
|
|
|
|
|
|
const fetchTableData = async (params:NvrQuery) => {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 调用后端
|
|
|
|
|
|
const res = await getNvrListApi({
|
|
|
|
|
|
...params,
|
|
|
|
|
|
page:PageParams.pageNum,
|
|
|
|
|
|
limit:PageParams.pageSize
|
|
|
|
|
|
});
|
|
|
|
|
|
tableData.value=res.data.rows;
|
|
|
|
|
|
PageParams.pageNum=res.data.current;
|
|
|
|
|
|
PageParams.pageSize=res.data.limit;
|
|
|
|
|
|
PageParams.total=res.data.total;
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('加载NVR列表失败', err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 3. 分页事件处理
|
|
|
|
|
|
const handleSizeChange = (val:number) => {
|
|
|
|
|
|
PageParams.pageSize = val;
|
|
|
|
|
|
channelPage.pageSize=val;
|
|
|
|
|
|
if(viewMode.value=='list')
|
|
|
|
|
|
{
|
|
|
|
|
|
fetchTableData({});
|
|
|
|
|
|
}else if(viewMode.value=='detail') {
|
|
|
|
|
|
getChannelPage();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleCurrentChange = (val:number) => {
|
|
|
|
|
|
PageParams.pageNum = val;
|
|
|
|
|
|
channelPage.pageNum=val;
|
|
|
|
|
|
if(viewMode.value=='list')
|
|
|
|
|
|
{
|
|
|
|
|
|
fetchTableData({});
|
|
|
|
|
|
}else if(viewMode.value=='detail') {
|
|
|
|
|
|
getChannelPage();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 摄像头通道刷新
|
|
|
|
|
|
const refreshCamera= async (data:NvrRow)=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
refreshLoading.value=true;
|
|
|
|
|
|
const res=await refresh({
|
|
|
|
|
|
id:Number(data.id),
|
|
|
|
|
|
deviceType:data.driver
|
|
|
|
|
|
});
|
|
|
|
|
|
if(res.code=="0000")
|
|
|
|
|
|
{
|
|
|
|
|
|
// 重新加载通道列表
|
|
|
|
|
|
await getChannelPage();
|
|
|
|
|
|
ElMessage.success('刷新成功');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error('刷新失败: ' + (res.message || '未知错误'));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error)
|
|
|
|
|
|
{
|
|
|
|
|
|
console.error('刷新通道失败:', error);
|
|
|
|
|
|
ElMessage.error('刷新失败,请重试');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
refreshLoading.value=false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 刷新通道对象
|
|
|
|
|
|
const refreshObj=reactive({
|
|
|
|
|
|
isSyncing:false,
|
|
|
|
|
|
isSuccess:false,
|
|
|
|
|
|
percentage:0,
|
|
|
|
|
|
status:'',
|
|
|
|
|
|
timer:null
|
|
|
|
|
|
})
|
|
|
|
|
|
let timer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
|
const handleRefresh=(data:NvrRow)=>{
|
|
|
|
|
|
// // 1. 初始化状态
|
|
|
|
|
|
refreshObj.percentage= 0;
|
|
|
|
|
|
refreshObj.isSyncing = true;
|
|
|
|
|
|
refreshObj.isSuccess = false;
|
|
|
|
|
|
refreshObj.status = '';
|
|
|
|
|
|
if(timer) clearInterval(timer);
|
|
|
|
|
|
// 模拟进度条增长
|
|
|
|
|
|
timer = setInterval( async () => {
|
|
|
|
|
|
const step = Math.floor(Math.random() * 10) + 30;
|
|
|
|
|
|
refreshObj.percentage += step;
|
|
|
|
|
|
// 卡在 99% 等待后端最终响应
|
|
|
|
|
|
if (refreshObj.percentage >= 99) {
|
|
|
|
|
|
refreshObj.percentage = 99;
|
|
|
|
|
|
clearInterval(timer!);
|
|
|
|
|
|
// 向后端请求
|
|
|
|
|
|
await refreshChannel(data);
|
|
|
|
|
|
refreshObj.percentage = 100;
|
|
|
|
|
|
refreshObj.status = 'success';
|
|
|
|
|
|
refreshObj.isSuccess = true;
|
|
|
|
|
|
// 停留 2 秒让用户看清楚大对号和“同步完成”,然后关闭弹窗
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
refreshObj.isSyncing = false;
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 400);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新通道
|
|
|
|
|
|
const refreshChannel= async (data:NvrRow)=>{
|
|
|
|
|
|
try {
|
|
|
|
|
|
data.isLoading=true;
|
|
|
|
|
|
refreshLoading.value=true;
|
|
|
|
|
|
const res=await refresh({
|
|
|
|
|
|
id:Number(data.id),
|
|
|
|
|
|
deviceType:data.driver
|
|
|
|
|
|
});
|
|
|
|
|
|
if(res.code=="0000")
|
|
|
|
|
|
{
|
|
|
|
|
|
ElMessage.success('刷新成功');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error('刷新失败: ' + (res.message || '未知错误'));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error)
|
|
|
|
|
|
{
|
|
|
|
|
|
console.error('刷新通道失败:', error);
|
|
|
|
|
|
ElMessage.error('刷新失败,请重试');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
refreshLoading.value=false;
|
|
|
|
|
|
data.isLoading=false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:返回列表页逻辑
|
|
|
|
|
|
const backToList = () => {
|
|
|
|
|
|
viewMode.value = 'list';
|
|
|
|
|
|
};
|
|
|
|
|
|
// 过滤树节点
|
|
|
|
|
|
watch(filterText, (val) => {
|
|
|
|
|
|
treeRef.value?.filter(val);
|
|
|
|
|
|
});
|
|
|
|
|
|
const filterNode = (value:string, data:TreeNode) => {
|
|
|
|
|
|
if (!value) return true;
|
|
|
|
|
|
return data.name.includes(value);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取树型列表数据
|
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getTreeData();
|
|
|
|
|
|
treeData.value = res.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取数据失败', error);
|
|
|
|
|
|
ElMessage.error('获取资源列表失败,请重试');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
fetchData();
|
|
|
|
|
|
fetchTableData({});
|
|
|
|
|
|
getAllSubstationVOs();
|
|
|
|
|
|
});
|
|
|
|
|
|
// 组件销毁前清理定时器,防止内存泄漏
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
if (timer) clearInterval(timer);
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
/* 保持原有布局 */
|
|
|
|
|
|
.div-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
|
width: 320px;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.box-card {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-card__body) {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.custom-tree-node {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.node-label {
|
|
|
|
|
|
margin-left: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ehv-text {
|
|
|
|
|
|
color: #d32f2f;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 详情页特有样式 */
|
|
|
|
|
|
.detail-container {
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.detail-tabs {
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.video-wrapper {
|
|
|
|
|
|
width: 100%; /* 宽度跟随 Dialog */
|
|
|
|
|
|
|
|
|
|
|
|
/* 核心代码:强制保持 16:9 比例 */
|
|
|
|
|
|
aspect-ratio: 16 / 9;
|
|
|
|
|
|
|
|
|
|
|
|
background-color: #000; /* 视频未加载时显示黑色背景,像专业监控屏 */
|
|
|
|
|
|
position: relative; /* 为内部绝对定位做准备(如果需要) */
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
border-radius: 4px; /* 稍微搞点圆角,好看 */
|
|
|
|
|
|
|
|
|
|
|
|
/* 如果你的播放器组件是基于 canvas 或 absolute 定位的,
|
|
|
|
|
|
可能需要给内部元素加上这个 */
|
|
|
|
|
|
:deep(video), :deep(canvas) {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 算法配置列样式 */
|
|
|
|
|
|
.alg-info-container {
|
|
|
|
|
|
min-height: 60px; /* 设置最小高度,确保行高一致 */
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.alg-tag-item {
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.alg-tag-item:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.no-alg-info {
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|