Эх сурвалжийг харах

feat(dispatch): 调度模块列表页与首页看板体验优化

- 查询栏:8 个列表页统一若依风格——条件加标题、补日期区间查询、
  搜索/重置按钮带图标(事件分级日期筛选配合后端新参数)
- 操作栏:8 个列表页统一 plain 彩色描边按钮(蓝主操作/橙跟进/绿解除/灰详情/红删除)
- 首页:处置中事件卡片按等级配色排序(紧急置顶呼吸灯);GIS 面板移除「进入调度」按钮;
  类型分布柱图 X 轴以字典 dis_event_type 为基准;等级占比环图图上直标数量与占比
- 预案编辑:模板库由抽屉改为页签(切页签自动加载)并加分页;分类树收窄为 160px;
  附件下载并入附件名称列(链接可点);编制单位改为部门下拉+手动输入;
  预案列表删除/查询参数适配日期数组
- 初判核实:修复核实完一条后再开弹窗整体飘红(rules 变更触发自动校验,
  加 validate-on-rule-change=false)
null 4 өдөр өмнө
parent
commit
9dd6b6fbc2

+ 53 - 21
src/views/subSystem/dispatch/disOnDuty/cphs.vue

@@ -1,20 +1,39 @@
 <template>
   <div class="app-container cphs-page">
-    <!-- 搜索区 -->
+    <!-- 搜索区(若依风格) -->
     <div class="filter-panel">
-      <div class="filter-form">
-        <el-select v-model="searchForm.eventCategory" placeholder="事件类别" clearable style="width:160px">
-          <el-option v-for="d in dis_event_category" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-select v-model="searchForm.eventLevel" placeholder="事件等级" clearable style="width:140px">
-          <el-option v-for="d in dis_event_level" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-select v-model="searchForm.status" placeholder="核实状态" clearable style="width:140px">
-          <el-option v-for="d in verifyStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-button type="primary" @click="handleSearch">搜索</el-button>
-        <el-button @click="handleReset">重置</el-button>
-      </div>
+      <el-form :inline="true" :model="searchForm" class="filter-form" label-width="80px">
+        <el-form-item label="事件类别">
+          <el-select v-model="searchForm.eventCategory" placeholder="请选择事件类别" clearable filterable style="width:160px">
+            <el-option v-for="d in dis_event_category" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件等级">
+          <el-select v-model="searchForm.eventLevel" placeholder="请选择事件等级" clearable style="width:140px">
+            <el-option v-for="d in dis_event_level" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="核实状态">
+          <el-select v-model="searchForm.status" placeholder="请选择核实状态" clearable style="width:140px">
+            <el-option v-for="d in verifyStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="接报时间">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            style="width:240px"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+          <el-button :icon="Refresh" @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
     </div>
 
     <!-- 数据表格 -->
@@ -43,9 +62,9 @@
         </el-table-column>
         <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
-            <el-button v-if="row.status === 0" type="primary" link size="small" @click="handleVerify(row)">核实</el-button>
-            <el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button>
-            <el-button v-if="row.editable" type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
+            <el-button v-if="row.status === 0" type="primary" plain size="small" @click="handleVerify(row)">核实</el-button>
+            <el-button type="info" plain size="small" @click="handleDetail(row)">详情</el-button>
+            <el-button v-if="row.editable" type="danger" plain size="small" @click="handleDelete(row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -74,7 +93,9 @@
 
     <!-- 核实弹窗 -->
     <el-dialog v-model="verifyVisible" title="事件核实" width="600px" :close-on-click-modal="false">
-      <el-form :model="verifyForm" :rules="verifyRules" ref="verifyFormRef" label-width="100px">
+      <!-- validate-on-rule-change 关闭:verifyRules 为 computed,打开弹窗整体替换 verifyForm
+           会生成新 rules 对象触发 el-form 自动校验(且异步落在 clearValidate 之后),导致一打开就整体飘红 -->
+      <el-form :model="verifyForm" :rules="verifyRules" ref="verifyFormRef" label-width="100px" :validate-on-rule-change="false">
         <div class="section-title">事件基本信息</div>
         <el-row :gutter="20">
           <el-col :span="12">
@@ -244,6 +265,7 @@ import { bd09ToWgs84 } from '@/utils/coordTransform'
 import { gisPointStatusIcons } from '@/utils/gisMapColors'
 import GisMapCanvas from '@/components/GisMapCanvas.vue'
 import { getEventPage, deleteEventInfo, verifyEventInfo, getEventStatusOptions, getEventLogs, getUserOptions, getDeptOptions } from '@/api/dispatch'
