|
|
@@ -0,0 +1,442 @@
|
|
|
+<template>
|
|
|
+ <div class="setting-camera-page">
|
|
|
+ <div class="camera-item">
|
|
|
+ <p class="camera-header">
|
|
|
+ <el-input
|
|
|
+ placeholder="请输入相机名称或设备ID"
|
|
|
+ :prefix-icon="Search"
|
|
|
+ v-model="queryForm.queryString"
|
|
|
+ @keyup.enter="getCameraData"
|
|
|
+ clearable
|
|
|
+ @clear="getCameraData"
|
|
|
+ />
|
|
|
+ </p>
|
|
|
+ <div class="camera-content">
|
|
|
+ <el-scrollbar class="tree-scroll">
|
|
|
+ <CameraTreeCom
|
|
|
+ ref="treeRef"
|
|
|
+ :treeData="cameraTreeTemp"
|
|
|
+ :isShowCheckbox="true"
|
|
|
+ :enablePreview="true"
|
|
|
+ @node-click="handleNodeClick"
|
|
|
+ @check="handleTreeCheck"
|
|
|
+ />
|
|
|
+ </el-scrollbar>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="camera-item">
|
|
|
+ <p class="camera-header"> 已选相机 </p>
|
|
|
+ <div class="camera-content">
|
|
|
+ <div class="select-item" v-for="item in selectedCameraList" :key="item.code">
|
|
|
+ <div class="select-botton">
|
|
|
+ <el-icon @click="handleSelectIcon(item)"
|
|
|
+ ><Star :color="item.isMainCamera === IsMainCamera.YES ? 'Gold' : 'gray'"
|
|
|
+ /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="select-content">
|
|
|
+ <div class="content-main" @click="handleSelect(item)">
|
|
|
+ <p class="name" :class="item.isActive ? 'active' : ''">{{ item.name }}</p>
|
|
|
+ <p class="message">{{ item.algoName }}</p>
|
|
|
+ </div>
|
|
|
+ <el-icon class="delete-icon" @click="handleDelete(item)"><Close color="#fff" /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="camera-item">
|
|
|
+ <p class="camera-header"> 联合算法列表</p>
|
|
|
+ <div class="camera-content">
|
|
|
+ <div class="mb-2 ml-4">
|
|
|
+ <el-radio-group :model-value="selectedAlgo" @change="handleRiadoChange">
|
|
|
+ <el-radio :value="item.code" size="large" v-for="item in groupAlgoData" :key="item.id">
|
|
|
+ {{ item.name }}</el-radio
|
|
|
+ >
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { onMounted, ref } from 'vue';
|
|
|
+ import { Search, Star, Close } from '@element-plus/icons-vue';
|
|
|
+ import { ElMessage } from 'element-plus';
|
|
|
+ import { uid } from 'uid';
|
|
|
+ import { CameraTree, CameraTreeNodeType, CameraQueryForm } from '@/api/camera/camera-preview';
|
|
|
+ import { queryCameraTreeByCondition, getDetectionGroupLAlgoList } from '@/api/camera/camera-preview-group';
|
|
|
+ import { GropAlgoProps, SelectOption, IsMainCamera } from '@/types/camera/camera-preview';
|
|
|
+ import CameraTreeCom from '@/modules/camera-tree/CameraTree.vue';
|
|
|
+ import { IntegrationState } from '@/modules/camera-tree/types/constant';
|
|
|
+
|
|
|
+ interface CameraTreeTempType extends CameraTree {
|
|
|
+ tempCode?: string;
|
|
|
+ }
|
|
|
+
|
|
|
+ const queryForm = ref<CameraQueryForm>({
|
|
|
+ isEnableAlgo: false,
|
|
|
+ isEnableRender: false,
|
|
|
+ queryString: '',
|
|
|
+ });
|
|
|
+
|
|
|
+ const cameraTreeTemp = ref<CameraTreeTempType[]>([]); // 保存修改name之后的树
|
|
|
+ const selectedCameraList = ref<SelectOption[]>([]); // 保存选中的算法列表
|
|
|
+ const handleNodeClick = (e: CameraTreeTempType) => {
|
|
|
+ selectedAlgo.value = '';
|
|
|
+ if (e.nodeType === CameraTreeNodeType.camera) {
|
|
|
+ if (selectedCameraList.value.some((item) => item.code === e.id)) {
|
|
|
+ ElMessage({
|
|
|
+ message: '该相机已被选中',
|
|
|
+ type: 'warning',
|
|
|
+ plain: true,
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e.integrationState === IntegrationState.DISABLED) {
|
|
|
+ ElMessage({
|
|
|
+ message: '该相机未开启',
|
|
|
+ type: 'warning',
|
|
|
+ plain: true,
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedCameraList.value.push({
|
|
|
+ code: e.id,
|
|
|
+ name: e.name,
|
|
|
+ isMainCamera: selectedCameraList.value.length === 0 ? IsMainCamera.YES : IsMainCamera.NO,
|
|
|
+ algoName: '',
|
|
|
+ algoCode: '',
|
|
|
+ isActive: false,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 获取当前节点在树中的完整节点对象
|
|
|
+ const treeNode = treeRef.value?.getCheckedNodes(e.tempCode);
|
|
|
+ // 勾选当前节点
|
|
|
+ treeRef.value?.setChecked(treeNode, true, false);
|
|
|
+ // 递归勾选所有父节点
|
|
|
+ const checkParentNodes = (node: Node) => {
|
|
|
+ if (node.parent) {
|
|
|
+ treeRef.value?.setChecked(node.parent, true, false);
|
|
|
+ checkParentNodes(node.parent);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ if (treeNode.parent) {
|
|
|
+ checkParentNodes(treeNode.parent);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleTreeCheck = (node: CameraTreeTempType, checked: { checkedNodes: CameraTreeTempType[] }) => {
|
|
|
+ // 获取当前所有选中节点中的相机节点
|
|
|
+ const currentCameraNodes = checked.checkedNodes.filter(
|
|
|
+ (n) => n.nodeType === CameraTreeNodeType.camera && n.integrationState !== IntegrationState.DISABLED,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 检查并过滤禁用节点
|
|
|
+ const disabledNodes = checked.checkedNodes.filter(
|
|
|
+ (n) => n.nodeType === CameraTreeNodeType.camera && n.integrationState === IntegrationState.DISABLED,
|
|
|
+ );
|
|
|
+ disabledNodes.forEach((n) => {
|
|
|
+ // ElMessage.warning(`${n.name} 已禁用,不可选中`);
|
|
|
+ treeRef.value?.setChecked(n.tempCode as string, false, true);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 生成当前选中的code集合
|
|
|
+ const currentCodes = new Set(currentCameraNodes.map((n) => n.code));
|
|
|
+
|
|
|
+ // 移除已取消选中的项
|
|
|
+ selectedCameraList.value = selectedCameraList.value.filter((item: SelectOption) =>
|
|
|
+ currentCodes.has(item.code + ''),
|
|
|
+ );
|
|
|
+ // 添加新选中的项
|
|
|
+ currentCameraNodes.forEach((camera) => {
|
|
|
+ if (!selectedCameraList.value.some((item) => item.code === camera.id)) {
|
|
|
+ selectedCameraList.value.push({
|
|
|
+ code: camera.id,
|
|
|
+ name: camera.name.replace(/ \[\w+\] $/, ''),
|
|
|
+ isMainCamera: selectedCameraList.value.length === 0 ? IsMainCamera.YES : IsMainCamera.NO,
|
|
|
+ algoName: '',
|
|
|
+ algoCode: '',
|
|
|
+ isActive: false,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const getCameraData = async () => {
|
|
|
+ await queryCameraTreeByCondition(queryForm.value).then((res) => {
|
|
|
+ cameraTreeTemp.value = getCameraNameCode(res);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 把树节点中所有 nodeType = camera 的 name 替换成 name + code
|
|
|
+ function getCameraNameCode(data) {
|
|
|
+ const cameraNameCode = data;
|
|
|
+ for (let i = 0; i < data.length; i++) {
|
|
|
+ const node = cameraNameCode[i];
|
|
|
+ node.tempCode = uid(); // 为相机树节点创建唯一code
|
|
|
+ if (node.nodeType === 'camera') {
|
|
|
+ node.name = node.name + ` [${node.code}] `;
|
|
|
+ }
|
|
|
+ if (node.children && node.children.length > 0) {
|
|
|
+ getCameraNameCode(node.children);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return cameraNameCode;
|
|
|
+ }
|
|
|
+
|
|
|
+ const groupAlgoData = ref<GropAlgoProps[]>([]);
|
|
|
+ const getGroupLAlgoList = () => {
|
|
|
+ getDetectionGroupLAlgoList().then((res) => {
|
|
|
+ groupAlgoData.value = res;
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSelect = (row: SelectOption) => {
|
|
|
+ selectedCameraList.value.map((item) => {
|
|
|
+ if (item.code === row.code) {
|
|
|
+ item.isActive = true;
|
|
|
+ } else {
|
|
|
+ item.isActive = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ selectedAlgo.value = '';
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSelectIcon = (row: SelectOption) => {
|
|
|
+ selectedCameraList.value.map((item) => {
|
|
|
+ if (item.code === row.code) {
|
|
|
+ item.isMainCamera = item.isMainCamera === IsMainCamera.YES ? IsMainCamera.NO : IsMainCamera.YES;
|
|
|
+ } else {
|
|
|
+ item.isMainCamera = IsMainCamera.NO;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const treeRef = ref<InstanceType<typeof CameraTreeCom>>();
|
|
|
+ const handleDelete = (row: SelectOption) => {
|
|
|
+ // 从已选列表删除
|
|
|
+ selectedCameraList.value = selectedCameraList.value.filter((item) => item.code !== row.code);
|
|
|
+
|
|
|
+ // 同步取消树节点的选中状态
|
|
|
+ const findNode = (nodes: CameraTreeTempType[]): CameraTreeTempType | undefined => {
|
|
|
+ for (const node of nodes) {
|
|
|
+ if (node.id === row.code && node.nodeType === CameraTreeNodeType.camera) return node;
|
|
|
+ if (node.children) {
|
|
|
+ const found = findNode(node.children);
|
|
|
+ if (found) return found;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const targetNode = findNode(cameraTreeTemp.value);
|
|
|
+ if (targetNode?.tempCode) {
|
|
|
+ treeRef.value?.setChecked(targetNode.tempCode, false, false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const selectedAlgo = ref();
|
|
|
+ const handleRiadoChange = (value) => {
|
|
|
+ const selectedCameraItem = selectedCameraList.value.filter((item) => item.isActive);
|
|
|
+ if (selectedCameraItem.length === 0) {
|
|
|
+ ElMessage({
|
|
|
+ message: '请先选中的相机',
|
|
|
+ type: 'warning',
|
|
|
+ plain: true,
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const selectedAlgoItem = groupAlgoData.value.find((item) => item.code === value);
|
|
|
+
|
|
|
+ selectedCameraItem.forEach((item) => {
|
|
|
+ item.algoName = selectedAlgoItem?.name as unknown as string;
|
|
|
+ item.algoCode = selectedAlgoItem?.code as unknown as string;
|
|
|
+ });
|
|
|
+
|
|
|
+ selectedAlgo.value = value;
|
|
|
+ };
|
|
|
+
|
|
|
+ const isValidate = () => {
|
|
|
+ // 校验至少选择两个相机
|
|
|
+ if (selectedCameraList.value.length < 2) {
|
|
|
+ ElMessage.error('请至少选择两个相机');
|
|
|
+ return { valid: false, message: '相机数量不足' };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验所有选中相机都已配置算法
|
|
|
+ const unsetAlgoCameras = selectedCameraList.value.filter((item) => !item.algoCode || item.algoCode === '');
|
|
|
+
|
|
|
+ if (unsetAlgoCameras.length > 0) {
|
|
|
+ ElMessage.error('存在未选择算法的相机');
|
|
|
+ return { valid: false, message: '算法未配置完整' };
|
|
|
+ }
|
|
|
+
|
|
|
+ return { valid: true, data: selectedCameraList.value };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 重置设置相机组
|
|
|
+ const clearForm = () => {
|
|
|
+ selectedCameraList.value = [];
|
|
|
+ selectedAlgo.value = '';
|
|
|
+ queryForm.value = {
|
|
|
+ isEnableAlgo: false,
|
|
|
+ isEnableRender: false,
|
|
|
+ queryString: '',
|
|
|
+ };
|
|
|
+ // 清除树组件的所有选中状态
|
|
|
+ if (treeRef.value) {
|
|
|
+ treeRef.value.setCheckedKeys([]);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 暴露校验方法给父组件
|
|
|
+ defineExpose({
|
|
|
+ isValidate,
|
|
|
+ clearForm,
|
|
|
+ });
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ getCameraData();
|
|
|
+ getGroupLAlgoList();
|
|
|
+ });
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+ .setting-camera-page {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 20px;
|
|
|
+
|
|
|
+ .camera-item {
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ &:first-child {
|
|
|
+ .camera-header {
|
|
|
+ padding-left: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .camera-header {
|
|
|
+ width: 100%;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 2px;
|
|
|
+ font-size: 15px;
|
|
|
+ line-height: 40px;
|
|
|
+ color: #333;
|
|
|
+ cursor: pointer;
|
|
|
+ background-color: #dcdfe6;
|
|
|
+ padding-left: 20px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ :deep(.el-input--default) {
|
|
|
+ width: 95%;
|
|
|
+ margin: 0 auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
+ border-radius: 50px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .camera-content {
|
|
|
+ width: 100%;
|
|
|
+ height: 400px;
|
|
|
+ background-color: #fff;
|
|
|
+ overflow-y: auto;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ padding: 10px 20px;
|
|
|
+
|
|
|
+ .select-item {
|
|
|
+ display: flex;
|
|
|
+ align-self: start;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 10px;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .select-botton {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .select-content {
|
|
|
+ position: relative; // 新增
|
|
|
+ margin-left: 5px;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .name {
|
|
|
+ color: #333;
|
|
|
+ padding-left: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message {
|
|
|
+ color: #dcdfe6;
|
|
|
+ padding-left: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: #409eff;
|
|
|
+ .name {
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ .delete-icon {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .delete-icon {
|
|
|
+ // 新增删除图标样式
|
|
|
+ display: none;
|
|
|
+ position: absolute;
|
|
|
+ right: 10px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-main {
|
|
|
+ // 新增内容容器
|
|
|
+ flex: 1;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .active {
|
|
|
+ color: #0052d9;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-input__icon {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-input__icon:hover {
|
|
|
+ color: #0052d9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cameraTreeCheckboxWrapper {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ .el-checkbox {
|
|
|
+ margin-right: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-radio-group) {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ }
|
|
|
+</style>
|