NotificationUpdate.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. <template>
  2. <ContentWrap>
  3. <div class="tab-header">
  4. <span class="tab-title">添加规则</span>
  5. </div>
  6. <div class="checkContent">
  7. <section>
  8. <span>通知规则种类: </span>
  9. <el-select v-model="noticeData.type" placeholder="请选择" style="width: 260px">
  10. <el-option
  11. v-for="dict in getIntDictOptions(DICT_TYPE.NOTICE_RULES_TYPE)"
  12. :key="dict.value"
  13. :label="dict.label"
  14. :value="dict.value"
  15. />
  16. </el-select>
  17. </section>
  18. <section>
  19. <span>规则:</span>
  20. <el-select v-model="noticeData.rule" placeholder="请选择" style="width: 260px">
  21. <!-- 作业通知-->
  22. <el-option
  23. v-show="noticeData.type == 1"
  24. v-for="dict in getStrDictOptions(DICT_TYPE.JOB_NOTICE_RULES)"
  25. :key="dict.value"
  26. :label="dict.label"
  27. :value="dict.value"
  28. />
  29. <!-- 操作通知-->
  30. <el-option
  31. v-show="noticeData.type == 3"
  32. v-for="dict in getIntDictOptions(DICT_TYPE.ACTION_NOTICE_RULES)"
  33. :key="dict.value"
  34. :label="dict.label"
  35. :value="dict.value"
  36. />
  37. <!-- 提前通知-->
  38. <el-option
  39. v-show="noticeData.type == 0"
  40. v-for="dict in getIntDictOptions(DICT_TYPE.ADVANCE_NOTICE_RULES)"
  41. :key="dict.value"
  42. :label="dict.label"
  43. :value="dict.value"
  44. />
  45. <!-- 步骤通知-->
  46. <el-option
  47. v-for="item in actionNoticeList"
  48. :key="item.id"
  49. :label="item.stepTitleShort"
  50. :value="item.id"
  51. v-show="noticeData.type == 2"
  52. />
  53. </el-select>
  54. </section>
  55. <section>
  56. <span>通知时间:</span>
  57. <el-select v-model="noticeData.notifyTimeType" placeholder="请选择" style="width: 260px">
  58. <el-option
  59. v-for="dict in getIntDictOptions(DICT_TYPE.NOTICE_TIME_TYPE)"
  60. :key="dict.value"
  61. :label="dict.label"
  62. :value="dict.value"
  63. />
  64. </el-select>
  65. <i v-if="noticeData.notifyTimeType == 0 || noticeData.notifyTimeType == 2">
  66. <el-input-number
  67. v-model="timeParts.day"
  68. :min="0"
  69. :max="10"
  70. class="mx-4"
  71. controls-position="right"
  72. />
  73. <el-input-number
  74. v-model="timeParts.hour"
  75. :min="0"
  76. :max="23"
  77. class="mx-4"
  78. controls-position="right"
  79. />
  80. <el-input-number
  81. v-model="timeParts.minute"
  82. :min="0"
  83. :max="59"
  84. class="mx-4"
  85. controls-position="right"
  86. />
  87. <el-input-number
  88. v-model="timeParts.second"
  89. :min="0"
  90. :max="59"
  91. class="mx-4"
  92. controls-position="right"
  93. />
  94. </i>
  95. </section>
  96. <section>
  97. <span>通知区域:</span>
  98. <el-tree-select
  99. style="width: 260px"
  100. v-model="noticeData.workstationId"
  101. :data="workstationOptions"
  102. :props="{ label: 'label', children: 'children', value: 'id' }"
  103. placeholder="请选择岗位"
  104. />
  105. </section>
  106. </div>
  107. </ContentWrap>
  108. <!-- 通知角色与通知模板-->
  109. <ContentWrap>
  110. <div class="tab-header">
  111. <span class="tab-title">通知角色与通知模板</span>
  112. </div>
  113. <div class="tableCon">
  114. <p>
  115. <span>显示:</span>
  116. <el-radio-group v-model="status">
  117. <el-radio label="showAll" :value="0">显示全部</el-radio>
  118. <el-radio label="showNotice" :value="1">仅显示通知</el-radio>
  119. </el-radio-group>
  120. </p>
  121. <el-table v-loading="loading" :data="filteredTableData" border style="width: 80%">
  122. <el-table-column prop="roleName" label="角色" width="180" />
  123. <el-table-column label="是否通知" align="center" width="100">
  124. <template #default="scope">
  125. <el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0" />
  126. </template>
  127. </el-table-column>
  128. <el-table-column label="仅通知当事人" align="center" width="110">
  129. <template #default="scope">
  130. <el-switch v-model="scope.row.isClient" :active-value="1" :inactive-value="0" />
  131. </template>
  132. </el-table-column>
  133. <el-table-column label="通知模板" align="center">
  134. <template #default="scope">
  135. <span>{{ scope.row.notifyTemplateName }}</span>
  136. <i
  137. style="margin-left: 8px; color: #409eff; cursor: pointer; font-style: normal"
  138. @click="changeTemplate(scope.row)"
  139. >
  140. {{ scope.row.notifyTemplateName ? '更换' : '选择' }}
  141. </i>
  142. </template>
  143. </el-table-column>
  144. </el-table>
  145. </div>
  146. <div class="btnstyle">
  147. <el-button type="primary" plain @click="submit()" v-if="route.query.type == 'create'"
  148. >确认
  149. </el-button>
  150. <el-button type="primary" plain @click="submitUpdate()" v-else>确认</el-button>
  151. <el-button plain @click="goBack()">取消</el-button>
  152. </div>
  153. </ContentWrap>
  154. <!-- 选择模板-->
  155. <el-dialog v-model="templateDialogVisible" title="请选择通知模板" width="80%" center>
  156. <!-- 搜索工作栏 -->
  157. <div class="templateSearch">
  158. <el-form
  159. class="-mb-15px"
  160. :model="templateQueryParams"
  161. ref="queryFormRef"
  162. :inline="true"
  163. label-width="68px"
  164. >
  165. <el-form-item label="模板名称" prop="name">
  166. <el-input
  167. v-model="templateQueryParams.name"
  168. placeholder="请输入模板名称"
  169. clearable
  170. @keyup.enter="handleQuery"
  171. class="!w-240px"
  172. />
  173. </el-form-item>
  174. <el-form-item label="模板编号" prop="code">
  175. <el-input
  176. v-model="templateQueryParams.code"
  177. placeholder="请输入模版编码"
  178. clearable
  179. @keyup.enter="handleQuery"
  180. class="!w-240px"
  181. />
  182. </el-form-item>
  183. <el-form-item label="状态" prop="status">
  184. <el-select
  185. v-model="templateQueryParams.status"
  186. placeholder="请选择开启状态"
  187. clearable
  188. class="!w-240px"
  189. >
  190. <el-option
  191. v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
  192. :key="dict.value"
  193. :label="dict.label"
  194. :value="dict.value"
  195. />
  196. </el-select>
  197. </el-form-item>
  198. <el-form-item label="创建时间" prop="createTime">
  199. <el-date-picker
  200. v-model="templateQueryParams.createTime"
  201. value-format="YYYY-MM-DD HH:mm:ss"
  202. type="daterange"
  203. start-placeholder="开始日期"
  204. end-placeholder="结束日期"
  205. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  206. class="!w-240px"
  207. />
  208. </el-form-item>
  209. <el-form-item>
  210. <el-button @click="handleQuery">
  211. <Icon icon="ep:search" class="mr-5px" />
  212. 搜索
  213. </el-button>
  214. <el-button @click="resetQuery">
  215. <Icon icon="ep:refresh" class="mr-5px" />
  216. 重置
  217. </el-button>
  218. </el-form-item>
  219. </el-form>
  220. </div>
  221. <el-table
  222. v-loading="loading"
  223. :data="templateList"
  224. @current-change="handleCurrentChange"
  225. highlight-current-row
  226. :row-class-name="(row) => (row.code == selectedTemplate?.code ? 'selected-row' : '')"
  227. style="width: 100%; cursor: pointer"
  228. >
  229. <!-- 单选通过点击行来实现 -->
  230. <el-table-column label="模板编码" prop="code" align="center" width="160" fixed />
  231. <el-table-column label="模板名称" prop="name" width="200" fixed />
  232. <el-table-column label="通知人姓名" prop="nickname" width="150" fixed />
  233. <el-table-column
  234. label="模板内容"
  235. align="center"
  236. prop="content"
  237. width="300"
  238. :show-overflow-tooltip="true"
  239. />
  240. <el-table-column label="类型" align="center" prop="type">
  241. <template #default="scope">
  242. <dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="scope.row.type" />
  243. </template>
  244. </el-table-column>
  245. <el-table-column label="开启状态" align="center" prop="status" width="80">
  246. <template #default="scope">
  247. <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
  248. </template>
  249. </el-table-column>
  250. <el-table-column label="备注" align="center" prop="remark" />
  251. <el-table-column
  252. label="创建时间"
  253. align="center"
  254. prop="createTime"
  255. width="180"
  256. :formatter="dateFormatter"
  257. />
  258. </el-table>
  259. <!-- 分页 -->
  260. <Pagination
  261. style="width: 100%; text-align: right"
  262. :total="total"
  263. v-model:page="templateQueryParams.pageNo"
  264. v-model:limit="templateQueryParams.pageSize"
  265. @pagination="getTemplateData"
  266. />
  267. <template #footer>
  268. <div class="dialog-footer">
  269. <el-button @click="templateDialogVisible = false">取消</el-button>
  270. <el-button type="primary" @click="templateconfirm()"> 确定</el-button>
  271. </div>
  272. </template>
  273. </el-dialog>
  274. </template>
  275. <script setup lang="ts">
  276. import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
  277. import * as NotificationRules from '@/api/sop/notificationRules'
  278. import { getRolePage } from '@/api/system/role'
  279. import { getNotifyTemplatePage } from '@/api/system/notify/template'
  280. import { handleTree } from '@/utils/tree'
  281. import { listMarsDept } from '@/api/system/marsdept'
  282. import { ref } from 'vue'
  283. import { dateFormatter } from '@/utils/formatTime'
  284. const router = useRouter()
  285. const route = useRoute()
  286. const noticeData = reactive({
  287. type: '',
  288. rule: '',
  289. notifyTimeType: null,
  290. notifyTime: '',
  291. workstationId: '',
  292. sopId: route.query.id
  293. })
  294. const timeParts = reactive({
  295. day: 0,
  296. hour: 0,
  297. minute: 1,
  298. second: 0
  299. })
  300. // const templateQueryParams = reactive({
  301. // pageNo: 1,
  302. // pageSize: 10
  303. // })
  304. // 角色列表参数
  305. const roleQueryParams = reactive({
  306. pageNo: 1,
  307. pageSize: -1
  308. })
  309. const workstationOptions = ref([]) // 岗位树选项
  310. const tableData = ref([])
  311. const actionNoticeList = ref([])
  312. const status = ref(0) // 0: 全部, 1: 仅显示通知项
  313. const templateDialogVisible = ref(false)
  314. const loading = ref(false)
  315. const templateList = ref([])
  316. const selectedTemplate = ref(null) // 当前选中的模板
  317. const currentTargetRow = ref(null) // 当前要修改的角色行
  318. const total = ref(0) // 列表的总页数
  319. const templateQueryParams = reactive({
  320. pageNo: 1,
  321. pageSize: 10,
  322. name: undefined,
  323. status: undefined,
  324. code: undefined,
  325. createTime: []
  326. })
  327. const queryFormRef = ref() // 搜索的表单
  328. const filteredTableData = computed(() => {
  329. if (status.value == 0) return tableData.value
  330. return tableData.value.filter((item) => item.status == 1)
  331. })
  332. onMounted(() => {
  333. getActionNoticeData() //步骤操作的规则数据
  334. getWorkArea() //获取区域数据
  335. getTableData()
  336. console.log(route.query.rowId, '是否传递成功')
  337. })
  338. // 返回
  339. const goBack = () => {
  340. router.go(-1)
  341. }
  342. // 获取工作流程list(步骤操作下拉框内容)
  343. const getActionNoticeData = async () => {
  344. try {
  345. const actionNoticesList = await NotificationRules.getSopWorkflowStepList(route.query.id)
  346. actionNoticeList.value = actionNoticesList
  347. console.log(actionNoticesList, '是否拿到sop步骤', actionNoticeList.value)
  348. } catch (err) {}
  349. }
  350. // 区域数据
  351. const getWorkArea = async () => {
  352. const { list } = await listMarsDept({ pageNo: 1, pageSize: -1 })
  353. console.log(list, '岗位筛选')
  354. const targetId = Number(route.query.workstationId)
  355. // 找到当前节点
  356. const currentNode = list.find((item) => item.id === targetId)
  357. if (!currentNode) return
  358. // 找父级节点
  359. const parentNode = list.find((item) => item.id === currentNode.parentId)
  360. // 构建父子结构
  361. const filteredTree = parentNode
  362. ? [
  363. {
  364. id: parentNode.id,
  365. label: parentNode.workstationName,
  366. children: [
  367. {
  368. id: currentNode.id,
  369. label: currentNode.workstationName
  370. }
  371. ]
  372. }
  373. ]
  374. : [
  375. {
  376. id: currentNode.id,
  377. label: currentNode.workstationName
  378. }
  379. ]
  380. // 加入“全部”作为独立选项
  381. workstationOptions.value = [{ id: 0, label: '全部', children: [] }, ...filteredTree]
  382. }
  383. // 通知角色模板中表格数据拼接
  384. const getTableData = async () => {
  385. if (route.query.type == 'create') {
  386. // 创建逻辑保持不变
  387. loading.value = true
  388. const [roleRes, notifyRes] = await Promise.all([
  389. getRolePage(roleQueryParams),
  390. NotificationRules.getNotifyConfigDetailPage()
  391. ])
  392. const roles = roleRes?.list || []
  393. const notifyMap = new Map()
  394. ;(notifyRes.list || []).forEach((item) => {
  395. notifyMap.set(item.roleId, item)
  396. })
  397. loading.value = false
  398. tableData.value = roles.map((role) => {
  399. const notify = notifyMap.get(role.id)
  400. return {
  401. id: role.id,
  402. roleName: role.name,
  403. status: 0,
  404. isClient: 0,
  405. notifyTemplateName: '',
  406. notifyConfigId: null
  407. }
  408. })
  409. } else if (route.query.type === 'update') {
  410. loading.value = true
  411. const [roleRes, notifyRes] = await Promise.all([
  412. getRolePage(roleQueryParams),
  413. NotificationRules.selectNotifyConfigById(route.query.rowId)
  414. ])
  415. const roles = roleRes?.list || []
  416. const notifyList = notifyRes.notifyConfigDetailRespVOList || []
  417. Object.assign(noticeData, notifyRes)
  418. noticeData.rule = Number(noticeData.rule)
  419. if (noticeData.notifyTimeType === 0 || noticeData.notifyTimeType === 2) {
  420. parseNotifyTimeToParts(noticeData.notifyTime)
  421. }
  422. const notifyMap = new Map()
  423. notifyList.forEach((item) => {
  424. notifyMap.set(item.roleId, item)
  425. })
  426. tableData.value = roles.map((role) => {
  427. const notify = notifyMap.get(role.id)
  428. return {
  429. id: role.id,
  430. roleName: role.name, // ✅ 修复字段
  431. status: notify?.status ?? 0,
  432. isClient: notify?.isClient ?? 0,
  433. notifyTemplateName: notify?.notifyTemplateName || '',
  434. notifyConfigId: notify?.id ?? null
  435. }
  436. })
  437. loading.value = false
  438. }
  439. }
  440. // 时间处理
  441. const parseNotifyTimeToParts = (totalSeconds: number) => {
  442. timeParts.day = Math.floor(totalSeconds / 86400)
  443. timeParts.hour = Math.floor((totalSeconds % 86400) / 3600)
  444. timeParts.minute = Math.floor((totalSeconds % 3600) / 60)
  445. timeParts.second = totalSeconds % 60
  446. }
  447. // 选择或更换模板
  448. const changeTemplate = async (row) => {
  449. // 这里你可以打开弹窗、弹出菜单或其他方式让用户选模板
  450. templateDialogVisible.value = true
  451. currentTargetRow.value = row
  452. await getTemplateData()
  453. }
  454. // 新增确认
  455. const submit = async () => {
  456. try {
  457. let data
  458. // let successMessage
  459. // 新增
  460. console.log(route.query.type, 'ggggdata')
  461. // 如果是类型 0 或 2,需要做时间转换
  462. if (noticeData.notifyTimeType == 0 || noticeData.notifyTimeType == 2) {
  463. const totalSeconds =
  464. timeParts.day * 86400 + timeParts.hour * 3600 + timeParts.minute * 60 + timeParts.second
  465. noticeData.notifyTime = totalSeconds
  466. } else {
  467. noticeData.notifyTime = 0
  468. }
  469. // ✅ Step 1: 遍历 table 中的数据(filteredTableData 是 computed,所以要加 .value)
  470. const notifyConfigDetailRespVOList = filteredTableData.value.map((item) => ({
  471. id: item.notifyConfigId ?? null, // 编辑时的 ID,新增为 null
  472. roleId: item.id, // 角色 ID
  473. status: item.status, // 是否通知
  474. isClient: item.isClient, // 是否仅通知当事人
  475. notifyTemplateCode: item.notifyTemplateCode, // ✅ 这里已经处理好了
  476. configId: item.notifyConfigId ?? null // 通知配置 ID
  477. }))
  478. // ✅ 校验 2:如果 status 或 isClient 为 1,则 notifyTemplateCode 不可为空
  479. const hasMissingTemplate = notifyConfigDetailRespVOList.some((item) => {
  480. return (item.status == 1 || item.isClient == 1) && !item.notifyTemplateCode
  481. })
  482. if (hasMissingTemplate) {
  483. ElMessage.warning('存在启用通知或仅通知当事人的角色,但模板未填写')
  484. return
  485. }
  486. // ✅ 合并表单部分数据 noticeData(例如通知名称、类型等)
  487. const payload = {
  488. ...noticeData,
  489. notifyConfigDetailRespVOList
  490. }
  491. data = await NotificationRules.insertNotifyConfig(payload)
  492. console.log(data, '是否正确')
  493. console.log(tableData.value, 'tableDataValue-赋值之前')
  494. if (data) {
  495. goBack()
  496. }
  497. ElMessage.success('保存成功')
  498. } catch (error) {
  499. console.error('保存失败:', error)
  500. // message.error('保存失败')
  501. }
  502. }
  503. // 修改确认
  504. const submitUpdate = async () => {
  505. if (noticeData.notifyTimeType == 0 || noticeData.notifyTimeType == 2) {
  506. const totalSeconds =
  507. timeParts.day * 86400 + timeParts.hour * 3600 + timeParts.minute * 60 + timeParts.second
  508. noticeData.notifyTime = totalSeconds
  509. } else {
  510. noticeData.notifyTime = 0
  511. }
  512. // ✅ 包含主表信息 + 详情数组(tableData.value)
  513. const payload = {
  514. ...noticeData,
  515. notifyConfigDetailRespVOList: filteredTableData.value.map((item) => ({
  516. notifyTemplateCode: item.notifyTemplateCode,
  517. roleName: item.roleName,
  518. roleId: item.id,
  519. status: item.status,
  520. isClient: item.isClient,
  521. notifyTemplateName: item.notifyTemplateName,
  522. id: item.notifyConfigId // 有id则为修改,无id则为新增
  523. }))
  524. }
  525. // const data = await NotificationRules.updateNotifyConfig(payload)
  526. const data = await NotificationRules.insertNotifyConfig(payload)
  527. if (data) {
  528. ElMessage.success('更新成功')
  529. }
  530. }
  531. // 获取模板数据
  532. const getTemplateData = async () => {
  533. try {
  534. const templateData = await getNotifyTemplatePage(templateQueryParams)
  535. templateList.value = templateData.list
  536. total.value = templateData.total
  537. console.log(templateData, '有什么数据', templateList.value)
  538. } finally {
  539. loading.value = false
  540. }
  541. }
  542. // 表格中点击行时触发
  543. const handleCurrentChange = (row) => {
  544. selectedTemplate.value = row
  545. }
  546. // 弹框确认
  547. const templateconfirm = async () => {
  548. if (!selectedTemplate.value || !currentTargetRow.value) return
  549. // ✅ 存 name(用于页面显示)
  550. currentTargetRow.value.notifyTemplateName = selectedTemplate.value.name
  551. // ✅ 存 code(用于接口提交)
  552. currentTargetRow.value.notifyTemplateCode = selectedTemplate.value.code
  553. templateDialogVisible.value = false
  554. }
  555. /** 搜索按钮操作 */
  556. const handleQuery = () => {
  557. templateQueryParams.pageNo = 1
  558. getTemplateData()
  559. }
  560. /** 重置按钮操作 */
  561. const resetQuery = () => {
  562. queryFormRef.value.resetFields()
  563. handleQuery()
  564. }
  565. </script>
  566. <style scoped lang="scss">
  567. .tab-header {
  568. background-color: #f5f7fa;
  569. border-bottom: 1px solid #dcdfe6;
  570. padding: 12px 20px;
  571. border-radius: 4px 4px 0 0;
  572. }
  573. .checkContent {
  574. width: 95%;
  575. height: 200px;
  576. margin: 10px auto;
  577. section {
  578. width: 100%;
  579. height: 49px;
  580. display: flex;
  581. //background: #000;
  582. span {
  583. display: block;
  584. padding: 0 5px 0 0;
  585. width: 120px;
  586. line-height: 25px;
  587. text-align: right;
  588. }
  589. i {
  590. font-style: normal;
  591. }
  592. }
  593. }
  594. .tableCon {
  595. width: 95%;
  596. min-height: 300px;
  597. margin: auto;
  598. p {
  599. margin: 20px 0;
  600. }
  601. }
  602. .btnstyle {
  603. text-align: right;
  604. }
  605. .dialog-footer {
  606. width: 100%;
  607. text-align: right;
  608. }
  609. /* 鼠标悬停时变小手 */
  610. .el-table__body tr:hover {
  611. cursor: pointer;
  612. }
  613. /* 选中行的背景设为蓝色,文字颜色可选白色以增强对比 */
  614. .el-table__body .selected-row {
  615. background-color: #e6f7ff !important; /* 浅蓝色 */
  616. /* background-color: #409EFF !important; */
  617. /* 如果你要主色蓝,可以用这个 */
  618. }
  619. .templateSearch {
  620. width: 100%;
  621. height: 100%;
  622. margin: 20px auto 30px;
  623. }
  624. </style>