gzcb.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. <template>
  2. <div class="app-container gzcb-page">
  3. <!-- 搜索区(若依风格) -->
  4. <div class="filter-panel">
  5. <el-form :inline="true" :model="searchForm" class="filter-form" label-width="80px">
  6. <el-form-item label="事件状态">
  7. <el-select v-model="searchForm.status" placeholder="请选择事件状态" clearable style="width:140px">
  8. <el-option v-for="d in urgeStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
  9. </el-select>
  10. </el-form-item>
  11. <el-form-item label="处置人">
  12. <el-input v-model="searchForm.handler" placeholder="请输入处置人" clearable style="width:140px" @keyup.enter="handleSearch" />
  13. </el-form-item>
  14. <el-form-item label="事件编号">
  15. <el-input v-model="searchForm.eventNo" placeholder="请输入事件编号" clearable style="width:160px" @keyup.enter="handleSearch" />
  16. </el-form-item>
  17. <el-form-item label="接报时间">
  18. <el-date-picker
  19. v-model="searchForm.dateRange"
  20. type="daterange"
  21. range-separator="至"
  22. start-placeholder="开始日期"
  23. end-placeholder="结束日期"
  24. style="width:240px"
  25. value-format="YYYY-MM-DD"
  26. />
  27. </el-form-item>
  28. <el-form-item>
  29. <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
  30. <el-button :icon="Refresh" @click="handleReset">重置</el-button>
  31. </el-form-item>
  32. </el-form>
  33. </div>
  34. <!-- 数据表格 -->
  35. <div class="table-panel">
  36. <el-table :data="tableData" border stripe style="width:100%" v-loading="loading">
  37. <el-table-column prop="eventNo" label="事件编号" width="150" />
  38. <el-table-column prop="eventType" label="管网类型" width="100">
  39. <template #default="{ row }">{{ dictLabel(dis_event_type, row.eventType) }}</template>
  40. </el-table-column>
  41. <el-table-column prop="summary" label="事件摘要" min-width="160" show-overflow-tooltip />
  42. <el-table-column prop="department" label="责任单位" width="110">
  43. <template #default="{ row }">{{ row.department || '-' }}</template>
  44. </el-table-column>
  45. <el-table-column prop="receiver" label="目标接收人" width="100">
  46. <template #default="{ row }">{{ row.receiver || row.handler || '-' }}</template>
  47. </el-table-column>
  48. <el-table-column prop="receiverDept" label="接收部门" width="110">
  49. <template #default="{ row }">{{ row.receiverDept || row.department || '-' }}</template>
  50. </el-table-column>
  51. <el-table-column prop="elapsed" label="接报后已过" width="110">
  52. <template #default="{ row }">{{ row.elapsed || '-' }}</template>
  53. </el-table-column>
  54. <el-table-column prop="urgeCount" label="催办次数" width="90" align="center" />
  55. <el-table-column prop="lastUrgeTime" label="最近催办时间" width="150">
  56. <template #default="{ row }">{{ row.lastUrgeTime || '-' }}</template>
  57. </el-table-column>
  58. <el-table-column prop="createTime" label="创建时间" width="150" />
  59. <el-table-column prop="statusName" label="状态" width="90">
  60. <template #default="{ row }">
  61. <el-tag :type="statusTagType(row.status)" size="small">{{ row.statusName }}</el-tag>
  62. </template>
  63. </el-table-column>
  64. <el-table-column label="操作" width="160" fixed="right">
  65. <template #default="{ row }">
  66. <el-button v-if="canUrge(row)" type="primary" plain size="small" @click="handleUrge(row)" v-hasPermi="['dispatch:event:urge']">催办</el-button>
  67. <el-button type="info" plain size="small" @click="handleViewRecords(row)">查看记录</el-button>
  68. </template>
  69. </el-table-column>
  70. </el-table>
  71. <div class="pagination-wrap">
  72. <el-pagination
  73. v-model:current-page="pagination.page"
  74. v-model:page-size="pagination.pageSize"
  75. :page-sizes="[10, 20, 50]"
  76. layout="total, sizes, prev, pager, next, jumper"
  77. :total="total"
  78. @size-change="handleSizeChange"
  79. @current-change="handleCurrentChange"
  80. />
  81. </div>
  82. </div>
  83. <!-- 催办弹窗 -->
  84. <el-dialog v-model="urgeVisible" title="工作催办" width="600px" :close-on-click-modal="false">
  85. <el-form :model="urgeForm" :rules="urgeRules" ref="urgeFormRef" label-width="100px">
  86. <div class="section-title">事件信息</div>
  87. <el-row :gutter="20">
  88. <el-col :span="12">
  89. <el-form-item label="事件编号">{{ urgeForm.eventNo }}</el-form-item>
  90. </el-col>
  91. <el-col :span="12">
  92. <el-form-item label="管网类型">{{ dictLabel(dis_event_type, urgeForm.eventType) }}</el-form-item>
  93. </el-col>
  94. </el-row>
  95. <el-row :gutter="20">
  96. <el-col :span="12">
  97. <el-form-item label="当前状态">
  98. <el-tag :type="statusTagType(urgeForm.status)" size="small">{{ urgeForm.statusName || '-' }}</el-tag>
  99. </el-form-item>
  100. </el-col>
  101. <el-col :span="12">
  102. <el-form-item label="接报后已过">{{ urgeForm.elapsed || '-' }}</el-form-item>
  103. </el-col>
  104. </el-row>
  105. <el-form-item label="事件摘要">
  106. <div class="desc-text">{{ urgeForm.summary || '-' }}</div>
  107. </el-form-item>
  108. <div class="section-title">接收人信息</div>
  109. <el-row :gutter="20">
  110. <el-col :span="12">
  111. <el-form-item label="姓名">{{ urgeForm.receiver || '-' }}</el-form-item>
  112. </el-col>
  113. <el-col :span="12">
  114. <el-form-item label="部门">{{ urgeForm.receiverDept || '-' }}</el-form-item>
  115. </el-col>
  116. </el-row>
  117. <el-form-item label="处置人电话">
  118. <span v-if="urgeForm.receiverPhone" style="color:#2f6fe4;font-weight:600;font-size:14px">{{ urgeForm.receiverPhone }}</span>
  119. <span v-else style="color:#c0c4cc;font-size:13px">处置人未维护联系电话,请改用电话/短信以外的催办方式</span>
  120. </el-form-item>
  121. <div class="section-title">催办内容</div>
  122. <el-form-item label="催办方式" prop="methods">
  123. <el-checkbox-group v-model="urgeForm.methods">
  124. <el-checkbox v-for="d in dis_event_urge_method" :key="d.value" :label="d.label" />
  125. </el-checkbox-group>
  126. </el-form-item>
  127. <el-form-item label="催办内容" prop="content">
  128. <el-input v-model="urgeForm.content" type="textarea" :rows="4" placeholder="请输入催办内容" />
  129. </el-form-item>
  130. </el-form>
  131. <template #footer>
  132. <el-button @click="urgeVisible = false">取消</el-button>
  133. <el-button type="primary" @click="confirmUrge">发送催办</el-button>
  134. </template>
  135. </el-dialog>
  136. <!-- 查看记录弹窗 -->
  137. <el-drawer v-model="recordsVisible" title="催办记录" size="700px" :close-on-click-modal="false" class="detail-drawer" direction="rtl">
  138. <div v-if="currentRow">
  139. <el-form label-width="100px" size="default" style="margin-bottom:12px">
  140. <el-row :gutter="20">
  141. <el-col :span="12">
  142. <el-form-item label="事件编号">{{ currentRow.eventNo }}</el-form-item>
  143. </el-col>
  144. <el-col :span="12">
  145. <el-form-item label="责任单位">{{ currentRow.department || '-' }}</el-form-item>
  146. </el-col>
  147. </el-row>
  148. <el-form-item label="事件摘要">
  149. <div class="desc-text">{{ currentRow.summary || '-' }}</div>
  150. </el-form-item>
  151. </el-form>
  152. <el-table :data="currentRow.records" border stripe style="width:100%">
  153. <el-table-column prop="urgeTime" label="催办时间" width="150" />
  154. <el-table-column prop="method" label="催办方式" width="100" />
  155. <el-table-column prop="receiver" label="目标接收人" width="90">
  156. <template #default="{ row }">{{ row.receiver || '-' }}</template>
  157. </el-table-column>
  158. <el-table-column prop="content" label="催办内容" show-overflow-tooltip />
  159. <el-table-column prop="result" label="结果" width="90">
  160. <template #default="{ row }">
  161. <el-tag :type="row.result === '已反馈' ? 'success' : 'info'" size="small">{{ row.result }}</el-tag>
  162. </template>
  163. </el-table-column>
  164. </el-table>
  165. </div>
  166. <template #footer>
  167. <el-button @click="recordsVisible = false">关闭</el-button>
  168. </template>
  169. </el-drawer>
  170. </div>
  171. </template>
  172. <script setup name="Gzcb">
  173. import { ref, nextTick, onMounted } from 'vue'
  174. import { ElMessage } from 'element-plus'
  175. import { useDict } from '@/utils/dict'
  176. import { getEventUrgePage, urgeEvent, getEventLogs, getEventStatusOptions } from '@/api/dispatch'
  177. import { Search, Refresh } from '@element-plus/icons-vue'
  178. // ==================== 字典 ====================
  179. const { dis_event_urge_method, dis_event_urge_result, dis_event_type } = useDict('dis_event_urge_method', 'dis_event_urge_result', 'dis_event_type')
  180. function dictLabel(list, value) {
  181. if (value === null || value === undefined || value === '') return '-'
  182. const hit = (list || []).find(item => String(item.value) === String(value) || item.label === value)
  183. return hit ? hit.label : value
  184. }
  185. // 多选方式以「、」连接存储,回显时逐个翻译
  186. function methodLabel(value) {
  187. if (!value) return '-'
  188. return String(value).split('、').map(item => dictLabel(dis_event_urge_method.value, item)).join('、')
  189. }
  190. // 工作催办只面向已核实、处置中事件。
  191. const urgeStatusOptions = ref([])
  192. async function loadStatusOptions() {
  193. try {
  194. const res = await getEventStatusOptions()
  195. urgeStatusOptions.value = (res.data || []).filter(item => [1, 2].includes(item.value))
  196. } catch {
  197. urgeStatusOptions.value = []
  198. }
  199. }
  200. // 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 已解除 4 不实事件)
  201. const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
  202. function statusTagType(status) {
  203. return STATUS_TAG[status] || 'info'
  204. }
  205. function canUrge(row) {
  206. return [1, 2].includes(row.status)
  207. }
  208. // ==================== 数据状态 ====================
  209. const tableData = ref([])
  210. const total = ref(0)
  211. const loading = ref(false)
  212. const searchForm = ref({ status: '', handler: '', eventNo: '', dateRange: null })
  213. const pagination = ref({ page: 1, pageSize: 10 })
  214. // ==================== 加载数据 ====================
  215. async function loadData() {
  216. loading.value = true
  217. try {
  218. const params = {}
  219. if (searchForm.value.status !== '' && searchForm.value.status !== null) params.status = searchForm.value.status
  220. if (searchForm.value.handler) params.handler = searchForm.value.handler
  221. if (searchForm.value.eventNo) params.eventNo = searchForm.value.eventNo
  222. if (searchForm.value.dateRange && searchForm.value.dateRange.length === 2) {
  223. params.startTime = searchForm.value.dateRange[0]
  224. params.endTime = searchForm.value.dateRange[1]
  225. }
  226. const res = await getEventUrgePage(pagination.value.page, pagination.value.pageSize, params)
  227. tableData.value = res.data?.rows || res.data?.records || res.data || []
  228. total.value = res.data?.total || 0
  229. } catch (error) {
  230. ElMessage.error('加载数据失败:' + (error.message || '未知错误'))
  231. } finally {
  232. loading.value = false
  233. }
  234. }
  235. onMounted(() => {
  236. loadStatusOptions()
  237. loadData()
  238. })
  239. // ==================== 搜索筛选 ====================
  240. function handleSearch() {
  241. pagination.value.page = 1
  242. loadData()
  243. }
  244. function handleReset() {
  245. searchForm.value = { status: '', handler: '', eventNo: '', dateRange: null }
  246. pagination.value.page = 1
  247. loadData()
  248. }
  249. // ==================== 分页 ====================
  250. function handleSizeChange(val) {
  251. pagination.value.pageSize = val
  252. pagination.value.page = 1
  253. loadData()
  254. }
  255. function handleCurrentChange(val) {
  256. pagination.value.page = val
  257. loadData()
  258. }
  259. // ==================== 催办 ====================
  260. const urgeVisible = ref(false)
  261. const urgeFormRef = ref(null)
  262. const currentUrgeId = ref(null)
  263. const urgeForm = ref({ eventNo: '', eventType: '', status: null, statusName: '', elapsed: '', summary: '', receiver: '', receiverDept: '', receiverPhone: '', methods: [], content: '' })
  264. const urgeRules = {
  265. methods: [{ required: true, message: '请选择催办方式', trigger: 'change', type: 'array' }],
  266. content: [{ required: true, message: '请输入催办内容', trigger: 'blur' }]
  267. }
  268. function handleUrge(row) {
  269. currentUrgeId.value = row.eventId
  270. urgeForm.value = {
  271. eventNo: row.eventNo,
  272. eventType: row.eventType,
  273. status: row.status,
  274. statusName: row.statusName || '',
  275. elapsed: row.elapsed || '',
  276. summary: row.summary || '',
  277. receiver: row.receiver || row.handler || '',
  278. receiverDept: row.receiverDept || row.department || '',
  279. // 电话取事件责任处置人的电话(初判核实录入),无则回退最近催办记录里的号码
  280. receiverPhone: row.handlerPhone || row.receiverPhone || '',
  281. // 默认勾选「系统消息」:发送后给处置人推一条站内催办消息
  282. methods: ['系统消息'],
  283. content: '关于【' + (row.summary || '') + '】的处置进度,请尽快反馈处理情况。'
  284. }
  285. urgeVisible.value = true
  286. nextTick(() => urgeFormRef.value?.clearValidate())
  287. }
  288. async function confirmUrge() {
  289. try {
  290. await urgeFormRef.value.validate()
  291. } catch {
  292. return
  293. }
  294. try {
  295. await urgeEvent({
  296. eventId: currentUrgeId.value,
  297. receiver: urgeForm.value.receiver,
  298. receiverDept: urgeForm.value.receiverDept,
  299. receiverPhone: urgeForm.value.receiverPhone,
  300. method: urgeForm.value.methods.join('、'),
  301. operDesc: urgeForm.value.content
  302. })
  303. ElMessage.success('催办已发送')
  304. urgeVisible.value = false
  305. loadData()
  306. } catch (error) {
  307. ElMessage.error('催办失败:' + (error.message || '未知错误'))
  308. }
  309. }
  310. // ==================== 查看记录 ====================
  311. const recordsVisible = ref(false)
  312. const currentRow = ref(null)
  313. async function handleViewRecords(row) {
  314. currentRow.value = { ...row, records: [] }
  315. recordsVisible.value = true
  316. // 催办记录就是该事件 operType=8 的操作日志
  317. try {
  318. const res = await getEventLogs(row.eventId, 8)
  319. currentRow.value = {
  320. ...row,
  321. records: (res.data || []).map(log => ({
  322. urgeTime: log.operTime,
  323. method: methodLabel(log.method),
  324. receiver: log.receiver || '',
  325. content: log.operDesc,
  326. result: dictLabel(dis_event_urge_result.value, log.urgeResult)
  327. }))
  328. }
  329. } catch {
  330. // 加载失败时保留空记录列表
  331. }
  332. }
  333. </script>
  334. <style scoped>
  335. .gzcb-page {
  336. padding: 16px;
  337. background: #f5f5f5;
  338. min-height: calc(100vh - 84px);
  339. }
  340. .filter-panel {
  341. background: #fff;
  342. border: 1px solid #dcdfe6;
  343. border-radius: 4px;
  344. padding: 12px 16px;
  345. margin-bottom: 12px;
  346. display: flex;
  347. justify-content: space-between;
  348. align-items: center;
  349. flex-wrap: wrap;
  350. gap: 12px;
  351. }
  352. .filter-form {
  353. display: flex;
  354. align-items: center;
  355. gap: 0 12px;
  356. flex-wrap: wrap;
  357. }
  358. /* 若依风格查询栏:内联表单项去默认外边距,间距统一交给 gap */
  359. .filter-form :deep(.el-form-item) {
  360. margin-right: 0;
  361. margin-bottom: 0;
  362. }
  363. .table-panel {
  364. background: #fff;
  365. border: 1px solid #dcdfe6;
  366. border-radius: 4px;
  367. padding: 16px;
  368. }
  369. .pagination-wrap {
  370. margin-top: 16px;
  371. display: flex;
  372. justify-content: flex-end;
  373. }
  374. .section-title {
  375. font-size: 14px;
  376. font-weight: 600;
  377. color: #303133;
  378. margin-bottom: 12px;
  379. padding-left: 8px;
  380. border-left: 3px solid #409EFF;
  381. }
  382. :deep(.el-form-item__label) {
  383. color: #909399;
  384. }
  385. :deep(.el-form-item__content) {
  386. color: #303133;
  387. }
  388. </style>