Explorar el Código

feat: 预警信息模块功能完善及bug修复

- LRelease: 发布人自动填充当前用户,处置人支持部门树选择用户
- LCorrelation: 改为服务端分页查询,修复模糊查询参数
- LTobeHandled: 对接待办列表/升级/处置/退回接口
- LReportUpload: 完善字典和状态展示
- request.js: 修复FormData请求Content-Type问题
- yjfb.js: 新增待办相关接口,修复数组参数序列化
- jcbjyzt.vue: 修复CSS选择器缺失导致打包失败
林仔 hace 1 semana
padre
commit
549da6bfca

+ 1 - 1
.env.development

@@ -8,4 +8,4 @@ VITE_APP_ENV = 'development'
 VITE_APP_BASE_API = '/dev-api'
 
 # MinIO文件访问基础地址
-VITE_APP_MINIO_BASE_URL = 'http://111.23.174.45:9000/'
+VITE_APP_MINIO_BASE_URL = 'http://111.23.174.45:9000/pipe/'

+ 5 - 2
.env.production

@@ -5,7 +5,10 @@ VITE_APP_TITLE = 地下管网管控平台
 VITE_APP_ENV = 'production'
 
 # 若依管理系统/生产环境
-VITE_APP_BASE_API = '/prod-api'
+VITE_APP_BASE_API = 'http://172.16.102.52:8301'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli
-VITE_BUILD_COMPRESS = gzip
+VITE_BUILD_COMPRESS = gzip
+
+# MinIO文件访问基础地址
+VITE_APP_MINIO_BASE_URL = 'http://172.16.102.52:9000/pipe/'

+ 24 - 12
src/api/smxyjldczxt/yjxxfb/yjfb.js

