DisEventGrade.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <!-- 应急事件分级 -->
  2. <template>
  3. <div class="app-container">
  4. <!-- 搜索区 -->
  5. <el-form :model="queryParams" :inline="true" class="search-form">
  6. <el-form-item label="事件类别">
  7. <el-select v-model="queryParams.eventCategory" placeholder="请选择" clearable style="width: 160px">
  8. <el-option v-for="d in dis_event_category" :key="d.value" :label="d.label" :value="d.value" />
  9. </el-select>
  10. </el-form-item>
  11. <el-form-item label="事件等级">
  12. <el-select v-model="queryParams.eventLevel" placeholder="请选择" clearable style="width: 140px">
  13. <el-option v-for="d in dis_event_level" :key="d.value" :label="d.label" :value="d.value" />
  14. </el-select>
  15. </el-form-item>
  16. <el-form-item label="事件状态">
  17. <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 120px">
  18. <el-option v-for="d in gradeStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
  19. </el-select>
  20. </el-form-item>
  21. <el-form-item label="创建时间">
  22. <el-date-picker
  23. v-model="queryParams.dateRange"
  24. type="daterange"
  25. range-separator="至"
  26. start-placeholder="开始日期"
  27. end-placeholder="结束日期"
  28. style="width: 240px"
  29. value-format="YYYY-MM-DD"
  30. />
  31. </el-form-item>
  32. <el-form-item>
  33. <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
  34. <el-button :icon="Refresh" @click="handleReset">重置</el-button>
  35. </el-form-item>
  36. <el-form-item style="float: right">
  37. <el-button type="success" @click="handleAdd" v-hasPermi="['dispatch:grade:add']">新增</el-button>
  38. <el-button type="danger" @click="handleBatchDelete" v-hasPermi="['dispatch:grade:remove']">批量删除</el-button>
  39. <el-button type="primary" @click="handleExport">导出</el-button>
  40. </el-form-item>
  41. </el-form>
  42. <!-- 数据表格 -->
  43. <el-table :data="pageList" border stripe @selection-change="handleSelectionChange" style="width: 100%">
  44. <el-table-column type="selection" width="55" align="center" />
  45. <el-table-column label="序号" type="index" width="60" align="center" />
  46. <el-table-column prop="gradeNo" label="标准编号" width="140" />
  47. <el-table-column prop="eventCategory" label="事件类别" min-width="120">
  48. <template #default="{ row }">{{ dictLabel(dis_event_category, row.eventCategory) }}</template>
  49. </el-table-column>
  50. <el-table-column prop="eventLevel" label="事件等级" width="100" align="center">
  51. <template #default="{ row }">
  52. <el-tag :type="levelTagType(row.eventLevel)" size="small">{{ dictLabel(dis_event_level, row.eventLevel) }}</el-tag>
  53. </template>
  54. </el-table-column>
  55. <el-table-column prop="scenario" label="适用场景" min-width="140" />
  56. <el-table-column prop="criteria" label="判断标准" min-width="200" show-overflow-tooltip />
  57. <el-table-column prop="response" label="响应要求" min-width="160" show-overflow-tooltip />
  58. <el-table-column prop="createTime" label="创建时间" width="150" align="center" />
  59. <el-table-column prop="status" label="状态" width="90" align="center">
  60. <template #default="{ row }">
  61. <el-tag :type="row.status === 0 ? 'success' : 'info'">{{ row.statusName }}</el-tag>
  62. </template>
  63. </el-table-column>
  64. <el-table-column label="操作" width="140" align="center">
  65. <template #default="{ row }">
  66. <el-button type="primary" plain size="small" @click="handleEdit(row)" v-hasPermi="['dispatch:grade:edit']">编辑</el-button>
  67. <el-button type="danger" plain size="small" @click="handleDelete(row)" v-hasPermi="['dispatch:grade:remove']">删除</el-button>
  68. </template>
  69. </el-table-column>
  70. </el-table>
  71. <!-- 分页 -->
  72. <el-pagination
  73. style="margin-top: 16px; justify-content: flex-end"
  74. v-model:current-page="pagination.page"
  75. v-model:page-size="pagination.size"
  76. :page-sizes="[10, 20, 50]"
  77. :total="tableData.length"
  78. layout="total, sizes, prev, pager, next, jumper"
  79. @size-change="onSizeChange"
  80. @current-change="onPageChange"
  81. />
  82. <!-- 新增/修改弹窗 -->
  83. <el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px" destroy-on-close>
  84. <el-form ref="formRef" :model="form" :rules="formRules" label-width="140px">
  85. <el-form-item label="事件类别" prop="eventCategory">
  86. <el-select v-model="form.eventCategory" placeholder="请选择事件类别" style="width: 100%">
  87. <el-option v-for="d in dis_event_category" :key="d.value" :label="d.label" :value="d.value" />
  88. </el-select>
  89. </el-form-item>
  90. <el-form-item label="事件等级" prop="eventLevel">
  91. <el-select v-model="form.eventLevel" placeholder="请选择" style="width: 100%">
  92. <el-option v-for="d in dis_event_level" :key="d.value" :label="d.label" :value="d.value" />
  93. </el-select>
  94. </el-form-item>
  95. <el-form-item label="类别细分" prop="subCategory">
  96. <el-input v-model="form.subCategory" placeholder="请输入类别细分" />
  97. </el-form-item>
  98. <el-form-item label="适用场景" prop="scenario">
  99. <el-input v-model="form.scenario" placeholder="请输入适用场景" />
  100. </el-form-item>
  101. <el-form-item label="分级分类判断标准" prop="criteria">
  102. <el-input
  103. v-model="form.criteria"
  104. type="textarea"
  105. :rows="4"
  106. placeholder="填写具体判断规则、适用条件、划分依据"
  107. />
  108. </el-form-item>
  109. <el-form-item label="响应要求" prop="response">
  110. <el-input v-model="form.response" placeholder="如:30 分钟内启动预案" />
  111. </el-form-item>
  112. <el-form-item label="标准生效状态" prop="status">
  113. <el-radio-group v-model="form.status">
  114. <el-radio v-for="d in gradeStatusOptions" :key="d.value" :label="d.value">{{ d.label }}</el-radio>
  115. </el-radio-group>
  116. </el-form-item>
  117. <el-form-item label="备注说明">
  118. <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注说明" />
  119. </el-form-item>
  120. </el-form>
  121. <template #footer>
  122. <el-button @click="dialogVisible = false">取消</el-button>
  123. <el-button type="primary" @click="handleSubmit">保存提交</el-button>
  124. </template>
  125. </el-dialog>
  126. </div>
  127. </template>
  128. <script setup>
  129. import { ref, computed, onMounted } from 'vue'
  130. import { ElMessage, ElMessageBox } from 'element-plus'
  131. import { useDict } from '@/utils/dict'
  132. import { getEventGradePage, addEventGrade, updateEventGrade, deleteEventGrade, getEventGradeStatusOptions } from '@/api/dispatch'
  133. import { Search, Refresh } from '@element-plus/icons-vue'
  134. // ---------- 字典 ----------
  135. const { dis_event_category, dis_event_level } = useDict('dis_event_category', 'dis_event_level')
  136. function dictLabel(list, value) {
  137. if (value === null || value === undefined || value === '') return '-'
  138. const hit = (list || []).find(item => String(item.value) === String(value))
  139. return hit ? hit.label : value
  140. }
  141. // ---------- 下拉选项 ----------
  142. // 标准生效状态:GradeStatusEnum 0启用 1停用
  143. const gradeStatusOptions = ref([])
  144. async function loadStatusOptions() {
  145. try {
  146. const res = await getEventGradeStatusOptions()
  147. gradeStatusOptions.value = res.data || []
  148. } catch {
  149. gradeStatusOptions.value = []
  150. }
  151. }
  152. // 等级字典值 -> 标签样式(1 Ⅰ级紧急 2 Ⅱ级较大 3 Ⅲ级一般)
  153. const LEVEL_TAG = { 1: 'danger', 2: 'warning', 3: 'info' }
  154. function levelTagType(level) {
  155. return LEVEL_TAG[level] || 'info'
  156. }
  157. // ---------- 查询参数 ----------
  158. const queryParams = ref({ eventCategory: '', eventLevel: '', status: '', dateRange: null })
  159. // ---------- 数据 ----------
  160. const tableData = ref([])
  161. async function loadData() {
  162. try {
  163. const params = {}
  164. if (queryParams.value.eventCategory) params.eventCategory = queryParams.value.eventCategory
  165. if (queryParams.value.eventLevel) params.eventLevel = queryParams.value.eventLevel
  166. if (queryParams.value.status !== '' && queryParams.value.status !== null) params.status = queryParams.value.status
  167. if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
  168. params.startDate = queryParams.value.dateRange[0]
  169. params.endDate = queryParams.value.dateRange[1]
  170. }
  171. const res = await getEventGradePage(1, 999, params)
  172. tableData.value = res?.data?.records ?? res?.data ?? res ?? []
  173. } catch {
  174. ElMessage.error('加载数据失败')
  175. }
  176. }
  177. onMounted(() => { loadStatusOptions(); loadData() })
  178. // ---------- 分页 ----------
  179. const pagination = ref({ page: 1, size: 10 })
  180. const pageList = computed(() => {
  181. const start = (pagination.value.page - 1) * pagination.value.size
  182. return tableData.value.slice(start, start + pagination.value.size)
  183. })
  184. const onSizeChange = () => { pagination.value.page = 1 }
  185. const onPageChange = () => {}
  186. // ---------- 搜索 / 重置 ----------
  187. const handleSearch = () => { pagination.value.page = 1; loadData() }
  188. const handleReset = () => {
  189. queryParams.value = { eventCategory: '', eventLevel: '', status: '', dateRange: null }
  190. pagination.value.page = 1
  191. loadData()
  192. }
  193. // ---------- 多选 ----------
  194. const selectedRows = ref([])
  195. const handleSelectionChange = (rows) => { selectedRows.value = rows }
  196. // ---------- 弹窗 ----------
  197. const dialogVisible = ref(false)
  198. const dialogTitle = ref('新增')
  199. const formRef = ref(null)
  200. const defaultForm = () => ({
  201. id: null, eventCategory: '', eventLevel: '', subCategory: '', scenario: '', criteria: '', response: '', status: 0, remark: ''
  202. })
  203. const form = ref(defaultForm())
  204. const formRules = {
  205. eventCategory: [{ required: true, message: '请选择事件类别', trigger: 'change' }],
  206. eventLevel: [{ required: true, message: '请选择事件等级', trigger: 'change' }],
  207. scenario: [{ required: true, message: '请输入适用场景', trigger: 'blur' }],
  208. criteria: [{ required: true, message: '请填写判断标准', trigger: 'blur' }],
  209. status: [{ required: true, message: '请选择状态', trigger: 'change' }],
  210. }
  211. const handleAdd = () => {
  212. form.value = defaultForm()
  213. dialogTitle.value = '新增'
  214. dialogVisible.value = true
  215. }
  216. const handleEdit = (row) => {
  217. form.value = { ...defaultForm(), ...row }
  218. dialogTitle.value = '修改'
  219. dialogVisible.value = true
  220. }
  221. const handleSubmit = () => {
  222. formRef.value?.validate(async (valid) => {
  223. if (!valid) return
  224. try {
  225. if (form.value.id) {
  226. await updateEventGrade(form.value)
  227. } else {
  228. await addEventGrade(form.value)
  229. }
  230. dialogVisible.value = false
  231. ElMessage.success('保存成功')
  232. loadData()
  233. } catch {
  234. ElMessage.error('保存失败')
  235. }
  236. })
  237. }
  238. // ---------- 删除 ----------
  239. const handleDelete = (row) => {
  240. ElMessageBox.confirm('确认删除该条记录?', '提示', { type: 'warning' }).then(async () => {
  241. try {
  242. await deleteEventGrade(row.id)
  243. ElMessage.success('删除成功')
  244. loadData()
  245. } catch {
  246. ElMessage.error('删除失败')
  247. }
  248. }).catch(() => {})
  249. }
  250. const handleBatchDelete = () => {
  251. if (!selectedRows.value.length) return ElMessage.warning('请先选择记录')
  252. ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录?`, '提示', { type: 'warning' }).then(async () => {
  253. try {
  254. await Promise.all(selectedRows.value.map(i => deleteEventGrade(i.id)))
  255. ElMessage.success('删除成功')
  256. loadData()
  257. } catch {
  258. ElMessage.error('删除失败')
  259. }
  260. }).catch(() => {})
  261. }
  262. // ---------- 导出 ----------
  263. const handleExport = () => {
  264. if (!tableData.value.length) return ElMessage.warning('暂无数据可导出')
  265. const headers = ['标准编号', '事件类别', '事件等级', '类别细分', '适用场景', '判断标准', '响应要求', '状态', '备注']
  266. const rows = tableData.value.map(row => [
  267. row.gradeNo,
  268. dictLabel(dis_event_category.value, row.eventCategory),
  269. dictLabel(dis_event_level.value, row.eventLevel),
  270. row.subCategory,
  271. row.scenario,
  272. row.criteria,
  273. row.response,
  274. row.statusName,
  275. row.remark
  276. ])
  277. const csvContent = [
  278. '' + headers.join(','),
  279. ...rows.map(cells => cells.map(c => '"' + (c || '') + '"').join(','))
  280. ].join('\n')
  281. const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
  282. const link = document.createElement('a')
  283. link.href = URL.createObjectURL(blob)
  284. link.download = '应急事件分级_' + new Date().toISOString().slice(0, 10) + '.csv'
  285. link.click()
  286. URL.revokeObjectURL(link.href)
  287. ElMessage.success('导出成功')
  288. }
  289. </script>
  290. <style scoped lang="scss">
  291. .search-form {
  292. margin-bottom: 12px;
  293. :deep(.el-form-item) {
  294. margin-bottom: 8px;
  295. }
  296. }
  297. </style>