CameraTree.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <template>
  2. <div class="cameraTreeWrapper">
  3. <div class="cameraTreeTitle">
  4. <span>场景树</span>
  5. <span class="detail-num" v-if="totalNum">
  6. (总相机:{{ totalNum }} 未联网:{{ noNetworkingNum }} 未进入平台:{{ noIntegrationNum }})
  7. </span>
  8. </div>
  9. <div class="cameraTreeInputWrapper">
  10. <el-input
  11. class="filterTextInput"
  12. v-model="queryForm.queryString"
  13. placeholder="请输入相机名称/设备ID/算法名称"
  14. @keyup.enter.native="handleSearchCamera"
  15. clearable
  16. @clear="handleSearchCamera"
  17. >
  18. <template #suffix>
  19. <el-icon class="el-input__icon" @click="handleSearchCamera"><search /></el-icon>
  20. </template>
  21. </el-input>
  22. <div class="cameraTreeCheckboxWrapper">
  23. <el-checkbox
  24. v-model="queryForm.isEnableAlgo"
  25. label="添加算法"
  26. @change="handleSearchCamera"
  27. />
  28. <el-checkbox
  29. v-model="queryForm.isEnableRender"
  30. label="开启渲染"
  31. @change="handleSearchCamera"
  32. />
  33. <el-button v-if="isSearch" text type="primary" @click="handleCollapseTree">收起</el-button>
  34. <el-button v-if="!isSearch" text type="primary" @click="handleExpandTree">展开</el-button>
  35. </div>
  36. <el-scrollbar class="tree-scroll">
  37. <el-tree
  38. v-if="treeCollapse"
  39. ref="treeRef"
  40. node-key="code"
  41. :data="cameraTreeTemp"
  42. :props="defaultProps"
  43. :default-expand-all="isSearch"
  44. :default-expanded-keys="workSpaceKeys"
  45. @node-click="handleNodeClick"
  46. >
  47. <template #default="{ node, data }">
  48. <span
  49. class="flexCenter"
  50. :class="{ integrationState: data.integrationState === 1, nodeSelect: isSelect(data) }"
  51. >
  52. <span
  53. v-if="data.nodeType === CameraTreeNodeType.camera"
  54. class="iconWrapper flexCenter"
  55. >
  56. <span
  57. class="cameraCommon"
  58. :class="{
  59. cameraSelect: isSelect(data),
  60. }"
  61. ></span>
  62. <el-icon
  63. class="cameraIcon"
  64. :class="{
  65. iconSelect: isSelect(data),
  66. }"
  67. >
  68. <VideoCamera />
  69. </el-icon>
  70. <el-icon class="invalidCamera" v-if="isInvalid(data)"><WarningFilled /></el-icon>
  71. </span>
  72. {{ node.label }}
  73. </span>
  74. </template>
  75. </el-tree>
  76. </el-scrollbar>
  77. </div>
  78. </div>
  79. </template>
  80. <script lang="ts" setup>
  81. import { nextTick, onMounted, onUnmounted, ref } from 'vue';
  82. import { ElMessage, ElTree } from 'element-plus';
  83. import { Search, VideoCamera, WarningFilled } from '@element-plus/icons-vue';
  84. import { useRouteQuery } from '@vueuse/router';
  85. import useCameraStatus from '../../store/useCameraStatus';
  86. import {
  87. CameraTree,
  88. CameraTreeNodeType,
  89. CameraQueryForm,
  90. getCameraTree,
  91. } from '@/api/camera/camera-preview';
  92. const cameraId = useRouteQuery('cameraId');
  93. const cameraStatus = useCameraStatus();
  94. const { noNetworkingNum, openInterval, closeInterval } = cameraStatus;
  95. const queryForm = ref<CameraQueryForm>({
  96. isEnableAlgo: false,
  97. isEnableRender: false,
  98. queryString: '',
  99. });
  100. const totalNum = ref(0); // 总相机数
  101. const noIntegrationNum = ref(0); // 未进入平台相机数
  102. const cameraTree = ref<CameraTree[]>([]); // 保存从接口获取的所有树节点信息
  103. const cameraTreeTemp = ref<CameraTree[]>([]); // 保存修改name之后的树
  104. const codeShowList = ref<string[]>([]); // 保存当前所有相机code列表
  105. const isSearch = ref(true); // 默认展开全部
  106. const treeCollapse = ref(true);
  107. const workSpaceKeys = ref<string[]>([]); // 当前要收起的工位code
  108. const treeRef = ref<InstanceType<typeof ElTree>>();
  109. const defaultProps = {
  110. children: 'children',
  111. label: 'name',
  112. };
  113. const handleNodeClick = (e: CameraTree) => {
  114. if (e.integrationState === 1) {
  115. ElMessage.error('该相机未进入平台');
  116. } else {
  117. if (e.nodeType === CameraTreeNodeType.camera) {
  118. cameraId.value = String(e.id);
  119. }
  120. }
  121. };
  122. const isSelect = (data) =>
  123. data.nodeType === CameraTreeNodeType.camera && data.id === Number(cameraId.value);
  124. const isInvalid = (data) => {
  125. return data.networkingState !== 0;
  126. };
  127. // 把树节点中所有 nodeType = camera 的 name 替换成 name + code
  128. function getCameraNameCode(data) {
  129. const cameraNameCode = data;
  130. for (let i = 0; i < data.length; i++) {
  131. const node = cameraNameCode[i];
  132. if (node.nodeType === 'camera') {
  133. node.name = node.name + ` [${node.code}] `;
  134. }
  135. if (node.children && node.children.length > 0) {
  136. getCameraNameCode(node.children);
  137. }
  138. }
  139. return cameraNameCode;
  140. }
  141. // 获取当前树结构下总相机code列表
  142. function getCameraList(data) {
  143. let cameraList = [] as string[];
  144. for (let i = 0; i < data.length; i++) {
  145. const node = data[i];
  146. if (node.nodeType === 'camera') {
  147. cameraList.push(node.code);
  148. }
  149. if (node.children && node.children.length > 0) {
  150. const childCameraList = getCameraList(node.children);
  151. cameraList.push(...childCameraList);
  152. }
  153. }
  154. return cameraList;
  155. }
  156. // 获取当前树结构下nodeType=workshop的节点code集合
  157. function getWorkShopCodeList(data) {
  158. let tempCodeList = [] as string[];
  159. for (let i = 0; i < data.length; i++) {
  160. const node = data[i];
  161. if (node.nodeType === 'workshop') tempCodeList.push(node.code);
  162. if (node.children && node.children.length > 0) {
  163. const childList = getWorkShopCodeList(node.children);
  164. tempCodeList.push(...childList);
  165. }
  166. }
  167. return tempCodeList;
  168. }
  169. // 更新/获取未进入平台相机数量
  170. function updateNetworkingState(data, targetData) {
  171. let integrationCount = 0;
  172. for (let i = 0; i < data.length; i++) {
  173. const node = data[i];
  174. const matchedNode = targetData.find((item) => item.cameraCode === node.code);
  175. if (matchedNode) {
  176. node.networkingState = matchedNode.status;
  177. node.integrationState = matchedNode.integrationState;
  178. }
  179. if (node.integrationState === 1) {
  180. integrationCount++;
  181. }
  182. if (node.children && node.children.length > 0) {
  183. const childIntegrationCount = updateNetworkingState(node.children, targetData);
  184. integrationCount += childIntegrationCount;
  185. }
  186. }
  187. noIntegrationNum.value = integrationCount;
  188. return integrationCount;
  189. }
  190. // 输入框回车搜索 + checkbox 搜索
  191. const handleSearchCamera = async () => {
  192. treeCollapse.value = false;
  193. workSpaceKeys.value = [];
  194. await getCameraData(queryForm.value);
  195. nextTick(() => {
  196. isSearch.value = true;
  197. treeCollapse.value = true;
  198. });
  199. };
  200. // 收起相机,收起到工位
  201. const handleCollapseTree = () => {
  202. treeCollapse.value = false;
  203. workSpaceKeys.value = getWorkShopCodeList(cameraTreeTemp.value);
  204. nextTick(() => {
  205. isSearch.value = false;
  206. treeCollapse.value = true;
  207. });
  208. };
  209. // 展开相机
  210. const handleExpandTree = () => {
  211. treeCollapse.value = false;
  212. workSpaceKeys.value = [];
  213. nextTick(() => {
  214. isSearch.value = true;
  215. treeCollapse.value = true;
  216. });
  217. };
  218. const getCameraData = async (tempQuery) => {
  219. await getCameraTree(tempQuery).then((res) => {
  220. cameraTree.value = res;
  221. cameraTreeTemp.value = getCameraNameCode(res);
  222. codeShowList.value = getCameraList(res);
  223. totalNum.value = codeShowList.value.length;
  224. openInterval(codeShowList.value, (targetData) => {
  225. updateNetworkingState(cameraTree.value, targetData);
  226. });
  227. });
  228. };
  229. onMounted(() => {
  230. getCameraData(queryForm.value);
  231. });
  232. onUnmounted(() => {
  233. closeInterval();
  234. });
  235. </script>
  236. <style scoped>
  237. .cameraCommon {
  238. width: 6px;
  239. height: 6px;
  240. display: inline-block;
  241. align-items: center;
  242. margin-right: 6px;
  243. }
  244. .tree-scroll {
  245. height: calc(100vh - 64px - 170px);
  246. }
  247. .cameraSelect {
  248. width: 6px;
  249. height: 6px;
  250. background: #0052d9;
  251. display: inline-block;
  252. border-radius: 6px;
  253. margin-right: 6px;
  254. }
  255. .cameraTreeTitle {
  256. background: #f0f2f5;
  257. padding: 12px;
  258. display: flex;
  259. }
  260. .detail-num {
  261. font-size: 10px;
  262. margin-top: 4px;
  263. margin-left: 6px;
  264. }
  265. .cameraTreeInputWrapper {
  266. padding: 8px;
  267. }
  268. .filterTextInput {
  269. margin: 8px 0;
  270. }
  271. .flexCenter {
  272. display: flex;
  273. align-items: center;
  274. }
  275. .integrationState {
  276. cursor: not-allowed;
  277. color: #ccc;
  278. }
  279. .cameraIcon {
  280. margin-right: 5px;
  281. font-size: 18px;
  282. }
  283. .iconSelect {
  284. color: #0052d9;
  285. }
  286. .iconWrapper {
  287. position: relative;
  288. }
  289. .invalidCamera {
  290. color: #dd5869;
  291. font-size: 12px;
  292. position: absolute;
  293. right: 2px;
  294. top: -4px;
  295. }
  296. .nodeSelect {
  297. color: #0052d9;
  298. }
  299. .el-input__icon {
  300. cursor: pointer;
  301. }
  302. .el-input__icon:hover {
  303. color: #0052d9;
  304. }
  305. .cameraTreeCheckboxWrapper {
  306. display: flex;
  307. justify-content: space-between;
  308. .el-checkbox {
  309. margin-right: 0;
  310. }
  311. }
  312. </style>