Browse Source

优化应急事件管理

zengy 3 giờ trước cách đây
mục cha
commit
06360bf85c

+ 1 - 0
memory/index.md

@@ -71,6 +71,7 @@ npm run dev       # 默认 http://localhost:80
 
 ## 会话记录
 
+- [2026-10-21:删除事件 department 字段及核实处置单位、调整事件状态编号](sessions/2026-10-21.md)
 - [2026-10-19:移动端故障权限及PC应急指挥与预案调整](sessions/2026-10-19.md)
 - [2026-10-16:预警统一详情、工单与预警选人及待办闭环](sessions/2026-10-16.md)
 - [2026-10-15:权限管理子系统、移动端工单权限、PC 登录页与门户高保真重构](sessions/2026-10-15.md)

+ 0 - 124
memory/long-term.md

@@ -1,124 +0,0 @@
-# 城市生命线长期记忆
-
-## 项目定位
-
-这是“城市地下管网及设施建设改造智能化系统项目”软件平台中“地下管网管控平台”的 Vue 3 前端,覆盖燃气、排水、供水、管网基础、告警、风险和应急调度等业务域。供水前端与 `E:\pipe-ner-server\pipe-network-service` 后端共同构成“供水管网安全运行监测系统”。
-
-## 技术架构
-
-- Vue 3 + Vite
-- Element Plus + Pinia
-- UnoCSS + SCSS
-- Axios 统一请求层,使用 JWT 鉴权
-- 百度地图 WebGL 与 Leaflet 并存
-- ECharts 用于监测、统计和大屏可视化
-- 业务路由由后端菜单和权限动态生成
-- 对应后端仓库为 `E:\pipe-ner-server`;管理后台基于 Spring Boot/RuoYi,网关路由由 Nacos 动态配置。
-
-## 当前功能成熟度
-
-- 基础台账、排水、应急调度的前端 API 接入相对完整。
-- 燃气、井盖、管网页面为真实接口与模拟数据混合。
-- 供水 19 个页面已按项目文档 5.3.2 命名完成第一阶段实现;五类设施 CRUD 复用既有页面,设备、运行监测、报警、统计和轻量 GIS 已接后端真实接口,后续按完整供水子系统验收。
-- 供水 T3 统计(Issue #7)与 T4 运行监测(Issue #8)已人工验收通过并在 Gogs 关闭(2026-09-05)。
-
-## 启动约定
-
-- 使用 npm,不依赖 Yarn。
-- 开发命令:`npm run dev`
-- 默认前端地址:`http://localhost:80`
-- 备用端口:`npm run dev -- --port 5173`
-- 开发代理:`/dev-api` → 本机 `8300/pipe` 后端
-- 正常登录和业务菜单需要对应后端服务运行。
-
-## 记忆库约定
-
-- 每次对话结束后更新当天的 `memory/sessions/YYYY-MM-DD.md`。
-- 稳定决策和长期有效事实同步到本文件。
-- 每累计 5 次会话或单日记录超过约 20 KB 时整理长期记忆。
-- 不记录密码、Token、API 密钥、地图密钥、内部账号或个人隐私。
-
-## 用户已确认决策
-
-- `DisPlanEditing.vue` 模板的“责任单位”统一展示为“编制单位”,预案编制单位使用纯文本手动输入,不再提供系统部门下拉;接口字段仍分别使用 `responsibleUnit`、`compileUnit`(2026-10-19)。
-- 预案正文和模板章节正文统一使用 sys_notice 的 wangEditor;独立预案附件上传、展示和数据库附件字段已移除,图片附件直接随正文 HTML 富文本上传(2026-10-19)。
-- 预案编辑、审批列表的操作按钮使用不换行布局和足够列宽,优先保持同一行显示(2026-10-19)。
-- 数字预案库 `dis_plan_library` 移除 `create_by` 登录名快照,创建人只保存 `create_user_id`;实体、模板保存、种子数据和升级脚本同步调整(2026-10-19)。
-- PC 应急值守的 `jbgl.vue`、`cphs.vue`、`gzjc.vue`、`zjbs.vue` 共用 `EventDetailDrawer.vue`,以接报管理的事件详情字段和布局为准;统一调用事件详情接口获取最新档案与日志(2026-10-19)。
-- 应急事件 `dis_event` 不再存储 `reporter` 名称快照,接报人统一由 `create_user_id` 关联用户昵称;PC 接报管理 `jbgl.vue` 移除管网类型的展示、输入和提交(2026-10-19)。
-- PC Portal 通知公告滚动栏的时间使用“今天/昨天/前天 HH:mm”,其他日期使用 `yyyy-MM-dd HH:mm`;详情发布时间固定 `yyyy-MM-dd HH:mm:ss`(2026-10-15)。
-- PC 门户的设备、今日预警、今日报警指标按全网统一口径统计,所有登录用户看到相同结果,不应用部门或角色数据范围;公告取启用状态的 `sys_notice`,点击公告展示详情。门户接口统一放在管理后端 `com.zksy.web.controller.system.PortalController`,不要求具体菜单权限但仍要求登录。登录页移动端下载二维码固定放在前端 `public/images/mobile-app-qr.png`(2026-10-15)。
-- 移动端专用接口统一放到后端 `zksy-admin` 的 `com.zksy.web.controller.mobile`,与 PC 共用现有业务 Service,数据权限也留在 Service 校验;不复制移动端业务流程。七类 Controller 的 URL 统一为 `/api/mobile/...`:工单 `/ops/workOrder`、巡检 `/ops/inspectTask`、设备 `/equipment-base`、故障通知 `/alarm`、公告 `/notice`、本人登录日志 `/monitor/logininfor/list`、通讯录 `/contacts`(均接在统一前缀后)。移动端调用同步更新,不保留旧移动端专用双路由;此决定取代同日初版保留旧 URL 的约定。登录、资料、通用上传及工单处置等两端共用接口仍保留原入口;报警不再单设 `MobileAlarmService`(2026-10-14)。
-- 移动端“我的-基本资料”性别仅提供男(0)、女(1)两个 radio 单选项,不再提供保密或下拉选择。历史保密(2)或未设置的资料显示为未选中,保存前需明确选择男或女,避免静默修改性别(2026-10-14)。
-- 移动端“日常-故障通知”通过报警自身 `alarm_data.device_code` 关联设备,不依赖工单字段;设备类型按当前用户部门自身/祖先 101/102/103 对应的直接父类型 1/2/3 加共享父类型 4 过滤,不扩展类型树;管理员同样按公司范围读取,客户端 `rootTypeId` 只能收窄所属公司范围。故障通知列表展示全部报警状态,首页“待处理”仅统计未审核(待核实)报警;报警等级筛选及待核实/确认报警/误报警 tab 保留。详情用“故障信息/处置全流程”左右页签,有 `alarm:review:audit` 权限时提供核实抽屉。状态文案、核实表单、附件审核和历史日志语义保持原约定(2026-10-19 更新)。
-- PC 与移动端报警业务统一展示“待核实/核实”:PC 共享列表、详情、提醒、研判、供水首页及台账导出同步;菜单旧标题“报警审核”由前端按报警组件映射为“报警核实”,兼容后端旧状态名,不修改数据库菜单、接口枚举、路由或权限。工单验收与延期仍使用审核文案(2026-10-14)。
-- 移动端照片预览按同一业务组连续切换,维修前、维修后、每条工单日志附件和巡检现场照片分别成组;工单详情无可执行操作时不展示底部提示栏(2026-10-14)。
-- 移动端“我的-登录日志”仅查询当前登录用户本人,管理员也不例外;服务端从登录态获取账号,按 `sys_logininfor.user_name` 精确匹配,不复用 PC 日志列表的账号模糊查询(2026-10-14)。
-- 工单仅在待派单(0)、待接单(1)时允许取消;移动端、PC 端展示和后端校验统一限制,保留既有取消权限与数据范围(2026-10-14)。
-- 移动端真实接口列表统一采用“首次进入加载 + 用户下拉刷新”,适合分页的业务列表再增加触底上拉分页;不因页面再次显示自动请求,不使用定时轮询。通讯录是明确的全量加载例外,不分页(2026-10-13)。
-- 移动端通讯录不限制部门范围,一次性返回全部状态正常、未删除的用户,并排除当前登录用户和超级管理员;移动端基于全量结果进行本地搜索(2026-10-13,取代部门范围及服务端分页方案)。
-- 移动端工单权限不依赖 PC 的 `ops:workOrder:query`。部门/公司设备范围只用于“待派单(0) + 当前用户拥有 `ops:workOrder:dispatch`”场景:无设备工单对所有有派单权限的用户可见、可派;有设备工单按用户部门自身/祖先 101、102、103 对应设备父类型 1/2/3 过滤,并共同包含共享类型 4。派单后不再按部门或设备公司过滤,工单管理按创建人、责任人、核验人控制可见,待办再按责任人/核验人与状态收窄;详情使用相同规则。流程权限统一为 `ops:workOrder:process`(接单/处置/提交验收/申请延期)和 `ops:workOrder:audit`(验收/延期审核),派单仍为 `ops:workOrder:dispatch`;派单候选责任人/核验人必须分别具有 process/audit 权限(2026-10-15,取代 2026-10-13 的公司范围全程过滤规则)。
-- `work_order.dept_id` 已废弃,工单归属和可见性不得再依赖该字段;PC `/api/ops/workOrder` 列表及状态统计对普通用户统一按 `create_by=当前用户 OR receive_user=当前用户 OR verify_user=当前用户` 过滤,超级管理员不受限制(2026-10-13)。
-- 工单处置仅允许“已接单(2)”和“延期中(6)”状态执行并进入“处理中(3)”;已经处于“处理中”的工单不得重复执行工单处置,应继续申请延期或提交验收(2026-10-13)。
-
-- 报警清单顶部展示蓝色“报警总数”、红色“监测异常”、绿色“监测已恢复”三张卡片;仅增加统计区,保留原查询、表格和分页。统计按当前查询范围的全部事件计数,监测恢复不等同于工单处置完成(2026-09-24)。
-
-- 报警审核、清单、提醒三个页面及其 `utils`、`styles` 统一放在 `src/views/subSystem/alarm/common`;详情、历史等共享组件继续保留在 `alarm` 根目录。后端菜单组件白名单、初装及增量 SQL 同步新路径,路由 path 与 query 不变(2026-09-24)。
-
-- 告警表 `alarm_data` 不再存储设备名称、设备类型、处理时间、处理人、处理备注及通用 `remark`(2026-10-10)。报警描述改为新增时按指标名称与触发阈值固化到 `alarm_content`,前端直接展示该字段;名称和类型从设备台账查询,处理状态仍使用 `alarm_status`,操作日志独立保留。
-
-- 采用仓库原生 `AGENTS.md + memory/` 方案。
-- 直接在当前项目目录维护记忆库,不创建额外 worktree。
-- 记忆库只使用 Markdown,不参与前端运行时,不新增 npm 依赖。
-- 前后端 Issue 均通过内网 Gogs 网页低频维护,不假定 CLI 或 API 集成。
-- Git 提交信息统一使用中文,说明改动目的与范围;一次提交只承载一个主题;用户未明确要求时不推送。
-- 工程技能采用默认 triage 标签:`needs-triage`、`needs-info`、`ready-for-agent`、`ready-for-human`、`wontfix`。
-- 前端领域文档采用单上下文布局;后端 `E:\pipe-ner-server` 采用按 Maven 模块划分的多上下文布局。
-
-## 供水前端约定
-
-- 供水动态菜单组件路径统一使用 `waterSupply/facility/*`、`waterSupply/device/*`、`waterSupply/monitoring/*`、`waterSupply/monitorAlarm/*`。
-- 业务菜单仍由 `/getRouters` 动态下发;前端组件入口存在不等于角色已经获得对应 `sys_menu` 权限。
-- GIS 第一阶段使用后端 GeoJSON 做分类、点线分布与详情联动,后续接入真实底图时保持接口和页面名称不变。
-- 供水报警与工单前端统一使用唯一接单责任人;无手机号不阻断派单。
-- 供水、排水、燃气、窨井共用统一工单业务流程,系统展示范围由关联设备类型过滤;前端优先复用既有 REST 接口,不因页面入口创建平行工单生命周期。
-- 工单系统归属以关联设备类型树稳定业务编码判定;`/api/work-order` 是跨系统规范 API,旧入口只做兼容适配;统一工单继续采用数据库状态机和统一日志语义。
-- 2026-09-28 #66:供水报警管理恢复供水专属只读分析页(报警流水、四级趋势、报警台账与分类占比),菜单仍沿用后端下发的路径,仅在前端按供水 rootTypeId 解析到独立页面;排水仍使用公共简化列表。统一报警 API 补充设备报警状态与后端推导的四态展示状态(含误报警),不恢复旧供水 ledger 接口。
-- 报警管理侧重只读态势与台账;四子系统报警审核共用 `src/views/subSystem/alarm/common/audit.vue`,`sys_menu.path` 均为 `alarmAudit`,菜单 `query.rootTypeId` 按设备类型树隔离,燃气再限定 `warning_code`。确认报警创建待派单工单但不改设备状态;工单后续操作仍在统一工单页。此约定取代早期“审核页承载工单全流程/误报解除”的设计。
-- 报警继续使用 `alarm_level`:1~4 为一级~四级报警,一级最严重;`audit_status` 独立于 `alarm_status`。本次数据库脚本不迁移历史数据,不引入 `source_key`;`alarm_log.log_id` 使用 Kingbase `int8` 和独立序列 `alarm_log_id_seq`。
-- 报警运营共用 `alarm/common/remind.vue`、`alarm/common/list.vue`,排水研判与 GIS 为 `drainage/alarm/analysis.vue`、`drainage/alarm/alarmGIS.vue`;GIS 菜单 path 为 `drainageAlarmGIS`,详情无周边环境页签。现有菜单 2139/2140/2141/2143/2117 精确更新,供水 root=1、排水 root=2;后端校验授权菜单后查根及后代,运营读取与审核授权分开。供水 GIS 最新改为复制排水页面至 `waterSupply/alarm/alarmGIS.vue`(含配套 SCSS),菜单 2120 的 path=`waterSupplyAlarmGIS`、query=`{"rootTypeId":1}`、perms=`alarm:operations:list`;旧地图页面、模型及专用地图/台账补全/详情/历史接口删除,首页地图取数也使用统一 `/api/alarm/map`。地图与曲线允许供水/排水,仍校验授权菜单及设备类型树;执行 `migration_water_supply_alarm_gis.sql` 后重新登录(2026-09-24 最新要求,取代保留旧供水 GIS 的决定)。
-- `alarm_data.alarm_no` 改由后端程序新增时生成 `BJyyyyMMddHHmmss` 加 4 位随机数(北京时间),覆盖请求带入的号码,普通更新不改号;不使用数据库赋号触发器/默认值/序列,保留唯一索引、不回填旧数据。管理端新增/演练及采集新增都赋号,采集复用与恢复不改号。相关列表首列展示报警单号,转工单创建日志使用该号;报警触发和采集自动恢复由 Java 在同事务记报警日志,不依赖自定义函数/触发器。研判仅使用真实采样,日/月均聚合原始有效值,不补零;没有数值数据源的指标明确提示无曲线(2026-09-24 用户最新调整,替代数据库生成及雪花 ID 约定)。
-- 报警确认只写一条审核日志,描述包含选填意见及“已转工单(实际工单号)”;关联工单和工单操作不再单独写 `alarm_log`,仍保留正常 `work_order_log`。人工日志使用 `sys_user.nick_name`;审核页与详情页共用 `AlarmHistory.vue`,按时间、同一时间按 ID 倒序,日期分组样式参考 `dispose.vue`,保留图片预览。按最新要求,`zksy-system` 与 `zk-api-service` 各自定义 `AlarmLogOperTypeEnum`(0 触发/1 确认/2 误报/3 处理),两份值保持一致,不新增共享模块,移除工单相关类型,不删除旧类型历史行(2026-09-24)。
-- 报警审核附件只支持 JPG/JPEG/PNG 图片(最多 5 张、单张 10 MB),前后端禁止 PDF;处置记录以缩略图展示,点击查看大图,可切换同条记录的多张图片。
-- 报警详情的监测状态、审核结果、处置状态采用紧凑横排“普通字重标签 + 小号浅色状态标记”,不使用灰底大卡片或加粗标签;状态文本仍来自接口,处置颜色复用工单字典(2026-09-24 截图样式要求)。
-- 供水首页保留报警态势/趋势/占比图,使用统一 `/api/alarm/dashboard`(rootTypeId=1、days默认7,后端范围校验),不引用已删除的 `getAlarmLedgerDashboard` 或旧 `/waterSupply/alarm/ledger/dashboard`。后端按全量授权数据聚合,仅趋势限制日期,不用分页明细推算总量(2026-09-24 修复)。
-- 报警提醒服务固定只查 `alarm_status=0`,页面不提供“报警清单”操作。报警清单时间条件叫“报警时间”,设备提示“请输入设备编码/名称”,移除审核结果筛选但保留表格结果列。GIS 曲线选项和研判下拉仅展示 `actual_value` 非空事件(0 有效);地图点位、报警历史不受此限制,研判通过 `actualValueOnly=true` 后端先过滤再分页,无实际值深链接不能重新加入下拉(2026-09-24)。
-- 排水 GIS 地图接口固定只查 `alarm_data.alarm_status=0`,前端不提供监测状态筛选,设备编码/名称和等级筛选使用左标签右控件;设备列表、地图点位、等级统计同口径,详情报警记录保留历史。统计中灰色“报警设备”位于数量前,去掉右上角视图文案及详情底部状态说明(2026-09-24)。
-- 排水 GIS 样式参考 `prd/outputs/alarm-gis.html`:顶部筛选/等级统计、左侧设备列表、大地图及覆盖式详情抽屉,打开详情不挤占地图。统计展示当前结果的设备数,不是报警事件总数;沿用红橙蓝绿等级色、真实 GisMapCanvas/API 和框架分页。详情仅监测曲线、报警记录、设备信息,数值快照明确标注为触发值,不使用原型合成数据或周边环境内容(2026-09-24)。
-- 报警处置历史日期标题:当天只显示“今天”,前一天只显示“昨天”,不附加日期;同年其他日期显示“M月D日”,跨年保留年份。按本地日历日判断,悬停保留完整日期,每条操作仍显示时分秒,不改变倒序规则(2026-09-24 最新要求)。
-- 报警实际值为空时不展示实际值/阈值行,0 仍正常展示;审核转工单的 `work_order.order_desc` 保存根据报警信息生成的故障描述,审核意见单独保存在报警日志中。
-- 报警审核意见选填,最多 500 字,确认报警和误报警均允许不填,审核结果仍必选。正常关闭审核弹窗或切换路由不再弹出放弃确认;上传、提交进行中仍保留退出保护。此约定取代早期意见必填和未保存退出需确认的交互(2026-09-24)。
-- 普通手动建单和隐患转工单取消同设备未办结工单限制;报警转工单仅按设备主键 + `alarm_data.warning_code` 保证一个未办结工单(0/1/2/3/4/6),5 已结案和 7 已取消可释放名额。不同编码不互斥,报警恢复不等于工单办结;查重在设备行锁保护的建单事务中完成,缺失报警编码时明确报错,不按显示名称替代。旧“同设备所有工单互斥”规则不再适用(2026-09-24)。
-- 日常巡检和设备保养工单由其他业务系统创建及首次派发;派给当前用户后可以在供水工单总览继续处置。
-- 设备报警状态、报警记录状态和工单处置状态必须分别展示,不得互相替代。
-- 管理端统一工单生命周期采用数据库与前端一致的 `0–7` 契约(0 待派单/1 待接单/2 已接单/3 处理中/4 待验收/5 已结案/6 延期中/7 已取消);终态仅已结案和已取消,延期中是处理旁路。验收驳回是“待验收(4)回退到处理中(3)”的动作而非状态。调用方必须按接口契约解释状态编码并以状态名展示。三仓共识见本仓 docs/domain/work-order-lifecycle.md。
-- 相关页面采用蓝白运行指挥台风格;一级至四级报警依次使用红、橙、蓝、绿,供水菜单全部配置图标。
-- 详细设计见 `docs/design/2026-08-22-water-supply-work-order-ui.md`。
-
-## 产品级决策
-
-- PC 与移动端详情的故障描述、预警内容使用普通字段内容的字体、字号和颜色,不加粗;保留首行独占布局与文本换行(2026-10-16)。
-- PC 与移动端详情布局统一:预警内容置于“预警信息”首行,人员区名为“处置相关人员”;工单信息按“故障信息 → 设备信息 → 处置相关人员”排列,故障描述独占故障信息首行,人员区展示处置人和核验人。工单责任人的产品文案统一为“处置人”,含列表、详情、派单选择、关联工单及操作提示;接口仍使用 receiveUser/receiveUserName,新增 verifyUserName 展示核验人昵称(2026-10-16)。
-- PC 与移动端工单、预警共用选人原则:设备类型直接父级 1/2/3 对应部门自身或祖先 101/102/103;工单无设备、两类业务设备父级为 4 时均不限制候选部门,有对应权限的公司外或无部门用户也可选。用户须启用且未删除,1/2/3 还要求部门有效;设备或类型关联损坏不得当作无设备放宽。工单责任人/核验人分别要求 `ops:workOrder:process`/`ops:workOrder:audit`,预警处置人/核验人分别要求 `warning:warning:handle`/`warning:warning:audit`,不复用用户管理的数据范围。当前账户仅在合格核验候选中默认选中,可改选;提交后端再次校验。候选查询须携带业务 ID 与人员角色,预警发布人仍默认创建人(2026-10-16)。
-- 预警管理、详情、归档及待办统一应用设备公司范围与参与人可见性;共享父类 4 的指定处置人/核验人可跨公司查看和办理本人预警,此例外不放宽其他草稿或非共享预警。拥有 publish 权限者额外可见公司范围内系统自动创建预警及所有 DRAFT 草稿。待办按 DRAFT+publish、本人处置+handle+PENDING/RECEIVED/PROCESSING、本人核验+audit+HANDLED 计算,不依赖待办表必须存在记录。移动端预警操作成功后返回列表允许定向刷新,普通再次显示仍不重复加载(2026-10-16)。
-- PC 预警详情统一使用 `lifeCompany/components/WarningDetailDrawer.vue` 和 `GET /warning/detail/full/{warningId}`;发布、待办、处置、查询、关联管理、首页/今日/历史概览及管网预警入口不得再各自维护详情抽屉。公共组件负责基础信息、关联事项/设备/报警、处置时间轴、督办、附件和电子存档,页面只保留通知已读、编辑、处置操作等入口特有逻辑。旧简版 `GET /warning/detail/{warningId}` 已移除(2026-10-16)。
-- 预警列表与详情查看权限统一为 `warning:warning:query`,不再使用 `warning:warning:list`;列表、待办、搜索、跟踪、归档和通知已读同步,现有详情、附件、流程与统计沿用 query。菜单迁移仅更新旧权限值,保留菜单 ID 和角色关联,处置 handle、核验 audit 独立不变(2026-10-15)。
-- 预警维修接收、开始和提交处置使用 `warning:warning:handle`;管理核验解除/退回使用独立的 `warning:warning:audit`,包括 `/clear`、`/resolve`、`/return`、`/misreport`。仍须 HANDLED 且为指定 reviewer(历史为空回退 publisher),权限不代替人员和状态限制。PC 待办、预警处置和管网预警入口统一为“核验”表单,必选“核查解除/退回办理”、必填核验意见;退回仍由原处置人办理。迁移脚本只新增菜单,不从处置权限自动继承核验权限(2026-10-15)。
-- 预警联动系统事件六状态统一颜色:DRAFT 草稿灰、PENDING 待接收黄、RECEIVED 已接收蓝、PROCESSING 处置中橙、HANDLED 已处置紫、CLOSED 已解除绿。所有事件状态标签和统计图表复用 `lifeCompany/warningStatus.js`,禁止各页面按数据位置自行分配状态色(2026-10-15)。
-- 预警事项关联为可选项,允许人工新增或采集生成的预警不关联事项直接发布;缺少匹配事项不能阻断草稿留存或发布(2026-09-29 用户明确确认)。
-- 预警清单和系统预警的等级下拉统一为一至四级,共用 `alert_level`;原因库和编码生成同步使用该字典。2026-09-29 开发库已补齐原缺少的四级,现有等级标签保留。
-- 正式产品定位为“城市地下管网及设施建设改造智能化系统项目”软件平台中的“地下管网管控平台”;供水交付单元为“供水管网安全运行监测系统”,由本前端与 `E:\pipe-ner-server\pipe-network-service` 后端共同构成。
-- KingbaseES 是生产和最终验收唯一数据库真相源;PostgreSQL 仅在无法避免时用于兼容性验证。
-- 供水整体开发按业务纵向切片推进,既有接口可复用时优先复用;真实接口失败不得回退到随机模拟数据。
-- 前后端保持独立仓库和独立构建,以版本化 REST 契约、真实登录态 E2E 和 KingbaseES 集成验收作为集成门禁。
-- 工单处置图片采用 MinIO + KingbaseES 附件元数据;现有工单图片 URL 字段继续兼容,新图片按处理前/处置后阶段展示。

+ 0 - 171
memory/sessions/2026-10-19.md

@@ -1,171 +0,0 @@
-# 2026-10-19:移动端故障数据范围对齐预警管理
-
-## 目标与结论
-
-- 移动端故障通知列表按预警管理范围对齐设备数据权限。
-- 列表和详情/日志/审核复用服务端 SQL 范围,客户端不能传入用户名作为授权依据。
-
-### 用户补充要求(后续调整)
-
-- 故障权限不再依赖 `work_order_id`,统一通过报警自身 `device_code` 匹配设备;用户所属公司设备类型及父类为 4 的共享窨井设备均可见。
-- 移动端故障通知展示全部报警状态;移动端首页“待处理”仅统计审核状态为“未审核/待核实”的报警,今日新增统计可见范围内当天全部报警。
-
-## 变更与验证
-
-- 后端 `AlarmOperationsMapper` 改为始终按 `alarm_data.device_code` 匹配设备;移动端范围包含公司设备类型直接子级及父类 4 子级,列表不再强制未处理状态;首页统计显式传入未审核状态。
-- 后续排查发现 Mapper 列表 SQL 对 `query.alarmStatus` 无条件执行等值筛选,而“全部”查询的该参数为空,导致 SQL `alarm_status = NULL` 返回 0 条。已改为仅显式传入状态时追加条件,并增加空状态/指定状态 SQL 断言。
-- `git diff --check` 通过,Mapper 调用签名已检索确认。
-- `mvn -pl zksy-system '-Dtest=AlarmMobileServiceTest,MobileAlarmMapperTest' test` 未能执行到测试:工程编译被既有 `SmsProperties.getSupervisionTemplateCode()` 缺失阻断(`EarlyWarningServiceImpl`、`WarningSupervisionNotifier`)。
-
-## 风险与后续
-
-- 需要修复或同步 SMS 配置模型后重跑目标测试;真实账号与共享窨井工单数据尚未端到端验证。
-
-# 2026-10-19:PC 应急指挥值守与预案管理调整
-
-## 目标与结论
-
-- 按 `prd/emergency/index.html` 调整 PC 应急指挥值守、应急预案分级/编辑/审批页面,不触及其他业务模块。
-- 接报人由当前登录用户确定,接报时间由服务端记录当前时间;移除来源渠道表单项,将联系电话文案改为上报人电话。
-- 核实结论前置;真实事件显示类别、等级、必选处置单位、处置人和核验人,处置人候选限定在处置单位及所有下级部门;不真实时隐藏其他业务字段但保留可选核实意见。
-- 已核实和处置中均可转接、报送,两个操作共用转接部门多选和备注表单。开始处置、跟踪共用备注和附件表单。
-- 分级页及预案列表/审批页字段筛选按原型收敛,保留预案模板、版本和审批查看流程。
-
-## 变更与验证
-
-- 前端改动:`jbgl.vue`、`cphs.vue`、`zjbs.vue`、`gzjc.vue`、`DisEventGrade.vue`、`DisPlanEditing.vue`、`DisPlanApproval.vue`。
-- 后端修改 `EventVerifyDTO` 和 `DisEventServiceImpl`:接报时间强制服务端生成;编辑缺省时保留旧时间/渠道;核验人可指定、未指定默认当前用户;真实事件校验处置单位有效,处置人可选且后端校验部门祖先关系。不真实核实意见可空并写入默认日志说明。
-- `npm run build:prod` 通过;`mvn -pl pipe-network-service/zksy-system -am -DskipTests compile` 通过;目标测试 `DispatchTimeFormatGuardTest` 1 项通过;两仓 `git diff --check` 通过。
-
-## 风险与后续
-
-- 仍需用实际登录用户及部门树做浏览器联调,尤其验证下级部门处置人候选、默认核验人回显及附件上传结果。
-- Maven 目标模块本次编译通过;没有覆盖数据库集成和完整应急事件服务状态流测试。
-
-## 本轮继续修改
-
-- 不真实核实时保留核实意见输入,意见改为可选;真实事件仍校验类别、等级和处置单位。
-- 新增接报表单不展示接报人/接报时间,服务端强制使用当前用户和当前时间落库;上报人电话必填。
-- `zjbs.vue` 报送层级不再展示附件;`gzcb.vue` 默认查询、筛选和催办按钮仅覆盖已核实/处置中,电话文案统一为处置人电话。
-- `cphs.vue` 列表列顺序调整为发生时间、初判时间、接报时间;接报与核实详情抽屉压缩间距,事件编号置左上、状态置右上。
-- 新增 `pipe-network-service/sql/dispatch_cleanup_unused_fields.sql`,清理迁移后的旧流程表和旧字段;脚本明确保留仍被代码引用的 `channel`、`handler_phone`、`attach_url`。
-
-## 本轮验证
-
-- `npm run build:prod` 通过(仅有既有 Sass 弃用提示)。
-- `mvn -pl pipe-network-service/zksy-system -am -DskipTests compile` 通过。
-- `DispatchTimeFormatGuardTest` 通过;两仓 `git diff --check` 通过。
-
-## 主键序列统一
-
-- 四张表统一使用显式序列:`dis_event_grade_id_seq`、`dis_plan_id_seq`、`dis_plan_log_id_seq`、`dis_plan_version_id_seq`。
-- 新建表定义已改为 `DEFAULT nextval(...)`;`dis_event_grade` 服务移除 `MAX(id)+1`,改由数据库序列发号。
-- 新增升级脚本 `pipe-network-service/sql/dispatch_named_id_sequences.sql`,负责移除旧 Identity、绑定固定序列并按现有最大 ID 校准。
-- 后端目标测试重新通过。
-
-## 预案库字段收敛
-
-- `dis_plan_library.event_type`、`event_level` 统一存储 `dis_plan_type`、`dis_plan_level` 字典编码,并在模板管理/预案选择页面使用对应字典显示标签。
-- `dis_plan_library.event_region`、`topic`、`plan_summary` 已从实体、匹配结果和前端交互移除;专题匹配接口同步删除,匹配改为类型/等级/责任单位三项。
-- `dispatch_refactor.sql` 增加历史中文标签到字典编码的转换、三列幂等删除、字段注释和类型等级索引;模板种子脚本同步改为新字段。
-- 前端 `npm run build:prod` 通过,后端增量编译和 `DispatchTimeFormatGuardTest` 曾通过;本轮 clean compile 暴露工程既有 `SmsProperties#getSupervisionTemplateCode()` 编译阻断(与本次预案库改动无关)。
-
-## 事件详情样式调整
-
-- 接报管理、初判核实、工作处置三个事件详情抽屉参考预警详情布局:顶部展示事件编号和状态,基础信息与处置相关人员使用分组描述表格,事件记录单独分组。
-- “事件记录(操作日志)”统一改为“事件记录”。
-- 前端生产构建与 `git diff --check` 通过(仅有既有 Sass 弃用提示)。
-
-## Kingbase 脚本修正
-
-- `dispatch_refactor.sql` 在存量 Identity 主键上提前执行 `ALTER SEQUENCE ... OWNED BY` 会触发 Kingbase“无法更改标识列的所有权”。已移除主脚本中的四处 OWNED BY,统一延后到先 DROP IDENTITY 的 `dispatch_named_id_sequences.sql`。
-- 发生该错误后需先在当前连接执行 `ROLLBACK;`,再整体重跑修正后的主脚本;不要继续在 aborted transaction 中执行后续语句。
-
-## 预案与事件分级字典统一
-
-- `DisPlanEditing.vue` 的预案类型、预案级别,以及模板新增/编辑表单和模板列表,统一使用 `useDict('dis_plan_type', 'dis_plan_level')` 对应 `sys_dict_data.dict_type`,模板列表和表单文案改为“预警类型”“预警级别”。
-- `DisEventGrade.vue` 的事件类别、事件等级下拉、列表展示和导出同步使用 `dis_plan_type`、`dis_plan_level`。
-- `npm run build:prod` 通过,仅保留既有 Sass 弃用提示;前端 `git diff --check` 通过。
-
-## dis_event_grade.event_type 评估
-
-- 前端 `DisEventGrade.vue` 已无 `eventType` 字段使用。
-- 后端 `DisEventGrade` 实体、`DisEventGradePageDTO`、`DisEventGradeServiceImpl` 的查询和更新仍引用 `event_type`。
-- 该列可删除,但必须同步清理 Java 字段/查询赋值、`dispatch_refactor.sql` 建表与加列/注释,并提供 Kingbase `DROP COLUMN IF EXISTS` 脚本;不能只删除数据库列。
-- `dis_event.event_type`、`dis_plan_library.event_type` 和预案匹配对象的 `eventType` 是其他业务字段,不能一并删除。
-
-## 清理 dis_event_grade.event_type
-
-- 已从后端 `DisEventGrade` 实体、分页 DTO、查询条件和更新赋值中移除 `event_type`。
-- 已从 `dispatch_refactor.sql` 的建表/补列/注释中移除,并加入 `DROP COLUMN IF EXISTS event_type`;合并 Kingbase 脚本同步加入幂等删除。
-- `mvn -pl pipe-network-service/zksy-system -am -DskipTests compile` 通过;数据库脚本仍需在目标 Kingbase 执行后才会实际删除存量列。
-
-## 预案类型取数复核
-
-- 用户要求 `DisPlanEditing.vue` 所有预案类型读取 `sys_dict_data` 中 `dict_type = 'dis_plan_type'` 的数据。
-- 已确认现有筛选、预案列表/表单、模板列表/表单、新建模板选择与导出均复用 `useDict('dis_plan_type', 'dis_plan_level')`,本轮无需修改业务代码。
-- 已核对调用链:`getDicts` 请求 `/system/dict/data/type/dis_plan_type`,后端查询 `app_user.sys_dict_data`,按字典类型和启用状态过滤,并按 `dict_sort` 排序;显示 `dict_label`、提交 `dict_value`。
-- Vue SFC 脚本与模板编译、`git diff --check` 均通过。未连接目标数据库验证实际字典内容。
-
-## 删除事件接报人冗余列与接报管理管网类型
-
-- 按用户要求移除 `dis_event.reporter`:删除实体与接报入参字段、接报保存写入逻辑;展示 VO 的接报人继续按 `create_user_id` 查询昵称,不再依赖名称快照。
-- `jbgl.vue` 移除管网类型的列表列、详情项、表单项、字典请求及新增/编辑提交字段。
-- 后端主建表脚本及已有清理/合并脚本同步删除 reporter,修正人员迁移脚本的过期说明与自检示例;新增仅删该列的 `pipe-network-service/sql/dispatch_drop_event_reporter.sql`,便于存量库单独升级。
-- 后端 `mvn -pl pipe-network-service/zksy-system -am -DskipTests compile -q` 通过,前端 `jbgl.vue` 脚本与模板编译通过。
-- 尚未执行目标 Kingbase 的删列脚本;需部署新版后端后执行。创建人缺失或用户已删除的事件不再有接报人名称快照可回退。
-
-## 接报表单字段顺序
-
-- `jbgl.vue` 新增/编辑共用表单将“事件描述”移至第一行,其他字段顺序、校验和提交逻辑保持原样。
-- Vue SFC 脚本/模板编译及 `git diff --check` 通过;无新增风险或后续迁移。
-
-## 工作处置列表字段调整
-
-- 按用户要求移除 `gzjc.vue` 列表“管网类型”列;详情展示及其字典引用保留。
-- Vue SFC 脚本/模板编译及 `git diff --check` 通过;无新增风险或后续迁移。
-- 后续按用户要求将列表 `handler` 列标题从“负责人”改为“处置人”,与详情文案一致;`git diff --check` 通过。
-
-## 四个应急值守页面共用事件详情
-
-- 按用户要求从 `jbgl.vue` 提取 `disOnDuty/EventDetailDrawer.vue`;`jbgl.vue`、`gzjc.vue`、`zjbs.vue`、补充指定的 `cphs.vue` 均接入该组件,字段与布局统一按接报管理详情展示。
-- 公共组件统一通过 `getEventDetail(id)` 加载最新事件档案及 `logs`,事件记录继续复用 `EventLogTimeline.vue`;删除各页面重复的抽屉、日志转换、附件详情辅助函数、详情样式和不再使用的字典引用。
-- 保留核实、开始处置/跟踪、解除、转接/报送表单与数据状态;初判核实 GIS 点位点击也进入公共详情。
-- 公共组件提供加载状态、失败重试,并忽略切换事件、关闭及卸载后的旧响应,避免不同事件数据串用。
-- 五个 Vue 文件脚本/模板/样式编译通过;模拟接口异步验证覆盖最新档案与日志、乱序响应、关闭/卸载、失败重试和空详情,全部通过;`npm run build:prod` 通过(既有 Sass 弃用提示)。
-- 未进行真实登录态浏览器联调,无数据库结构或后端接口改动。
-
-## 数字预案库移除 create_by
-
-- 按用户要求移除后端 `DisPlanLibrary.createBy` 及模板新增时的登录名赋值,保留 `create_user_id`。
-- 种子脚本改用按登录名查询用户 ID 写入 `create_user_id`;主结构脚本和序列/字典合并脚本同步幂等删列;人员 ID 迁移脚本移除对预案库旧 `create_by` 的回填引用并修正说明。
-- 新增后端 `pipe-network-service/sql/dispatch_drop_plan_library_create_by.sql`,供存量 Kingbase 单独删列并核验。
-- `mvn -pl pipe-network-service/zksy-admin -am -DskipTests compile -q` 通过;后端 `git diff --check` 通过;前端未使用该字段,无业务代码改动。
-- 尚未执行目标数据库脚本,需部署新版后端后执行;历史记录已存在的 `create_user_id` 保留,未关联 ID 的旧登录名在删列后不再保留。
-
-## 预案与模板编制单位表单调整
-
-- 按用户要求将 `DisPlanEditing.vue` 模板新增/编辑表单及列表的“责任单位”改为“编制单位”,输入提示同步调整。
-- 预案新增/编辑共用表单的编制单位改为普通文本输入,移除部门下拉、候选加载和该页不再使用的 API 导入;保留原字段与提交逻辑。
-- Vue SFC 脚本与模板编译通过,`git diff --check` 通过。
-- 无数据库或接口变更;未进行浏览器登录态联调。
-
-## 预案正文改用公告富文本编辑器
-
-- `DisPlanEditing.vue` 的预案正文和模板章节正文改用 sys_notice 使用的 `NoticeEditor.vue`(wangEditor),统一工具栏、HTML 内容格式和图片上传能力。
-- `NoticeEditor.vue` 增加可配置 `placeholder` 属性,公告原有默认提示保持不变;预案场景传入对应提示。
-- 相关 Vue SFC 编译通过,`npm run build:prod` 通过(仅既有 Sass 弃用警告),`git diff --check` 通过。
-- 未进行真实登录态浏览器联调;正文仍通过原 `content`、`planContent` 字段提交。
-
-## 预案附件并入富文本
-
-- `DisPlanEditing.vue` 移除独立 PDF/Word 附件上传、粘贴上传、版本附件下载和导出附件列;预案正文与模板正文继续通过 sys_notice 的 wangEditor 上传图片。
-- 预案审批页同步移除附件展示入口;正文仍使用 `content` / `planContent`。
-- 后端移除 `DisPlan`、`DisPlanVersion`、`PlanSaveDTO`、`PlanVersionVO` 的附件字段及服务层映射;主结构/迁移 SQL 不再创建或搬迁这些字段。
-- 新增 `pipe-network-service/sql/dispatch_drop_plan_attachments.sql`,并将历史 `dispatch_plan_version_attach.sql` 改为幂等删列脚本,清理 `dis_plan`、`dis_plan_version` 的存量附件列。
-- 前端两个预案页面 SFC 编译、后端 Maven 编译、两端 `git diff --check` 均通过;数据库脚本尚未在目标 KingbaseES 执行。
-
-## 预案列表操作按钮布局
-
-- `DisPlanEditing.vue`、`DisPlanApproval.vue` 的表格操作按钮改为 flex 不换行布局,取消按钮默认间距并调整操作列宽度;模板操作和审批操作同样处理。
-- 两个 Vue SFC 模板/样式编译通过,`git diff --check` 通过。

+ 56 - 0
memory/sessions/2026-10-21.md

@@ -0,0 +1,56 @@
+# 2026-10-21:删除事件 department 字段
+
+## 目标与结论
+
+- 按用户要求移除 `dis_event.department` 及前后端对应字段引用。
+- PC 删除转接报送及综合查询的责任单位筛选、综合查询/催办的责任单位列、公共事件详情及催办详情展示、催办接收部门的旧字段回退;核实表单不再回显或提交单位名称。
+- 最初保留核实 `deptId` 作为处置人筛选条件;该决定已被下文“核实操作移除处置单位”的用户要求取代。
+- 后端删除实体、核实/分页 DTO、事件/催办 VO 的字段及写入/查询/映射逻辑;移动端 Controller 删除按旧名称反查部门 ID 的兼容逻辑。
+- 移动端 `src/services/emergency.ts` 与 `src/pages/emergency/event-detail.vue` 同步移除类型字段、提交参数和详情展示。
+
+## 文件变更
+
+- PC:`disOnDuty/cphs.vue`、`zjbs.vue`、`gzcb.vue`、`EventDetailDrawer.vue`、`disAssess/DisInquiry.vue`。
+- 后端:`DisEvent`、`DisEventPageDTO`、`DisEventVO`、`EventUrgeRowVO`、`EventVerifyDTO`、`DisEventServiceImpl`、`MobileEmergencyController`,以及部门选择器相关说明。
+- 主结构脚本 `pipe-network-service/sql/dispatch_refactor.sql` 移除建列/补列/注释并加入幂等删列;新增独立升级脚本 `pipe-network-service/sql/dispatch_drop_event_department.sql`。
+
+## 验证与风险
+
+- 后端 `mvn -pl pipe-network-service/zksy-admin -am -DskipTests compile -q` 通过。
+- PC `npm run build:prod` 通过,构建日志位于工作区 `department-frontend-build.log`。
+- 移动端 `npm run type-check` 通过,三个仓库 `git diff --check` 通过。
+- 应急事件运行时代码检索已无 `department` 字段引用;SQL 中仅保留删列和删列核验引用。
+- 尚未连接目标 KingbaseES 执行脚本或验证真实登录态页面;部署新版前后端后需执行独立删列脚本。既有其他未提交修改和已暂存文档删除均保留。
+
+## 事件状态编号调整
+
+- 目标:按用户要求将 `EventStatusEnum` 调整为 3 待验收、4 已解除、5 不实事件,0/1/2 保持原意。
+- 后端调整枚举顺序与编码、实体/Service/DTO 状态说明;业务流转、统计和日志名称沿用枚举引用。
+- PC 同步核实页不实事件筛选、跟踪页状态范围及核验按钮、总结评估已解除筛选,以及所有事件标签映射;API 流程注释同步六态。
+- 移动端同步事件状态名称/枚举名/颜色映射、列表标签筛选值和详情核验入口。
+- 新增 `pipe-network-service/sql/migration_dispatch_event_status.sql`:旧值 3→4、4→5、5→3;同一事务更新 `dis_event.status` 及 `dis_event_log.status_from/status_to`,加锁并记录迁移标记以防重复轮换。
+- 主结构脚本同步六态说明;历史中文迁移继续生成旧数字编码,明确必须在其完成后执行状态升级脚本。空库新建不执行状态升级脚本,已升级库不得重跑历史迁移。
+- 验证:后端编译通过;`EventStatusContractTest` 3 项通过(待验收可核验且未办结、终态不可核验、列表编码与活动状态范围);PC `npm run build:prod`、移动端 `npm run type-check` 通过;三个仓库 `git diff --check` 通过。
+- 风险与后续:数据库脚本尚未实际执行;存量环境须停止旧应用写入后执行状态升级脚本,再同步部署前后端。未验证真实登录态页面;保留所有既有未提交变更和文档删除。
+
+## 核实操作移除处置单位
+
+- 目标与结论:用户明确要求 PC `cphs.vue` 和移动端 `event-detail.vue` 的核实操作也删除“处置单位”;纠正此前将其保留为处置人筛选条件的理解。
+- 两端删除处置单位控件、部门请求、部门树/下级部门筛选、必填校验、回显和 `deptId` 提交参数;处置人直接从有效系统用户中选择,用户候选标签保留所属部门名称以区分人员。
+- 移动端 `src/services/emergency.ts` 删除无其他调用的部门选项类型与请求;后端 `EventVerifyDTO` 删除 `deptId`,`DisEventServiceImpl` 删除部门必填和处置人所属部门校验,保留有效用户校验,并同步相关说明。
+- 验证:PC `npm run build:prod`、移动端 `npm run type-check`、后端管理模块依赖编译均通过;`EventVerifyWithoutDepartmentTest` 2 项通过,覆盖无所属部门的有效处置人可核实,以及无效处置人仍被拒绝;三个仓库 `git diff --check` 通过,两个核实页面已无处置单位/`deptId` 引用。
+- 风险与后续:本次修正无需新增数据库迁移;需同步部署 PC、移动端和后端后验证真实登录态操作。保留既有未提交变更及已暂存文档删除。
+
+## 核实选人按权限过滤
+
+- 用户确认处置人、核验人必须具备后续操作权限;候选接口拆分为 `/api/dispatch/event/handlerOptions`(`dispatch:event:handle`)和 `/api/dispatch/event/verifierOptions`(`dispatch:event:audit`),仅返回启用且未删除的用户。
+- PC、移动端核实表单分别使用两组候选列表;候选接口要求当前登录用户具备事件核实权限。
+- 后端 `DisEventServiceImpl` 在核实提交时再次检查处置人/核验人权限,管理员按系统管理员规则放行;无对应权限即拒绝,避免绕过前端提交无权限人员。
+- 验证:后端管理模块编译、5 项 `EventVerifyWithoutDepartmentTest`、PC 生产构建、移动端类型检查均通过。无需数据库迁移;需同步部署后端与两端前端。
+
+## 事件日志多附件地址统一处理
+
+- 结论:附件访问地址由后端统一转换,数据库继续保存相对路径,接口展示出口返回完整可访问 URL;PC/移动端只负责按逗号拆分并渲染,不再各自拼接 MinIO 前缀。
+- 修复 `DispatchAttachUrlSupport`:`toAccessibleUrl` 和 `toRelative` 均按逗号逐项处理,支持 `logs.attachUrl` 多图、混合历史完整 URL 与相对路径,避免把整串多图地址拼成一个错误 URL。
+- `api/mobile/emergency/events/{id}` 通过事件日志服务返回的 `logs.attachUrl` 将得到 `url1,url2,...`,每一项均可直接用于图片预览。
+- 验证:`DispatchAttachUrlSupportTest` 2 项通过(多图完整地址转换、混合地址相对化),后端管理模块编译通过,`git diff --check` 通过;无需数据库迁移。

BIN
public/logo.png


+ 16 - 12
src/api/dispatch.js

@@ -1,7 +1,7 @@
 import request from '@/utils/request'
 
 // ============ 应急事件统一档案(全生命周期闭环) ============
-// 接报登记(0 待核实) -> 初判核实(1 已核实 / 4 不实事件) -> 开始处置(2 处置中) -> 事件解除(3 已解除)
+// 接报登记(0 待核实) -> 初判核实(1 已核实 / 5 不实事件) -> 开始处置(2 处置中) -> 提交核验(3 待验收) -> 核验通过(4 已解除)
 // 转接 / 报送 / 处置跟踪 / 工作催办只写操作日志,不改变事件状态
 export function getEventPage(pageNum, pageSize, params) {
   return request({ url: '/api/dispatch/event/findByPage', method: 'get', params: { pageNum, pageSize, ...params } })
@@ -30,9 +30,13 @@ export function deleteEventInfo(id) {
 export function verifyEventInfo(id, data) {
   return request({ url: '/api/dispatch/event/verify/' + id, method: 'put', data })
 }
-// 责任处置人下拉项(系统用户:昵称 + 联系电话 + 所属部门),选中后自动带出联系电话
-export function getUserOptions() {
-  return request({ url: '/api/dispatch/event/userOptions', method: 'get' })
+// 核实处置人下拉项(具备处置权限的系统用户)
+export function getHandlerOptions() {
+  return request({ url: '/api/dispatch/event/handlerOptions', method: 'get' })
+}
+// 核实核验人下拉项(具备核验权限的系统用户)
+export function getVerifierOptions() {
+  return request({ url: '/api/dispatch/event/verifierOptions', method: 'get' })
 }
 // 责任单位候选(系统部门,前端据 parentId 建树)
 export function getDeptOptions() {
@@ -61,15 +65,18 @@ export function transferEvent(data) {
 export function reportEvent(data) {
   return request({ url: '/api/dispatch/event/report', method: 'post', data })
 }
-// 处置:开始处置 / 跟踪 / 解除
+// 处置:开始处置 / 跟踪 / 提交核验 / 最终核验
 export function startDisposeEvent(data) {
   return request({ url: '/api/dispatch/event/startDispose', method: 'post', data })
 }
 export function trackEvent(data) {
   return request({ url: '/api/dispatch/event/track', method: 'post', data })
 }
-export function releaseEventInfo(id, data) {
-  return request({ url: '/api/dispatch/event/release/' + id, method: 'post', data })
+export function submitVerifyEvent(data) {
+  return request({ url: '/api/dispatch/event/submitVerify', method: 'post', data })
+}
+export function auditEvent(id, data) {
+  return request({ url: '/api/dispatch/event/audit/' + id, method: 'post', data })
 }
 // 工作催办(不改状态)
 export function getEventUrgePage(pageNum, pageSize, params) {
@@ -124,11 +131,8 @@ export function deleteEventPlan(id) {
 export function submitEventPlan(id) {
   return request({ url: '/api/dispatch/plan/submit/' + id, method: 'post' })
 }
-export function approvePlan(id, data) {
-  return request({ url: '/api/dispatch/plan/approve/' + id, method: 'post', data })
-}
-export function rejectPlan(id, data) {
-  return request({ url: '/api/dispatch/plan/reject/' + id, method: 'post', data })
+export function reviewPlan(id, data) {
+  return request({ url: '/api/dispatch/plan/review/' + id, method: 'post', data })
 }
 export function getPlanVersions(planId) {
   return request({ url: '/api/dispatch/plan/versions/' + planId, method: 'get' })

BIN
src/assets/logo/logo.png


+ 1 - 1
src/layout/components/Navbar.vue

@@ -18,7 +18,7 @@
         <!--        </el-tooltip>-->
 
         <el-tooltip content="返回门户" effect="dark" placement="bottom">
-          <el-button class="portal-btn" type="success" round @click="goToPortal">返回门户</el-button>
+          <el-button class="portal-btn" type="primary" round @click="goToPortal">返回门户</el-button>
         </el-tooltip>
 
         <screenfull id="screenfull" class="right-menu-item hover-effect"/>

+ 1 - 1
src/layout/components/Sidebar/Logo.vue

@@ -15,7 +15,6 @@
 
 <script setup>
 import variables from '@/assets/styles/variables.module.scss'
-import logo from '@/assets/logo/logo.png'
 import useSettingsStore from '@/store/modules/settings'
 
 defineProps({
@@ -26,6 +25,7 @@ defineProps({
 })
 
 const title = import.meta.env.VITE_APP_TITLE;
+const logo = `${import.meta.env.BASE_URL}logo.png`;
 const settingsStore = useSettingsStore();
 const sideTheme = computed(() => settingsStore.sideTheme);
 </script>

+ 5 - 8
src/views/login.vue

@@ -7,11 +7,9 @@
       <section class="panel" aria-label="登录">
         <div class="brand-side">
           <div class="brand-top">
-            <div class="logo" aria-hidden="true">
-              <svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" /></svg>
-            </div>
-            <div class="brand-name">沅陵县城市地下管网管控平台</div>
-          </div>
+                    <img class="logo" src="/logo.png" alt="平台 Logo">
+                    <div class="brand-name">沅陵县城市地下管网管控平台</div>
+                  </div>
 
           <div class="hero">
             <h1>汇聚地下管网运行态势<br><span>统览城市生命线安全</span></h1>
@@ -61,7 +59,7 @@
               <label for="login-username">账号</label>
               <div class="input-box">
                 <svg viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" /></svg>
-                <input id="login-username" v-model.trim="loginForm.username" type="text" autocomplete="username" placeholder="请输入账号">
+                <input id="login-username" v-model.trim="loginForm.username" type="text" autocomplete="username" placeholder="请输入用户名/手机号">
               </div>
             </el-form-item>
             <el-form-item prop="password" class="field">
@@ -195,8 +193,7 @@ getCode()
 .panel { width: 100%; max-width: 1180px; height: min(700px, calc(100vh - 110px)); min-height: 590px; display: flex; overflow: hidden; background: rgba(255, 255, 255, .55); border: 1px solid rgba(255, 255, 255, .9); border-radius: 20px; box-shadow: 0 30px 80px rgba(42, 92, 170, .18); backdrop-filter: blur(10px); }
 .brand-side { flex: 1.15; min-width: 0; padding: 44px 48px 36px; display: flex; flex-direction: column; background: radial-gradient(circle at 80% 20%, rgba(90, 160, 255, .18), transparent 55%), linear-gradient(160deg, rgba(255, 255, 255, .35), rgba(231, 240, 255, .25)); }
 .brand-top { display: flex; align-items: center; gap: 11px; }
-.logo { width: 38px; height: 38px; flex: 0 0 auto; border-radius: 9px; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #3b8bff, #1e66e8); box-shadow: 0 8px 18px rgba(47, 124, 246, .35); }
-.logo svg { width: 21px; height: 21px; fill: #fff; }
+.logo { width: 38px; height: 38px; flex: 0 0 auto; border-radius: 9px; display: block; object-fit: contain; background: #fff; }
 .brand-name { font-size: 32px; font-weight: 800; line-height: 1.3; }
 .hero { margin-top: 72px; position: relative; z-index: 1; }
 .hero h1 { margin: 0; font-size: 17px; line-height: 1.6; font-weight: 700; }

+ 2 - 3
src/views/portal/index.vue

@@ -3,7 +3,7 @@
     <a class="skip-link" href="#portal-main">跳转到主要内容</a>
     <header class="top-bar">
       <div class="logo-area">
-        <div class="logo-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" /></svg></div>
+        <img class="logo-icon" src="/logo.png" alt="平台 Logo">
         <span class="logo-text">沅陵县城市地下管网管控平台</span>
       </div>
       <div class="top-right">
@@ -308,8 +308,7 @@ onUnmounted(() => {
 .skip-link:focus { top: 16px; }
 .top-bar { height: 56px; padding: 0 48px; position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--border); background: var(--panel); }
 .logo-area { display: flex; align-items: center; gap: 10px; }
-.logo-icon { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 7px; background: linear-gradient(135deg, #3b82f6, #06b6d4); }
-.logo-icon svg { width: 16px; height: 16px; fill: #fff; }
+.logo-icon { width: 30px; height: 30px; display: block; border-radius: 7px; object-fit: contain; background: #fff; }
 .logo-text { font-size: 15px; font-weight: 700; letter-spacing: 1px; }
 .top-right { display: flex; align-items: center; gap: 16px; }
 .datetime { color: var(--text-muted); font-size: 12px; font-variant-numeric: tabular-nums; }

+ 6 - 18
src/views/subSystem/dispatch/disAssess/DisInquiry.vue

@@ -28,12 +28,6 @@
                 <el-option v-for="d in dis_event_region" :key="d.value" :label="d.label" :value="d.value" />
               </el-select>
             </el-form-item>
-            <el-form-item label="责任部门">
-              <el-select v-model="queryForm.department" placeholder="请选择" clearable filterable allow-create
-                default-first-option style="width: 100%">
-                <el-option v-for="item in departmentOptions" :key="item" :label="item" :value="item" />
-              </el-select>
-            </el-form-item>
             <el-form-item label="损失情况">
               <el-select v-model="queryForm.lossRange" placeholder="请选择" clearable style="width: 100%">
                 <el-option v-for="item in lossRangeOptions" :key="item" :label="item" :value="item" />
@@ -93,7 +87,6 @@
             <el-table-column prop="eventRegion" label="地域" width="90">
               <template #default="{ row }">{{ dictLabel(dis_event_region, row.eventRegion) }}</template>
             </el-table-column>
-            <el-table-column prop="department" label="责任部门" width="110" />
             <el-table-column prop="status" label="状态" width="90">
               <template #default="{ row }">
                 <el-tag :type="statusTagType(row.status)" size="small">{{ row.statusName }}</el-tag>
@@ -168,7 +161,7 @@
 </template>
 
 <script setup>
-import { ref, reactive, computed } from 'vue'
+import { ref, reactive } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useDict } from '@/utils/dict'
 import useUserStore from '@/store/modules/user'
@@ -182,10 +175,10 @@ function currentOperatorName() {
 
 // ==================== 字典 ====================
 const {
-  dis_event_type, dis_event_level, dis_event_region, dis_event_source, dis_event_channel, dis_transfer_dept
+  dis_event_type, dis_event_level, dis_event_region, dis_event_source, dis_event_channel
 } = useDict(
   'dis_event_type', 'dis_event_level', 'dis_event_region',
-  'dis_event_source', 'dis_event_channel', 'dis_transfer_dept'
+  'dis_event_source', 'dis_event_channel'
 )
 
 function dictLabel(list, value) {
@@ -198,13 +191,10 @@ function dictLabel(list, value) {
 const LEVEL_TAG = { 1: 'danger', 2: 'warning', 3: 'info' }
 const levelTagType = (level) => LEVEL_TAG[level] || 'info'
 
-// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 已解除 4 不实事件)
-const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
+// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 待验收 4 已解除 5 不实事件)
+const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'warning', 5: 'info' }
 const statusTagType = (status) => STATUS_TAG[status] || 'info'
 
-// 责任部门存的是初判核实录入的责任单位名称,下拉只作候选,允许直接输入
-const departmentOptions = computed(() => (dis_transfer_dept.value || []).map(d => d.label))
-
 // 损失区间取值需与后端 applyLossRange 严格一致
 const lossRangeOptions = ['0-1万', '1-10万', '10-100万', '100万以上']
 
@@ -214,7 +204,6 @@ const queryForm = reactive({
   eventTime: '',
   eventLevel: '',
   eventRegion: '',
-  department: '',
   lossRange: ''
 })
 
@@ -334,7 +323,6 @@ const buildParams = () => {
   }
   if (queryForm.eventLevel) params.eventLevel = queryForm.eventLevel
   if (queryForm.eventRegion) params.eventRegion = queryForm.eventRegion
-  if (queryForm.department) params.department = queryForm.department
   if (queryForm.lossRange) params.lossRange = queryForm.lossRange
   return params
 }
@@ -370,7 +358,7 @@ const handleSizeChange = () => {
 // 重置
 const handleReset = () => {
   Object.assign(queryForm, {
-    eventType: '', eventTime: '', eventLevel: '', eventRegion: '', department: '', lossRange: ''
+    eventType: '', eventTime: '', eventLevel: '', eventRegion: '', lossRange: ''
   })
   pageNum.value = 1
   loadData()

+ 2 - 2
src/views/subSystem/dispatch/disAssess/DisSummary.vue

@@ -139,8 +139,8 @@ const levelTagType = (label) => {
   return (hit && hit.elTagType) || 'info'
 }
 
-// 可评估事件:事件状态 3 已解除(EventStatusEnum)
-const RELEASED_STATUS = 3
+// 可评估事件:事件状态 4 已解除(EventStatusEnum)
+const RELEASED_STATUS = 4
 
 // 已解除事件列表(可评估)
 const events = ref([])

+ 2 - 3
src/views/subSystem/dispatch/disOnDuty/EventDetailDrawer.vue

@@ -18,7 +18,7 @@
         <div class="section-heading"><span>事件信息</span></div>
         <el-descriptions :column="2" border>
           <el-descriptions-item label="事件描述" :span="2"><div class="detail-text">{{ currentRow.description || '-' }}</div></el-descriptions-item>
-          <el-descriptions-item label="事件来源">{{ dictLabel(dis_event_source, currentRow.eventSource) }}</el-descriptions-item>
+          <el-descriptions-item label="事件来源">{{ currentRow.eventSourceName || dictLabel(dis_event_source, currentRow.eventSource) }}</el-descriptions-item>
           <el-descriptions-item label="发生地点">{{ currentRow.location || '-' }}</el-descriptions-item>
           <el-descriptions-item label="发生时间">{{ currentRow.happenTime || '-' }}</el-descriptions-item>
           <el-descriptions-item label="接报时间">{{ currentRow.reportTime || '-' }}</el-descriptions-item>
@@ -31,7 +31,6 @@
         <div class="section-heading"><span>处置相关人员</span></div>
         <el-descriptions :column="2" border>
           <el-descriptions-item label="接报人">{{ currentRow.reporter || '-' }}</el-descriptions-item>
-          <el-descriptions-item label="处置单位">{{ currentRow.department || '-' }}</el-descriptions-item>
           <el-descriptions-item label="处置人">{{ currentRow.handler || '-' }}</el-descriptions-item>
           <el-descriptions-item label="核验人">{{ currentRow.verifier || '-' }}</el-descriptions-item>
         </el-descriptions>
@@ -62,7 +61,7 @@ const loading = ref(false)
 const loadFailed = ref(false)
 let requestId = 0
 
-const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
+const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'warning', 5: 'info' }
 function statusTagType(status) {
   return STATUS_TAG[status] || 'info'
 }

+ 26 - 92
src/views/subSystem/dispatch/disOnDuty/cphs.vue

@@ -133,34 +133,20 @@
             <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="处置单位" prop="deptId">
-          <el-tree-select
-            v-model="verifyForm.deptId"
-            :data="deptTree"
-            :props="{ label: 'deptName', children: 'children' }"
-            node-key="deptId"
-            check-strictly
-            filterable
-            placeholder="请选择处置单位(系统部门)"
-            style="width:100%"
-            @change="handleDeptChange"
-          />
-        </el-form-item>
         <el-form-item label="处置人" prop="handlerId">
           <el-select
             v-model="verifyForm.handlerId"
-            :placeholder="verifyForm.deptId ? '请选择处置人(该单位及下级部门人员)' : '请先选择处置单位'"
+            placeholder="请选择处置人"
             filterable
             style="width:100%"
-            :disabled="!verifyForm.deptId"
             @change="handleHandlerChange"
           >
-            <el-option v-for="u in filteredHandlerOptions" :key="u.userId" :label="u.deptName ? `${u.nickName}(${u.deptName})` : u.nickName" :value="u.userId" />
+            <el-option v-for="u in handlerOptions" :key="u.userId" :label="u.deptName ? `${u.nickName}(${u.deptName})` : u.nickName" :value="u.userId" />
           </el-select>
         </el-form-item>
         <el-form-item label="核验人" prop="verifierId">
           <el-select v-model="verifyForm.verifierId" placeholder="请选择核验人" filterable clearable style="width:100%" @change="handleVerifierChange">
-            <el-option v-for="u in userOptions" :key="u.userId" :label="u.deptName ? `${u.nickName}(${u.deptName})` : u.nickName" :value="u.userId" />
+            <el-option v-for="u in verifierOptions" :key="u.userId" :label="u.deptName ? `${u.nickName}(${u.deptName})` : u.nickName" :value="u.userId" />
           </el-select>
         </el-form-item>
         </template>
@@ -182,13 +168,12 @@
 import { ref, computed, nextTick, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useDict } from '@/utils/dict'
-import { handleTree } from '@/utils/ruoyi'
 import useUserStore from '@/store/modules/user'
 import { bd09ToWgs84 } from '@/utils/coordTransform'
 import { gisPointStatusIcons } from '@/utils/gisMapColors'
 import GisMapCanvas from '@/components/GisMapCanvas.vue'
 import EventDetailDrawer from './EventDetailDrawer.vue'
-import { getEventPage, deleteEventInfo, verifyEventInfo, getEventStatusOptions, getUserOptions, getDeptOptions } from '@/api/dispatch'
+import { getEventPage, deleteEventInfo, verifyEventInfo, getEventStatusOptions, getHandlerOptions, getVerifierOptions } from '@/api/dispatch'
 import { Search, Refresh } from '@element-plus/icons-vue'
 
 // ==================== 字典 ====================
@@ -198,13 +183,13 @@ const { dis_event_category, dis_event_level } = useDict(
 const userStore = useUserStore()
 const currentUserId = Number(userStore.id) || null
 
-// 核实环节可选状态:待核实(0) / 已核实(1) / 不实事件(4),其余状态不属于本页
+// 核实环节可选状态:待核实(0) / 已核实(1) / 不实事件(5),其余状态不属于本页
 const verifyStatusOptions = ref([])
 
 async function loadStatusOptions() {
   try {
     const res = await getEventStatusOptions()
-    verifyStatusOptions.value = (res.data || []).filter(item => [0, 1, 4].includes(item.value))
+    verifyStatusOptions.value = (res.data || []).filter(item => [0, 1, 5].includes(item.value))
   } catch {
     verifyStatusOptions.value = []
   }
@@ -222,86 +207,42 @@ function formatGeo(lng, lat) {
   return `${Number(lng).toFixed(6)}, ${Number(lat).toFixed(6)}`
 }
 
-// ==================== 处置单位 + 处置人 ====================
-// 处置人候选限定在所选单位及其全部下级部门。
-const userOptions = ref([])
-const deptList = ref([])
-const deptTree = computed(() => {
-  if (!deptList.value.length) return []
-  return handleTree(JSON.parse(JSON.stringify(deptList.value)), 'deptId')
-})
+// ==================== 处置人 / 核验人 ====================
+const handlerOptions = ref([])
+const verifierOptions = ref([])
 
 async function loadUserOptions() {
   try {
-    const res = await getUserOptions()
-    userOptions.value = res.data || []
+    const [handlerRes, verifierRes] = await Promise.all([getHandlerOptions(), getVerifierOptions()])
+    handlerOptions.value = handlerRes.data || []
+    verifierOptions.value = verifierRes.data || []
   } catch {
-    userOptions.value = []
+    handlerOptions.value = []
+    verifierOptions.value = []
   }
 }
 
-async function loadDeptOptions() {
-  try {
-    const res = await getDeptOptions()
-    deptList.value = res.data || []
-  } catch {
-    deptList.value = []
-  }
-}
-
-// 处置人候选包含所选处置单位及其所有下级部门。
-const filteredHandlerOptions = computed(() => {
-  if (!verifyForm.value.deptId) return []
-  const allowed = new Set([String(verifyForm.value.deptId)])
-  let changed = true
-  while (changed) {
-    changed = false
-    deptList.value.forEach(dept => {
-      if (allowed.has(String(dept.parentId)) && !allowed.has(String(dept.deptId))) {
-        allowed.add(String(dept.deptId))
-        changed = true
-      }
-    })
-  }
-  return userOptions.value.filter(u => allowed.has(String(u.deptId)))
-})
-
-function handleDeptChange() {
-  // 切换处置单位后,原处置人需重新选择。
-  verifyForm.value.handler = ''
-  verifyForm.value.handlerId = null
-}
-
 function handleHandlerChange(userId) {
-  verifyForm.value.handler = filteredHandlerOptions.value.find(item => String(item.userId) === String(userId))?.nickName || ''
+  verifyForm.value.handler = handlerOptions.value.find(item => String(item.userId) === String(userId))?.nickName || ''
 }
 
 function handleVerifierChange(userId) {
-  verifyForm.value.verifier = userOptions.value.find(item => String(item.userId) === String(userId))?.nickName || ''
+  verifyForm.value.verifier = verifierOptions.value.find(item => String(item.userId) === String(userId))?.nickName || ''
 }
 
 function handleVerifyResultChange(value) {
   if (value !== 1) return
   verifyForm.value.eventCategory = ''
   verifyForm.value.eventLevel = ''
-  verifyForm.value.deptId = null
-  verifyForm.value.department = ''
   verifyForm.value.handler = ''
   verifyForm.value.handlerId = null
   verifyForm.value.verifier = ''
   verifyForm.value.verifierId = null
-  nextTick(() => verifyFormRef.value?.clearValidate(['eventCategory', 'eventLevel', 'deptId', 'handlerId', 'verifierId']))
-}
-
-// 按部门名称反查部门ID(核实回显用:department 存的是部门中文名)
-function deptIdByName(name) {
-  if (!name) return null
-  const hit = deptList.value.find(d => d.deptName === name)
-  return hit ? hit.deptId : null
+  nextTick(() => verifyFormRef.value?.clearValidate(['eventCategory', 'eventLevel', 'handlerId', 'verifierId']))
 }
 
-// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 已解除 4 不实事件)
-const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
+// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 待验收 4 已解除 5 不实事件)
+const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'warning', 5: 'info' }
 function statusTagType(status) {
   return STATUS_TAG[status] || 'info'
 }
@@ -464,7 +405,6 @@ function handleMapPointClick(point) {
 onMounted(() => {
   loadStatusOptions()
   loadUserOptions()
-  loadDeptOptions()
   loadData()
 })
 
@@ -499,15 +439,16 @@ const currentVerifyId = ref(null)
 const verifyForm = ref({
   eventNo: '', location: '', longitude: null, latitude: null, reporter: '', description: '',
   eventCategory: '', eventLevel: '',
-  deptId: null, department: '', handler: '', handlerId: null, verifier: userStore.nickName || '', verifierId: currentUserId, verifyOpinion: '', verifyResult: 0
+  handler: '', handlerId: null, verifier: '', verifierId: null, verifyOpinion: '', verifyResult: 0
 })
-// 类别、等级、处置单位只在真实事件时展示并校验;不真实事件同样允许填写核实意见。
+// 类别、等级、处置人只在真实事件时展示并校验;不真实事件同样允许填写核实意见。
 const verifyRules = computed(() => {
   const isReal = verifyForm.value.verifyResult === 0
   return {
     eventCategory: isReal ? [{ required: true, message: '请选择事件类别', trigger: 'change' }] : [],
     eventLevel: isReal ? [{ required: true, message: '请选择事件等级', trigger: 'change' }] : [],
-    deptId: isReal ? [{ required: true, message: '请选择处置单位', trigger: 'change' }] : [],
+    handlerId: isReal ? [{ required: true, message: '请选择处置人', trigger: 'change' }] : [],
+    verifierId: isReal ? [{ required: true, message: '请选择有核验权限的核验人', trigger: 'change' }] : [],
     verifyOpinion: [],
     verifyResult: [{ required: true, message: '请选择核实结论', trigger: 'change' }]
   }
@@ -524,13 +465,10 @@ function handleVerify(row) {
     description: row.description,
     eventCategory: row.eventCategory || '',
     eventLevel: row.eventLevel || '',
-    // department 存的是部门中文名,回显时反查部门ID以恢复树形选择
-    deptId: deptIdByName(row.department),
-    department: row.department || '',
     handler: row.handler || '',
     handlerId: row.handlerId || null,
-    verifier: userStore.nickName || '',
-    verifierId: currentUserId,
+    verifier: '',
+    verifierId: null,
     verifyOpinion: row.verifyOpinion || '',
     verifyResult: 0
   }
@@ -542,20 +480,16 @@ function handleVerify(row) {
 async function confirmVerify() {
   try {
     await verifyFormRef.value.validateField(verifyForm.value.verifyResult === 0
-      ? ['verifyResult', 'eventCategory', 'eventLevel', 'deptId', 'verifyOpinion']
+      ? ['verifyResult', 'eventCategory', 'eventLevel', 'handlerId', 'verifierId', 'verifyOpinion']
       : ['verifyResult', 'verifyOpinion'])
   } catch {
     return
   }
   try {
-    // 提交口径不变:department 写部门中文名(工作催办、多条件筛选均按名称匹配)
-    const selectedDept = deptList.value.find(d => String(d.deptId) === String(verifyForm.value.deptId))
     await verifyEventInfo(currentVerifyId.value, {
       verifyResult: verifyForm.value.verifyResult,
       eventCategory: verifyForm.value.eventCategory,
       eventLevel: verifyForm.value.eventLevel,
-      department: selectedDept ? selectedDept.deptName : verifyForm.value.department,
-      deptId: verifyForm.value.deptId,
       handler: verifyForm.value.handler,
       handlerId: verifyForm.value.handlerId,
       verifyOpinion: verifyForm.value.verifyOpinion,

+ 4 - 10
src/views/subSystem/dispatch/disOnDuty/gzcb.vue

@@ -40,14 +40,11 @@
           <template #default="{ row }">{{ dictLabel(dis_event_type, row.eventType) }}</template>
         </el-table-column>
         <el-table-column prop="summary" label="事件摘要" min-width="160" show-overflow-tooltip />
-        <el-table-column prop="department" label="责任单位" width="110">
-          <template #default="{ row }">{{ row.department || '-' }}</template>
-        </el-table-column>
         <el-table-column prop="receiver" label="目标接收人" width="100">
           <template #default="{ row }">{{ row.receiver || row.handler || '-' }}</template>
         </el-table-column>
         <el-table-column prop="receiverDept" label="接收部门" width="110">
-          <template #default="{ row }">{{ row.receiverDept || row.department || '-' }}</template>
+          <template #default="{ row }">{{ row.receiverDept || '-' }}</template>
         </el-table-column>
         <el-table-column prop="elapsed" label="接报后已过" width="110">
           <template #default="{ row }">{{ row.elapsed || '-' }}</template>
@@ -144,9 +141,6 @@
             <el-col :span="12">
               <el-form-item label="事件编号">{{ currentRow.eventNo }}</el-form-item>
             </el-col>
-            <el-col :span="12">
-              <el-form-item label="责任单位">{{ currentRow.department || '-' }}</el-form-item>
-            </el-col>
           </el-row>
           <el-form-item label="事件摘要">
             <div class="desc-text">{{ currentRow.summary || '-' }}</div>
@@ -207,8 +201,8 @@ async function loadStatusOptions() {
   }
 }
 
-// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 已解除 4 不实事件)
-const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
+// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 待验收 4 已解除 5 不实事件)
+const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'warning', 5: 'info' }
 function statusTagType(status) {
   return STATUS_TAG[status] || 'info'
 }
@@ -295,7 +289,7 @@ function handleUrge(row) {
     elapsed: row.elapsed || '',
     summary: row.summary || '',
     receiver: row.receiver || row.handler || '',
-    receiverDept: row.receiverDept || row.department || '',
+    receiverDept: row.receiverDept || '',
     // 电话取事件责任处置人的电话(初判核实录入),无则回退最近催办记录里的号码
     receiverPhone: row.handlerPhone || row.receiverPhone || '',
     // 默认勾选「系统消息」:发送后给处置人推一条站内催办消息

+ 56 - 165
src/views/subSystem/dispatch/disOnDuty/gzjc.vue

@@ -64,9 +64,10 @@
         <el-table-column prop="createTime" label="创建时间" width="150" />
         <el-table-column label="操作" width="260" fixed="right">
           <template #default="{ row }">
-            <el-button v-if="row.status === 1" type="primary" plain size="small" @click="openOperation(row, 'start')" v-hasPermi="['dispatch:event:dispose']">开始处置</el-button>
-            <el-button v-if="row.status === 2" type="warning" plain size="small" @click="openOperation(row, 'track')" v-hasPermi="['dispatch:event:track']">跟踪</el-button>
-            <el-button v-if="row.status === 2" type="success" plain size="small" @click="handleRelease(row)" v-hasPermi="['dispatch:event:release']">解除</el-button>
+            <el-button v-if="row.status === 1 && isHandler(row)" type="primary" plain size="small" @click="openOperation(row, 'start')" v-hasPermi="['dispatch:event:handle']">开始处置</el-button>
+            <el-button v-if="row.status === 2 && isHandler(row)" type="warning" plain size="small" @click="openOperation(row, 'track')" v-hasPermi="['dispatch:event:handle']">跟踪</el-button>
+            <el-button v-if="row.status === 2 && isHandler(row)" type="primary" plain size="small" @click="openOperation(row, 'submit')" v-hasPermi="['dispatch:event:handle']">提交核验</el-button>
+            <el-button v-if="row.status === 3 && isVerifier(row)" type="success" plain size="small" @click="handleAudit(row)" v-hasPermi="['dispatch:event:audit']">核验</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)" v-hasPermi="['dispatch:event:remove']">删除</el-button>
           </template>
@@ -86,7 +87,7 @@
     </div>
 
     <!-- 开始处置和跟踪共用备注+附件表单 -->
-    <el-dialog v-model="operationVisible" :title="operationType === 'start' ? '开始处置' : '事件跟踪'" width="600px" :close-on-click-modal="false">
+    <el-dialog v-model="operationVisible" :title="operationType === 'start' ? '开始处置' : operationType === 'submit' ? '提交核验' : '事件跟踪'" width="600px" :close-on-click-modal="false">
       <el-form :model="operationForm" :rules="operationRules" ref="operationFormRef" label-width="100px">
         <el-form-item label="备注" prop="operDesc">
           <el-input v-model="operationForm.operDesc" type="textarea" :rows="4" maxlength="1000" show-word-limit placeholder="请输入备注" />
@@ -112,73 +113,19 @@
       </el-form>
       <template #footer>
         <el-button @click="operationVisible = false">取消</el-button>
-        <el-button type="primary" :loading="operationSubmitting" @click="confirmOperation">{{ operationType === 'start' ? '开始处置' : '保存跟踪' }}</el-button>
+        <el-button type="primary" :loading="operationSubmitting" @click="confirmOperation">{{ operationType === 'start' ? '开始处置' : operationType === 'submit' ? '提交核验' : '保存跟踪' }}</el-button>
       </template>
     </el-dialog>
 
-    <!-- 解除弹窗 -->
-    <el-dialog v-model="releaseVisible" title="事件解除" width="600px" :close-on-click-modal="false">
-      <el-form :model="releaseForm" :rules="releaseRules" ref="releaseFormRef" label-width="100px">
-        <el-form-item label="事件编号">{{ releaseForm.eventNo }}</el-form-item>
-        <el-form-item label="事件摘要">
-          <div class="desc-text">{{ releaseForm.summary || '-' }}</div>
+    <el-dialog v-model="auditVisible" title="事件核验" width="560px" :close-on-click-modal="false">
+      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
+        <el-form-item label="事件编号">{{ auditForm.eventNo }}</el-form-item>
+        <el-form-item label="核验结果" prop="resolved">
+          <el-radio-group v-model="auditForm.resolved"><el-radio :value="true">已解除</el-radio><el-radio :value="false">未解除</el-radio></el-radio-group>
         </el-form-item>
-        <el-form-item label="解除原因" prop="releaseReason">
-          <el-select v-model="releaseForm.releaseReason" placeholder="请选择" style="width:100%">
-            <el-option v-for="d in dis_event_release_reason" :key="d.value" :label="d.label" :value="d.value" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="解除说明" prop="releaseNote">
-          <el-input v-model="releaseForm.releaseNote" type="textarea" :rows="4" placeholder="请输入解除说明" />
-        </el-form-item>
-        <el-form-item label="损失(万元)">
-          <el-input-number v-model="releaseForm.lossAmount" :min="0" :precision="2" style="width:100%" />
-        </el-form-item>
-        <template v-if="isHandledRelease">
-          <el-form-item label="处置前佐证">
-            <el-upload
-              ref="beforeUploadRef"
-              v-model:file-list="releaseForm.beforeFiles"
-              :action="uploadUrl"
-              :headers="uploadHeaders"
-              :limit="5"
-              multiple
-              accept=".jpg,.jpeg,.png,.gif,.bmp,.webp,.mp4,.mov,.m4v,.webm"
-              :on-success="(res) => handleAttachSuccess(res, 'before')"
-              :on-error="handleAttachError"
-              :on-exceed="() => ElMessage.warning('处置前佐证最多 5 个')"
-              :before-upload="handleAttachBefore"
-              style="width:100%"
-            >
-              <el-button type="primary" size="small">上传处置前照片/视频</el-button>
-              <template #tip><div class="el-upload__tip">图片或视频,单个不超过 50MB,最多 5 个</div></template>
-            </el-upload>
-          </el-form-item>
-          <el-form-item label="处置后佐证">
-            <el-upload
-              ref="afterUploadRef"
-              v-model:file-list="releaseForm.afterFiles"
-              :action="uploadUrl"
-              :headers="uploadHeaders"
-              :limit="5"
-              multiple
-              accept=".jpg,.jpeg,.png,.gif,.bmp,.webp,.mp4,.mov,.m4v,.webm"
-              :on-success="(res) => handleAttachSuccess(res, 'after')"
-              :on-error="handleAttachError"
-              :on-exceed="() => ElMessage.warning('处置后佐证最多 5 个')"
-              :before-upload="handleAttachBefore"
-              style="width:100%"
-            >
-              <el-button type="primary" size="small">上传处置后照片/视频</el-button>
-              <template #tip><div class="el-upload__tip">图片或视频,单个不超过 50MB,最多 5 个</div></template>
-            </el-upload>
-          </el-form-item>
-        </template>
+        <el-form-item label="核验意见" prop="opinion"><el-input v-model="auditForm.opinion" type="textarea" :rows="4" placeholder="请输入核验意见" /></el-form-item>
       </el-form>
-      <template #footer>
-        <el-button @click="releaseVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmRelease">确认</el-button>
-      </template>
+      <template #footer><el-button @click="auditVisible = false">取消</el-button><el-button type="primary" @click="confirmAudit">确认提交</el-button></template>
     </el-dialog>
 
     <EventDetailDrawer ref="eventDetailRef" />
@@ -186,22 +133,26 @@
 </template>
 
 <script setup name="Gzjc">
-import { ref, nextTick, computed, onMounted } from 'vue'
+import { ref, nextTick, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useDict } from '@/utils/dict'
 import { getToken } from '@/utils/auth'
+import useUserStore from '@/store/modules/user'
 import {
-  getEventPage, deleteEventInfo, startDisposeEvent, trackEvent,
-  releaseEventInfo, getEventStatusOptions
+  getEventPage, deleteEventInfo, startDisposeEvent, trackEvent, submitVerifyEvent, auditEvent,
+  getEventStatusOptions
 } from '@/api/dispatch'
 import { Search, Refresh } from '@element-plus/icons-vue'
 
 import EventDetailDrawer from './EventDetailDrawer.vue'
 
 // ==================== 字典 ====================
-const { dis_event_category, dis_event_level, dis_event_trend, dis_event_release_reason, dis_yes_no } = useDict(
-  'dis_event_category', 'dis_event_level', 'dis_event_trend', 'dis_event_release_reason', 'dis_yes_no'
+const { dis_event_category, dis_event_level, dis_event_trend, dis_yes_no } = useDict(
+  'dis_event_category', 'dis_event_level', 'dis_event_trend', 'dis_yes_no'
 )
+const userStore = useUserStore()
+const isHandler = row => Number(userStore.id) > 0 && Number(userStore.id) === Number(row.handlerId)
+const isVerifier = row => Number(userStore.id) > 0 && Number(userStore.id) === Number(row.verifierId)
 
 function dictLabel(list, value) {
   if (value === null || value === undefined || value === '') return '-'
@@ -209,20 +160,20 @@ function dictLabel(list, value) {
   return hit ? hit.label : value
 }
 
-// 本页覆盖「已核实(1) 待开始处置 / 处置中(2) 待跟踪解除 / 已解除(3) 终态查看」
+// 本页覆盖「已核实(1) 待开始处置 / 处置中(2) 待跟踪 / 待验收(3) 待核验 / 已解除(4) 终态查看」
 const trackStatusOptions = ref([])
 
 async function loadStatusOptions() {
   try {
     const res = await getEventStatusOptions()
-    trackStatusOptions.value = (res.data || []).filter(item => [1, 2, 3].includes(item.value))
+    trackStatusOptions.value = (res.data || []).filter(item => [1, 2, 3, 4].includes(item.value))
   } catch {
     trackStatusOptions.value = []
   }
 }
 
-// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 已解除 4 不实事件)
-const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
+// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 待验收 4 已解除 5 不实事件)
+const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'warning', 5: 'info' }
 function statusTagType(status) {
   return STATUS_TAG[status] || 'info'
 }
@@ -247,7 +198,7 @@ const hasValue = (val) => val !== '' && val !== null && val !== undefined
 async function loadData() {
   loading.value = true
   try {
-    const params = { statusList: [1, 2, 3], orderAsc: true }
+    const params = { statusList: [1, 2, 3, 4], orderAsc: true }
     if (hasValue(searchForm.value.status)) params.statusList = [searchForm.value.status]
     if (searchForm.value.eventCategory) params.eventCategory = searchForm.value.eventCategory
     if (searchForm.value.dateRange && searchForm.value.dateRange.length === 2) {
@@ -304,6 +255,18 @@ const operationForm = ref({ operDesc: '', fileList: [], attachUrls: [] })
 const operationRules = {
   operDesc: [{ required: true, message: '请输入备注', trigger: 'blur' }]
 }
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/minio/upload?dir=dispatch'
+const uploadHeaders = { Authorization: 'Bearer ' + getToken() }
+const ALLOWED_ATTACH_EXT = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp4', 'mov', 'm4v', 'webm']
+function handleAttachBefore(file) {
+  const ext = String(file.name).split('.').pop().toLowerCase()
+  if (!ALLOWED_ATTACH_EXT.includes(ext) || file.size / 1024 / 1024 >= 50) {
+    ElMessage.error('仅支持图片/视频格式且单个不超过 50MB')
+    return false
+  }
+  return true
+}
+function handleAttachError() { ElMessage.error('文件上传失败') }
 
 function openOperation(row, type) {
   currentRow.value = { ...row }
@@ -337,6 +300,9 @@ async function confirmOperation() {
     if (operationType.value === 'start') {
       await startDisposeEvent(payload)
       ElMessage.success('已开始处置')
+    } else if (operationType.value === 'submit') {
+      await submitVerifyEvent(payload)
+      ElMessage.success('已提交核验')
     } else {
       await trackEvent(payload)
       ElMessage.success('跟踪记录保存成功')
@@ -351,100 +317,25 @@ async function confirmOperation() {
   }
 }
 
-// ==================== 解除 ====================
-const releaseVisible = ref(false)
-const releaseFormRef = ref(null)
 const currentReleaseRow = ref(null)
-const releaseForm = ref({ eventNo: '', summary: '', releaseReason: '', releaseNote: '', lossAmount: undefined, beforeFiles: [], afterFiles: [], beforeUrls: [], afterUrls: [] })
-const releaseRules = {
-  releaseReason: [{ required: true, message: '请选择解除原因', trigger: 'change' }],
-  releaseNote: [{ required: true, message: '请输入解除说明', trigger: 'blur' }]
-}
-
-// ==================== 解除佐证附件(处置前 / 处置后) ====================
-// 解除原因为「事件已处置完成」(字典值 '1') 时展示上传控件;图片/视频各最多 5 个、单个 ≤50MB
-// 佐证附件统一存 MinIO(图片/视频直链可预览)
-const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/minio/upload?dir=dispatch_release'
-const uploadHeaders = { Authorization: 'Bearer ' + getToken() }
-const beforeUploadRef = ref(null)
-const afterUploadRef = ref(null)
-const ALLOWED_ATTACH_EXT = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp4', 'mov', 'm4v', 'webm']
-
-const isHandledRelease = computed(() => dictLabel(dis_event_release_reason.value, releaseForm.value.releaseReason) === '事件已处置完成')
-
-function handleAttachBefore(file) {
-  const ext = String(file.name).split('.').pop().toLowerCase()
-  if (!ALLOWED_ATTACH_EXT.includes(ext)) {
-    ElMessage.error('仅支持图片(jpg/png/gif 等)或视频(mp4/mov 等)格式')
-    return false
-  }
-  if (file.size / 1024 / 1024 >= 50) {
-    ElMessage.error('文件大小不能超过 50MB')
-    return false
-  }
-  return true
-}
-
-function handleAttachSuccess(res, slot) {
-  if (res.code === 200 && res.url) {
-    // 库内只存相对路径(迁移环境不用改数据),展示/下载由后端拼 MinIO 地址
-    releaseForm.value[slot + 'Urls'].push(res.relativePath || res.url)
-  } else {
-    ElMessage.error(res.msg || '上传失败')
-  }
-}
-
-function handleAttachError() {
-  ElMessage.error('文件上传失败')
-}
-
-function handleRelease(row) {
+// ==================== 最终核验 ====================
+const auditVisible = ref(false)
+const auditFormRef = ref(null)
+const auditForm = ref({ eventNo: '', resolved: true, opinion: '' })
+const auditRules = { opinion: [{ required: true, message: '请输入核验意见', trigger: 'blur' }] }
+function handleAudit(row) {
   currentReleaseRow.value = row
-  releaseForm.value = {
-    eventNo: row.eventNo,
-    summary: row.description || '',
-    releaseReason: '',
-    releaseNote: '',
-    lossAmount: row.lossAmount !== null && row.lossAmount !== undefined ? Number(row.lossAmount) : undefined,
-    beforeFiles: [],
-    afterFiles: [],
-    beforeUrls: [],
-    afterUrls: []
-  }
-  releaseVisible.value = true
-  // 清掉上次打开残留的上传文件列表
-  nextTick(() => {
-    beforeUploadRef.value?.clearFiles()
-    afterUploadRef.value?.clearFiles()
-  })
+  auditForm.value = { eventNo: row.eventNo, resolved: true, opinion: '' }
+  auditVisible.value = true
 }
-
-async function confirmRelease() {
+async function confirmAudit() {
+  try { await auditFormRef.value.validate() } catch { return }
   try {
-    await releaseFormRef.value.validate()
-  } catch {
-    return
-  }
-  try {
-    await releaseEventInfo(currentReleaseRow.value.id, {
-      eventId: currentReleaseRow.value.id,
-      releaseReason: releaseForm.value.releaseReason,
-      releaseNote: releaseForm.value.releaseNote,
-      lossAmount: releaseForm.value.lossAmount,
-      // 处置前/后佐证(多文件英文逗号分隔),仅「事件已处置完成」时采集
-      beforeUrl: releaseForm.value.beforeUrls.join(','),
-      afterUrl: releaseForm.value.afterUrls.join(',')
-    })
-    ElMessage.success('事件已解除')
-    releaseVisible.value = false
-    nextTick(() => {
-      beforeUploadRef.value?.clearFiles()
-      afterUploadRef.value?.clearFiles()
-    })
+    await auditEvent(currentReleaseRow.value.id, { resolved: auditForm.value.resolved, auditOpinion: auditForm.value.opinion })
+    ElMessage.success('核验完成')
+    auditVisible.value = false
     loadData()
-  } catch (error) {
-    ElMessage.error('操作失败:' + (error.message || '未知错误'))
-  }
+  } catch (error) { ElMessage.error('操作失败:' + (error.message || '未知错误')) }
 }
 
 // ==================== 删除 ====================

+ 3 - 3
src/views/subSystem/dispatch/disOnDuty/jbgl.vue

@@ -39,7 +39,7 @@
       <el-table :data="tableData" border stripe style="width:100%" v-loading="loading">
         <el-table-column prop="eventNo" label="事件编号" width="140" />
         <el-table-column prop="eventSource" label="事件来源" width="110">
-          <template #default="{ row }">{{ dictLabel(dis_event_source, row.eventSource) }}</template>
+          <template #default="{ row }">{{ row.eventSourceName || dictLabel(dis_event_source, row.eventSource) }}</template>
         </el-table-column>
         <el-table-column prop="location" label="发生地点" show-overflow-tooltip />
         <el-table-column prop="happenTime" label="发生时间" width="150" />
@@ -128,8 +128,8 @@ function dictLabel(list, value) {
   return hit ? hit.label : value
 }
 
-// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 已解除 4 不实事件)
-const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
+// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 待验收 4 已解除 5 不实事件)
+const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'warning', 5: 'info' }
 function statusTagType(status) {
   return STATUS_TAG[status] || 'info'
 }

+ 6 - 12
src/views/subSystem/dispatch/disOnDuty/zjbs.vue

@@ -11,11 +11,6 @@
             <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"
@@ -69,8 +64,8 @@
         </el-table-column>
         <el-table-column label="操作" width="240" fixed="right">
           <template #default="{ row }">
-            <el-button v-if="[1, 2].includes(row.status)" type="primary" plain size="small" @click="openOperation(row, 'transfer')" v-hasPermi="['dispatch:event:transfer']">转接</el-button>
-            <el-button v-if="[1, 2].includes(row.status)" type="warning" plain size="small" @click="openOperation(row, 'report')" v-hasPermi="['dispatch:event:report']">报送</el-button>
+            <el-button v-if="[1, 2].includes(row.status)" type="primary" plain size="small" @click="openOperation(row, 'transfer')" v-hasPermi="['dispatch:event:dispatch']">转接</el-button>
+            <el-button v-if="[1, 2].includes(row.status)" type="warning" plain size="small" @click="openOperation(row, 'report')" v-hasPermi="['dispatch:event:dispatch']">报送</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)" v-hasPermi="['dispatch:event:remove']">删除</el-button>
           </template>
@@ -145,8 +140,8 @@ async function loadStatusOptions() {
   }
 }
 
-// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 已解除 4 不实事件)
-const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
+// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 待验收 4 已解除 5 不实事件)
+const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'warning', 5: 'info' }
 function statusTagType(status) {
   return STATUS_TAG[status] || 'info'
 }
@@ -161,7 +156,7 @@ function levelTagType(level) {
 const tableData = ref([])
 const total = ref(0)
 const loading = ref(false)
-const searchForm = ref({ eventNo: '', status: '', department: '', dateRange: null })
+const searchForm = ref({ eventNo: '', status: '', dateRange: null })
 const pagination = ref({ page: 1, pageSize: 10 })
 
 // 下拉清空后可能是 '' / null / undefined,统一判定,避免拼出空查询条件
@@ -176,7 +171,6 @@ async function loadData() {
     if (hasValue(searchForm.value.status)) {
       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]
@@ -203,7 +197,7 @@ function handleSearch() {
 }
 
 function handleReset() {
-  searchForm.value = { eventNo: '', status: '', department: '', dateRange: null }
+  searchForm.value = { eventNo: '', status: '', dateRange: null }
   pagination.value.page = 1
   loadData()
 }

+ 5 - 5
src/views/subSystem/dispatch/disPlanManage/DisPlanApproval.vue

@@ -129,8 +129,8 @@
       <div class="approve-header">
         <el-descriptions :column="2" border>
           <el-descriptions-item label="预案名称">{{ currentRow.planName }}</el-descriptions-item>
-          <el-descriptions-item label="预案类型">{{ dictLabel(dis_plan_type, currentRow.planType) }}</el-descriptions-item>
-          <el-descriptions-item label="预案等级">{{ dictLabel(dis_plan_level, currentRow.planLevel) }}</el-descriptions-item>
+          <el-descriptions-item label="预案类型">{{ currentRow.planTypeName || dictLabel(dis_plan_type, currentRow.planType) }}</el-descriptions-item>
+          <el-descriptions-item label="预案等级">{{ currentRow.planLevelName || dictLabel(dis_plan_level, currentRow.planLevel) }}</el-descriptions-item>
           <el-descriptions-item label="编制单位">{{ currentRow.compileUnit || '-' }}</el-descriptions-item>
           <el-descriptions-item label="修订说明">{{ currentRow.changeNote || '—' }}</el-descriptions-item>
         </el-descriptions>
@@ -164,7 +164,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 { getEventPlanPage, reviewPlan, getPlanLogs, getPlanStatusOptions } from '@/api/dispatch'
 import { Search, Refresh } from '@element-plus/icons-vue'
 
 // ---------- 字典 ----------
@@ -291,7 +291,7 @@ const handleReject = async () => {
   const note = (approveOpinion.value || '').trim()
   if (!note) return ElMessage.warning('驳回时必须填写审批意见(驳回原因)')
   try {
-    await rejectPlan(currentRow.value.id, { approveNote: note })
+    await reviewPlan(currentRow.value.id, { approved: false, approveNote: note })
     approveVisible.value = false
     ElMessage.success('已驳回')
     loadData()
@@ -304,7 +304,7 @@ const handleComplete = async () => {
   const note = (approveOpinion.value || '').trim()
   approving.value = true
   try {
-    await approvePlan(currentRow.value.id, { approveNote: note })
+    await reviewPlan(currentRow.value.id, { approved: true, approveNote: note })
     approveVisible.value = false
     ElMessage.success('审批通过,预案已发布')
     loadData()

+ 4 - 4
src/views/subSystem/dispatch/disPlanManage/DisPlanEditing.vue

@@ -36,11 +36,11 @@
           <el-table-column prop="planNo" label="预案编号" width="140" />
           <el-table-column prop="planName" label="预案名称" min-width="180" show-overflow-tooltip />
           <el-table-column prop="planType" label="预案类型" width="130">
-            <template #default="{ row }">{{ dictLabel(dis_plan_type, row.planType) }}</template>
+            <template #default="{ row }">{{ row.planTypeName || dictLabel(dis_plan_type, row.planType) }}</template>
           </el-table-column>
           <el-table-column prop="planLevel" label="预案级别" width="100" align="center">
             <template #default="{ row }">
-              <el-tag v-if="row.planLevel" :type="levelTagType(row.planLevel)" size="small">{{ dictLabel(dis_plan_level, row.planLevel) }}</el-tag>
+              <el-tag v-if="row.planLevel" :type="levelTagType(row.planLevel)" size="small">{{ row.planLevelName || dictLabel(dis_plan_level, row.planLevel) }}</el-tag>
               <span v-else>-</span>
             </template>
           </el-table-column>
@@ -574,8 +574,8 @@ const handleExport = () => {
     aoa.push([
       row.planNo,
       row.planName,
-      dictLabel(dis_plan_type.value, row.planType),
-      dictLabel(dis_plan_level.value, row.planLevel),
+      row.planTypeName || dictLabel(dis_plan_type.value, row.planType),
+      row.planLevelName || dictLabel(dis_plan_level.value, row.planLevel),
       row.version,
       row.publishDate,
       row.compileUnit,