AlgoSettingCard.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. <template>
  2. <div id="algoCardWrapper" class="algoCardWrapper">
  3. <div class="algoCardTitle">
  4. <div>{{ selectedAlgoDetail?.algoInfo?.name }} 算法参数配置</div>
  5. <div style="display: flex; align-items: center">
  6. <ElSwitch
  7. v-model="selectedAlgoDetail.enableCardBool"
  8. size="small"
  9. @change="handleAlgoEnable"
  10. />
  11. <el-tooltip class="box-item" effect="dark" placement="top">
  12. <template #content> 关闭后该算法对<br />此台相机不生效</template>
  13. <el-icon color="#d4d5d8" :size="16" class="tipIcon"><InfoCircleOutlined /></el-icon>
  14. </el-tooltip>
  15. </div>
  16. </div>
  17. <div class="algoCardMain">
  18. <!-- <div class="algoRow" style="display: flex; align-items: center">
  19. <div class="algoLabel">绘制电子围栏:</div>
  20. <div>
  21. <div style="display: flex; align-items: center">
  22. <ElSwitch v-model="selectedAlgoDetail.electronicFenceBool" size="small" />
  23. <el-tooltip class="box-item" effect="dark" placement="top">
  24. <template #content>
  25. 默认检测全部范围,如需<br />指定范围,可打开开关,<br />在相机界面完成绘制
  26. </template>
  27. <el-icon color="#e2e2e2" :size="16" class="tipIcon"><InfoCircleOutlined /></el-icon>
  28. </el-tooltip>
  29. <el-radio-group
  30. v-model="selectedAlgoDetail.regionJudge"
  31. :disabled="!selectedAlgoDetail.electronicFenceBool"
  32. class="ml-3"
  33. >
  34. <el-radio :label="0">内</el-radio>
  35. <el-radio :label="1">外</el-radio>
  36. </el-radio-group>
  37. </div>
  38. <div class="presetList"> 预置位1 预置位2 预置位3 </div>
  39. </div>
  40. </div> -->
  41. <div class="algoRow">
  42. <div class="algoLabel">检测时间段:</div>
  43. <div class="algoTimeContent">
  44. <div class="timeAdd" @click="handleAddTimePeriod">
  45. <el-icon color="#d0d0d0"><Plus /></el-icon>
  46. </div>
  47. <div class="timeList">
  48. <PeriodCard
  49. v-for="(x, index) in selectedAlgoDetail.timeRangeArr"
  50. :key="x.id"
  51. :ref="(el) => (periodCardRefs[index] = el)"
  52. :id="x.id"
  53. />
  54. </div>
  55. </div>
  56. </div>
  57. <div class="algoRow">
  58. <div class="algoLabel">检测元数据:</div>
  59. <div class="algoTimeContent">
  60. <div
  61. class="timeAdd"
  62. :class="{ addDisable: unEmptyLabels.length >= metaObjList.length }"
  63. @click="handleAddMetaObj"
  64. >
  65. <el-icon color="#d0d0d0"><Plus /></el-icon>
  66. </div>
  67. <div class="timeList">
  68. <ParamCard
  69. v-for="(v, index) in selectedAlgoDetail.metaValues"
  70. :key="v.id"
  71. :ref="(el) => (paramCardRefs[index] = el)"
  72. :id="v.id"
  73. />
  74. </div>
  75. </div>
  76. </div>
  77. <div class="algoRow" style="align-items: center">
  78. <div class="algoLabel">检测数量范围:</div>
  79. <el-radio-group v-model="selectedAlgoDetail.judge" class="ml-4">
  80. <el-radio :label="0">小于</el-radio>
  81. <el-radio :label="1">大于</el-radio>
  82. <el-radio :label="2">等于</el-radio>
  83. </el-radio-group>
  84. </div>
  85. <div class="algoRow" style="align-items: center">
  86. <div class="algoLabel">检测频率:</div>
  87. <ElInputNumber
  88. v-model="selectedAlgoDetail.detectionFrequency"
  89. controls-position="right"
  90. :min="1"
  91. :step="1"
  92. size="small"
  93. style="width: 186px"
  94. @blur="checkFrequencyValid"
  95. />
  96. <span style="font-size: 12px; margin-left: 5px">S/次</span>
  97. </div>
  98. <div class="algoRow" style="align-items: center">
  99. <div class="algoLabel">持续时间:</div>
  100. <ElInputNumber
  101. v-model="selectedAlgoDetail.eventDurationMinMs"
  102. controls-position="right"
  103. :min="1"
  104. :step="1"
  105. size="small"
  106. style="width: 186px"
  107. @blur="checkDurationMinMsValid"
  108. />
  109. <span style="font-size: 12px; margin-left: 5px">毫秒</span>
  110. </div>
  111. <div class="algoRow" style="align-items: center">
  112. <div class="algoLabel">持续帧数:</div>
  113. <ElInputNumber
  114. v-model="selectedAlgoDetail.eventDurationMinFrames"
  115. controls-position="right"
  116. :min="1"
  117. :step="1"
  118. size="small"
  119. style="width: 186px"
  120. @blur="checkDurationMinFramesValid"
  121. />
  122. <span style="font-size: 12px; margin-left: 5px">帧</span>
  123. </div>
  124. <div class="algoRow" style="align-items: center">
  125. <div class="algoLabel">报警时间间隔:</div>
  126. <ElInputNumber
  127. v-model="selectedAlgoDetail.eventAlarmIntervalMs"
  128. controls-position="right"
  129. :min="1"
  130. :step="1"
  131. size="small"
  132. style="width: 186px"
  133. @blur="checkAlarmIntervalMsValid"
  134. />
  135. <span style="font-size: 12px; margin-left: 5px">秒</span>
  136. </div>
  137. <div class="algoRow" style="align-items: center">
  138. <div class="algoLabel">报警帧间隔:</div>
  139. <ElInputNumber
  140. v-model="selectedAlgoDetail.eventAlarmIntervalFrames"
  141. controls-position="right"
  142. :min="1"
  143. :step="1"
  144. size="small"
  145. style="width: 186px"
  146. @blur="checkAlarmIntervalFramesValid"
  147. />
  148. <span style="font-size: 12px; margin-left: 5px">帧</span>
  149. </div>
  150. <div
  151. v-if="selectedAlgoDetail.timeWindow !== undefined"
  152. class="algoRow"
  153. style="align-items: center"
  154. >
  155. <div class="algoLabel">检测窗口时长:</div>
  156. <ElInputNumber
  157. v-model="selectedAlgoDetail.timeWindow"
  158. controls-position="right"
  159. :min="1"
  160. size="small"
  161. style="width: 186px"
  162. placeholder="请输入允许的最长时间"
  163. @blur="checkTimeWindowValid"
  164. />
  165. <span style="font-size: 12px; margin-left: 5px">S</span>
  166. </div>
  167. <div style="display: flex; justify-content: flex-end">
  168. <ElButton size="small" :disabled="!selectedAlgoId" @click="handleRemoveAlgo">取消</ElButton>
  169. <ElButton size="small" type="primary" @click="handleSave(false)" :disabled="!selectedAlgoId"
  170. >保存</ElButton
  171. >
  172. </div>
  173. </div>
  174. </div>
  175. </template>
  176. <script lang="ts" setup>
  177. import {
  178. ElSelect,
  179. ElOption,
  180. ElSwitch,
  181. ElInputNumber,
  182. ElTimePicker,
  183. ElMessage,
  184. ElMessageBox,
  185. } from 'element-plus';
  186. import { CircleCloseFilled, Plus } from '@element-plus/icons-vue';
  187. import { storeToRefs } from 'pinia';
  188. import dayjs, { Dayjs } from 'dayjs';
  189. import addTimeIcon from '@/assets/icons/addTimeIcon.png';
  190. import useCameraAlgoStore, { AlgoParamMetaItem } from '../../store/useCameraAlgoStore';
  191. import { createDefaultTime, frequencyOptions, getTimeCompletion } from './utils';
  192. import { InfoCircleOutlined } from '@vicons/antd';
  193. import { ALGO_ENABLED_STATUS, FENCE_ENBALED_STATUS } from '@/api/camera/camera-preview';
  194. import PeriodCard from './AlgoPeriodCard.vue';
  195. import ParamCard from './AlgoParamsCard.vue';
  196. import { uid } from 'uid';
  197. import { computed, ref } from 'vue';
  198. // const { data: algoList, loading } = useAllAlgos();
  199. const cameraAlgoStore = useCameraAlgoStore();
  200. const {
  201. selectedAlgoId,
  202. selectedAlgoDetail,
  203. metaObjList,
  204. markedTimeRangeIds,
  205. markedParamCardIds,
  206. } = storeToRefs(cameraAlgoStore);
  207. interface Param {
  208. /** 算法id */
  209. algoId: number;
  210. /** 检测频率,单位是秒 */
  211. detectionFrequency: number;
  212. /** 检测时间段,可以有多个,转化为字符串,格式为 09:00-10:00;11:00-12:00 */
  213. detectionTime: string;
  214. /** 电子围栏是否开启 */
  215. electronicFence: FENCE_ENBALED_STATUS;
  216. /** 算法是否启用 */
  217. status: ALGO_ENABLED_STATUS;
  218. }
  219. const emits = defineEmits<{
  220. (e: 'onSubmit', param: Param): Promise<unknown>;
  221. (e: 'onCancel', algoId: number): Promise<unknown>;
  222. }>();
  223. const periodCardRefs = ref<any>([]);
  224. const paramCardRefs = ref<any>([]);
  225. const handleAddTimePeriod = () => {
  226. const emptyList = selectedAlgoDetail.value.timeRangeArr.filter(
  227. (item) => !getTimeCompletion(item),
  228. );
  229. if (emptyList && emptyList.length > 0) {
  230. emptyList.forEach((item) => markedTimeRangeIds.value.push(item.id));
  231. ElMessage.error('请先完善检测时间段数据');
  232. return;
  233. }
  234. selectedAlgoDetail.value.timeRangeArr.push(createDefaultTime());
  235. };
  236. const unEmptyLabels = computed(() => {
  237. return selectedAlgoDetail.value.metaValues.filter((item) => item.label);
  238. });
  239. const handleAddMetaObj = () => {
  240. if (unEmptyLabels.value.length < selectedAlgoDetail.value.metaValues.length) {
  241. selectedAlgoDetail.value.metaValues
  242. .filter((item) => !item.label)
  243. .forEach((val) => markedParamCardIds.value.push(val.id));
  244. ElMessage.error('存在未完善的检测元数据');
  245. return;
  246. }
  247. // if (unemptyList.length == metaObjList.value.length) {
  248. // ElMessage.warning('暂无更多检测对象');
  249. // return;
  250. // }
  251. selectedAlgoDetail.value.metaValues.push({ id: uid() } as AlgoParamMetaItem);
  252. };
  253. const removeTime = (id: string) => {
  254. const timeRangeArr = selectedAlgoDetail.value.timeRangeArr;
  255. selectedAlgoDetail.value.timeRangeArr = timeRangeArr.filter((x) => x.id !== id);
  256. };
  257. const getTimeStr = (d: Dayjs) => {
  258. return dayjs(d).format('HH:mm:ss');
  259. };
  260. const getTimeStrs = (d: [Dayjs, Dayjs]) => {
  261. return getTimeStr(d[0]) + '-' + getTimeStr(d[1]);
  262. };
  263. const handleAlgoEnable = () => {
  264. handleSave(true);
  265. };
  266. const checkFrequencyValid = () => {
  267. selectedAlgoDetail.value.detectionFrequency = Math.ceil(
  268. selectedAlgoDetail.value.detectionFrequency,
  269. );
  270. };
  271. const checkDurationMinMsValid = () => {
  272. selectedAlgoDetail.value.eventDurationMinMs = Math.ceil(
  273. selectedAlgoDetail.value.eventDurationMinMs,
  274. );
  275. };
  276. const checkDurationMinFramesValid = () => {
  277. selectedAlgoDetail.value.eventDurationMinFrames = Math.ceil(
  278. selectedAlgoDetail.value.eventDurationMinFrames,
  279. );
  280. };
  281. const checkAlarmIntervalMsValid = () => {
  282. selectedAlgoDetail.value.eventAlarmIntervalMs = Math.ceil(
  283. selectedAlgoDetail.value.eventAlarmIntervalMs,
  284. );
  285. };
  286. const checkAlarmIntervalFramesValid = () => {
  287. selectedAlgoDetail.value.eventAlarmIntervalFrames = Math.ceil(
  288. selectedAlgoDetail.value.eventAlarmIntervalFrames,
  289. );
  290. };
  291. const checkTimeWindowValid = () => {
  292. selectedAlgoDetail.value.timeWindow = Math.ceil(selectedAlgoDetail.value.timeWindow || 1);
  293. };
  294. const handleSave = async (isSwitch: boolean) => {
  295. //判断时间段是否合格
  296. if (markedTimeRangeIds.value.length > 0) {
  297. ElMessage.error('请正确填写检测时间段');
  298. return;
  299. }
  300. const emptyTimes = selectedAlgoDetail.value.timeRangeArr.filter(
  301. (item) => !item.startDay || !item.endDay,
  302. );
  303. if (emptyTimes && emptyTimes.length > 0) {
  304. emptyTimes.forEach((item) => {
  305. markedTimeRangeIds.value.push(item.id);
  306. });
  307. ElMessage.error('请完善检测时间段');
  308. return;
  309. }
  310. for (let i = 0; i < periodCardRefs.value.length; i++) {
  311. const item = periodCardRefs.value[i];
  312. if (!item) continue;
  313. switch (item.checkValid()) {
  314. case 0:
  315. ElMessage.error('请正确填写检测时间段');
  316. return;
  317. case 1:
  318. ElMessage.error('请完善检测时间段');
  319. return;
  320. case 2:
  321. ElMessage.error('至少有一个时间段');
  322. return;
  323. case 3:
  324. break;
  325. }
  326. }
  327. //判断元数据是否合格
  328. for (let i = 0; i < paramCardRefs.value.length; i++) {
  329. const item = paramCardRefs.value[i];
  330. if (!item) continue;
  331. const res = await item.checkValid();
  332. if (res) {
  333. } else {
  334. ElMessage.error('请正确填写检测元数据');
  335. return;
  336. }
  337. }
  338. //判断时间段和元数据都有值
  339. const detail = selectedAlgoDetail.value;
  340. if (!detail) return;
  341. const timeRanges = detail.timeRangeArr;
  342. // if (timeRanges.length == 0) {
  343. // ElMessage.error('至少添加一个检测时间段');
  344. // return;
  345. // }
  346. const metaValues = detail.metaValues;
  347. // 鹰眼追踪不需要元数据,先放开所有算法的元数据约束
  348. // if (metaValues.length == 0) {
  349. // ElMessage.error('至少添加一个检测元数据');
  350. // return;
  351. // }
  352. //数据处理
  353. const metaObjs = metaValues.map((meta) => {
  354. const obj = metaObjList.value.find((item) => item.label === meta.label);
  355. const val = {
  356. label: meta.label,
  357. confidence: meta.confidence / 100,
  358. min_width: meta['min_width'],
  359. min_height: meta['min_height'],
  360. } as any;
  361. const nextValues = [] as any[];
  362. obj.nextObjs.forEach((next) => {
  363. if (meta[`${next.label}.confidence`]) {
  364. nextValues.push({
  365. label: next.label,
  366. confidence: meta[`${next.label}.confidence`] / 100,
  367. min_width: meta[next.label + '.' + 'min_width'],
  368. min_height: meta[next.label + '.' + 'min_height'],
  369. nextObjs: [],
  370. });
  371. }
  372. });
  373. val.nextObjs = nextValues;
  374. return val;
  375. });
  376. const param = {
  377. id: detail.id,
  378. isSwitch,
  379. inferCode: detail.inferCode,
  380. algoId: detail.algoId,
  381. detectionFrequency: detail.detectionFrequency,
  382. eventDurationMinMs: detail.eventDurationMinMs,
  383. eventDurationMinFrames: detail.eventDurationMinFrames,
  384. eventAlarmIntervalMs: detail.eventAlarmIntervalMs,
  385. eventAlarmIntervalFrames: detail.eventAlarmIntervalFrames,
  386. regionJudge: detail.regionJudge,
  387. detectionTime: JSON.stringify(detail.timeRangeArr),
  388. metaObjs,
  389. criticalCounts: metaValues.map((item) => item.criticalCount),
  390. judge: detail.judge,
  391. electronicFence: detail.electronicFenceBool
  392. ? FENCE_ENBALED_STATUS.enabled
  393. : FENCE_ENBALED_STATUS.disabled,
  394. status: detail.enableCardBool ? ALGO_ENABLED_STATUS.enabled : ALGO_ENABLED_STATUS.disabled,
  395. } as any;
  396. if (detail.timeWindow) {
  397. param.timeWindow = detail.timeWindow;
  398. }
  399. emits('onSubmit', param);
  400. console.log('param', param);
  401. };
  402. const handleRemoveAlgo = () => {
  403. if (!selectedAlgoId.value) return;
  404. const el = document.getElementById('algoCardWrapper') as HTMLElement;
  405. ElMessageBox.confirm(
  406. '<strong>确认取消算法配置吗?</strong><br />取消后配置的参数将不会被保存。',
  407. '',
  408. {
  409. confirmButtonText: '确定',
  410. cancelButtonText: '取消',
  411. type: 'warning',
  412. dangerouslyUseHTMLString: true,
  413. appendTo: el,
  414. },
  415. )
  416. .then(() => {
  417. emits('onCancel', selectedAlgoId.value!);
  418. })
  419. .catch(() => {});
  420. };
  421. </script>
  422. <style scoped>
  423. .algoCardWrapper {
  424. border: 1px solid #ccc;
  425. border-radius: 4px;
  426. /* padding: 10px; */
  427. width: 780px;
  428. }
  429. .algoRow {
  430. display: flex;
  431. margin: 10px 0;
  432. }
  433. .algoLabel {
  434. margin-right: 10px;
  435. width: 90px;
  436. text-align: right;
  437. }
  438. .algoTimeContent {
  439. display: flex;
  440. }
  441. .presetList {
  442. font-size: 12px;
  443. display: none;
  444. }
  445. .removeIcon {
  446. margin-left: 10px;
  447. color: #8c8c8c;
  448. cursor: pointer;
  449. }
  450. .addTimeIcon {
  451. cursor: pointer;
  452. }
  453. .algoCardTitle {
  454. background: #f0f2f5;
  455. display: flex;
  456. justify-content: space-between;
  457. padding: 4px 20px;
  458. align-items: center;
  459. }
  460. .algoCardMain {
  461. padding: 10px 15px;
  462. }
  463. .timeAdd {
  464. width: 28px;
  465. height: 90px;
  466. background: #ebebeb;
  467. border-radius: 4px;
  468. border: 1px dashed #00000026;
  469. display: flex;
  470. justify-content: center;
  471. align-items: center;
  472. margin-right: 5px;
  473. cursor: pointer;
  474. }
  475. .addDisable {
  476. opacity: 0.5;
  477. cursor: not-allowed;
  478. }
  479. .timeList {
  480. width: 610px;
  481. display: flex;
  482. flex-wrap: wrap;
  483. align-items: flex-start;
  484. }
  485. .tipIcon {
  486. margin-left: 10px;
  487. }
  488. :deep(.ml-4) {
  489. margin-left: 0 !important;
  490. }
  491. :deep(.el-message-box__status.el-icon) {
  492. top: 0 !important;
  493. transform: none !important;
  494. }
  495. </style>