add-alarmRules.vue 37 KB

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