+import { Search, Refresh } from '@element-plus/icons-vue'
 
 // ==================== 字典 ====================
 const { dis_event_source, dis_event_type, dis_event_category, dis_event_level } = useDict(
@@ -365,7 +387,7 @@ function toTimeline(logs) {
 const tableData = ref([])
 const total = ref(0)
 const loading = ref(false)
-const searchForm = ref({ eventCategory: '', eventLevel: '', status: 0 })
+const searchForm = ref({ eventCategory: '', eventLevel: '', status: 0, dateRange: null })
 const pagination = ref({ page: 1, pageSize: 10 })
 
 // ==================== 加载数据 ====================
@@ -377,6 +399,10 @@ async function loadData() {
     if (searchForm.value.eventCategory) params.eventCategory = searchForm.value.eventCategory
     if (searchForm.value.eventLevel) params.eventLevel = searchForm.value.eventLevel
     if (searchForm.value.status !== '' && searchForm.value.status !== null) params.status = searchForm.value.status
+    if (searchForm.value.dateRange && searchForm.value.dateRange.length === 2) {
+      params.startDate = searchForm.value.dateRange[0]
+      params.endDate = searchForm.value.dateRange[1]
+    }
     const res = await getEventPage(pagination.value.page, pagination.value.pageSize, params)
     tableData.value = res.data?.rows || res.data?.records || res.data || []
     total.value = res.data?.total || 0
@@ -519,7 +545,7 @@ function handleSearch() {
 }
 
 function handleReset() {
-  searchForm.value = { eventCategory: '', eventLevel: '', status: 0 }
+  searchForm.value = { eventCategory: '', eventLevel: '', status: 0, dateRange: null }
   pagination.value.page = 1
   loadData()
 }
@@ -662,10 +688,16 @@ async function handleDetail(row) {
 .filter-form {
   display: flex;
   align-items: center;
-  gap: 12px;
+  gap: 0 12px;
   flex-wrap: wrap;
 }
 
+/* 若依风格查询栏:内联表单项去默认外边距,间距统一交给 gap */
+.filter-form :deep(.el-form-item) {
+  margin-right: 0;
+  margin-bottom: 0;
+}
+
 .table-panel {
   background: #fff;
   border: 1px solid #dcdfe6;

+ 45 - 15
src/views/subSystem/dispatch/disOnDuty/gzcb.vue

@@ -1,16 +1,35 @@
 <template>
   <div class="app-container gzcb-page">
-    <!-- 搜索区 -->
+    <!-- 搜索区(若依风格) -->
     <div class="filter-panel">
-      <div class="filter-form">
-        <el-select v-model="searchForm.status" placeholder="事件状态" clearable style="width:140px">
-          <el-option v-for="d in urgeStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-input v-model="searchForm.handler" placeholder="责任人" clearable style="width:140px" />
-        <el-input v-model="searchForm.eventNo" placeholder="事件编号" clearable style="width:160px" />
-        <el-button type="primary" @click="handleSearch">搜索</el-button>
-        <el-button @click="handleReset">重置</el-button>
-      </div>
+      <el-form :inline="true" :model="searchForm" class="filter-form" label-width="80px">
+        <el-form-item label="事件状态">
+          <el-select v-model="searchForm.status" placeholder="请选择事件状态" clearable style="width:140px">
+            <el-option v-for="d in urgeStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="责任人">
+          <el-input v-model="searchForm.handler" placeholder="请输入责任人" clearable style="width:140px" @keyup.enter="handleSearch" />
+        </el-form-item>
+        <el-form-item label="事件编号">
+          <el-input v-model="searchForm.eventNo" placeholder="请输入事件编号" clearable style="width:160px" @keyup.enter="handleSearch" />
+        </el-form-item>
+        <el-form-item label="接报时间">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            style="width:240px"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+          <el-button :icon="Refresh" @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
     </div>
 
     <!-- 数据表格 -->
@@ -44,8 +63,8 @@
         </el-table-column>
         <el-table-column label="操作" width="160" fixed="right">
           <template #default="{ row }">
-            <el-button v-if="canUrge(row)" type="primary" link size="small" @click="handleUrge(row)">催办</el-button>
-            <el-button type="primary" link size="small" @click="handleViewRecords(row)">查看记录</el-button>
+            <el-button v-if="canUrge(row)" type="primary" plain size="small" @click="handleUrge(row)">催办</el-button>
+            <el-button type="info" plain size="small" @click="handleViewRecords(row)">查看记录</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -158,6 +177,7 @@ import { ref, nextTick, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
 import { useDict } from '@/utils/dict'
 import { getEventUrgePage, urgeEvent, getEventLogs, getEventStatusOptions } from '@/api/dispatch'
+import { Search, Refresh } from '@element-plus/icons-vue'
 
 // ==================== 字典 ====================
 const { dis_event_urge_method, dis_event_urge_result, dis_event_type } = useDict('dis_event_urge_method', 'dis_event_urge_result', 'dis_event_type')
@@ -200,7 +220,7 @@ function canUrge(row) {
 const tableData = ref([])
 const total = ref(0)
 const loading = ref(false)
-const searchForm = ref({ status: '', handler: '', eventNo: '' })
+const searchForm = ref({ status: '', handler: '', eventNo: '', dateRange: null })
 const pagination = ref({ page: 1, pageSize: 10 })
 
 // ==================== 加载数据 ====================
@@ -211,6 +231,10 @@ async function loadData() {
     if (searchForm.value.status !== '' && searchForm.value.status !== null) params.status = searchForm.value.status
     if (searchForm.value.handler) params.handler = searchForm.value.handler
     if (searchForm.value.eventNo) params.eventNo = searchForm.value.eventNo
+    if (searchForm.value.dateRange && searchForm.value.dateRange.length === 2) {
+      params.startTime = searchForm.value.dateRange[0]
+      params.endTime = searchForm.value.dateRange[1]
+    }
     const res = await getEventUrgePage(pagination.value.page, pagination.value.pageSize, params)
     tableData.value = res.data?.rows || res.data?.records || res.data || []
     total.value = res.data?.total || 0
@@ -233,7 +257,7 @@ function handleSearch() {
 }
 
 function handleReset() {
-  searchForm.value = { status: '', handler: '', eventNo: '' }
+  searchForm.value = { status: '', handler: '', eventNo: '', dateRange: null }
   pagination.value.page = 1
   loadData()
 }
@@ -353,10 +377,16 @@ async function handleViewRecords(row) {
 .filter-form {
   display: flex;
   align-items: center;
-  gap: 12px;
+  gap: 0 12px;
   flex-wrap: wrap;
 }
 
+/* 若依风格查询栏:内联表单项去默认外边距,间距统一交给 gap */
+.filter-form :deep(.el-form-item) {
+  margin-right: 0;
+  margin-bottom: 0;
+}
+
 .table-panel {
   background: #fff;
   border: 1px solid #dcdfe6;

+ 41 - 26
src/views/subSystem/dispatch/disOnDuty/gzjc.vue

@@ -1,26 +1,34 @@
 <template>
   <div class="app-container gzjc-page">
-    <!-- 搜索区 -->
+    <!-- 搜索区(若依风格) -->
     <div class="filter-panel">
-      <div class="filter-form">
-        <el-select v-model="searchForm.status" placeholder="事件状态" clearable style="width:140px">
-          <el-option v-for="d in trackStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-select v-model="searchForm.eventCategory" placeholder="事件类别" clearable style="width:140px">
-          <el-option v-for="d in dis_event_category" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-date-picker
-          v-model="searchForm.dateRange"
-          type="daterange"
-          range-separator="至"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          style="width:260px"
-          value-format="YYYY-MM-DD"
-        />
-        <el-button type="primary" @click="handleSearch">搜索</el-button>
-        <el-button @click="handleReset">重置</el-button>
-      </div>
+      <el-form :inline="true" :model="searchForm" class="filter-form" label-width="80px">
+        <el-form-item label="事件状态">
+          <el-select v-model="searchForm.status" placeholder="请选择事件状态" clearable style="width:140px">
+            <el-option v-for="d in trackStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件类别">
+          <el-select v-model="searchForm.eventCategory" placeholder="请选择事件类别" clearable filterable style="width:140px">
+            <el-option v-for="d in dis_event_category" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="接报时间">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            style="width:240px"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+          <el-button :icon="Refresh" @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
     </div>
 
     <!-- 数据表格 -->
@@ -58,11 +66,11 @@
         </el-table-column>
         <el-table-column label="操作" width="260" fixed="right">
           <template #default="{ row }">
-            <el-button v-if="row.status === 1" type="primary" link size="small" @click="handleStart(row)">开始处置</el-button>
-            <el-button v-if="row.status === 2" type="primary" link size="small" @click="handleTrack(row)">跟踪</el-button>
-            <el-button v-if="row.status === 2" type="primary" link size="small" @click="handleRelease(row)">解除</el-button>
-            <el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button>
-            <el-button v-if="row.editable" type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
+            <el-button v-if="row.status === 1" type="primary" plain size="small" @click="handleStart(row)">开始处置</el-button>
+            <el-button v-if="row.status === 2" type="warning" plain size="small" @click="handleTrack(row)">跟踪</el-button>
+            <el-button v-if="row.status === 2" type="success" plain size="small" @click="handleRelease(row)">解除</el-button>
+            <el-button type="info" plain size="small" @click="handleDetail(row)">详情</el-button>
+            <el-button v-if="row.editable" type="danger" plain size="small" @click="handleDelete(row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -339,6 +347,7 @@ import {
   getEventPage, deleteEventInfo, startDisposeEvent, trackEvent,
   releaseEventInfo, getEventLogs, getEventStatusOptions
 } from '@/api/dispatch'
+import { Search, Refresh } from '@element-plus/icons-vue'
 
 // ==================== 字典 ====================
 const { dis_event_source, dis_event_type, dis_event_category, dis_event_level, dis_event_trend, dis_event_release_reason, dis_yes_no } = useDict(
@@ -700,10 +709,16 @@ async function handleDetail(row) {
 .filter-form {
   display: flex;
   align-items: center;
-  gap: 12px;
+  gap: 0 12px;
   flex-wrap: wrap;
 }
 
+/* 若依风格查询栏:内联表单项去默认外边距,间距统一交给 gap */
+.filter-form :deep(.el-form-item) {
+  margin-right: 0;
+  margin-bottom: 0;
+}
+
 .table-panel {
   background: #fff;
   border: 1px solid #dcdfe6;

+ 39 - 24
src/views/subSystem/dispatch/disOnDuty/jbgl.vue

@@ -1,26 +1,34 @@
 <template>
   <div class="app-container jbgl-page">
-    <!-- 搜索区 -->
+    <!-- 搜索区(若依风格:条件带标题 + 日期区间 + 图标按钮) -->
     <div class="filter-panel">
-      <div class="filter-form">
-        <el-select v-model="searchForm.eventSource" placeholder="事件来源" clearable style="width:160px">
-          <el-option v-for="d in dis_event_source" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-date-picker
-          v-model="searchForm.dateRange"
-          type="daterange"
-          range-separator="至"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          style="width:260px"
-          value-format="YYYY-MM-DD"
-        />
-        <el-select v-model="searchForm.status" placeholder="事件状态" clearable style="width:140px">
-          <el-option v-for="d in eventStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-button type="primary" @click="handleSearch">搜索</el-button>
-        <el-button @click="handleReset">重置</el-button>
-      </div>
+      <el-form :inline="true" :model="searchForm" class="filter-form" label-width="80px">
+        <el-form-item label="事件来源">
+          <el-select v-model="searchForm.eventSource" placeholder="请选择事件来源" clearable filterable style="width:160px">
+            <el-option v-for="d in dis_event_source" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件状态">
+          <el-select v-model="searchForm.status" placeholder="请选择事件状态" clearable style="width:140px">
+            <el-option v-for="d in eventStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="接报时间">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            style="width:240px"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+          <el-button :icon="Refresh" @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
       <div class="filter-actions">
         <el-button type="primary" @click="handleAdd">新增接报</el-button>
       </div>
@@ -47,9 +55,9 @@
         </el-table-column>
         <el-table-column label="操作" width="220" fixed="right">
           <template #default="{ row }">
-            <el-button v-if="row.editable" type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
-            <el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button>
-            <el-button v-if="row.editable" type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
+            <el-button v-if="row.editable" type="primary" plain size="small" @click="handleEdit(row)">编辑</el-button>
+            <el-button type="info" plain size="small" @click="handleDetail(row)">详情</el-button>
+            <el-button v-if="row.editable" type="danger" plain size="small" @click="handleDelete(row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -194,6 +202,7 @@ import { useDict } from '@/utils/dict'
 import useUserStore from '@/store/modules/user'
 import { bd09ToWgs84 } from '@/utils/coordTransform'
 import { getEventPage, saveEventReport, updateEventInfo, deleteEventInfo, getEventStatusOptions, getEventLogs } from '@/api/dispatch'
+import { Search, Refresh } from '@element-plus/icons-vue'
 
 // ==================== 字典 ====================
 const { dis_event_source, dis_event_channel, dis_event_type } = useDict('dis_event_source', 'dis_event_channel', 'dis_event_type')
@@ -473,10 +482,16 @@ async function handleDelete(row) {
 .filter-form {
   display: flex;
   align-items: center;
-  gap: 12px;
+  gap: 0 12px;
   flex-wrap: wrap;
 }
 
+/* 若依风格查询栏:内联表单项去默认外边距,间距统一交给 gap */
+.filter-form :deep(.el-form-item) {
+  margin-right: 0;
+  margin-bottom: 0;
+}
+
 .table-panel {
   background: #fff;
   border: 1px solid #dcdfe6;

+ 49 - 20
src/views/subSystem/dispatch/disOnDuty/zjbs.vue

@@ -1,18 +1,37 @@
 <template>
   <div class="app-container zjbs-page">
-    <!-- 搜索区 -->
+    <!-- 搜索区(若依风格) -->
     <div class="filter-panel">
-      <div class="filter-form">
-        <el-input v-model="searchForm.eventNo" placeholder="事件编号" clearable style="width:160px" />
-        <el-select v-model="searchForm.status" placeholder="事件状态" clearable style="width:140px">
-          <el-option v-for="d in transferStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
-        </el-select>
-        <el-select v-model="searchForm.department" placeholder="责任单位" clearable style="width:160px">
-          <el-option v-for="d in dis_transfer_dept" :key="d.value" :label="d.label" :value="d.label" />
-        </el-select>
-        <el-button type="primary" @click="handleSearch">搜索</el-button>
-        <el-button @click="handleReset">重置</el-button>
-      </div>
+      <el-form :inline="true" :model="searchForm" class="filter-form" label-width="80px">
+        <el-form-item label="事件编号">
+          <el-input v-model="searchForm.eventNo" placeholder="请输入事件编号" clearable style="width:160px" @keyup.enter="handleSearch" />
+        </el-form-item>
+        <el-form-item label="事件状态">
+          <el-select v-model="searchForm.status" placeholder="请选择事件状态" clearable style="width:140px">
+            <el-option v-for="d in transferStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="责任单位">
+          <el-select v-model="searchForm.department" placeholder="请选择责任单位" clearable filterable style="width:160px">
+            <el-option v-for="d in dis_transfer_dept" :key="d.value" :label="d.label" :value="d.label" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="接报时间">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            style="width:240px"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+          <el-button :icon="Refresh" @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
     </div>
 
     <!-- 数据表格 -->
@@ -57,10 +76,10 @@
         </el-table-column>
         <el-table-column label="操作" width="240" fixed="right">
           <template #default="{ row }">
-            <el-button v-if="row.status === 1" type="primary" link size="small" @click="handleTransfer(row)">转接</el-button>
-            <el-button v-if="canReport(row)" type="primary" link size="small" @click="handleReport(row)">报送</el-button>
-            <el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button>
-            <el-button v-if="row.editable" type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
+            <el-button v-if="row.status === 1" type="primary" plain size="small" @click="handleTransfer(row)">转接</el-button>
+            <el-button v-if="canReport(row)" type="warning" plain size="small" @click="handleReport(row)">报送</el-button>
+            <el-button type="info" plain size="small" @click="handleDetail(row)">详情</el-button>
+            <el-button v-if="row.editable" type="danger" plain size="small" @click="handleDelete(row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -231,7 +250,7 @@
 <script setup name="Zjbs">
 import { ref, nextTick, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { UploadFilled } from '@element-plus/icons-vue'
+import { UploadFilled, Search, Refresh } from '@element-plus/icons-vue'
 import { getToken } from '@/utils/auth'
 import { useDict } from '@/utils/dict'
 import { getEventPage, getEventDetail, getEventStatusOptions, deleteEventInfo, transferEvent, reportEvent } from '@/api/dispatch'
@@ -277,7 +296,7 @@ function levelTagType(level) {
 const tableData = ref([])
 const total = ref(0)
 const loading = ref(false)
-const searchForm = ref({ eventNo: '', status: '', department: '' })
+const searchForm = ref({ eventNo: '', status: '', department: '', dateRange: null })
 const pagination = ref({ page: 1, pageSize: 10 })
 
 // 下拉清空后可能是 '' / null / undefined,统一判定,避免拼出空查询条件
@@ -293,6 +312,10 @@ async function loadData() {
       params.statusList = [searchForm.value.status]
     }
     if (searchForm.value.department) params.department = searchForm.value.department
+    if (searchForm.value.dateRange && searchForm.value.dateRange.length === 2) {
+      params.startDate = searchForm.value.dateRange[0]
+      params.endDate = searchForm.value.dateRange[1]
+    }
     const res = await getEventPage(pagination.value.page, pagination.value.pageSize, params)
     tableData.value = res.data?.rows || res.data?.records || res.data || []
     total.value = res.data?.total || 0
@@ -315,7 +338,7 @@ function handleSearch() {
 }
 
 function handleReset() {
-  searchForm.value = { eventNo: '', status: '', department: '' }
+  searchForm.value = { eventNo: '', status: '', department: '', dateRange: null }
   pagination.value.page = 1
   loadData()
 }
@@ -574,10 +597,16 @@ async function handleDetail(row) {
 .filter-form {
   display: flex;
   align-items: center;
-  gap: 12px;
+  gap: 0 12px;
   flex-wrap: wrap;
 }
 
+/* 若依风格查询栏:内联表单项去默认外边距,间距统一交给 gap */
+.filter-form :deep(.el-form-item) {
+  margin-right: 0;
+  margin-bottom: 0;
+}
+
 .table-panel {
   background: #fff;
   border: 1px solid #dcdfe6;

+ 22 - 6
src/views/subSystem/dispatch/disPlanManage/DisEventGrade.vue

@@ -18,9 +18,20 @@
           <el-option v-for="d in gradeStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
         </el-select>
       </el-form-item>
+      <el-form-item label="创建时间">
+        <el-date-picker
+          v-model="queryParams.dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          style="width: 240px"
+          value-format="YYYY-MM-DD"
+        />
+      </el-form-item>
       <el-form-item>
-        <el-button type="primary" @click="handleSearch">搜索</el-button>
-        <el-button @click="handleReset">重置</el-button>
+        <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+        <el-button :icon="Refresh" @click="handleReset">重置</el-button>
       </el-form-item>
       <el-form-item style="float: right">
         <el-button type="success" @click="handleAdd">新增</el-button>
@@ -52,8 +63,8 @@
       </el-table-column>
       <el-table-column label="操作" width="140" align="center">
         <template #default="{ row }">
-          <el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
-          <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
+          <el-button type="primary" plain size="small" @click="handleEdit(row)">编辑</el-button>
+          <el-button type="danger" plain size="small" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -122,6 +133,7 @@ import { ref, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useDict } from '@/utils/dict'
 import { getEventGradePage, addEventGrade, updateEventGrade, deleteEventGrade, getEventGradeStatusOptions } from '@/api/dispatch'
+import { Search, Refresh } from '@element-plus/icons-vue'
 
 // ---------- 字典 ----------
 const { dis_event_category, dis_event_level } = useDict('dis_event_category', 'dis_event_level')
@@ -152,7 +164,7 @@ function levelTagType(level) {
 }
 
 // ---------- 查询参数 ----------
-const queryParams = ref({ eventCategory: '', eventLevel: '', status: '' })
+const queryParams = ref({ eventCategory: '', eventLevel: '', status: '', dateRange: null })
 
 // ---------- 数据 ----------
 const tableData = ref([])
@@ -163,6 +175,10 @@ async function loadData() {
     if (queryParams.value.eventCategory) params.eventCategory = queryParams.value.eventCategory
     if (queryParams.value.eventLevel) params.eventLevel = queryParams.value.eventLevel
     if (queryParams.value.status !== '' && queryParams.value.status !== null) params.status = queryParams.value.status
+    if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
+      params.startDate = queryParams.value.dateRange[0]
+      params.endDate = queryParams.value.dateRange[1]
+    }
     const res = await getEventGradePage(1, 999, params)
     tableData.value = res?.data?.records ?? res?.data ?? res ?? []
   } catch {
@@ -186,7 +202,7 @@ const onPageChange = () => {}
 // ---------- 搜索 / 重置 ----------
 const handleSearch = () => { pagination.value.page = 1; loadData() }
 const handleReset = () => {
-  queryParams.value = { eventCategory: '', eventLevel: '', status: '' }
+  queryParams.value = { eventCategory: '', eventLevel: '', status: '', dateRange: null }
   pagination.value.page = 1
   loadData()
 }

+ 22 - 6
src/views/subSystem/dispatch/disPlanManage/DisPlanApproval.vue

@@ -11,9 +11,20 @@
           <el-option v-for="d in approveStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
         </el-select>
       </el-form-item>
+      <el-form-item label="创建时间">
+        <el-date-picker
+          v-model="queryParams.dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          style="width: 240px"
+          value-format="YYYY-MM-DD"
+        />
+      </el-form-item>
       <el-form-item>
-        <el-button type="primary" @click="handleSearch">搜索</el-button>
-        <el-button @click="handleReset">重置</el-button>
+        <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+        <el-button :icon="Refresh" @click="handleReset">重置</el-button>
       </el-form-item>
     </el-form>
 
@@ -41,8 +52,8 @@
       </el-table-column>
       <el-table-column label="操作" width="130" align="center">
         <template #default="{ row }">
-          <el-button link type="primary" @click="handleView(row)">查看</el-button>
-          <el-button v-if="row.status === 1" link type="primary" @click="handleApprove(row)">审批</el-button>
+          <el-button type="info" plain size="small" @click="handleView(row)">查看</el-button>
+          <el-button v-if="row.status === 1" type="primary" plain size="small" @click="handleApprove(row)">审批</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -170,6 +181,7 @@ import { ref, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useDict } from '@/utils/dict'
 import { getEventPlanPage, approvePlan, rejectPlan, getPlanLogs, getPlanStatusOptions } from '@/api/dispatch'
+import { Search, Refresh } from '@element-plus/icons-vue'
 
 // ---------- 字典 ----------
 const { dis_plan_type, dis_plan_level } = useDict('dis_plan_type', 'dis_plan_level')
@@ -213,7 +225,7 @@ async function loadStatusOptions() {
 const LOG_TL_TYPE = { 0: 'primary', 1: 'primary', 2: 'success', 3: 'warning', 4: 'primary' }
 
 // ---------- 查询 ----------
-const queryParams = ref({ planName: '', status: 1 })
+const queryParams = ref({ planName: '', status: 1, dateRange: null })
 
 // ---------- 数据 ----------
 const tableData = ref([])
@@ -223,6 +235,10 @@ async function loadData() {
     const params = {}
     if (queryParams.value.planName) params.planName = queryParams.value.planName
     if (queryParams.value.status !== '' && queryParams.value.status !== null) params.status = queryParams.value.status
+    if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
+      params.startDate = queryParams.value.dateRange[0]
+      params.endDate = queryParams.value.dateRange[1]
+    }
     const res = await getEventPlanPage(1, 999, params)
     tableData.value = res?.data?.records ?? res?.data ?? res ?? []
   } catch {
@@ -244,7 +260,7 @@ const onPageChange = () => {}
 // ---------- 搜索 / 重置 ----------
 const handleSearch = () => { pagination.value.page = 1; loadData() }
 const handleReset = () => {
-  queryParams.value = { planName: '', status: 1 }
+  queryParams.value = { planName: '', status: 1, dateRange: null }
   pagination.value.page = 1
   loadData()
 }

+ 164 - 64
src/views/subSystem/dispatch/disPlanManage/DisPlanEditing.vue

@@ -1,18 +1,31 @@
 <!-- 应急预案编辑 -->
 <template>
   <div class="app-container">
+    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+      <el-tab-pane label="预案列表" name="plans">
     <!-- 顶部操作区 -->
     <el-form :model="queryParams" :inline="true" class="search-form">
       <el-form-item label="预案名称">
-        <el-input v-model="queryParams.planName" placeholder="请输入预案名称" clearable style="width: 200px" />
+        <el-input v-model="queryParams.planName" placeholder="请输入预案名称" clearable style="width: 200px" @keyup.enter="handleSearch" />
+      </el-form-item>
+      <el-form-item label="创建时间">
+        <el-date-picker
+          v-model="queryParams.dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          style="width: 240px"
+          value-format="YYYY-MM-DD"
+        />
       </el-form-item>
       <el-form-item>
-        <el-button type="primary" @click="handleSearch">搜索</el-button>
-        <el-button @click="handleReset">重置</el-button>
+        <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
+        <el-button :icon="Refresh" @click="handleReset">重置</el-button>
       </el-form-item>
       <el-form-item style="float: right">
         <el-button type="success" @click="handleAdd">新增</el-button>
-        <el-button @click="openTemplateDrawer">模板库</el-button>
+        <el-button @click="openTemplateLibrary">模板库</el-button>
         <el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
         <el-button type="primary" @click="handleExport">导出</el-button>
         <el-button type="primary" @click="handleDownloadTemplate">下载模版</el-button>
@@ -21,7 +34,7 @@
 
     <!-- 主体:左侧树 + 右侧表格 -->
     <div class="main-layout">
-      <!-- 左侧预案分类树 -->
+      <!-- 左侧预案分类树(紧凑窄栏,不再挤压表格) -->
       <div class="left-tree">
         <div class="tree-title">预案分类</div>
         <el-tree
@@ -52,12 +65,19 @@
             </template>
           </el-table-column>
           <el-table-column prop="version" label="版本" width="90" />
-          <el-table-column prop="attachName" label="附件名称" min-width="200" show-overflow-tooltip />
-          <el-table-column label="附件下载" width="90" align="center">
+          <el-table-column prop="attachName" label="附件名称" min-width="200" show-overflow-tooltip>
             <template #default="{ row }">
-              <el-button link type="primary" @click="handleDownload(row)" :disabled="!row.attachPath">
-                <el-icon><Download /></el-icon>
-              </el-button>
+              <!-- 附件名称即下载入口:有附件时可点击下载,无附件显示 - -->
+              <el-link
+                v-if="row.attachName"
+                type="primary"
+                :underline="false"
+                :disabled="!row.attachPath"
+                @click="handleDownload(row)"
+              >
+                <el-icon style="margin-right:2px"><Download /></el-icon>{{ row.attachName }}
+              </el-link>
+              <span v-else>-</span>
             </template>
           </el-table-column>
           <el-table-column prop="publishDate" label="发布时间" width="120" align="center" />
@@ -67,12 +87,12 @@
               <el-tag :type="statusTagType(row.status)">{{ row.statusName }}</el-tag>
             </template>
           </el-table-column>
-          <el-table-column label="操作" width="200" align="center">
+          <el-table-column label="操作" width="270" align="center">
             <template #default="{ row }">
-              <el-button link type="primary" @click="handleEdit(row)">{{ isReviseRow(row) ? '修订' : '编辑' }}</el-button>
-              <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
-              <el-button v-if="row.submittable" link type="success" @click="handleSubmitApproval(row)">提交审核</el-button>
-              <el-button link type="primary" @click="handleVersions(row)">版本</el-button>
+              <el-button type="primary" plain size="small" @click="handleEdit(row)">{{ isReviseRow(row) ? '修订' : '编辑' }}</el-button>
+              <el-button type="danger" plain size="small" @click="handleDelete(row)">删除</el-button>
+              <el-button v-if="row.submittable" type="success" plain size="small" @click="handleSubmitApproval(row)">提交审核</el-button>
+              <el-button type="warning" plain size="small" @click="handleVersions(row)">版本</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -90,6 +110,44 @@
         />
       </div>
     </div>
+      </el-tab-pane>
+
+      <!-- 模板库:页签内嵌管理(替代原抽屉式页面) -->
+      <el-tab-pane label="预案模板库" name="templates">
+        <div style="margin-bottom:12px;display:flex;justify-content:space-between;align-items:center">
+          <span style="color:#909399;font-size:13px">模板用于「新建预案」时预填名称与章节正文</span>
+          <el-button type="primary" size="small" @click="handleTemplateAdd">新增模板</el-button>
+        </div>
+        <el-table :data="templatePageList" border stripe style="width:100%" v-loading="templateLoading">
+          <el-table-column type="index" :index="templateIndex" label="序号" width="60" align="center" />
+          <el-table-column prop="planName" label="模板名称" min-width="200" show-overflow-tooltip />
+          <el-table-column prop="eventType" label="类型" width="100" align="center" />
+          <el-table-column prop="eventLevel" label="等级" width="90" align="center" />
+          <el-table-column prop="responsibleUnit" label="责任单位" width="120">
+            <template #default="{ row }">{{ row.responsibleUnit || '-' }}</template>
+          </el-table-column>
+          <el-table-column prop="planSummary" label="概要" min-width="260" show-overflow-tooltip>
+            <template #default="{ row }">{{ row.planSummary || '-' }}</template>
+          </el-table-column>
+          <el-table-column label="操作" width="140" align="center">
+            <template #default="{ row }">
+              <el-button type="primary" plain size="small" @click="handleTemplateEdit(row)">编辑</el-button>
+              <el-button type="danger" plain size="small" @click="handleTemplateDelete(row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <!-- 模板库分页(列表为全量加载,前端切片分页) -->
+        <el-pagination
+          style="margin-top: 16px; justify-content: flex-end"
+          v-model:current-page="templatePagination.page"
+          v-model:page-size="templatePagination.pageSize"
+          :page-sizes="[10, 20, 50]"
+          :total="templateList.length"
+          layout="total, sizes, prev, pager, next, jumper"
+        />
+      </el-tab-pane>
+    </el-tabs>
 
     <!-- 新建预案:空白 or 选择模板 -->
     <el-dialog v-model="createPickVisible" title="新建预案" width="560px">
@@ -140,7 +198,23 @@
             <el-input v-model="form.version" disabled placeholder="由系统在审批通过后生成" />
           </el-form-item>
           <el-form-item label="编制单位" prop="compileUnit">
-            <el-input v-model="form.compileUnit" placeholder="请输入编制单位" />
+            <!-- 可从系统部门选择,也可手动输入(allow-create 允许创建新条目) -->
+            <el-select
+              v-model="form.compileUnit"
+              filterable
+              allow-create
+              default-first-option
+              clearable
+              placeholder="选择系统部门或手动输入"
+              style="width: 100%"
+            >
+              <el-option
+                v-for="d in deptOptions"
+                :key="d.deptId"
+                :label="d.deptName"
+                :value="d.deptName"
+              />
+            </el-select>
           </el-form-item>
           <el-form-item v-if="isRevising" label="修订说明" prop="changeNote">
             <el-input v-model="form.changeNote" type="textarea" :rows="3" placeholder="请填写本次修订说明(修订已通过预案必填)" />
@@ -184,32 +258,8 @@
       </template>
     </el-dialog>
 
-    <!-- 模板库管理抽屉 -->
-    <el-drawer v-model="templateDrawerVisible" title="预案模板库" size="760px" destroy-on-close>
-      <div style="margin-bottom:12px;display:flex;justify-content:space-between;align-items:center">
-        <span style="color:#909399;font-size:13px">模板用于「新建预案」时预填名称与章节正文</span>
-        <el-button type="primary" size="small" @click="handleTemplateAdd">新增模板</el-button>
-      </div>
-      <el-table :data="templateList" border stripe style="width:100%" v-loading="templateLoading">
-        <el-table-column prop="planName" label="模板名称" min-width="180" show-overflow-tooltip />
-        <el-table-column prop="eventType" label="类型" width="80" align="center" />
-        <el-table-column prop="eventLevel" label="等级" width="70" align="center" />
-        <el-table-column prop="responsibleUnit" label="责任单位" width="100">
-          <template #default="{ row }">{{ row.responsibleUnit || '-' }}</template>
-        </el-table-column>
-        <el-table-column prop="planSummary" label="概要" min-width="200" show-overflow-tooltip>
-          <template #default="{ row }">{{ row.planSummary || '-' }}</template>
-        </el-table-column>
-        <el-table-column label="操作" width="130" align="center" fixed="right">
-          <template #default="{ row }">
-            <el-button link type="primary" @click="handleTemplateEdit(row)">编辑</el-button>
-            <el-button link type="danger" @click="handleTemplateDelete(row)">删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-
-      <!-- 模板新增/编辑弹窗 -->
-      <el-dialog v-model="templateFormVisible" :title="templateForm.id ? '编辑模板' : '新增模板'" width="620px" append-to-body destroy-on-close>
+    <!-- 模板新增/编辑弹窗 -->
+    <el-dialog v-model="templateFormVisible" :title="templateForm.id ? '编辑模板' : '新增模板'" width="620px" destroy-on-close>
         <el-form ref="templateFormRef" :model="templateForm" :rules="templateRules" label-width="90px">
           <el-form-item label="模板名称" prop="planName">
             <el-input v-model="templateForm.planName" maxlength="60" placeholder="请输入模板名称" />
@@ -245,7 +295,6 @@
           <el-button type="primary" :loading="templateSaving" @click="handleTemplateSubmit">保存</el-button>
         </template>
       </el-dialog>
-    </el-drawer>
 
     <!-- 版本管理弹窗 -->
     <el-dialog v-model="versionVisible" title="版本管理" width="760px" destroy-on-close>
@@ -270,7 +319,7 @@
         </el-table-column>
         <el-table-column label="操作" width="100" align="center">
           <template #default="{ row }">
-            <el-button link type="primary" @click="handlePreviewVersion(row)">查看正文</el-button>
+            <el-button type="info" plain size="small" @click="handlePreviewVersion(row)">查看正文</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -295,14 +344,14 @@
 <script setup>
 import { ref, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Download } from '@element-plus/icons-vue'
+import { Download, Search, Refresh } from '@element-plus/icons-vue'
 import * as XLSX from 'xlsx'
 import { getToken } from '@/utils/auth'
 import { useDict } from '@/utils/dict'
 import {
   getEventPlanPage, getEventPlanById, saveEventPlan, deleteEventPlan,
   submitEventPlan, getPlanVersions, getPlanVersion, getPlanLibraryList,
-  savePlanLibrary, deletePlanLibrary
+  savePlanLibrary, deletePlanLibrary, getDeptOptions
 } from '@/api/dispatch'
 
 // ---------- 字典 ----------
@@ -339,17 +388,22 @@ const handleNodeClick = (node) => {
 }
 
 // ---------- 查询 ----------
-const queryParams = ref({ planName: '' })
+const queryParams = ref({ planName: '', dateRange: null })
 
 // ---------- 数据 ----------
 const tableData = ref([])
 
 async function loadData() {
   try {
-    const params = { ...queryParams.value }
+    // 手工拼参数:dateRange 是数组不能直接展开进查询参数
+    const params = { planName: queryParams.value.planName }
     if (selectedTreeNode.value) {
       params.planType = selectedTreeNode.value.id
     }
+    if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
+      params.startDate = queryParams.value.dateRange[0]
+      params.endDate = queryParams.value.dateRange[1]
+    }
     const res = await getEventPlanPage(1, 999, params)
     tableData.value = res?.data?.records ?? res?.data ?? res ?? []
   } catch {
@@ -357,7 +411,18 @@ async function loadData() {
   }
 }
 
-onMounted(() => { loadData() })
+onMounted(() => { loadData(); loadDeptOptions() })
+
+// ---------- 编制单位候选(系统部门,可手输) ----------
+const deptOptions = ref([])
+async function loadDeptOptions() {
+  try {
+    const res = await getDeptOptions()
+    deptOptions.value = res.data || []
+  } catch {
+    deptOptions.value = []
+  }
+}
 
 // ---------- 分页 ----------
 const pagination = ref({ page: 1, size: 10 })
@@ -372,6 +437,7 @@ const onPageChange = () => {}
 const handleSearch = () => { pagination.value.page = 1; loadData() }
 const handleReset = () => {
   queryParams.value.planName = ''
+  queryParams.value.dateRange = null
   selectedTreeNode.value = null
   treeRef.value?.setCurrentKey(null)
   pagination.value.page = 1
@@ -423,8 +489,17 @@ function pickCreate(tpl) {
 }
 
 // ---------- 模板库管理 ----------
-const templateDrawerVisible = ref(false)
+// 模板库已改为页签内嵌页面(不再是抽屉):activeTab 切到 templates 并加载数据
+const activeTab = ref('plans')
 const templateList = ref([])
+// 模板库前端分页:列表一次全量加载,本地切片展示
+const templatePagination = ref({ page: 1, pageSize: 10 })
+const templatePageList = computed(() => {
+  const start = (templatePagination.value.page - 1) * templatePagination.value.pageSize
+  return templateList.value.slice(start, start + templatePagination.value.pageSize)
+})
+// 序号跨页连续(第2页从11开始)
+const templateIndex = (index) => (templatePagination.value.page - 1) * templatePagination.value.pageSize + index + 1
 const templateLoading = ref(false)
 const templateFormVisible = ref(false)
 const templateFormRef = ref(null)
@@ -434,17 +509,29 @@ const templateRules = {
   planName: [{ required: true, message: '请输入模板名称', trigger: 'blur' }]
 }
 
-// 打开抽屉时加载模板列表(此前按钮只开了抽屉没加载数据,导致首次打开“暂无数据”)
-function openTemplateDrawer() {
-  templateDrawerVisible.value = true
-  loadTemplatesForDrawer()
+// 切到模板库页签时加载模板列表(此前按钮只开了抽屉没加载数据,导致首次打开“暂无数据”)
+function openTemplateLibrary() {
+  activeTab.value = 'templates'
+  loadTemplateList()
+}
+
+// 直接点页签切换也要加载数据(不经「模板库」按钮时 openTemplateLibrary 不会触发)
+function handleTabChange(name) {
+  if (name === 'templates') {
+    loadTemplateList()
+  }
 }
 
-async function loadTemplatesForDrawer() {
+async function loadTemplateList() {
   templateLoading.value = true
   try {
     const res = await getPlanLibraryList()
     templateList.value = res?.data ?? []
+    // 数据刷新后若当前页超出范围,回到第 1 页(如删除后只剩 1 页)
+    const maxPage = Math.max(1, Math.ceil(templateList.value.length / templatePagination.value.pageSize))
+    if (templatePagination.value.page > maxPage) {
+      templatePagination.value.page = 1
+    }
   } catch {
     ElMessage.error('模板列表加载失败')
   } finally {
@@ -469,7 +556,7 @@ function handleTemplateDelete(row) {
     try {
       await deletePlanLibrary(row.id)
       ElMessage.success('删除成功')
-      loadTemplatesForDrawer()
+      loadTemplateList()
     } catch {
       ElMessage.error('删除失败')
     }
@@ -484,7 +571,7 @@ function handleTemplateSubmit() {
       await savePlanLibrary(templateForm.value)
       ElMessage.success('保存成功')
       templateFormVisible.value = false
-      loadTemplatesForDrawer()
+      loadTemplateList()
     } catch {
       ElMessage.error('保存失败')
     } finally {
@@ -753,20 +840,33 @@ async function handlePreviewVersion(row) {
 }
 
 .left-tree {
-  width: 25%;
-  min-width: 200px;
+  /* 固定窄栏:分类只有少数几项,25% 宽度过大会挤压右侧表格 */
+  width: 160px;
   border: 1px solid #ebeef5;
   border-radius: 4px;
-  padding: 12px;
+  padding: 10px 8px;
+  align-self: flex-start;
   flex-shrink: 0;
 
   .tree-title {
-    font-weight: bold;
-    font-size: 15px;
-    margin-bottom: 10px;
-    padding-bottom: 8px;
+    font-weight: 600;
+    font-size: 13px;
+    color: #303133;
+    margin-bottom: 6px;
+    padding-bottom: 6px;
     border-bottom: 1px solid #ebeef5;
   }
+
+  /* 紧凑树节点:小字号 + 收紧内边距 */
+  :deep(.el-tree-node__content) {
+    height: 28px;
+    font-size: 13px;
+    border-radius: 4px;
+  }
+
+  :deep(.el-tree-node__content:hover) {
+    background: #f5f7fa;
+  }
 }
 
 .right-table {

+ 127 - 44
src/views/subSystem/dispatch/dishome/disHome.vue

@@ -93,7 +93,6 @@
       <div class="panel gis-panel">
         <div class="panel-head">
           <span class="panel-title">GIS 实时态势图</span>
-          <el-button type="primary" size="small" @click="goDispatchPage">进入调度</el-button>
         </div>
         <div class="gis-map-wrap">
           <GisMapCanvas :points="mapPoints" :zoom="12" :enable-clustering="false" @error="gisError = $event" />
@@ -111,18 +110,21 @@
           <span class="panel-title">处置中事件</span>
         </div>
         <div class="dispose-list">
-          <div v-for="item in disposingEvents" :key="item.id" class="dispose-item" @click="goDispatchPage">
-            <div class="dispose-line1">
+          <!-- 卡片按事件等级配色:紧急红 / 较大黄 / 一般绿(与 GIS 图例一致),紧急置顶闪烁提醒 -->
+          <div
+            v-for="item in sortedDisposingEvents"
+            :key="item.id"
+            class="dispose-item"
+            :class="'level-' + (item.eventLevel || 3)"
+            @click="goDispatchPage"
+          >
+            <div class="dispose-top">
+              <span class="level-badge"><i class="level-dot"></i>{{ levelLabel(item.eventLevel) }}</span>
               <span class="dispose-no">{{ item.eventNo }}</span>
-              <el-tag :type="levelTagType(item.eventLevel)" size="small" effect="dark">{{ levelLabel(item.eventLevel) }}</el-tag>
-            </div>
-            <div class="dispose-line2">
-              <span class="dispose-title">{{ typeLabel(item.eventType) }}-{{ categoryLabel(item.eventCategory) }}</span>
-            </div>
-            <div class="dispose-line3">
-              <span class="dispose-desc">{{ item.description || '暂无描述' }}</span>
               <span class="dispose-status">{{ item.statusName }}</span>
             </div>
+            <div class="dispose-title">{{ typeLabel(item.eventType) }}-{{ categoryLabel(item.eventCategory) }}</div>
+            <div class="dispose-desc">{{ item.description || '暂无描述' }}</div>
           </div>
           <div v-if="!disposingEvents.length" class="empty-tip">当前暂无处置中的事件</div>
         </div>
@@ -150,7 +152,7 @@
 </template>
 
 <script setup name="DisHome">
-import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
+import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
 import { useRouter } from 'vue-router'
 import * as echarts from 'echarts'
 import { ElMessage } from 'element-plus'
@@ -182,9 +184,17 @@ const typeLabel = (v) => dictLabel(dis_event_type.value, v)
 const categoryLabel = (v) => dictLabel(dis_event_category.value, v)
 // 等级:1 Ⅰ级(紧急) 2 Ⅱ级(较大) 3 Ⅲ级(一般)
 const LEVEL_NAME = { 1: '紧急', 2: '较大', 3: '一般' }
-const LEVEL_TAG = { 1: 'danger', 2: 'warning', 3: 'info' }
 const levelLabel = (v) => LEVEL_NAME[v] || '一般'
-const levelTagType = (v) => LEVEL_TAG[v] || 'info'
+
+// 处置中事件按等级排序展示:紧急(1) > 较大(2) > 一般(3),同级按编号倒序(新的在前)
+const sortedDisposingEvents = computed(() =>
+  [...(board.value.disposingEvents || [])].sort((a, b) => {
+    const la = a.eventLevel || 3
+    const lb = b.eventLevel || 3
+    if (la !== lb) return la - lb
+    return String(b.eventNo || '').localeCompare(String(a.eventNo || ''))
+  })
+)
 
 // ==================== 数据 ====================
 const board = ref({})
@@ -286,12 +296,22 @@ function renderCharts() {
   })
 }
 
+// 字典异步加载完成后重绘类型柱图(X 轴依赖 dis_event_type)
+watch(dis_event_type, () => renderTypeChart())
+
 function renderTypeChart() {
   if (!typeChartRef.value) return
   if (!typeChart) typeChart = echarts.init(typeChartRef.value)
+  // X 轴以字典 dis_event_type 为基准:字典内的类型全部展示(无数据计 0),
+  // 后端返回的其他类型(字典外兜底)追加在末尾
   const stats = board.value.typeStats || []
-  const labels = stats.map(s => typeLabel(s.type))
-  const values = stats.map(s => Number(s.count) || 0)
+  const countByType = {}
+  stats.forEach(s => { countByType[s.type] = (countByType[s.type] || 0) + (Number(s.count) || 0) })
+  const dictTypes = (dis_event_type.value || []).map(d => String(d.value))
+  const extraTypes = Object.keys(countByType).filter(t => !dictTypes.includes(String(t)))
+  const types = [...dictTypes, ...extraTypes]
+  const labels = types.map(t => typeLabel(t) || t)
+  const values = types.map(t => countByType[t] || 0)
   typeChart.setOption({
     grid: { left: 40, right: 24, top: 30, bottom: 30 },
     tooltip: { trigger: 'axis', formatter: '{b}:{c} 起' },
@@ -327,10 +347,18 @@ function renderLevelChart() {
     legend: { orient: 'vertical', right: 10, top: 'center', itemWidth: 10, itemHeight: 10, textStyle: { color: '#606266' } },
     series: [{
       type: 'pie',
-      radius: ['52%', '76%'],
+      radius: ['38%', '58%'],
       center: ['38%', '50%'],
       avoidLabelOverlap: true,
-      label: { show: false },
+      // 图上直接标注名称+数量+占比,无需悬浮
+      label: {
+        show: true,
+        formatter: '{b}\n{c}起({d}%)',
+        color: '#606266',
+        fontSize: 12,
+        lineHeight: 17
+      },
+      labelLine: { show: true, length: 12, length2: 10, lineStyle: { color: '#c0c4cc' } },
       data
     }]
   })
@@ -664,52 +692,113 @@ onBeforeUnmount(() => {
 .dispose-list {
   height: 420px;
   overflow-y: auto;
+  padding: 4px 2px;
 }
 
+/* 等级驱动配色的卡片:左侧色条 + 浅色渐变底 + 悬停浮起 */
 .dispose-item {
-  padding: 10px 8px;
-  border-bottom: 1px solid #f0f2f5;
+  position: relative;
+  padding: 10px 12px 10px 16px;
+  border: 1px solid #ebeef5;
+  border-left-width: 4px;
+  border-radius: 8px;
+  margin-bottom: 8px;
   cursor: pointer;
-  border-radius: 6px;
-  transition: background 0.15s;
+  background: #fff;
+  transition: transform 0.2s, box-shadow 0.2s;
 
   &:hover {
-    background: #f0f6ff;
+    transform: translateX(2px);
+    box-shadow: 0 4px 12px rgba(31, 56, 88, 0.12);
   }
 }
 
-.dispose-line1 {
+/* 与 GIS 图例同色系:紧急红 / 较大黄 / 一般绿;紧急事件浅红渐变底突出 */
+.dispose-item.level-1 {
+  border-left-color: #e74c3c;
+  background: linear-gradient(90deg, #fef2f2, #ffffff 55%);
+}
+.dispose-item.level-2 {
+  border-left-color: #f1c40f;
+  background: linear-gradient(90deg, #fffbeb, #ffffff 55%);
+}
+.dispose-item.level-3 {
+  border-left-color: #2ecc71;
+  background: #ffffff;
+}
+
+.dispose-top {
   display: flex;
   align-items: center;
-  justify-content: space-between;
+  gap: 8px;
+}
+
+/* 等级徽章:紧急为实底红 + 呼吸灯圆点,其余浅底彩字 */
+.level-badge {
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 11px;
+  font-weight: 700;
+  line-height: 18px;
+  padding: 1px 8px;
+  border-radius: 10px;
+  flex-shrink: 0;
+}
+.level-dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background: currentColor;
+}
+.level-1 .level-badge {
+  color: #fff;
+  background: #e74c3c;
+}
+.level-1 .level-dot {
+  animation: levelPulse 1.2s ease-in-out infinite;
+}
+.level-2 .level-badge {
+  color: #b7791f;
+  background: #fdf3d8;
+}
+.level-3 .level-badge {
+  color: #1e9e57;
+  background: #e6f7ee;
+}
+@keyframes levelPulse {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.25; }
 }
 
 .dispose-no {
-  font-weight: 600;
-  color: #303133;
-  font-size: 13px;
+  flex: 1;
+  font-size: 12px;
+  color: #8a94a6;
+  font-family: Consolas, Menlo, monospace;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
-.dispose-line2 {
-  margin-top: 4px;
+.dispose-status {
+  font-size: 11px;
+  color: #2f6fe4;
+  background: #ecf3ff;
+  padding: 1px 8px;
+  border-radius: 10px;
+  flex-shrink: 0;
 }
 
 .dispose-title {
+  margin-top: 6px;
   font-size: 13px;
   color: #303133;
   font-weight: 600;
 }
 
-.dispose-line3 {
-  margin-top: 4px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 8px;
-}
-
 .dispose-desc {
-  flex: 1;
+  margin-top: 4px;
   font-size: 12px;
   color: #909399;
   white-space: nowrap;
@@ -717,12 +806,6 @@ onBeforeUnmount(() => {
   text-overflow: ellipsis;
 }
 
-.dispose-status {
-  font-size: 12px;
-  color: #2f6fe4;
-  flex-shrink: 0;
-}
-
 .dispose-foot {
   margin-top: 10px;
   font-size: 12px;