LinkRuleEditor.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. <template>
  2. <div class="long-list1">
  3. <div class="list-title">
  4. <span>联动规则编辑器</span>
  5. </div>
  6. <div class="basic-box">
  7. <basicBorder title="触发条件设置">
  8. <div class="edit-wrap">
  9. <div class="edit-right">
  10. <div class="right-title">条件逻辑说明</div>
  11. <div class="right-content">
  12. <div v-for="(item, index) in formData" :key="index" class="right-item-row">
  13. <span class="right-item index-label">{{ index + 1 }}</span>
  14. <span class="right-item">设备名称: {{ item.alarmName }}</span>
  15. <span class="right-item">监测地点: {{ item.devicePos }}</span>
  16. <span class="right-item">IF {{ getConditionText(item) }}</span>
  17. <span class="right-item">AND 持续时间:{{ item.cxTime ?? '' }}s</span>
  18. <span class="right-item">AND 时间在生效范围内</span>
  19. <span class="right-item">THEN 执行联动动作</span>
  20. </div>
  21. </div>
  22. </div>
  23. </div>
  24. </basicBorder>
  25. </div>
  26. <div class="basic-box1">
  27. <basicBorder title="联动动作设置">
  28. <div class="basic-table">
  29. <div class="table-title">
  30. <div class="title-item" v-for="(item, index) in titleList" :key="index">{{ item.label }}</div>
  31. </div>
  32. <div class="table-content">
  33. <div class="content-item" v-for="(item, index) in tableData" :key="index">
  34. <div class="item-text">{{ item.indnex }}</div>
  35. <div class="item-text item-text1">
  36. <span>{{ item.actionType }}</span>
  37. </div>
  38. <div class="item-text">
  39. <span> {{ item.action }}</span>
  40. </div>
  41. <div class="item-text">
  42. <div class="switch" :class="{ active: item.qy === 1 }">
  43. <div class="switch-dot"></div>
  44. </div>
  45. </div>
  46. <div class="item-text">{{ item.delayTime }}</div>
  47. <div class="item-text">{{ item.duration }}</div>
  48. <div class="item-text">
  49. <span class="text-look">查看</span>
  50. </div>
  51. </div>
  52. </div>
  53. </div>
  54. </basicBorder>
  55. </div>
  56. <div class="basic-box2">
  57. <basicBorder title="联动执行流程图">
  58. <div class="workflow-container">
  59. <div class="steps-wrapper">
  60. <div v-for="(step, index) in steps" :key="index" class="step-item">
  61. <div class="step-icon" :class="`icon-${step.id}`"></div>
  62. <div class="step-frame" :class="`frame-${step.id}`">
  63. <span class="step-text">{{ step.name }}</span>
  64. </div>
  65. <!-- 底部小圆点 -->
  66. <div class="step-dot">
  67. <div class="dot" :class="{ active: step.status === 1 }"></div>
  68. </div>
  69. <div class="steps-arrow"></div>
  70. </div>
  71. </div>
  72. </div>
  73. </basicBorder>
  74. </div>
  75. </div>
  76. </template>
  77. <script setup lang="ts">
  78. import { reactive, ref, onMounted, watch, computed } from 'vue';
  79. import basicBorder from './basicBorder.vue';
  80. import { useLinkRuleStore } from '/@/store/modules/linkRule';
  81. import { getWarningDeviceList } from '../hsqHome.api.js';
  82. const props = defineProps<{
  83. selectedRow?: any | null;
  84. }>();
  85. const linkRuleStore = useLinkRuleStore();
  86. const currentSelectedRow = computed(() => linkRuleStore.getSelectedRow);
  87. let titleList = ref<any[]>([
  88. { label: '序号', value: '1' },
  89. { label: '动作类型', value: '2' },
  90. { label: '动作说明', value: '3' },
  91. { label: '启用', value: '4' },
  92. { label: '延迟', value: '5' },
  93. { label: '持续', value: '6' },
  94. { label: '操作', value: '7' },
  95. ]);
  96. let tableData = ref<any[]>([
  97. { indnex: '1', actionType: '声光报警器', action: '触发报警声+红色闪烁', qy: 1, delayTime: '0s', duration: '300s' },
  98. { indnex: '2', actionType: '声光报警器', action: '触发报警声', qy: 1, delayTime: '1s', duration: '200s' },
  99. { indnex: '3', actionType: '声光报警器', action: '红色闪烁', qy: 0, delayTime: '2s', duration: '180s' },
  100. ]);
  101. // 流程图,添加 status 字段
  102. const steps = ref([
  103. { id: 'alarm', name: '报警触发', status: 0 },
  104. { id: 'condition', name: '条件判断', status: 0 },
  105. { id: 'search', name: '查找规则', status: 0 },
  106. { id: 'action', name: '执行动作', status: 0 },
  107. { id: 'log', name: '记录日志', status: 0 },
  108. ]);
  109. // 模拟接口请求获取步骤状态
  110. const fetchStepStatus = async () => {
  111. const apiResponse = {
  112. alarmTrigger: 1, // 报警触发为 1
  113. conditionJudge: 0, // 条件判断为 1
  114. searchRule: 0, // 查找规则为 0
  115. executeAction: 0, // 执行动作为 0
  116. recordLog: 0, // 记录日志为 0
  117. };
  118. // 根据接口返回数据更新状态
  119. steps.value[0].status = apiResponse.alarmTrigger;
  120. steps.value[1].status = apiResponse.conditionJudge;
  121. steps.value[2].status = apiResponse.searchRule;
  122. steps.value[3].status = apiResponse.executeAction;
  123. steps.value[4].status = apiResponse.recordLog;
  124. };
  125. const formData = ref([]);
  126. // 获取预警条目数据
  127. const getWarningList = async (val) => {
  128. const params = {
  129. sysId: val.sysId,
  130. monitorType: 2,
  131. alarmId: val.id,
  132. pageSize: 1000,
  133. };
  134. const res = await getWarningDeviceList(params);
  135. formData.value = res.records;
  136. };
  137. // 获取单位
  138. const getSensorUnit = (item: any) => {
  139. const type = item.deviceType;
  140. if (type === 'modelsensor_co' || type === 'modelsensor_hcl') return 'ppm';
  141. if (type === 'modelsensor_temperature') return '℃';
  142. return '%';
  143. };
  144. // 拼接IF条件文本
  145. const getConditionText = (item: any) => {
  146. const { fmin, fmax, deviceType } = item;
  147. const unit = getSensorUnit(item);
  148. const minVal = fmin ?? '';
  149. const maxVal = fmax ?? '';
  150. // 同时存在最小值、最大值:区间 fmin~fmax
  151. if (minVal !== '' && maxVal !== '') {
  152. return `${minVal}~${maxVal} ${unit}`;
  153. }
  154. // 只有最大值,无最小值
  155. if (maxVal !== '' && minVal === '') {
  156. if (deviceType === 'modelsensor_co') {
  157. // CO:大于fmax
  158. return `> ${maxVal} ${unit}`;
  159. } else if (deviceType === 'modelsensor_hcl' || deviceType === 'modelsensor_temperature') {
  160. // HCl / 温度:大于等于fmax
  161. return `≥ ${maxVal} ${unit}`;
  162. } else {
  163. return `≥ ${maxVal} ${unit}`;
  164. }
  165. }
  166. // 只有最小值(无最大值),直接显示最小值+单位
  167. if (minVal !== '' && maxVal === '') {
  168. return `${minVal} ${unit}`;
  169. }
  170. // 两者都为空兜底
  171. return '';
  172. };
  173. onMounted(() => {
  174. fetchStepStatus();
  175. if (currentSelectedRow.value) {
  176. updateFormData(currentSelectedRow.value);
  177. getWarningList(currentSelectedRow.value);
  178. }
  179. });
  180. watch(
  181. currentSelectedRow,
  182. (newVal) => {
  183. console.log(newVal, 'newVal from store');
  184. if (newVal) {
  185. updateFormData(newVal);
  186. getWarningList(newVal);
  187. }
  188. },
  189. { deep: true }
  190. );
  191. const updateFormData = (row: any) => {
  192. console.log('更新表单数据:', row);
  193. // 在这里根据 row 数据更新表单或其他逻辑
  194. // 例如: formData.gzmc = row.alarmName;
  195. };
  196. </script>
  197. <style lang="less" scoped>
  198. @import '/@/design/theme.less';
  199. .long-list1 {
  200. --image-box-bg: url('@/assets/images/home-container/configurable/hsq/2-5.png');
  201. --image-box-bg1: url('@/assets/images/home-container/configurable/hsq/2-13.png');
  202. --image-box-bg2: url('@/assets/images/home-container/configurable/hsq/2-14.png');
  203. --image-box-bg3: url('@/assets/images/home-container/configurable/hsq/2-15.png');
  204. --image-box-bg4: url('@/assets/images/home-container/configurable/hsq/2-16.png');
  205. --image-box-bg5: url('@/assets/images/home-container/configurable/hsq/2-17.png');
  206. --image-box-bg6: url('@/assets/images/home-container/configurable/hsq/2-24.png');
  207. position: relative;
  208. width: 100%;
  209. height: 100%;
  210. padding: 15px 30px;
  211. box-sizing: border-box;
  212. display: flex;
  213. flex-direction: column;
  214. .basic-box {
  215. height: 30%;
  216. margin-top: 10px;
  217. }
  218. .basic-box1 {
  219. flex: 1;
  220. margin-top: 10px;
  221. }
  222. .basic-box2 {
  223. margin-top: 10px;
  224. }
  225. .edit-wrap {
  226. display: flex;
  227. width: 100%;
  228. height: 100%;
  229. gap: 10px;
  230. box-sizing: border-box;
  231. }
  232. .item-label {
  233. width: 80px;
  234. text-align: right;
  235. margin-right: 5px;
  236. }
  237. .edit-right {
  238. background: #0e395d;
  239. width: 100%;
  240. height: 100%;
  241. display: flex;
  242. flex-direction: column;
  243. margin-right: 20px;
  244. }
  245. .right-title {
  246. display: flex;
  247. align-items: center;
  248. width: 146px;
  249. height: 30px;
  250. color: #fff;
  251. padding-left: 15px;
  252. background: var(--image-box-bg) no-repeat;
  253. background-size: 100% 100%;
  254. font-size: 15px;
  255. font-weight: bold;
  256. line-height: 24px;
  257. }
  258. .right-content {
  259. display: flex;
  260. flex-direction: column;
  261. gap: 6px;
  262. max-height: 300px;
  263. overflow-y: auto;
  264. }
  265. .right-item-row {
  266. display: flex;
  267. flex-wrap: wrap;
  268. align-items: center;
  269. gap: 8px;
  270. padding: 6px 20px;
  271. background: rgba(255, 255, 255, 0.03);
  272. border-radius: 4px;
  273. border: 1px solid rgba(255, 255, 255, 0.06);
  274. }
  275. .right-item {
  276. font-size: 14px;
  277. color: #e0f2fe;
  278. white-space: nowrap;
  279. }
  280. /* 方案二:序号样式 */
  281. .index-label {
  282. color: #7dd3fc;
  283. font-weight: 600;
  284. min-width: 30px;
  285. }
  286. /* 方案三:内联样式 */
  287. .right-item.inline {
  288. display: inline-block;
  289. }
  290. .right-item {
  291. color: #29a1d4;
  292. font-size: 12px;
  293. line-height: 25px;
  294. font-weight: bold;
  295. padding: 0 5px;
  296. }
  297. .right-item:nth-child(4) {
  298. color: #fc002a;
  299. }
  300. .right-item:first-child {
  301. color: #fff;
  302. }
  303. .right-item:last-child {
  304. color: #46e8a3;
  305. }
  306. .list-title {
  307. width: 177px;
  308. height: 30px;
  309. line-height: 22px;
  310. padding-left: 5px;
  311. padding-bottom: 5px;
  312. color: #01fefc;
  313. font-weight: bolder;
  314. background: var(--image-box-bg) no-repeat;
  315. background-size: 80% 100%;
  316. margin-bottom: 5px;
  317. }
  318. .basic-table {
  319. height: 100%;
  320. }
  321. .item-label1 {
  322. width: 105px;
  323. text-align: right;
  324. margin-right: 5px;
  325. }
  326. .table-title {
  327. display: flex;
  328. align-items: center;
  329. height: 34px;
  330. background: var(--image-box-bg6) no-repeat;
  331. background-size: 100% 100%;
  332. margin-bottom: 6px;
  333. }
  334. .title-item {
  335. flex: 1;
  336. min-width: 60px;
  337. display: flex;
  338. justify-content: center;
  339. align-items: center;
  340. color: #01fefc;
  341. white-space: nowrap;
  342. padding: 0 6px;
  343. }
  344. .table-content {
  345. height: calc(100% - 40px);
  346. overflow-y: auto;
  347. }
  348. .content-item {
  349. display: flex;
  350. align-items: center;
  351. height: 36px;
  352. &:nth-child(odd) {
  353. background-color: #0e3455;
  354. }
  355. &:nth-child(even) {
  356. background-color: #114268;
  357. }
  358. }
  359. .item-text {
  360. flex: 1;
  361. min-width: 60px;
  362. display: flex;
  363. justify-content: center;
  364. align-items: center;
  365. color: #fff;
  366. white-space: nowrap;
  367. padding: 0 6px;
  368. }
  369. .item-text1 {
  370. color: #fc002a;
  371. }
  372. .item-text2 {
  373. color: orange;
  374. }
  375. .switch {
  376. width: 36px;
  377. height: 18px;
  378. border-radius: 9px;
  379. background: none;
  380. position: relative;
  381. transition: all 0.2s ease;
  382. cursor: pointer;
  383. border: 1px solid #35d9e5;
  384. .switch-dot {
  385. width: 14px;
  386. height: 14px;
  387. border-radius: 50%;
  388. background: #fff;
  389. position: absolute;
  390. top: 1px;
  391. left: 1px;
  392. transition: all 0.2s ease;
  393. }
  394. }
  395. .switch.active .switch-dot {
  396. left: 20px;
  397. }
  398. .text-look {
  399. color: #01fefc;
  400. cursor: pointer;
  401. }
  402. }
  403. /*流程图*/
  404. .workflow-container {
  405. width: 100%;
  406. padding: 20px 0;
  407. position: relative;
  408. }
  409. .steps-wrapper {
  410. display: flex;
  411. justify-content: space-between;
  412. align-items: flex-end;
  413. padding: 0 20px;
  414. position: relative;
  415. z-index: 2;
  416. &::before {
  417. content: '';
  418. position: absolute;
  419. left: 12%;
  420. right: calc(50% - 12px);
  421. top: 95%;
  422. transform: translateY(-50%);
  423. height: 2px;
  424. background: #4a9eff;
  425. z-index: 1;
  426. }
  427. &::after {
  428. content: '';
  429. position: absolute;
  430. left: calc(50% + 12px);
  431. right: 12%;
  432. top: 95%;
  433. transform: translateY(-50%);
  434. height: 2px;
  435. background: #4a9eff;
  436. z-index: 1;
  437. }
  438. .steps-arrow {
  439. position: absolute;
  440. left: 5%;
  441. top: 95%;
  442. transform: translate(-50%, -50%);
  443. width: 24px;
  444. height: 24px;
  445. background: url('@/assets/images/home-container/configurable/hsq/arrow.svg') center / contain no-repeat;
  446. z-index: 2;
  447. }
  448. .step-item:first-child .steps-arrow {
  449. display: none;
  450. }
  451. }
  452. .step-item {
  453. display: flex;
  454. flex-direction: column;
  455. align-items: center;
  456. flex: 1;
  457. position: relative;
  458. }
  459. .step-icon {
  460. width: 56px;
  461. height: 56px;
  462. background-size: contain;
  463. background-repeat: no-repeat;
  464. background-position: center;
  465. margin-bottom: -6px;
  466. }
  467. .icon-alarm {
  468. background-image: url('@/assets/images/home-container/configurable/hsq/bjcf.png');
  469. }
  470. .icon-condition {
  471. background-image: url('@/assets/images/home-container/configurable/hsq/tjpd.png');
  472. }
  473. .icon-search {
  474. background-image: url('@/assets/images/home-container/configurable/hsq/czgz.png');
  475. }
  476. .icon-action {
  477. background-image: url('@/assets/images/home-container/configurable/hsq/zxdz.png');
  478. }
  479. .icon-log {
  480. background-image: url('@/assets/images/home-container/configurable/hsq/jlrz.png');
  481. }
  482. .step-frame {
  483. position: relative;
  484. width: 100%;
  485. max-width: 120px;
  486. padding: 2px;
  487. background-image: url('@/assets/images/home-container/configurable/hsq/3-6.png');
  488. background-size: 100% 100%;
  489. background-repeat: no-repeat;
  490. background-position: center;
  491. display: flex;
  492. align-items: center;
  493. justify-content: center;
  494. margin-bottom: 5px;
  495. }
  496. .step-text {
  497. font-size: 14px;
  498. font-weight: 500;
  499. color: #fff;
  500. text-align: center;
  501. position: relative;
  502. z-index: 1;
  503. }
  504. .step-dot {
  505. display: flex;
  506. justify-content: center;
  507. align-items: center;
  508. position: relative;
  509. z-index: 3;
  510. background: transparent;
  511. width: 12px;
  512. height: 12px;
  513. }
  514. .dot {
  515. width: 10px;
  516. height: 10px;
  517. border-radius: 50%;
  518. background: #2c5a8c;
  519. border: 1px solid #4a9eff;
  520. transition: all 0.3s ease;
  521. }
  522. .dot.active {
  523. background: #4facfe;
  524. border-color: #4facfe;
  525. box-shadow: 0 0 6px rgba(79, 172, 254, 0.6);
  526. }
  527. </style>