@@ -3,18 +3,16 @@ import request from '@/utils/request'
 function buildWarningFormData(payload = {}) {
   const formData = new FormData()
   const warning = payload.warning || {}
+  // 后端 @RequestPart("warning") 要求单个名为 "warning" 的 part,内容为整段 JSON 字符串
+  if (warning.warningId) {
+    formData.append('warning', JSON.stringify(warning))
+  } else {
+    const { warningId, ...rest } = warning
+    formData.append('warning', JSON.stringify(rest))
+  }
 
-  Object.keys(warning).forEach((key) => {
-    const value = warning[key]
-    if (key === 'warningId' && !value) {
-      return
-    }
-    formData.append(`warning.${key}`, value ?? '')
-  })
-
-  ;(payload.alarmIds || []).forEach((id) => {
-    formData.append('alarmIds', id)
-  })
+  // 后端 @RequestPart("alarmIds") 要求单个名为 "alarmIds" 的 part,内容为 JSON 数组字符串
+  formData.append('alarmIds', JSON.stringify(payload.alarmIds || []))
 
   ;(payload.files || []).forEach((file) => {
     formData.append('files', file)
@@ -23,6 +21,15 @@ function buildWarningFormData(payload = {}) {
   return formData
 }
 
+// 待办预警列表
+export function getTodoListData(query) {
+  return request({
+    url: '/warning/todo/list',
+    method: 'get',
+    params: query
+  })
+}
+
 // 预警发布列表/综合查询
 export function getdbyjlistData(query) {
   return request({
@@ -114,10 +121,15 @@ export function getWarningReasonListData(query) {
 
 // 关联报警
 export function updateWarningAlarm(data) {
+  const params = { ...data };
+  // Spring @RequestParam List<String> 期望逗号分隔,axios 默认序列化 alarmIds[]=x 会 400
+  if (Array.isArray(params.alarmIds)) {
+    params.alarmIds = params.alarmIds.join(',');
+  }
   return request({
     url: '/warning/link/alarms',
     method: 'post',
-    params: data
+    params
   })
 }
 

+ 5 - 0
src/utils/request.js

@@ -26,6 +26,11 @@ service.interceptors.request.use(config => {
   const isToken = (config.headers || {}).isToken === false
   // 是否需要防止数据重复提交
   const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
+  // FormData 上传:清除全局默认的 application/json,让 axios 自动设置 multipart/form-data + boundary
+  if (typeof FormData !== 'undefined' && config.data instanceof FormData) {
+    delete config.headers['Content-Type']
+    delete config.headers['content-type']
+  }
   if (getToken() && !isToken) {
     config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
   }

+ 25 - 28
src/views/subSystem/lifeCompany/lAlertInformation/LCorrelation.vue

@@ -47,15 +47,14 @@
             @keyup.enter="handleQuery"
           />
         </el-form-item>
-        <el-form-item label="预警类型">
-          <el-select v-model="queryParams.warningType" placeholder="请选择预警类型" clearable style="width: 180px">
-            <el-option
-              v-for="item in warningTypeOptions"
-              :key="item.dictValue"
-              :label="item.dictLabel"
-              :value="item.dictValue"
-            />
-          </el-select>
+        <el-form-item label="告警类型">
+          <el-input
+            v-model="queryParams.warningType"
+            placeholder="请输入告警类型"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
+          />
         </el-form-item>
         <el-form-item label="报警状态">
           <el-select v-model="queryParams.alarmStatus" placeholder="请选择报警状态" clearable style="width: 180px">
@@ -80,8 +79,8 @@
       <el-table v-loading="loading" :data="pagedAlarmList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" />
         <el-table-column label="设备编码" prop="deviceCode" min-width="160" />
-        <el-table-column label="警类型" prop="warningTypeText" width="120" />
-        <el-table-column label="警编码" prop="warningCode" width="160" />
+        <el-table-column label="警类型" prop="warningTypeText" width="120" />
+        <el-table-column label="警编码" prop="warningCode" width="160" />
         <el-table-column label="阈值范围" prop="thresholdText" width="160" />
         <el-table-column label="实际值" prop="actualValue" width="120" />
         <el-table-column label="报警时间" prop="alarmTime" width="180" />
@@ -144,7 +143,7 @@
         <el-form-item label="设备编码">
           <el-input :model-value="associationDialog.row?.deviceCode || '-'" disabled />
         </el-form-item>
-        <el-form-item label="警类型">
+        <el-form-item label="警类型">
           <el-input :model-value="associationDialog.row?.warningTypeText || '-'" disabled />
         </el-form-item>
         <el-form-item label="报警内容">
@@ -203,7 +202,7 @@
         <el-descriptions :column="2" border>
           <el-descriptions-item label="预警编号">{{ warningDetailData.basicInfo.warningNo || "-" }}</el-descriptions-item>
           <el-descriptions-item label="预警名称">{{ warningDetailData.basicInfo.warningName || "-" }}</el-descriptions-item>
-          <el-descriptions-item label="警类型">{{ formatTypeText(warningDetailData.basicInfo.warningType) }}</el-descriptions-item>
+          <el-descriptions-item label="警类型">{{ formatTypeText(warningDetailData.basicInfo.warningType) }}</el-descriptions-item>
           <el-descriptions-item label="预警级别">{{ formatLevelText(warningDetailData.basicInfo.warningLevel) }}</el-descriptions-item>
           <el-descriptions-item label="预警位置" :span="2">{{ warningDetailData.basicInfo.location || "-" }}</el-descriptions-item>
           <el-descriptions-item label="发布时间">{{ warningDetailData.basicInfo.publishTime || "-" }}</el-descriptions-item>
@@ -214,8 +213,8 @@
         <el-divider content-position="left">已关联报警</el-divider>
         <el-table :data="warningDetailData.alarmList || []" border size="small">
           <el-table-column label="设备编码" prop="device_code" min-width="160" />
-          <el-table-column label="警类型" prop="warning_type" width="120" />
-          <el-table-column label="警编码" prop="warning_code" width="160" />
+          <el-table-column label="警类型" prop="warning_type" width="120" />
+          <el-table-column label="警编码" prop="warning_code" width="160" />
           <el-table-column label="阈值" prop="warning_value" width="120" />
           <el-table-column label="实际值" prop="actual_value" width="120" />
           <el-table-column label="报警时间" prop="alarm_time" width="180" />
@@ -227,8 +226,8 @@
       <el-descriptions :column="2" border v-if="currentAlarmDetail">
         <el-descriptions-item label="报警ID">{{ currentAlarmDetail.id || "-" }}</el-descriptions-item>
         <el-descriptions-item label="设备编码">{{ currentAlarmDetail.deviceCode || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="警类型">{{ currentAlarmDetail.warningTypeText || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="警编码">{{ currentAlarmDetail.warningCode || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="警类型">{{ currentAlarmDetail.warningTypeText || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="警编码">{{ currentAlarmDetail.warningCode || "-" }}</el-descriptions-item>
         <el-descriptions-item label="阈值范围">{{ currentAlarmDetail.thresholdText || "-" }}</el-descriptions-item>
         <el-descriptions-item label="实际值">{{ currentAlarmDetail.actualValue || "-" }}</el-descriptions-item>
         <el-descriptions-item label="报警时间">{{ currentAlarmDetail.alarmTime || "-" }}</el-descriptions-item>
@@ -302,15 +301,10 @@ const filteredAlarmList = computed(() => {
   if (queryParams.associationStatus === "unlinked") {
     list = list.filter((item) => !item.linkedWarning);
   }
-  total.value = list.length;
   return list;
 });
 
-const pagedAlarmList = computed(() => {
-  const start = (queryParams.pageNum - 1) * queryParams.pageSize;
-  const end = start + queryParams.pageSize;
-  return filteredAlarmList.value.slice(start, end);
-});
+const pagedAlarmList = computed(() => filteredAlarmList.value);
 
 const stats = computed(() => {
   const linkedCount = rawAlarmList.value.filter((item) => item.linkedWarning).length;
@@ -393,11 +387,11 @@ function buildAlarmContent(row) {
   if (!row) {
     return "-";
   }
-  return `警类型:${row.warningTypeText || "-"}\n阈值范围:${row.thresholdText || "-"}\n实际值:${row.actualValue || "-"}`;
+  return `警类型:${row.warningTypeText || "-"}\n阈值范围:${row.thresholdText || "-"}\n实际值:${row.actualValue || "-"}`;
 }
 
 async function loadWarningTypes() {
-  const res = await getAlertTypeDictionaryData({ dictType: "warning_type" });
+  const res = await getAlertTypeDictionaryData({ dictType: "alert_type" });
   warningTypeOptions.value = res.rows || [];
 }
 
@@ -432,13 +426,15 @@ async function buildLinkedAlarmMap() {
 
 async function loadAlarms() {
   const res = await getAlarmPageData({
-    pageNum: 1,
-    pageSize: 1000,
+    pageNum: queryParams.pageNum,
+    pageSize: queryParams.pageSize,
     deviceCode: queryParams.deviceCode || undefined,
     warningType: queryParams.warningType || undefined,
     alarmStatus: queryParams.alarmStatus
   });
-  const records = res.data?.records || [];
+  const pageData = res.data || {};
+  const records = pageData.records || [];
+  total.value = pageData.total || 0;
   rawAlarmList.value = records.map(normalizeAlarmRow);
 }
 
@@ -474,6 +470,7 @@ function resetQuery() {
 
 function handlePageChange() {
   selectedRows.value = [];
+  loadAlarms();
 }
 
 function openAssociationDialog(row) {

+ 223 - 33
src/views/subSystem/lifeCompany/lAlertInformation/LRelease.vue

@@ -148,12 +148,16 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="发布人">
-              <el-input v-model="formData.publisher" placeholder="请输入发布人" />
+              <el-input v-model="formData.publisher" placeholder="自动填充当前用户" disabled />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="处置人">
-              <el-input v-model="formData.handler" placeholder="请输入处置人" />
+              <el-input v-model="formData.handler" placeholder="请选择处置人" readonly @click="openUserPicker">
+                <template #suffix>
+                  <el-icon style="cursor: pointer;" @click="openUserPicker"><Search /></el-icon>
+                </template>
+              </el-input>
             </el-form-item>
           </el-col>
           <el-col :span="24">
@@ -190,9 +194,12 @@
           <el-col :span="12">
             <el-form-item label="状态">
               <el-select v-model="formData.status" style="width: 100%">
-                <el-option label="启用" value="ENABLED" />
-                <el-option label="禁用" value="DISABLED" />
-                <el-option label="作废" value="INVALID" />
+                <el-option
+                  v-for="item in warningStatusOptions"
+                  :key="item.dictValue"
+                  :label="item.dictLabel"
+                  :value="item.dictValue"
+                />
               </el-select>
             </el-form-item>
           </el-col>
@@ -327,12 +334,61 @@
         <el-empty v-else description="暂无附件信息" />
       </template>
     </el-dialog>
+
+    <!-- 处置人选择对话框 -->
+    <el-dialog v-model="userPickerVisible" title="选择处置人" width="900px" append-to-body @open="loadUserPickerData">
+      <div class="user-picker-container">
+        <!-- 左侧部门树 -->
+        <div class="user-picker-left">
+          <div class="picker-title">部门列表</div>
+          <el-input v-model="deptFilterText" placeholder="搜索部门" clearable style="margin-bottom: 12px;" />
+          <el-tree
+            ref="deptTreeRef"
+            :data="deptTree"
+            :props="{ label: 'label', children: 'children' }"
+            :filter-node-method="filterDeptNode"
+            :highlight-current="true"
+            default-expand-all
+            @node-click="handleDeptClick"
+          />
+        </div>
+        <!-- 右侧用户列表 -->
+        <div class="user-picker-right">
+          <div class="picker-title">
+            用户列表
+            <span v-if="currentDeptName" class="current-dept">({{ currentDeptName }})</span>
+          </div>
+          <el-input v-model="userFilterText" placeholder="搜索用户" clearable style="margin-bottom: 12px;" @input="filterUserList" />
+          <el-table
+            v-loading="userPickerLoading"
+            :data="filteredUserList"
+            highlight-current-row
+            @current-change="handleUserCurrentChange"
+            height="400"
+          >
+            <el-table-column label="用户名称" prop="userName" min-width="120" show-overflow-tooltip />
+            <el-table-column label="用户昵称" prop="nickName" min-width="120" show-overflow-tooltip />
+            <el-table-column label="部门" prop="deptName" min-width="140" show-overflow-tooltip />
+            <el-table-column label="手机号码" prop="phonenumber" width="130" />
+          </el-table>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="userPickerVisible = false">取 消</el-button>
+          <el-button type="primary" :disabled="!selectedUser" @click="confirmSelectUser">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import {computed, onMounted, reactive, ref} from "vue";
+import {computed, onMounted, reactive, ref, watch} from "vue";
 import {ElMessage, ElMessageBox} from "element-plus";
+import { Search } from "@element-plus/icons-vue";
+import useUserStore from "@/store/modules/user";
+import { deptTreeSelect, listUser } from "@/api/system/user";
 import {
   deleteYjxxData,
   getAlertTypeDictionaryData,
@@ -344,12 +400,15 @@ import {
   updateYjxxData
 } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
 
+const userStore = useUserStore();
+
 const loading = ref(false);
 const submitLoading = ref(false);
 const total = ref(0);
 const warningList = ref([]);
 const warningTypeOptions = ref([]);
 const warningLevelOptions = ref([]);
+const warningStatusOptions = ref([]);
 const reasonOptions = ref([]);
 const uploadRef = ref();
 const formAttachments = ref([]);
@@ -357,11 +416,24 @@ const minioBaseUrl = (import.meta.env.VITE_APP_MINIO_BASE_URL || "").trim();
 
 const editVisible = ref(false);
 const detailVisible = ref(false);
+const userPickerVisible = ref(false);
 const dialogTitle = ref("新增预警");
 const isEdit = ref(false);
 const formRef = ref();
 const detailData = ref({});
 
+// 用户选择器相关
+const deptTree = ref([]);
+const userList = ref([]);
+const filteredUserList = ref([]);
+const selectedDeptId = ref("");
+const currentDeptName = ref("");
+const selectedUser = ref(null);
+const userPickerLoading = ref(false);
+const deptFilterText = ref("");
+const userFilterText = ref("");
+const deptTreeRef = ref();
+
 const queryParams = reactive({
   pageNum: 1,
   pageSize: 10,
@@ -395,6 +467,13 @@ const warningLevelLabelMap = computed(() => {
   }, {});
 });
 
+const warningStatusLabelMap = computed(() => {
+  return warningStatusOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
 function createEmptyForm() {
   return {
     warningId: "",
@@ -410,7 +489,7 @@ function createEmptyForm() {
     publisher: "",
     publishTime: "",
     handler: "",
-    status: "ENABLED",
+    status: "DRAFT",
     warningContent: "",
     remark: ""
   };
@@ -491,33 +570,19 @@ function getLevelTagType(level) {
 }
 
 function getStatusText(status) {
-  return {
-    ENABLED: "启用",
-    DISABLED: "禁用",
-    INVALID: "作废",
-    DRAFT: "草稿",
-    RELEASED: "已发布",
-    PENDING: "待处理",
-    PROCESSING: "处理中",
-    HANDLED: "已处理",
-    CLOSED: "已解除",
-    RETURNED: "退回"
-  }[status] || status || "-";
+  return warningStatusLabelMap.value[status] || status || "-";
 }
 
 function getStatusTagType(status) {
+  // 按照预警状态语义设置 tag 颜色
   return {
-    ENABLED: "success",
-    DISABLED: "warning",
-    INVALID: "danger",
-    DRAFT: "info",
-    RELEASED: "success",
-    PENDING: "warning",
-    PROCESSING: "warning",
-    HANDLED: "success",
-    CLOSED: "info",
-    RETURNED: "danger"
-  }[status] || "";
+    DRAFT: "info",        // 草稿 - 灰色(未发布)
+    PENDING: "warning",   // 待办 - 黄色(等待处置)
+    PROCESSING: "warning", // 处置中 - 黄色(进行中)
+    RELEASED: "success",  // 已发布 - 绿色
+    HANDLED: "success",   // 已处置 - 绿色(已完成)
+    CLOSED: "info"        // 已解除 - 灰色
+  }[status] || "info";
 }
 
 function normalizeWarningRow(item) {
@@ -563,12 +628,14 @@ function normalizeDisposalRow(item) {
 }
 
 async function loadDicts() {
-  const [typeRes, levelRes] = await Promise.all([
+  const [typeRes, levelRes, statusRes] = await Promise.all([
     getAlertTypeDictionaryData({ dictType: "alert_type" }),
-    getAlertTypeDictionaryData({ dictType: "alert_level" })
+    getAlertTypeDictionaryData({ dictType: "alert_level" }),
+    getAlertTypeDictionaryData({ dictType: "pre_alarm_status" })
   ]);
   warningTypeOptions.value = typeRes.rows || [];
   warningLevelOptions.value = levelRes.rows || [];
+  warningStatusOptions.value = statusRes.rows || [];
 }
 
 async function loadReasonOptions() {
@@ -629,10 +696,97 @@ function resetQuery() {
   loadList();
 }
 
+// 部门树过滤
+watch(deptFilterText, (val) => {
+  deptTreeRef.value?.filter(val);
+});
+
+function filterDeptNode(value, data) {
+  if (!value) return true;
+  return data.label.indexOf(value) !== -1;
+}
+
+// 用户选择器相关函数
+async function loadUserPickerData() {
+  userPickerLoading.value = true;
+  try {
+    await loadDeptTree();
+    // 默认加载第一个部门的用户
+    if (deptTree.value.length > 0) {
+      const firstDept = deptTree.value[0];
+      selectedDeptId.value = firstDept.id;
+      currentDeptName.value = firstDept.label;
+      await loadUserList(firstDept.id);
+    }
+  } finally {
+    userPickerLoading.value = false;
+  }
+}
+
+async function loadDeptTree() {
+  const res = await deptTreeSelect();
+  deptTree.value = res.data || [];
+}
+
+async function loadUserList(deptId) {
+  userPickerLoading.value = true;
+  try {
+    const res = await listUser({ deptId, pageNum: 1, pageSize: 100 });
+    userList.value = res.rows || [];
+    filteredUserList.value = userList.value;
+  } finally {
+    userPickerLoading.value = false;
+  }
+}
+
+function handleDeptClick(data) {
+  selectedDeptId.value = data.id;
+  currentDeptName.value = data.label;
+  loadUserList(data.id);
+  selectedUser.value = null;
+  userFilterText.value = "";
+}
+
+function filterUserList() {
+  const keyword = userFilterText.value.toLowerCase();
+  if (!keyword) {
+    filteredUserList.value = userList.value;
+    return;
+  }
+  filteredUserList.value = userList.value.filter((user) => {
+    const userName = (user.userName || "").toLowerCase();
+    const nickName = (user.nickName || "").toLowerCase();
+    const deptName = (user.deptName || "").toLowerCase();
+    return userName.includes(keyword) || nickName.includes(keyword) || deptName.includes(keyword);
+  });
+}
+
+function handleUserCurrentChange(row) {
+  selectedUser.value = row;
+}
+
+function openUserPicker() {
+  selectedUser.value = null;
+  userFilterText.value = "";
+  deptFilterText.value = "";
+  userPickerVisible.value = true;
+}
+
+function confirmSelectUser() {
+  if (!selectedUser.value) {
+    ElMessage.warning("请选择用户");
+    return;
+  }
+  formData.handler = selectedUser.value.userName;
+  userPickerVisible.value = false;
+}
+
 function handleAdd() {
   isEdit.value = false;
   dialogTitle.value = "新增预警";
   resetForm();
+  // 自动填充发布人为当前用户
+  formData.publisher = userStore.name;
   editVisible.value = true;
 }
 
@@ -667,7 +821,7 @@ async function submitForm() {
         publisher: formData.publisher,
         publishTime: formData.publishTime,
         handler: formData.handler,
-        status: formData.status || "ENABLED",
+        status: formData.status || "DRAFT",
         warningContent: formData.warningContent,
         remark: formData.remark
       },
@@ -825,4 +979,40 @@ onMounted(async () => {
     align-items: flex-start;
   }
 }
+
+/* 用户选择器样式 */
+.user-picker-container {
+  display: flex;
+  gap: 16px;
+  height: 460px;
+}
+
+.user-picker-left {
+  width: 280px;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  padding: 12px;
+  overflow-y: auto;
+}
+
+.user-picker-right {
+  flex: 1;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  padding: 12px;
+  overflow: hidden;
+}
+
+.picker-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #374151;
+  margin-bottom: 12px;
+}
+
+.picker-title .current-dept {
+  font-weight: 400;
+  color: #6b7280;
+  font-size: 13px;
+}
 </style>

+ 35 - 4
src/views/subSystem/lifeCompany/lAlertInformation/LReportUpload.vue

@@ -150,6 +150,11 @@
             <el-descriptions-item label="发布人">{{ currentBasicInfo.publisher || "-" }}</el-descriptions-item>
             <el-descriptions-item label="发布时间">{{ currentBasicInfo.publishTime || "-" }}</el-descriptions-item>
             <el-descriptions-item label="权属单位">{{ currentBasicInfo.ownershipUnit || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="状态">
+              <el-tag :type="getStatusTagType(currentBasicInfo.status)">
+                {{ getStatusText(currentBasicInfo.status) }}
+              </el-tag>
+            </el-descriptions-item>
           </el-descriptions>
         </el-card>
 
@@ -277,6 +282,7 @@ let curveChart = null;
 
 const warningTypeOptions = ref([]);
 const warningLevelOptions = ref([]);
+const warningStatusOptions = ref([]);
 const warningOptions = ref([]);
 const selectedWarningId = ref("");
 const warningDetail = ref({});
@@ -303,6 +309,13 @@ const warningLevelLabelMap = computed(() => {
   }, {});
 });
 
+const warningStatusLabelMap = computed(() => {
+  return warningStatusOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
 const currentBasicInfo = computed(() => {
   const basic = warningDetail.value.basicInfo || {};
   return {
@@ -319,7 +332,7 @@ const currentBasicInfo = computed(() => {
     publishTime: basic.publishTime || basic.publish_time || "",
     handler: basic.handler || "",
     status: basic.status || "",
-    statusText: basic.statusText || "",
+    statusText: warningStatusLabelMap.value[basic.status] || basic.statusText || basic.status || "",
     longitude: basic.longitude || "",
     latitude: basic.latitude || "",
     warningContent: basic.warningContent || basic.warning_content || "",
@@ -357,6 +370,7 @@ const environmentConclusion = computed(() => {
 
 function getLevelTagType(level) {
   return {
+    "0": "danger",
     "1": "danger",
     "2": "warning",
     "3": "success",
@@ -364,6 +378,21 @@ function getLevelTagType(level) {
   }[level] || "info";
 }
 
+function getStatusText(status) {
+  return warningStatusLabelMap.value[status] || status || "-";
+}
+
+function getStatusTagType(status) {
+  return {
+    DRAFT: "info",
+    PENDING: "warning",
+    PROCESSING: "warning",
+    RELEASED: "success",
+    HANDLED: "success",
+    CLOSED: "info"
+  }[status] || "info";
+}
+
 function validateFile(file) {
   const allowExt = [".pdf", ".doc", ".docx", ".zip"];
   const lowerName = file.name.toLowerCase();
@@ -428,12 +457,14 @@ function normalizeWarningRow(item) {
 }
 
 async function loadDicts() {
-  const [typeRes, levelRes] = await Promise.all([
-    getAlertTypeDictionaryData({ dictType: "warning_type" }),
-    getAlertTypeDictionaryData({ dictType: "warning_level" })
+  const [typeRes, levelRes, statusRes] = await Promise.all([
+    getAlertTypeDictionaryData({ dictType: "alert_type" }),
+    getAlertTypeDictionaryData({ dictType: "alert_level" }),
+    getAlertTypeDictionaryData({ dictType: "pre_alarm_status" })
   ]);
   warningTypeOptions.value = typeRes.rows || [];
   warningLevelOptions.value = levelRes.rows || [];
+  warningStatusOptions.value = statusRes.rows || [];
 }
 
 async function loadWarningOptions() {

+ 232 - 15
src/views/subSystem/lifeCompany/lAlertInformation/LTobeHandled.vue

@@ -139,9 +139,14 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" fixed="right" width="120" align="center">
+        <el-table-column label="操作" fixed="right" width="220" align="center">
           <template #default="scope">
             <el-button link type="primary" @click.stop="handleViewDetail(scope.row)">查看详情</el-button>
+            <template v-if="activeTab === 'TODO'">
+              <el-button link type="warning" @click.stop="openUpgradeDialog(scope.row)">升级</el-button>
+              <el-button link type="success" @click.stop="openResolveDialog(scope.row)">解除</el-button>
+              <el-button link type="danger" @click.stop="openReturnDialog(scope.row)">退回</el-button>
+            </template>
           </template>
         </el-table-column>
       </el-table>
@@ -195,15 +200,104 @@
         <el-empty v-else description="暂无处置记录" />
       </template>
     </el-drawer>
+
+    <!-- 预警升级对话框 -->
+    <el-dialog v-model="upgradeDialog.visible" title="预警升级" width="500px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="预警名称">
+          <el-input :model-value="upgradeDialog.row?.warningName || '-'" disabled />
+        </el-form-item>
+        <el-form-item label="当前级别">
+          <el-tag :type="getLevelTagType(upgradeDialog.row?.warningLevel)">
+            {{ upgradeDialog.row?.warningLevelText || "-" }}
+          </el-tag>
+        </el-form-item>
+        <el-form-item label="新预警级别" required>
+          <el-select v-model="upgradeDialog.newLevel" placeholder="请选择新级别" style="width: 100%">
+            <el-option
+              v-for="item in warningLevelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="处置说明" required>
+          <el-input
+            v-model="upgradeDialog.disposalContent"
+            type="textarea"
+            :rows="4"
+            maxlength="500"
+            show-word-limit
+            placeholder="请输入处置说明"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="upgradeDialog.visible = false">取 消</el-button>
+          <el-button type="primary" :loading="actionLoading" @click="confirmUpgrade">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 预警解除对话框 -->
+    <el-dialog v-model="resolveDialog.visible" title="解除预警" width="500px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="预警名称">
+          <el-input :model-value="resolveDialog.row?.warningName || '-'" disabled />
+        </el-form-item>
+        <el-form-item label="处置说明" required>
+          <el-input
+            v-model="resolveDialog.disposalContent"
+            type="textarea"
+            :rows="4"
+            maxlength="500"
+            show-word-limit
+            placeholder="请输入处置说明"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="resolveDialog.visible = false">取 消</el-button>
+          <el-button type="primary" :loading="actionLoading" @click="confirmResolve">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 退回重办对话框 -->
+    <el-dialog v-model="returnDialog.visible" title="退回重办" width="500px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="预警名称">
+          <el-input :model-value="returnDialog.row?.warningName || '-'" disabled />
+        </el-form-item>
+        <el-form-item label="退回说明" required>
+          <el-input
+            v-model="returnDialog.disposalContent"
+            type="textarea"
+            :rows="4"
+            maxlength="500"
+            show-word-limit
+            placeholder="请输入退回说明"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="returnDialog.visible = false">取 消</el-button>
+          <el-button type="primary" :loading="actionLoading" @click="confirmReturn">确 定</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { computed, onMounted, reactive, ref } from "vue";
 import useUserStore from "@/store/modules/user";
-import { ElMessage } from "element-plus";
-import { getAlertTypeDictionaryData, getWarningDetailData } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
-import { getdbyjlistData } from "@/api/smxyjldczxt/yjxxfb/dbyj.js";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { getAlertTypeDictionaryData, getTodoListData, getWarningDetailData, upgradeWarningData, resolveWarningData, returnWarningData } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
 
 const userStore = useUserStore();
 const loading = ref(false);
@@ -212,8 +306,29 @@ const todoList = ref([]);
 const doneList = ref([]);
 const warningTypeOptions = ref([]);
 const warningLevelOptions = ref([]);
+const warningStatusOptions = ref([]);
 const detailVisible = ref(false);
 const detailData = ref({});
+const actionLoading = ref(false);
+
+const upgradeDialog = reactive({
+  visible: false,
+  row: null,
+  newLevel: "",
+  disposalContent: ""
+});
+
+const resolveDialog = reactive({
+  visible: false,
+  row: null,
+  disposalContent: ""
+});
+
+const returnDialog = reactive({
+  visible: false,
+  row: null,
+  disposalContent: ""
+});
 
 const queryParams = reactive({
   warningName: "",
@@ -248,6 +363,13 @@ const warningLevelLabelMap = computed(() => {
   }, {});
 });
 
+const warningStatusLabelMap = computed(() => {
+  return warningStatusOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
 const currentList = computed(() => (activeTab.value === "TODO" ? todoList.value : doneList.value));
 const todoCount = computed(() => todoList.value.length);
 const doneCount = computed(() => doneList.value.length);
@@ -308,21 +430,23 @@ function getLevelTagType(level) {
   }[level] || "info";
 }
 
-async function ensureUserId() {
-  if (userStore.id) {
-    return userStore.id;
+async function ensureUserName() {
+  if (userStore.name) {
+    return userStore.name;
   }
   await userStore.getInfo();
-  return userStore.id;
+  return userStore.name;
 }
 
 async function loadDicts() {
-  const [typeRes, levelRes] = await Promise.all([
-    getAlertTypeDictionaryData({ dictType: "warning_type" }),
-    getAlertTypeDictionaryData({ dictType: "warning_level" })
+  const [typeRes, levelRes, statusRes] = await Promise.all([
+    getAlertTypeDictionaryData({ dictType: "alert_type" }),
+    getAlertTypeDictionaryData({ dictType: "alert_level" }),
+    getAlertTypeDictionaryData({ dictType: "pre_alarm_status" })
   ]);
   warningTypeOptions.value = typeRes.rows || [];
   warningLevelOptions.value = levelRes.rows || [];
+  warningStatusOptions.value = statusRes.rows || [];
 }
 
 async function enrichTodoRows(records) {
@@ -359,15 +483,15 @@ async function enrichTodoRows(records) {
 }
 
 async function loadTodoList(type) {
-  const userId = await ensureUserId();
-  const res = await getdbyjlistData({
+  const userName = await ensureUserName();
+  const res = await getTodoListData({
     pageNum: 1,
     pageSize: 100,
-    userId,
+    userName,
     todoType: type,
     warningName: queryParams.warningName || undefined
   });
-  const records = res.data?.records || [];
+  const records = res.data?.records || res.data || [];
   return enrichTodoRows(records);
 }
 
@@ -429,6 +553,99 @@ function handleViewDetail(row) {
   detailVisible.value = true;
 }
 
+function openUpgradeDialog(row) {
+  upgradeDialog.row = row;
+  upgradeDialog.newLevel = "";
+  upgradeDialog.disposalContent = "";
+  upgradeDialog.visible = true;
+}
+
+function openResolveDialog(row) {
+  resolveDialog.row = row;
+  resolveDialog.disposalContent = "";
+  resolveDialog.visible = true;
+}
+
+function openReturnDialog(row) {
+  returnDialog.row = row;
+  returnDialog.disposalContent = "";
+  returnDialog.visible = true;
+}
+
+async function confirmUpgrade() {
+  if (!upgradeDialog.newLevel) {
+    ElMessage.warning("请选择新预警级别");
+    return;
+  }
+  if (!upgradeDialog.disposalContent) {
+    ElMessage.warning("请输入处置说明");
+    return;
+  }
+  actionLoading.value = true;
+  try {
+    const res = await upgradeWarningData({
+      warningId: upgradeDialog.row.warningId,
+      newLevel: upgradeDialog.newLevel,
+      disposalContent: upgradeDialog.disposalContent
+    });
+    if (res.code === 200) {
+      ElMessage.success("升级成功");
+      upgradeDialog.visible = false;
+      await loadCurrentTabData();
+    } else {
+      ElMessage.error(res.msg || "升级失败");
+    }
+  } finally {
+    actionLoading.value = false;
+  }
+}
+
+async function confirmResolve() {
+  if (!resolveDialog.disposalContent) {
+    ElMessage.warning("请输入处置说明");
+    return;
+  }
+  actionLoading.value = true;
+  try {
+    const res = await resolveWarningData({
+      warningId: resolveDialog.row.warningId,
+      disposalContent: resolveDialog.disposalContent
+    });
+    if (res.code === 200) {
+      ElMessage.success("解除成功");
+      resolveDialog.visible = false;
+      await loadCurrentTabData();
+    } else {
+      ElMessage.error(res.msg || "解除失败");
+    }
+  } finally {
+    actionLoading.value = false;
+  }
+}
+
+async function confirmReturn() {
+  if (!returnDialog.disposalContent) {
+    ElMessage.warning("请输入退回说明");
+    return;
+  }
+  actionLoading.value = true;
+  try {
+    const res = await returnWarningData({
+      warningId: returnDialog.row.warningId,
+      disposalContent: returnDialog.disposalContent
+    });
+    if (res.code === 200) {
+      ElMessage.success("退回成功");
+      returnDialog.visible = false;
+      await loadCurrentTabData();
+    } else {
+      ElMessage.error(res.msg || "退回失败");
+    }
+  } finally {
+    actionLoading.value = false;
+  }
+}
+
 onMounted(async () => {
   await loadDicts();
   await preloadCounts();

+ 1 - 1
src/views/subSystem/waterSupply/wjcbj/jcbjyzt.vue

@@ -501,7 +501,7 @@ onUnmounted(() => window.removeEventListener('resize', renderMap))
 .alert-value { font-size: 12px; color: #f56c6c; margin-bottom: 4px; }
 .alert-time { font-size: 11px; color: #aaa; }
 
-
+.gis-container {
   flex: 1;
   background: white;
   border-radius: 16px;