add-alarmRules.vue 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. <template>
  2. <div class="layout-pd">
  3. <el-dialog
  4. v-model="Data.isShowDialog"
  5. style="width: 75%;"
  6. @close="handleDialogClose"
  7. @open="handleDialogOpen"
  8. >
  9. <!-- 标题区域 -->
  10. <div style="width: 100%;display: flex;justify-content: left;align-items: center;">
  11. <h1 style="margin-bottom: 10px;font-size: 24px;">{{ isEditing ? '编辑报警推送规则' : '添加报警推送规则' }}</h1>
  12. </div>
  13. <div style="margin-bottom: 10px ;">
  14. <hr />
  15. </div>
  16. <!-- 规则名称 -->
  17. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  18. <el-form :inline="true" @submit.stop.prevent>
  19. <el-form-item label="规则名称:" style="width: 100%;" prop="ruleName">
  20. <el-input v-model="Data.Filter.ruleName" style="width: 100%;" placeholder="请输入" />
  21. </el-form-item>
  22. </el-form>
  23. </div>
  24. <!-- 标签选择 -->
  25. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  26. <el-form :inline="true" @submit.stop.prevent>
  27. <el-form-item label="标签:" style="width: 100%;" prop="labelId">
  28. <el-select v-model="Data.Filter.labelId" placeholder="请选择标签">
  29. <el-option v-for="item in Data.labelList" :key="item.id" :label="item.name" :value="item.id.toString()" />
  30. </el-select>
  31. </el-form-item>
  32. </el-form>
  33. </div>
  34. <!-- 推送用户选择(树形组件) -->
  35. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  36. <el-form :inline="true" @submit.stop.prevent>
  37. <el-form-item label="推送用户:" style="width: 100%;" prop="pushUserid">
  38. <el-tree
  39. ref="userTreeRef"
  40. :data="treeUserData"
  41. show-checkbox
  42. node-key="id"
  43. :props="treeProps"
  44. :check-strictly="false"
  45. :default-checked-keys="selectedUserIds"
  46. :default-expanded-keys="expandedKeys"
  47. @check="handleUserCheck"
  48. @node-click="handleNodeClick"
  49. style="width: 100%; max-height: 300px; overflow-y: auto; border: 1px solid #e5e7eb; border-radius: 4px; padding: 10px;"
  50. >
  51. <template #default="{ node, data }">
  52. <div class="tree-node-content">
  53. <span :class="{'role-node': data.isRole}">{{ data.label }}</span>
  54. <!-- 角色节点显示展开/折叠按钮 -->
  55. <el-button
  56. v-if="data.isRole"
  57. type="text"
  58. size="small"
  59. @click.stop="toggleRoleExpand(node)"
  60. class="expand-btn"
  61. >
  62. <!-- {{ node.expanded ? '折叠' : '展开' }} -->
  63. </el-button>
  64. </div>
  65. </template>
  66. </el-tree>
  67. </el-form-item>
  68. </el-form>
  69. </div>
  70. <!-- 新增:报警等级筛选下拉框 -->
  71. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  72. <el-form :inline="true" @submit.stop.prevent>
  73. <el-form-item label="报警等级:" style="width: 100%;" prop="alarmLevel">
  74. <el-select
  75. v-model="Data.Filter.alarmLevel"
  76. placeholder="请选择报警等级"
  77. clearable
  78. @change="handleAlarmLevelChange"
  79. >
  80. <el-option
  81. v-for="item in alarmLevelDict"
  82. :key="item.id"
  83. :label="`${item.name}(${item.value})`"
  84. :value="item.value"
  85. ></el-option>
  86. </el-select>
  87. </el-form-item>
  88. </el-form>
  89. </div>
  90. <!-- 油站选择 -->
  91. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  92. <el-form :inline="true" @submit.stop.prevent>
  93. <el-form-item label="油站:" style="width: 100%;" prop="stationid">
  94. <el-select
  95. v-model="selectedStationIds"
  96. placeholder="请选择油站"
  97. multiple
  98. style="width: 100%;"
  99. ref="stationSelectRef"
  100. @change="handleStationChange"
  101. :key="stationSelectKey"
  102. >
  103. <el-option
  104. v-for="station in Data.stationList"
  105. :key="station.id"
  106. :label="station.name"
  107. :value="station.id"
  108. />
  109. </el-select>
  110. </el-form-item>
  111. </el-form>
  112. </div>
  113. <!-- 备注信息 -->
  114. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  115. <el-form :inline="true" @submit.stop.prevent>
  116. <el-form-item label="备&#8195;&#8195;注:" style="width: 100%;">
  117. <el-input type="textarea" rows="5" v-model="Data.Filter.remark" style="width: 100%;" placeholder="请输入" />
  118. </el-form-item>
  119. </el-form>
  120. </div>
  121. <!-- 推送方式选择 -->
  122. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  123. <el-form :inline="true" @submit.stop.prevent>
  124. <el-form-item label="推送方式:" style="width: 100%;" prop="pushMethod">
  125. <!-- 推送总开关 -->
  126. <el-row style="width: 100%;margin-bottom: 10px;">
  127. 是否推送<el-switch v-model="Data.pushEnabled" style="margin-left: 3%;" />
  128. </el-row>
  129. <!-- 微信和邮箱开关(仅在总开关打开时显示) -->
  130. <template v-if="Data.pushEnabled">
  131. <el-row style="width: 100%;">
  132. 微信公众号<el-switch v-model="Data.radioValue1" style="margin-left: 3%;" @change="radioChange" />
  133. </el-row>
  134. <el-row style="width: 100%;">
  135. 邮&#8195;&#8195;&#8195;箱<el-switch v-model="Data.radioValue2" style="margin-left: 3%;" @change="radioChange" />
  136. </el-row>
  137. </template>
  138. </el-form-item>
  139. </el-form>
  140. </div>
  141. <!-- 模板选择(受总开关控制) -->
  142. <div style="width: 100%;display: flex;justify-content: center;align-items: center;" v-if="Data.pushEnabled">
  143. <el-form :inline="true" @submit.stop.prevent>
  144. <el-form-item label="模&#8195;&#8195;板:" style="width: 50%;" prop="mode1">
  145. <el-select v-model="Data.mode1" class="m-2" placeholder="微信模板" v-if="Data.radioValue1" @change="modeChange">
  146. <el-option v-for="item in templateData.wxList" :key="item.id" :label="item.templateName" :value="item.id.toString()" />
  147. </el-select>
  148. </el-form-item>
  149. <el-form-item label="&#8195;&#8195;&#8195;&#8195;" style="width: 100%;" prop="mode2">
  150. <el-select v-model="Data.mode2" class="m-2" placeholder="邮箱模板" v-if="Data.radioValue2" @change="modeChange">
  151. <el-option v-for="item in templateData.emailList" :key="item.id" :label="item.templateName" :value="item.id.toString()" />
  152. </el-select>
  153. </el-form-item>
  154. </el-form>
  155. </div>
  156. <!-- 正则匹配 -->
  157. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  158. <el-form :inline="true" @submit.stop.prevent>
  159. <el-form-item label="正则匹配:" style="width: 100%;">
  160. <el-input type="textarea" rows="2" v-model="Data.Filter.regular" style="width: 100%;" placeholder="请输入" />
  161. </el-form-item>
  162. </el-form>
  163. </div>
  164. <!-- 关键字匹配 -->
  165. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  166. <el-form :inline="true" @submit.stop.prevent>
  167. <el-form-item label="关键字匹配:" style="width: 100%;">
  168. <el-input type="textarea" rows="5" v-model="Data.Filter.keyWord" style="width: 100%;" placeholder="如有多个关键字,请用英文“,”分割" />
  169. </el-form-item>
  170. </el-form>
  171. </div>
  172. <!-- 优先级设置 -->
  173. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  174. <el-form :inline="true" @submit.stop.prevent>
  175. <el-form-item label="&#8195;优先级:" style="width: 100%;" prop="taskPriority">
  176. <el-input-number v-model="Data.Filter.taskPriority" :controls="false" :min="1" :max="9" />
  177. </el-form-item>
  178. </el-form>
  179. </div>
  180. <!-- 报警条件配置 -->
  181. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  182. <el-form :inline="true" @submit.stop.prevent>
  183. <el-form-item label="报警条件配置" style="width: 100%;">
  184. </el-form-item>
  185. </el-form>
  186. </div>
  187. <!-- 报警条件部分 -->
  188. <div v-if="Data.showAlarmConditions">
  189. <div v-for="(condition, index) in Data.condition" :key="index"
  190. style="width: 100%;display: flex;justify-content: center;align-items: center; margin-top: 10px;">
  191. <el-form :inline="true" @submit.stop.prevent :model="condition" :rules="conditionRules">
  192. <el-form-item :label="index === 0 ? '条&#8195;&#8195;件:' : '附加条件:'" style="width: 130%; ">
  193. <el-row :gutter="20" style="width: 100%;">
  194. <!-- 报警设备 -->
  195. <el-col :span="8">
  196. <div style="flex:1;">
  197. <span style="margin-right: 13px;">报警设备:</span>
  198. <el-form-item prop="Left" style="display:flex">
  199. <el-select v-model="condition.Left" placeholder="请选择" >
  200. <el-option
  201. v-for="item in Data.alarmEquipment"
  202. :key="item"
  203. :label="item"
  204. :value="item"
  205. />
  206. </el-select>
  207. </el-form-item>
  208. </div>
  209. </el-col>
  210. <!-- 报警类型 -->
  211. <el-col :span="8">
  212. <div style="flex:1;">
  213. <span style="margin-right: 13px;">报警类型:</span>
  214. <el-form-item prop="inthe" style="display:flex">
  215. <el-select v-model="condition.inthe" placeholder="请选择" >
  216. <el-option
  217. v-for="item in Data.alarmType"
  218. :key="item"
  219. :label="item"
  220. :value="item"
  221. />
  222. </el-select>
  223. </el-form-item>
  224. </div>
  225. </el-col>
  226. <!-- 报警来源 -->
  227. <el-col :span="8">
  228. <div style="flex:1;">
  229. <span style="margin-right: 13px;">报警来源:</span>
  230. <el-form-item prop="Right" style="display:flex">
  231. <el-select v-model="condition.Right" placeholder="请选择">
  232. <el-option
  233. v-for="item in Data.alarmSource"
  234. :key="item"
  235. :label="item"
  236. :value="item"
  237. />
  238. </el-select>
  239. </el-form-item>
  240. </div>
  241. </el-col>
  242. </el-row>
  243. </el-form-item>
  244. </el-form>
  245. </div>
  246. </div>
  247. <!-- 报警条件添加/删除按钮 -->
  248. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  249. <el-button type="primary" icon="ele-Plus" style="font-size: large" @click="addCondition" />
  250. <el-button v-if="Data.condition.length > 1" type="primary" icon="ele-Minus" style="font-size: large" @click="removeCondition" />
  251. </div>
  252. <!-- 维修条件配置 -->
  253. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  254. <el-form :inline="true" @submit.stop.prevent>
  255. <el-form-item label="维修条件配置" style="width: 100%;">
  256. </el-form-item>
  257. </el-form>
  258. </div>
  259. <!-- 维修条件部分 -->
  260. <div v-if="Data.showRepairConditions">
  261. <div v-for="(condition2, index) in Data.condition2" :key="index"
  262. style="width: 100%;display: flex;justify-content: center;align-items: center; margin-top: 10px;">
  263. <el-form :inline="true" @submit.stop.prevent :model="condition2" :rules="condition2Rules">
  264. <el-form-item :label="index === 0 ? '条&#8195;&#8195;件:' : '附加条件:'" style="width: 130%;">
  265. <el-row :gutter="20" style="width: 100%;">
  266. <el-col :span="8">
  267. <div style="flex:1;">
  268. <span style="margin-right: 13px;">维修类型:</span>
  269. <el-form-item prop="Left" style="display:flex">
  270. <el-select v-model="condition2.Left" placeholder="请选择">
  271. <el-option
  272. v-for="item in Data.alarmproType"
  273. :key="item"
  274. :label="item"
  275. :value="item"
  276. />
  277. </el-select>
  278. </el-form-item>
  279. </div>
  280. </el-col>
  281. <el-col :span="8">
  282. <div style="flex:1;">
  283. <span style="margin-right: 13px;">维修状态:</span>
  284. <el-form-item prop="Right" style="display:flex">
  285. <el-select v-model="condition2.Right" placeholder="请选择" >
  286. <el-option
  287. v-for="item in Data.alarmproStatus"
  288. :key="item"
  289. :label="item"
  290. :value="item"
  291. />
  292. </el-select>
  293. </el-form-item>
  294. </div>
  295. </el-col>
  296. </el-row>
  297. </el-form-item>
  298. </el-form>
  299. </div>
  300. </div>
  301. <!-- 维修条件添加/删除按钮 -->
  302. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  303. <el-button type="primary" icon="ele-Plus" style="font-size: large" @click="addRepairCondition" />
  304. <el-button v-if="Data.condition2.length > 1" type="primary" icon="ele-Minus" style="font-size: large" @click="removeCondition2" />
  305. </div>
  306. <!-- 保存按钮 -->
  307. <div style="width: 100%;display: flex;justify-content: center;align-items: center;">
  308. <el-form :inline="true" @submit.stop.prevent style="width: 60%;">
  309. <el-row justify="end" style="margin-top: 30px;">
  310. <el-button @click="Data.isShowDialog = false">取消</el-button>
  311. <el-button type="primary" @click="submitForm" style="margin-left: 10px;">{{ isEditing ? '更新' : '保存' }}</el-button>
  312. </el-row>
  313. </el-form>
  314. </div>
  315. </el-dialog>
  316. </div>
  317. </template>
  318. <script setup lang="ts" name="AlarmRuleDialog">
  319. import { onMounted, reactive, ref, nextTick, watch, computed } from 'vue';
  320. import { alarmRluesFilterModel } from "/@/api/admin/AlarmService/alarmRulesDto";
  321. import { alarmRulesApi, stationApi } from "/@/api/admin/AlarmService/alarmRulesApi";
  322. import { RoleApi } from '/@/api/admin/Role'
  323. import { pushTemplateApi } from "/@/api/admin/AlarmService/pushTemplateApi";
  324. import { TemplateFilterDto } from "/@/api/admin/AlarmService/pushTemplateDto";
  325. import eventBus from "/@/utils/mitt";
  326. import { ElMessage, type FormRules } from 'element-plus'
  327. import { UserListItem, StationItem } from "/@/api/admin/AlarmService/alarmRulesDto";
  328. import { DictApi } from "/@/api/admin/Dict";
  329. import { PageInputDictGetPageDto } from "/@/api/admin/data-contracts";
  330. // 报警等级字典项接口定义
  331. interface DictItem {
  332. id: number;
  333. name: string;
  334. code: string;
  335. value: string;
  336. enabled: boolean;
  337. sort: number;
  338. }
  339. // 状态标识:是否为编辑模式
  340. const isEditing = ref(false);
  341. // 油站选择器相关引用和状态
  342. const stationSelectRef = ref();
  343. const stationSelectKey = ref(0);
  344. const selectedStationIds = ref<number[]>([]);
  345. const stationListLoaded = ref(false);
  346. // 树形组件引用
  347. const userTreeRef = ref();
  348. // 推送用户选择专用变量
  349. const selectedUserIds = ref<number[]>([]); // 存储选中的用户ID
  350. const expandedKeys = ref<string[]>([]); // 存储展开的角色节点ID
  351. // 树形结构配置
  352. const treeProps = {
  353. label: 'label',
  354. children: 'children',
  355. disabled: 'disabled'
  356. };
  357. // 条件验证规则
  358. const conditionRules = reactive<FormRules>({
  359. Left: [{ required: true, message: '请选择报警设备', trigger: 'change' }],
  360. inthe: [{ required: true, message: '请选择报警类型', trigger: 'change' }],
  361. Right: [{ required: true, message: '请选择报警来源', trigger: 'change' }]
  362. })
  363. const condition2Rules = reactive<FormRules>({
  364. Left: [{ required: true, message: '请选择维修类型', trigger: 'change' }],
  365. Right: [{ required: true, message: '请选择维修状态', trigger: 'change' }]
  366. })
  367. // 报警等级字典数据
  368. const alarmLevelDict = ref<DictItem[]>([]);
  369. const Data = reactive({
  370. isShowDialog: false,
  371. showAlarmConditions: false,
  372. showRepairConditions: false,
  373. pushEnabled: false, // 推送总开关状态
  374. stationList: [] as StationItem[],
  375. Filter: {
  376. ruleName: '',
  377. roleMappingId: [] as number[],
  378. labelId: "",
  379. remark: '',
  380. pushMethod: '',
  381. pushTemplateMappingID: [] as number[],
  382. regular: '',
  383. keyWord:"",
  384. isExclusive: false,
  385. isExclusiveMaintenance: false,
  386. taskPriority: 1,
  387. triggerMethod: null,
  388. maintenanceTriggerMethod: null,
  389. conditionsJson: '' as any,
  390. maintenanceJson: '' as any,
  391. pushUserid: [] as number[], // 最终提交的用户ID数组
  392. stationid: [] as number[],
  393. userName: "",
  394. userPhone: "",
  395. userId: "",
  396. condition2: "",
  397. condition: "",
  398. mode1: '',
  399. id: 0,
  400. alarmLevel: undefined, // 新增:报警等级筛选值
  401. } as unknown as alarmRluesFilterModel & { alarmLevel?: string },
  402. roleList: [] as any,
  403. labelList: [] as any,
  404. userList: [] as UserListItem[],
  405. radioValue1: false,
  406. radioValue2: false,
  407. mode1: '',
  408. mode2: '',
  409. condition: [{ Left: '', inthe: '', Right: '' }],
  410. condition2: [{ Right: '', Left: '' }],
  411. alarmEquipment: ["安全装置","编码器","计控主板","监控微处理器","智能型控制阀","油气回收控制板","显示屏","计量器","加油机","油枪"],
  412. alarmType: ["加油机离线","通信异常","非法部件","厂商不符","绑定错误","监控微处理器报警","安全装置报警","加油机报警","检定"],
  413. alarmSource: ['云平台', '安全装置'],
  414. alarmproType: ["油机维修","装置维修"],
  415. alarmproStatus: ["正在维修","结束维修"]
  416. })
  417. // 转换用户列表为树形结构数据 - 确保用户ID为数字,角色ID为字符串
  418. const treeUserData = computed(() => {
  419. return Data.userList.map((group: any) => {
  420. // 为角色生成唯一ID(字符串类型,带前缀)
  421. const roleId = `role_${group.roleName.replace(/\s+/g, '_')}`;
  422. return {
  423. id: roleId,
  424. label: `角色:${group.roleName}`,
  425. isRole: true,
  426. children: group.users.map((user: any) => ({
  427. id: Number(user.id), // 强制转换为数字类型
  428. label: user.name || `用户${user.id}`,
  429. phone: user.phone || '',
  430. isRole: false,
  431. disabled: false
  432. }))
  433. };
  434. });
  435. });
  436. // 处理角色展开/折叠
  437. const toggleRoleExpand = (node: any) => {
  438. node.expanded = !node.expanded;
  439. if (node.expanded) {
  440. expandedKeys.value.push(node.id);
  441. } else {
  442. expandedKeys.value = expandedKeys.value.filter(key => key !== node.id);
  443. }
  444. };
  445. // 处理节点点击
  446. const handleNodeClick = (data: any, node: any) => {
  447. if (data.isRole) {
  448. toggleRoleExpand(node);
  449. }
  450. };
  451. // 处理用户选择
  452. const handleUserCheck = (data: any, checkInfo: any) => {
  453. // 获取所有选中的节点ID
  454. const allCheckedIds = checkInfo.checkedKeys || [];
  455. // 过滤出用户ID(仅保留数字类型,排除角色ID)
  456. const userIds = allCheckedIds
  457. .filter((id: any) => {
  458. // 角色ID是带前缀的字符串,用户ID是数字
  459. return typeof id === 'number' && !isNaN(id);
  460. });
  461. // 去重并更新选中的用户ID
  462. const uniqueUserIds = [...new Set(userIds)];
  463. selectedUserIds.value = uniqueUserIds;
  464. Data.Filter.pushUserid = uniqueUserIds;
  465. };
  466. // 监听油站选择变化
  467. const handleStationChange = (values: number[]) => {
  468. Data.Filter.stationid = [...values];
  469. };
  470. // 处理报警等级变化
  471. const handleAlarmLevelChange = (value: string) => {
  472. Data.Filter.alarmLevel = value;
  473. };
  474. // 获取用户列表
  475. const getUserList = async () => {
  476. try {
  477. const res = await new alarmRulesApi().getWxUserRole({});
  478. const userDataArray = res.data || [];
  479. const userMap = new Map<string, UserListItem[]>();
  480. userDataArray.forEach((roleObj: any) => {
  481. const roleName = roleObj.roleName || '未知角色';
  482. // 过滤出有效用户(确保ID存在且可转换为数字)
  483. const validUsers = (roleObj.users || []).filter((user: any) => {
  484. const id = Number(user.id);
  485. return !isNaN(id) && id > 0;
  486. });
  487. validUsers.forEach((userObj: any) => {
  488. if (!userMap.has(roleName)) {
  489. userMap.set(roleName, []);
  490. }
  491. userMap.get(roleName)!.push({
  492. id: Number(userObj.id), // 确保ID是数字
  493. name: userObj.name || `用户${userObj.id}`,
  494. phone: userObj.phone || '',
  495. roleName: roleName
  496. });
  497. });
  498. });
  499. Data.userList = Array.from(userMap.entries()).map(([role, users]) => ({
  500. roleName: role,
  501. users: users
  502. }));
  503. } catch (error) {
  504. console.error("获取用户列表失败:", error);
  505. ElMessage.error("用户列表加载失败");
  506. Data.userList = [];
  507. }
  508. };
  509. // 获取油站列表
  510. const getStationList = async () => {
  511. try {
  512. const requestData = {
  513. currentPage: 1,
  514. pageSize: 1000,
  515. dynamicFilter: {},
  516. filter: {}
  517. };
  518. const res = await new stationApi().getStationList(requestData);
  519. Data.stationList = res.data?.list || [];
  520. stationListLoaded = ref(true);
  521. } catch (error) {
  522. stationListLoaded = ref(true);
  523. }
  524. };
  525. // 获取报警等级字典数据
  526. const fetchAlarmLevelDict = async () => {
  527. try {
  528. // 构造字典查询参数
  529. const data: PageInputDictGetPageDto = {
  530. CurrentPage: 1,
  531. PageSize: 100,
  532. Filter: {
  533. dictTypeId: 685895581360197, // 报警等级字典类型ID
  534. name: ""
  535. }
  536. };
  537. // 调用字典接口
  538. const res = await new DictApi().getPage(data);
  539. // 处理返回数据
  540. if (res.success && res.data) {
  541. alarmLevelDict.value = res.data.list || [];
  542. console.log("报警等级字典数据获取成功", alarmLevelDict.value);
  543. } else {
  544. console.error("获取报警等级字典数据失败", res.msg);
  545. }
  546. } catch (error) {
  547. console.error("获取报警等级字典数据异常", error);
  548. }
  549. };
  550. // 设置油站选择
  551. const setStationSelection = (ids: number[]) => {
  552. if (!stationListLoaded.value) {
  553. const checkLoaded = setInterval(() => {
  554. if (stationListLoaded.value) {
  555. clearInterval(checkLoaded);
  556. setStationSelection(ids);
  557. }
  558. }, 100);
  559. return;
  560. }
  561. const validIds = ids.filter(id =>
  562. Data.stationList.some(station => station.id === id)
  563. );
  564. nextTick(() => {
  565. selectedStationIds.value = [...validIds];
  566. Data.Filter.stationid = [...validIds];
  567. stationSelectKey.value++;
  568. });
  569. };
  570. const templateData = reactive({
  571. Filter: {
  572. currentPage: 1,
  573. pageSize: 100,
  574. filter: {
  575. templateName: "",
  576. templateType: "",
  577. templateContent: "",
  578. id: 0
  579. }
  580. } as TemplateFilterDto,
  581. wxList: [] as any,
  582. emailList: [] as any
  583. })
  584. const radioChange = () => {
  585. if (Data.pushEnabled) { // 只有总开关打开时才处理推送方式
  586. if (Data.radioValue1) {
  587. Data.Filter.pushMethod = 'wx'
  588. if (Data.radioValue2) {
  589. Data.Filter.pushMethod += ',email'
  590. }
  591. } else {
  592. Data.Filter.pushMethod = Data.radioValue2 ? 'email' : '';
  593. }
  594. } else {
  595. Data.Filter.pushMethod = ''; // 总开关关闭时清空推送方式
  596. }
  597. };
  598. // 获取报警标签
  599. const getLabel = async () => {
  600. try {
  601. const res = await new RoleApi().getLabel({
  602. currentPage: 1,
  603. pageSize: 100,
  604. filter: {
  605. dictTypeId: 677693042573381
  606. }
  607. })
  608. Data.labelList = res.data?.list?.map((item: any) => ({
  609. id: item.id.toString(),
  610. name: item.name
  611. })) || []
  612. } catch (error) {
  613. console.error('获取标签失败:', error)
  614. Data.labelList = []
  615. }
  616. }
  617. // 获取角色列表
  618. const getRole = async () => {
  619. const res = await new RoleApi().getList()
  620. Data.roleList = res.data?.map((item: any) => ({ 'id': item.id, 'name': item.name })) || []
  621. }
  622. // 查询模板信息
  623. const funSelect = async () => {
  624. try {
  625. const res = await new pushTemplateApi().getData(templateData.Filter)
  626. const data = res?.data || [];
  627. templateData.wxList = data
  628. .filter((item: any) => item.templateType === "微信")
  629. .map((item: any) => ({ ...item, id: item.id.toString() }));
  630. templateData.emailList = data
  631. .filter((item: any) => item.templateType === "邮箱")
  632. .map((item: any) => ({ ...item, id: item.id.toString() }));
  633. } catch (error) {
  634. console.error('获取模板列表失败:', error);
  635. }
  636. }
  637. const modeChange = () => {
  638. Data.Filter.pushTemplateMappingID = [];
  639. if (Data.pushEnabled && Data.radioValue1 && Data.mode1) {
  640. const wxItem = templateData.wxList.find((item: any) => item.id === Data.mode1);
  641. if (wxItem) Data.Filter.pushTemplateMappingID.push(Number(wxItem.id));
  642. }
  643. if (Data.pushEnabled && Data.radioValue2 && Data.mode2) {
  644. const emailItem = templateData.emailList.find((item: any) => item.id === Data.mode2);
  645. if (emailItem) Data.Filter.pushTemplateMappingID.push(Number(emailItem.id));
  646. }
  647. }
  648. // 弹窗关闭处理
  649. const handleDialogClose = () => {};
  650. // 弹窗打开处理
  651. const handleDialogOpen = () => {
  652. if (!stationListLoaded.value) {
  653. getStationList();
  654. } else if (!isEditing.value) {
  655. selectedStationIds.value = [];
  656. }
  657. };
  658. // 重置表单
  659. const resetForm = () => {
  660. selectedUserIds.value = [];
  661. expandedKeys.value = [];
  662. Data.Filter = {
  663. ruleName: '',
  664. roleMappingId: [],
  665. labelId: "",
  666. remark: '',
  667. pushMethod: '',
  668. pushTemplateMappingID: [],
  669. regular: '',
  670. keyWord: "",
  671. isExclusive: false,
  672. isExclusiveMaintenance: false,
  673. taskPriority: 1,
  674. triggerMethod: null,
  675. maintenanceTriggerMethod: null,
  676. conditionsJson: '',
  677. maintenanceJson: '',
  678. pushUserid: [],
  679. stationid: [],
  680. userName: "",
  681. userPhone: "",
  682. userId: "",
  683. condition2: "",
  684. condition: "",
  685. mode1: '',
  686. id: 0,
  687. alarmLevel: undefined, // 重置报警等级筛选
  688. } as unknown as alarmRluesFilterModel & { alarmLevel?: string };
  689. Data.radioValue1 = false;
  690. Data.radioValue2 = false;
  691. Data.pushEnabled = false; // 重置总开关状态
  692. Data.mode1 = '';
  693. Data.mode2 = '';
  694. Data.showAlarmConditions = false;
  695. Data.showRepairConditions = false;
  696. Data.condition = [{ Left: '', inthe: '', Right: '' }];
  697. Data.condition2 = [{ Left: '', Right: '' }];
  698. selectedStationIds.value = [];
  699. };
  700. onMounted(() => {
  701. getLabel();
  702. getRole();
  703. funSelect();
  704. getUserList();
  705. getStationList();
  706. fetchAlarmLevelDict(); // 加载报警等级字典数据
  707. });
  708. // 添加报警条件
  709. const addCondition = () => {
  710. if (!Data.showAlarmConditions) {
  711. Data.showAlarmConditions = true
  712. } else {
  713. const lastCondition = Data.condition[Data.condition.length - 1]
  714. if (!lastCondition.Left || !lastCondition.inthe || !lastCondition.Right) {
  715. ElMessage.warning('请先填写当前条件的所有字段')
  716. return
  717. }
  718. Data.condition.push({ Left: '', inthe: '', Right: '' })
  719. }
  720. }
  721. // 添加维修条件
  722. const addRepairCondition = () => {
  723. if (!Data.showRepairConditions) {
  724. Data.showRepairConditions = true
  725. } else {
  726. const lastCondition = Data.condition2[Data.condition2.length - 1]
  727. if (!lastCondition.Left || !lastCondition.Right) {
  728. ElMessage.warning('请先填写当前条件的所有字段')
  729. return
  730. }
  731. Data.condition2.push({ Left: '', Right: '' })
  732. }
  733. }
  734. // 删除报警条件
  735. const removeCondition = () => {
  736. if (Data.condition.length > 1) {
  737. Data.condition.pop()
  738. } else {
  739. Data.showAlarmConditions = false
  740. Data.condition = [{ Left: '', inthe: '', Right: '' }]
  741. }
  742. }
  743. // 删除维修条件
  744. const removeCondition2 = () => {
  745. if (Data.condition2.length > 1) {
  746. Data.condition2.pop()
  747. } else {
  748. Data.showRepairConditions = false
  749. Data.condition2 = [{ Left: '', Right: '' }]
  750. }
  751. }
  752. // 提交表单
  753. const submitForm = async () => {
  754. console.log('提交时选中的用户ID:', selectedUserIds.value);
  755. // 验证规则名称
  756. if (!Data.Filter.ruleName) {
  757. ElMessage.warning('请输入规则名称')
  758. return
  759. }
  760. // 验证推送用户
  761. if (!Array.isArray(selectedUserIds.value) || selectedUserIds.value.length === 0) {
  762. ElMessage.warning('请选择推送用户');
  763. return;
  764. }
  765. // 验证油站选择
  766. if (selectedStationIds.value.length === 0) {
  767. ElMessage.warning('请选择油站');
  768. return;
  769. }
  770. // 如果开启了推送,验证模板选择
  771. if (Data.pushEnabled) {
  772. if (Data.radioValue1 && !Data.mode1) {
  773. ElMessage.warning('请选择微信模板')
  774. return
  775. }
  776. if (Data.radioValue2 && !Data.mode2) {
  777. ElMessage.warning('请选择邮箱模板')
  778. return
  779. }
  780. // 验证至少选择一种推送方式
  781. if (!Data.radioValue1 && !Data.radioValue2) {
  782. ElMessage.warning('请至少选择一种推送方式')
  783. return
  784. }
  785. }
  786. // 准备提交数据
  787. const submitData = {
  788. ...Data.Filter,
  789. conditionsJson: Data.showAlarmConditions ? JSON.stringify(Data.condition) : null,
  790. maintenanceJson: Data.showRepairConditions ? JSON.stringify(Data.condition2) : null,
  791. pushUserid: selectedUserIds.value,
  792. stationid: selectedStationIds.value,
  793. pushTemplateMappingID: Data.Filter.pushTemplateMappingID,
  794. labelId: Data.Filter.labelId || null,
  795. alarmLevel: Data.Filter.alarmLevel // 包含报警等级筛选值
  796. }
  797. try {
  798. const api = new alarmRulesApi();
  799. if (isEditing.value && Data.Filter.id) {
  800. await api.addForm(submitData);
  801. ElMessage.success('更新成功');
  802. } else {
  803. await api.addForm(submitData);
  804. ElMessage.success('保存成功');
  805. }
  806. eventBus.emit('refreshView');
  807. Data.isShowDialog = false;
  808. } catch (error) {
  809. console.error(`${isEditing.value ? '更新' : '保存'}失败:`, error);
  810. ElMessage.error(`${isEditing.value ? '更新' : '保存'}失败,请稍后重试`);
  811. }
  812. }
  813. /**
  814. * 打开表单对话框
  815. */
  816. const openDialog = (row?: alarmRluesFilterModel & { alarmLevel?: string }) => {
  817. try {
  818. isEditing.value = !!row;
  819. resetForm();
  820. if (row) {
  821. // 处理用户ID
  822. let pushUserIds: number[] = [];
  823. if (row.pushUserid) {
  824. if (Array.isArray(row.pushUserid)) {
  825. pushUserIds = row.pushUserid.map(id => Number(id)).filter(id => !isNaN(id) && id > 0);
  826. } else if (typeof row.pushUserid === 'string') {
  827. pushUserIds = row.pushUserid
  828. .split(',')
  829. .map(id => Number(id))
  830. .filter(id => !isNaN(id) && id > 0);
  831. } else if (typeof row.pushUserid === 'number' && !isNaN(row.pushUserid)) {
  832. pushUserIds = [row.pushUserid];
  833. }
  834. }
  835. // 设置用户选择
  836. selectedUserIds.value = pushUserIds;
  837. Data.Filter.pushUserid = pushUserIds;
  838. // 处理油站ID
  839. const stationIds = row.stationid
  840. ? (Array.isArray(row.stationid)
  841. ? row.stationid.map(Number)
  842. : [Number(row.stationid)])
  843. : [];
  844. // 复制行数据到表单
  845. Data.Filter = {
  846. ...row,
  847. roleMappingId: row.roleMappingId || [],
  848. pushTemplateMappingID: row.pushTemplateMappingID ?
  849. (Array.isArray(row.pushTemplateMappingID)
  850. ? row.pushTemplateMappingID.map(Number)
  851. : [Number(row.pushTemplateMappingID)]) : [],
  852. stationid: stationIds,
  853. labelId: row.labelId?.toString() || "",
  854. id: row.id || 0,
  855. alarmLevel: row.alarmLevel // 设置报警等级
  856. } as unknown as alarmRluesFilterModel & { alarmLevel?: string };
  857. // 处理推送方式和总开关状态
  858. const pushMethods = Array.isArray(row.pushMethod)
  859. ? row.pushMethod
  860. : (row.pushMethod?.split(',') || []);
  861. Data.pushEnabled = pushMethods.length > 0;
  862. Data.radioValue1 = pushMethods.includes('wx');
  863. Data.radioValue2 = pushMethods.includes('email');
  864. radioChange();
  865. // 处理油站数据
  866. setStationSelection(stationIds);
  867. // 处理模板
  868. const templateIds = Data.Filter.pushTemplateMappingID.map(id => id.toString());
  869. Data.mode1 = '';
  870. Data.mode2 = '';
  871. templateIds.forEach(strId => {
  872. const wxItem = templateData.wxList.find((item: any) => item.id === strId);
  873. if (wxItem) Data.mode1 = strId;
  874. const emailItem = templateData.emailList.find((item: any) => item.id === strId);
  875. if (emailItem) Data.mode2 = strId;
  876. });
  877. }
  878. Data.isShowDialog = true;
  879. } catch (error) {
  880. console.error('打开弹窗时出错:', error);
  881. Data.isShowDialog = true;
  882. ElMessage.error('打开弹窗时出错,但已尝试显示');
  883. }
  884. };
  885. // 监听模板列表加载完成
  886. watch(
  887. () => [templateData.wxList.length, templateData.emailList.length],
  888. () => {
  889. if (isEditing.value && Data.Filter.pushTemplateMappingID.length > 0) {
  890. const templateIds = Data.Filter.pushTemplateMappingID.map(id => id.toString());
  891. Data.mode1 = '';
  892. Data.mode2 = '';
  893. templateIds.forEach(strId => {
  894. const wxItem = templateData.wxList.find((item: any) => item.id === strId);
  895. if (wxItem) Data.mode1 = strId;
  896. const emailItem = templateData.emailList.find((item: any) => item.id === strId);
  897. if (emailItem) Data.mode2 = strId;
  898. });
  899. modeChange();
  900. }
  901. }
  902. );
  903. defineExpose({
  904. openDialog,
  905. })
  906. </script>
  907. <style scoped lang="scss">
  908. .my-el-link {
  909. font-size: 12px;
  910. }
  911. .el-form {
  912. width: 60%;
  913. }
  914. .el-form .el-col{
  915. margin: 0 !important;
  916. }
  917. ::v-deep .el-form-item__label {
  918. width: 100px;
  919. justify-content: start;
  920. }
  921. .el-input {
  922. width: 240px;
  923. }
  924. .el-select .el-input {
  925. width: 500px;
  926. }
  927. .el-row {
  928. margin-bottom: 20px;
  929. }
  930. .el-col {
  931. padding: 0 10px;
  932. }
  933. /* 树形组件样式 */
  934. .tree-node-content {
  935. display: flex;
  936. justify-content: space-between;
  937. align-items: center;
  938. width: 100%;
  939. padding: 2px 0;
  940. }
  941. .expand-btn {
  942. color: #409eff;
  943. padding: 0 5px;
  944. font-size: 12px;
  945. }
  946. ::v-deep .el-tree-node__content {
  947. padding: 2px 0;
  948. }
  949. ::v-deep .el-tree-node__label {
  950. font-weight: normal;
  951. }
  952. ::v-deep .el-tree-node.is-current > .el-tree-node__content {
  953. background-color: #f5f7fa;
  954. }
  955. ::v-deep .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
  956. background-color: #f5f7fa;
  957. }
  958. </style>