Explorar o código

refactor(manholeCover): 重构井盖案件流转页面

- 将页面布局改为响应式设计,使用 Tailwind CSS 样式
- 替换模拟数据为真实 API 数据,集成案件分页查询功能
- 优化案件列表展示,添加设备编号和时间格式化显示
- 重构案件详情面板,按案件信息、设备信息分类展示
- 移除原有的任务督办和办理流程相关功能模块
- 添加案件附件图片预览功能
- 优化筛选和搜索功能,支持按案件编号和设备位置搜索
- 统一状态管理和数据格式化逻辑
nahida hai 1 semana
pai
achega
0a3f8e3574
Modificáronse 1 ficheiros con 229 adicións e 388 borrados
  1. 229 388
      src/views/subSystem/manholeCover/caseHandling/circulation.vue

+ 229 - 388
src/views/subSystem/manholeCover/caseHandling/circulation.vue

@@ -1,462 +1,303 @@
 <!--案件流转管理-->
 <!--案件流转管理-->
 <template>
 <template>
-  <div class="case-management">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <div class="header-stats">
-        <div class="stat" @click="filterStatus('')">
-          <div class="num">{{ totalCases }}</div>
+  <div class="min-h-screen bg-[#f0f2f6] p-5">
+    <div class="mb-5 rounded-[20px] bg-white px-6 py-4">
+      <div class="flex flex-wrap gap-6">
+        <div class="min-w-[120px] cursor-pointer rounded-2xl px-4 py-2 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === '' }" @click="filterStatus('')">
+          <div class="text-[26px] font-bold">{{ totalCases }}</div>
           <div>全部案件</div>
           <div>全部案件</div>
         </div>
         </div>
-        <div class="stat" @click="filterStatus('pending')">
-          <div class="num warning">{{ pendingCount }}</div>
+        <div class="min-w-[120px] cursor-pointer rounded-2xl px-4 py-2 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === 'pending' }" @click="filterStatus('pending')">
+          <div class="text-[26px] font-bold text-[#e6a23c]">{{ pendingCount }}</div>
           <div>待核查</div>
           <div>待核查</div>
         </div>
         </div>
-        <div class="stat" @click="filterStatus('processing')">
-          <div class="num">{{ processingCount }}</div>
+        <div class="min-w-[120px] cursor-pointer rounded-2xl px-4 py-2 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === 'processing' }" @click="filterStatus('processing')">
+          <div class="text-[26px] font-bold">{{ processingCount }}</div>
           <div>办理中</div>
           <div>办理中</div>
         </div>
         </div>
-        <div class="stat" @click="filterStatus('completed')">
-          <div class="num">{{ completedCount }}</div>
+        <div class="min-w-[120px] cursor-pointer rounded-2xl px-4 py-2 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === 'completed' }" @click="filterStatus('completed')">
+          <div class="text-[26px] font-bold">{{ completedCount }}</div>
           <div>已办结</div>
           <div>已办结</div>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
 
 
-    <div class="main-layout">
-      <!-- 左侧案件列表 -->
-      <div class="case-list-panel">
-        <div class="list-header">
+    <div class="flex gap-5">
+      <div class="w-[380px] flex flex-col overflow-hidden rounded-[20px] bg-white" v-loading="loading">
+        <div class="flex justify-between border-b border-[#e9ecef] px-4 py-4 font-600">
           <span>案件列表</span>
           <span>案件列表</span>
-          <el-input v-model="searchKeyword" placeholder="搜索案件编号/位置" prefix-icon="Search" clearable size="small" style="width: 160px" />
+          <el-input v-model="searchKeyword" placeholder="搜索案件编号/设备位置" :prefix-icon="Search" clearable size="small" class="w-[180px]" @keyup.enter="loadCases" />
         </div>
         </div>
-        <div class="case-list">
+        <div class="flex-1 overflow-y-auto p-3">
           <div
           <div
-              v-for="caseItem in filteredCases"
-              :key="caseItem.id"
-              class="case-item"
-              :class="{ active: selectedCase?.id === caseItem.id, [caseItem.status]: true }"
-              @click="selectCase(caseItem)"
+            v-for="caseItem in paginatedCases"
+            :key="caseItem.caseId"
+            class="mb-2 flex cursor-pointer items-center gap-3 rounded-[14px] border border-transparent bg-[#fafbfc] p-3"
+            :class="{ 'border-[#409eff] bg-[#ecf5ff]': selectedCase?.caseId === caseItem.caseId }"
+            @click="selectCase(caseItem)"
           >
           >
             <div class="case-level">
             <div class="case-level">
-              <el-tag :type="caseItem.level === 'critical' ? 'danger' : 'warning'" size="small">
-                {{ caseItem.level === 'critical' ? '严重' : '一般' }}
-              </el-tag>
+              <el-tag :type="caseItem.levelTag" size="small">{{ caseItem.levelText }}</el-tag>
             </div>
             </div>
-            <div class="case-info">
-              <div class="case-title">{{ caseItem.code }}</div>
-              <div class="case-location"><el-icon><Location /></el-icon> {{ caseItem.locationShort }}</div>
-              <div class="case-time">{{ caseItem.alertTime }}</div>
+            <div class="flex-1">
+              <div class="mb-1 font-600">{{ caseItem.caseNo }}</div>
+              <div class="flex items-center gap-1 text-[12px] text-[#6c757d]"><el-icon><Location /></el-icon> 设备编号 {{ caseItem.deviceCode || '—' }}</div>
+              <div class="text-[11px] text-[#aaa]">{{ caseItem.createTimeText }}</div>
             </div>
             </div>
             <div class="case-status">
             <div class="case-status">
-              <el-tag :type="getStatusType(caseItem.status)" size="small">{{ getStatusText(caseItem.status) }}</el-tag>
+              <el-tag :type="caseItem.statusTag" size="small">{{ caseItem.statusText }}</el-tag>
             </div>
             </div>
           </div>
           </div>
+          <el-empty v-if="!paginatedCases.length" description="暂无案件数据" :image-size="80" />
         </div>
         </div>
-        <div class="pagination-area">
+        <div class="border-t border-[#eee] p-3 text-center">
           <el-pagination small layout="prev, pager, next" :total="filteredCases.length" :page-size="pageSize" v-model:current-page="currentPage" />
           <el-pagination small layout="prev, pager, next" :total="filteredCases.length" :page-size="pageSize" v-model:current-page="currentPage" />
         </div>
         </div>
       </div>
       </div>
 
 
-      <!-- 右侧案件详情与核查操作区 -->
-      <div class="detail-panel" v-if="selectedCase">
-        <!-- 基本信息与地理位置双栏 -->
-        <div class="detail-two-col">
-          <div class="info-section">
-            <div class="section-title">
+      <div class="max-h-[calc(100vh-140px)] flex-1 overflow-y-auto rounded-[20px] bg-white p-5" v-if="selectedCase" v-loading="detailLoading">
+        <div class="mb-5 flex gap-5">
+          <div class="flex-1">
+            <div class="mb-[14px] flex items-center gap-2 text-[16px] font-600">
               <span><el-icon><InfoFilled /></el-icon> 案件信息</span>
               <span><el-icon><InfoFilled /></el-icon> 案件信息</span>
-              <div class="task-role">
-                <el-select v-model="selectedRole" placeholder="指定督办角色" size="small" style="width: 140px">
-                  <el-option label="网格巡查员" value="inspector" />
-                  <el-option label="维修班组" value="repair" />
-                  <el-option label="应急中心" value="emergency" />
-                </el-select>
-                <el-button type="primary" size="small" @click="assignTask">指派督办</el-button>
-              </div>
             </div>
             </div>
             <el-descriptions :column="1" border>
             <el-descriptions :column="1" border>
-              <el-descriptions-item label="案件编号">{{ selectedCase.code }}</el-descriptions-item>
-              <el-descriptions-item label="报警级别">
-                <el-tag :type="selectedCase.level === 'critical' ? 'danger' : 'warning'">{{ selectedCase.level === 'critical' ? '严重报警' : '一般报警' }}</el-tag>
+              <el-descriptions-item label="案件ID">{{ selectedCase.caseId || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="案件编号">{{ selectedCase.caseNo }}</el-descriptions-item>
+              <el-descriptions-item label="报警ID">{{ selectedCase.alarmId || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="案件等级">
+                <el-tag :type="selectedCase.levelTag">{{ selectedCase.levelText }}</el-tag>
               </el-descriptions-item>
               </el-descriptions-item>
-              <el-descriptions-item label="报警描述">{{ selectedCase.alertDesc }}</el-descriptions-item>
-              <el-descriptions-item label="报警时间">{{ selectedCase.alertTime }}</el-descriptions-item>
-              <el-descriptions-item label="当前状态">{{ getStatusText(selectedCase.status) }}</el-descriptions-item>
-              <el-descriptions-item label="指定督办角色">{{ selectedCase.assignRole || '未指定' }}</el-descriptions-item>
+              <el-descriptions-item label="案件来源">{{ selectedCase.sourceText }}</el-descriptions-item>
+              <el-descriptions-item label="案件描述">{{ selectedCase.caseDesc || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="案件状态">{{ selectedCase.statusText }}</el-descriptions-item>
+              <el-descriptions-item label="立案人ID">{{ selectedCase.fileUser || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="立案时间">{{ selectedCase.createTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="核查人ID">{{ selectedCase.verifyUser || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="核查时间">{{ selectedCase.verifyTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="办结时间">{{ selectedCase.finishTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="处置结果">{{ selectedCase.handleResult || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="更新时间">{{ selectedCase.updateTimeText }}</el-descriptions-item>
             </el-descriptions>
             </el-descriptions>
           </div>
           </div>
-          <div class="location-section">
-            <div class="section-title"><el-icon><MapLocation /></el-icon> 地理位置</div>
-            <div class="map-mini">
-              <div class="mock-map">
-                <el-icon :size="36" color="#f56c6c"><Location /></el-icon>
-                <div>{{ selectedCase.address }}</div>
-                <div class="coords">经度: {{ selectedCase.lng }}, 纬度: {{ selectedCase.lat }}</div>
-              </div>
-            </div>
-            <!-- 井盖设备信息 -->
-            <div class="device-info">
-              <div class="sub-title"><el-icon><Monitor /></el-icon> 井盖设备信息</div>
-              <el-descriptions :column="2" size="small" border>
-                <el-descriptions-item label="设备编号">{{ selectedCase.deviceId }}</el-descriptions-item>
-                <el-descriptions-item label="设备类型">{{ selectedCase.deviceType }}</el-descriptions-item>
-                <el-descriptions-item label="设备型号">{{ selectedCase.deviceModel }}</el-descriptions-item>
-                <el-descriptions-item label="安装日期">{{ selectedCase.installDate }}</el-descriptions-item>
-                <el-descriptions-item label="当前状态">{{ selectedCase.deviceStatus }}</el-descriptions-item>
-                <el-descriptions-item label="最近通讯">{{ selectedCase.lastComm }}</el-descriptions-item>
-              </el-descriptions>
-            </div>
-          </div>
-        </div>
-
-        <!-- 报警多媒体信息 -->
-        <div class="media-section">
-          <div class="section-title"><el-icon><Picture /></el-icon> 报警多媒体信息</div>
-          <div class="media-gallery">
-            <el-image v-for="(img, idx) in selectedCase.mediaImages" :key="idx" :src="img" fit="cover" class="media-img" />
-            <div v-if="selectedCase.videoUrl" class="video-preview" @click="playVideo">▶ 查看现场视频</div>
-            <div v-if="!selectedCase.mediaImages?.length && !selectedCase.videoUrl" class="no-media">暂无多媒体附件</div>
+          <div class="flex-1">
+            <div class="mb-[14px] flex items-center gap-2 text-[16px] font-600"><el-icon><Monitor /></el-icon> 设备信息</div>
+            <el-descriptions :column="1" border>
+              <el-descriptions-item label="设备ID">{{ selectedCase.deviceId || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="设备编号">{{ selectedCase.deviceCode || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="设备类别">{{ selectedCase.equipmentTypeName || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="运维状态">{{ selectedCase.currentStatusText }}</el-descriptions-item>
+              <el-descriptions-item label="在线状态">{{ selectedCase.onlineStatusText }}</el-descriptions-item>
+              <el-descriptions-item label="报警状态">{{ selectedCase.alarmStatusText }}</el-descriptions-item>
+              <el-descriptions-item label="状态更新时间">{{ selectedCase.statusUpdateTimeText }}</el-descriptions-item>
+            </el-descriptions>
           </div>
           </div>
         </div>
         </div>
 
 
-        <!-- 办理经过 & 处置信息时间轴 -->
-        <div class="process-section">
-          <div class="section-title"><el-icon><Document /></el-icon> 办理经过 & 处置信息</div>
-          <el-timeline>
-            <el-timeline-item v-for="process in selectedCase.processLogs" :key="process.id" :timestamp="process.time" :type="process.type">
-              {{ process.content }}
-              <span v-if="process.operator" class="operator">操作人: {{ process.operator }}</span>
-            </el-timeline-item>
-          </el-timeline>
-        </div>
-
-        <!-- 核查操作栏 (任务督办核心) -->
-        <div class="action-bar">
-          <div class="section-title"><el-icon><Operation /></el-icon> 案件处置操作</div>
-          <div class="action-buttons" v-if="selectedCase.status !== 'completed'">
-            <el-button type="primary" @click="startVerification" :disabled="selectedCase.status !== 'pending'">核查</el-button>
-            <el-button type="success" @click="completeCase" :disabled="selectedCase.status === 'pending'">办结</el-button>
-            <el-button type="warning" @click="redispatchCase" :disabled="selectedCase.status === 'pending'">再次派遣</el-button>
-            <el-button @click="viewTaskDetail">查看处理详情</el-button>
+        <div class="mt-6 border-t border-[#e9ecef] pt-4">
+          <div class="mb-[14px] flex items-center gap-2 text-[16px] font-600"><el-icon><Picture /></el-icon> 案件附件</div>
+          <div class="flex flex-wrap gap-3" v-if="selectedCase.attachImages.length">
+            <el-image
+              v-for="(img, idx) in selectedCase.attachImages"
+              :key="idx"
+              :src="img"
+              fit="cover"
+              class="h-[80px] w-[100px] cursor-pointer rounded-lg object-cover"
+              :preview-src-list="selectedCase.attachImages"
+              preview-teleported
+            />
           </div>
           </div>
-          <div v-else class="completed-info">案件已于 {{ selectedCase.completeTime }} 办结,全程督办记录可查。</div>
+          <el-empty v-else description="暂无案件附件" :image-size="80" />
         </div>
         </div>
       </div>
       </div>
 
 
-      <!-- 未选中案件的空状态 -->
-      <div v-else class="empty-panel">
-        <el-empty description="请从左侧选择案件进行核查与督办" :image-size="140" />
+      <div v-else class="flex flex-1 items-center justify-center rounded-[20px] bg-white">
+        <el-empty description="请从左侧选择案件进行核查" :image-size="140" />
       </div>
       </div>
     </div>
     </div>
-
-    <!-- 处理详情弹窗 -->
-    <el-dialog v-model="detailDialogVisible" title="任务处理详情 - 全程督办记录" width="600px">
-      <el-timeline>
-        <el-timeline-item v-for="log in selectedCase?.processLogs" :key="log.id" :timestamp="log.time" :type="log.type">
-          {{ log.content }} <span v-if="log.operator" class="operator-dialog">【{{ log.operator }}】</span>
-        </el-timeline-item>
-      </el-timeline>
-      <template #footer>
-        <el-button @click="detailDialogVisible = false">关闭</el-button>
-      </template>
-    </el-dialog>
-
-    <!-- 再次派遣对话框 -->
-    <el-dialog v-model="redispatchDialogVisible" title="再次派遣" width="400px">
-      <el-form>
-        <el-form-item label="指派角色">
-          <el-select v-model="newAssignRole" placeholder="选择角色">
-            <el-option label="网格巡查员" value="inspector" />
-            <el-option label="维修班组" value="repair" />
-            <el-option label="应急中心" value="emergency" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="派遣备注">
-          <el-input type="textarea" v-model="dispatchRemark" placeholder="请输入派遣说明" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="redispatchDialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmRedispatch">确认派遣</el-button>
-      </template>
-    </el-dialog>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref, computed } from 'vue'
-import { Tickets, Location, InfoFilled, MapLocation, Monitor, Picture, Document, Operation, Search } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-
-// 模拟案件数据
-const mockCases = [
-  {
-    id: 1, code: 'CASE-20250402-001', level: 'critical', alertDesc: '井盖倾斜角度14.2°,超过阈值8°,有跌落风险', alertTime: '2025-04-02 14:23:15',
-    locationShort: '人民路与解放路口', address: '人民路与解放路口东50m', lng: 121.487, lat: 31.249,
-    deviceId: 'MN-1001', deviceType: '智能井盖监测仪', deviceModel: 'WATCHMAN-S2', installDate: '2024-03-15', deviceStatus: '倾斜报警', lastComm: '2025-04-02 14:20',
-    mediaImages: ['https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'],
-    videoUrl: null,
-    status: 'pending',  // pending, processing, completed
-    assignRole: null,
-    processLogs: [
-      { id: 1, time: '2025-04-02 14:23:20', content: '系统自动生成报警案件', type: 'danger', operator: '系统' }
-    ],
-    completeTime: null
-  },
-  {
-    id: 2, code: 'CASE-20250402-002', level: 'critical', alertDesc: '井下水位62cm,超过阈值40cm,溢水风险', alertTime: '2025-04-02 13:58:12',
-    locationShort: '滨江路化工园区', address: '滨江路化工园区南门', lng: 121.502, lat: 31.235,
-    deviceId: 'MN-1002', deviceType: '防爆井盖监测仪', deviceModel: 'WATCHMAN-EX', installDate: '2024-06-20', deviceStatus: '水位超限', lastComm: '2025-04-02 13:55',
-    mediaImages: [],
-    videoUrl: 'demo.mp4',
-    status: 'processing',
-    assignRole: 'repair',
-    processLogs: [
-      { id: 1, time: '2025-04-02 13:58:20', content: '系统自动生成报警案件', type: 'danger', operator: '系统' },
-      { id: 2, time: '2025-04-02 14:10:00', content: '已指派维修班组现场勘查', type: 'warning', operator: '调度中心-李工' },
-      { id: 3, time: '2025-04-02 14:30:15', content: '维修人员已到达现场,开始排水作业', type: 'info', operator: '维修班组-张三' }
-    ],
-    completeTime: null
-  },
-  {
-    id: 3, code: 'CASE-20250402-003', level: 'warning', alertDesc: '异常震动112mg,超过阈值80mg,疑似外力冲击', alertTime: '2025-04-02 12:10:33',
-    locationShort: '古城街北段', address: '古城街北段树下', lng: 121.478, lat: 31.258,
-    deviceId: 'MN-1003', deviceType: '普通井盖监测仪', deviceModel: 'WATCHMAN-Basic', installDate: '2023-11-10', deviceStatus: '震动报警', lastComm: '2025-04-02 12:08',
-    mediaImages: ['https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg'],
-    videoUrl: null,
-    status: 'completed',
-    assignRole: 'inspector',
-    processLogs: [
-      { id: 1, time: '2025-04-02 12:10:40', content: '系统自动生成报警案件', type: 'danger', operator: '系统' },
-      { id: 2, time: '2025-04-02 12:35:00', content: '网格员现场核查,确认为重型车辆碾轧导致,井盖轻微移位', type: 'primary', operator: '巡查员-王明' },
-      { id: 3, time: '2025-04-02 13:20:00', content: '维修人员复位井盖,异常消除', type: 'success', operator: '维修班组' },
-      { id: 4, time: '2025-04-02 14:00:00', content: '案件办结', type: 'success', operator: '系统' }
-    ],
-    completeTime: '2025-04-02 14:00:00'
-  },
-  {
-    id: 4, code: 'CASE-20250402-004', level: 'warning', alertDesc: '井盖异常开启,非正常打开', alertTime: '2025-04-02 11:45:20',
-    locationShort: '南港路旧车站', address: '南港路旧车站段', lng: 121.495, lat: 31.228,
-    deviceId: 'MN-1005', deviceType: '智能井盖监测仪', deviceModel: 'WATCHMAN-S2', installDate: '2024-01-18', deviceStatus: '开启报警', lastComm: '2025-04-02 11:44',
-    mediaImages: [],
-    videoUrl: null,
-    status: 'pending',
-    assignRole: null,
-    processLogs: [{ id: 1, time: '2025-04-02 11:45:25', content: '系统自动生成报警案件', type: 'danger', operator: '系统' }],
-    completeTime: null
-  }
-]
+import {computed, onMounted, ref} from 'vue'
+import {InfoFilled, Location, Monitor, Picture, Search} from '@element-plus/icons-vue'
+import {getManholeCaseDetail, getManholeCasePage} from '@/api/pipeNetwork/basic'
 
 
+const cases = ref([])
+const loading = ref(false)
+const detailLoading = ref(false)
 const searchKeyword = ref('')
 const searchKeyword = ref('')
 const statusFilter = ref('')
 const statusFilter = ref('')
 const currentPage = ref(1)
 const currentPage = ref(1)
 const pageSize = 5
 const pageSize = 5
-const selectedCase = ref(mockCases[0])
-const selectedRole = ref('')
-const detailDialogVisible = ref(false)
-const redispatchDialogVisible = ref(false)
-const newAssignRole = ref('')
-const dispatchRemark = ref('')
+const selectedCase = ref(null)
 
 
-const filteredCases = computed(() => {
-  let list = mockCases
-  if (statusFilter.value) list = list.filter(c => c.status === statusFilter.value)
-  if (searchKeyword.value) {
-    const kw = searchKeyword.value.toLowerCase()
-    list = list.filter(c => c.code.toLowerCase().includes(kw) || c.locationShort.toLowerCase().includes(kw))
-  }
-  return list
-})
+function formatDateTime(value) {
+  if (!value) return '—'
+  return String(value).replace('T', ' ')
+}
 
 
-const totalCases = computed(() => mockCases.length)
-const pendingCount = computed(() => mockCases.filter(c => c.status === 'pending').length)
-const processingCount = computed(() => mockCases.filter(c => c.status === 'processing').length)
-const completedCount = computed(() => mockCases.filter(c => c.status === 'completed').length)
+function getCaseLevelMeta(level) {
+  const map = {
+    1: { text: '严重', tag: 'danger' },
+    2: { text: '一般', tag: 'warning' },
+    3: { text: '轻微', tag: 'info' }
+  }
+  return map[Number(level)] || { text: '—', tag: 'info' }
+}
 
 
-const filterStatus = (status) => { statusFilter.value = status; currentPage.value = 1 }
-const selectCase = (caseItem) => { selectedCase.value = caseItem; selectedRole.value = caseItem.assignRole || '' }
-const getStatusType = (status) => {
-  if (status === 'pending') return 'danger'
-  if (status === 'processing') return 'warning'
-  return 'success'
+function getCaseStatusMeta(status) {
+  const map = {
+    1: { key: 'pending', text: '待核查', tag: 'danger' },
+    2: { key: 'processing', text: '办理中', tag: 'warning' },
+    3: { key: 'completed', text: '已办结', tag: 'success' },
+    4: { key: 'closed', text: '误报关闭', tag: 'info' }
+  }
+  return map[Number(status)] || { key: '', text: '—', tag: 'info' }
 }
 }
-const getStatusText = (status) => {
-  if (status === 'pending') return '待核查'
-  if (status === 'processing') return '办理中'
-  return '已办结'
+
+function getCaseSourceText(source) {
+  const map = {
+    1: '系统自动报警',
+    2: '运维巡检上报',
+    3: '人工投诉上报',
+    4: '平台录入'
+  }
+  return map[Number(source)] || '—'
 }
 }
 
 
-// 核查操作
-const startVerification = () => {
-  if (selectedCase.value.status !== 'pending') return
-  selectedCase.value.status = 'processing'
-  selectedCase.value.processLogs.push({
-    id: Date.now(),
-    time: new Date().toLocaleString(),
-    content: '开始核查,已指派人员现场核实',
-    type: 'primary',
-    operator: '核查中心'
-  })
-  ElMessage.success('已启动核查,案件状态更新为办理中')
+function splitAttachUrls(value) {
+  if (!value) return []
+  return String(value)
+    .split(/[,\n;]/)
+    .map(item => item.trim())
+    .filter(Boolean)
 }
 }
 
 
-// 办结操作
-const completeCase = () => {
-  if (selectedCase.value.status === 'completed') return
-  selectedCase.value.status = 'completed'
-  selectedCase.value.completeTime = new Date().toLocaleString()
-  selectedCase.value.processLogs.push({
-    id: Date.now(),
-    time: new Date().toLocaleString(),
-    content: '案件办结,已完成处置并验收',
-    type: 'success',
-    operator: '督办员'
-  })
-  ElMessage.success('案件已办结')
+function getOnlineStatusText(status) {
+  const map = {
+    0: '离线',
+    1: '在线'
+  }
+  return map[Number(status)] || '—'
 }
 }
 
 
-// 再次派遣
-const redispatchCase = () => {
-  redispatchDialogVisible.value = true
+function getAlarmStatusText(status) {
+  const map = {
+    0: '正常',
+    1: '报警'
+  }
+  return map[Number(status)] || '—'
 }
 }
-const confirmRedispatch = () => {
-  if (newAssignRole.value) {
-    selectedCase.value.assignRole = newAssignRole.value
-    selectedCase.value.processLogs.push({
-      id: Date.now(),
-      time: new Date().toLocaleString(),
-      content: `再次派遣至角色: ${newAssignRole.value === 'inspector' ? '网格巡查员' : newAssignRole.value === 'repair' ? '维修班组' : '应急中心'},备注: ${dispatchRemark.value || '无'}`,
-      type: 'warning',
-      operator: '督办中心'
-    })
-    ElMessage.success('再次派遣成功,已通知对应角色')
-  } else {
-    ElMessage.warning('请选择指派角色')
+
+function getCurrentStatusText(status) {
+  const map = {
+    1: '在用',
+    2: '闲置',
+    3: '维修',
+    4: '报废',
+    5: '待入库'
   }
   }
-  redispatchDialogVisible.value = false
-  newAssignRole.value = ''
-  dispatchRemark.value = ''
+  return map[Number(status)] || '—'
 }
 }
 
 
-const assignTask = () => {
-  if (!selectedRole.value) {
-    ElMessage.warning('请选择督办角色')
-    return
+function normalizeCase(item) {
+  const levelMeta = getCaseLevelMeta(item.caseLevel)
+  const statusMeta = getCaseStatusMeta(item.caseStatus)
+  return {
+    ...item,
+    levelText: levelMeta.text,
+    levelTag: levelMeta.tag,
+    statusKey: statusMeta.key,
+    statusText: statusMeta.text,
+    statusTag: statusMeta.tag,
+    sourceText: getCaseSourceText(item.caseSource),
+    createTimeText: formatDateTime(item.createTime),
+    verifyTimeText: formatDateTime(item.verifyTime),
+    finishTimeText: formatDateTime(item.finishTime),
+    updateTimeText: formatDateTime(item.updateTime),
+    attachImages: splitAttachUrls(item.attachUrl),
+    currentStatusText: getCurrentStatusText(item.equipmentStatus?.currentStatus),
+    onlineStatusText: getOnlineStatusText(item.equipmentStatus?.onlineStatus),
+    alarmStatusText: getAlarmStatusText(item.equipmentStatus?.alarmStatus),
+    statusUpdateTimeText: formatDateTime(item.equipmentStatus?.statusUpdateTime)
   }
   }
-  selectedCase.value.assignRole = selectedRole.value
-  selectedCase.value.processLogs.push({
-    id: Date.now(),
-    time: new Date().toLocaleString(),
-    content: `已指派角色 ${selectedRole.value === 'inspector' ? '网格巡查员' : selectedRole.value === 'repair' ? '维修班组' : '应急中心'} 进行全程督办`,
-    type: 'primary',
-    operator: '系统'
-  })
-  ElMessage.success(`已指定${selectedRole.value}角色进行任务督办`)
 }
 }
 
 
-const viewTaskDetail = () => { detailDialogVisible.value = true }
-const playVideo = () => { ElMessage.info('视频播放功能演示') }
+const filteredCases = computed(() => {
+  if (!statusFilter.value) return cases.value
+  return cases.value.filter(item => item.statusKey === statusFilter.value)
+})
 
 
-</script>
+const paginatedCases = computed(() => {
+  const start = (currentPage.value - 1) * pageSize
+  return filteredCases.value.slice(start, start + pageSize)
+})
 
 
-<style scoped>
-.case-management {
-  background: #f0f2f6;
-  min-height: 100vh;
-  padding: 20px;
-}
-.page-header {
-  background: white;
-  border-radius: 20px;
-  padding: 16px 24px;
-  margin-bottom: 20px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.header-title { display: flex; align-items: center; gap: 14px; font-size: 20px; font-weight: 600; }
-.header-stats { display: flex; gap: 24px; }
-.stat { text-align: center; cursor: pointer; padding: 4px 16px; border-radius: 30px; }
-.stat:hover { background: #ecf5ff; }
-.num { font-size: 26px; font-weight: bold; }
-.num.warning { color: #e6a23c; }
-.main-layout { display: flex; gap: 20px; }
-.case-list-panel {
-  width: 380px;
-  background: white;
-  border-radius: 20px;
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
-}
-.list-header {
-  padding: 16px;
-  border-bottom: 1px solid #e9ecef;
-  display: flex;
-  justify-content: space-between;
-  font-weight: 600;
-}
-.case-list { flex: 1; overflow-y: auto; padding: 12px; }
-.case-item {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  padding: 12px;
-  margin-bottom: 8px;
-  border-radius: 14px;
-  background: #fafbfc;
-  cursor: pointer;
-  border: 1px solid transparent;
-}
-.case-item.active { background: #ecf5ff; border-color: #409eff; }
-.case-item.pending { border-left: 3px solid #f56c6c; }
-.case-item.processing { border-left: 3px solid #e6a23c; }
-.case-info { flex: 1; }
-.case-title { font-weight: 600; margin-bottom: 4px; }
-.case-location { font-size: 12px; color: #6c757d; display: flex; align-items: center; gap: 4px; }
-.case-time { font-size: 11px; color: #aaa; }
-.detail-panel {
-  flex: 1;
-  background: white;
-  border-radius: 20px;
-  padding: 20px;
-  overflow-y: auto;
-  max-height: calc(100vh - 140px);
-}
-.detail-two-col { display: flex; gap: 20px; margin-bottom: 20px; }
-.info-section { flex: 1; }
-.location-section { flex: 1; }
-.section-title {
-  font-weight: 600;
-  font-size: 16px;
-  margin-bottom: 14px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
+const totalCases = computed(() => cases.value.length)
+const pendingCount = computed(() => cases.value.filter(item => item.statusKey === 'pending').length)
+const processingCount = computed(() => cases.value.filter(item => item.statusKey === 'processing').length)
+const completedCount = computed(() => cases.value.filter(item => item.statusKey === 'completed').length)
+
+async function loadCases() {
+  loading.value = true
+  try {
+    const keyword = searchKeyword.value.trim()
+    const res = await getManholeCasePage({
+      pageNum: 1,
+      pageSize: 200,
+      caseNo: keyword || undefined,
+      equipmentLocation: keyword || undefined
+    })
+    const records = Array.isArray(res?.data?.records) ? res.data.records : []
+    cases.value = records.map(item => normalizeCase(item))
+    currentPage.value = 1
+    const first = filteredCases.value[0]
+    if (!first) {
+      selectedCase.value = null
+      return
+    }
+    if (selectedCase.value) {
+      const matched = cases.value.find(item => item.caseId === selectedCase.value.caseId)
+      if (matched) {
+        await selectCase(matched)
+        return
+      }
+    }
+    await selectCase(first)
+  } catch (error) {
+    cases.value = []
+    selectedCase.value = null
+  } finally {
+    loading.value = false
+  }
 }
 }
-.mock-map {
-  background: #e9f5e9;
-  border-radius: 16px;
-  height: 130px;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 16px;
+
+async function selectCase(caseItem) {
+  detailLoading.value = true
+  try {
+    const res = await getManholeCaseDetail(caseItem.caseId)
+    const detail = res?.data || caseItem
+    selectedCase.value = normalizeCase(detail)
+  } catch (error) {
+    selectedCase.value = caseItem
+  } finally {
+    detailLoading.value = false
+  }
 }
 }
-.coords { font-size: 11px; color: #666; margin-top: 4px; }
-.device-info { margin-top: 12px; }
-.sub-title { font-weight: 500; margin-bottom: 8px; display: flex; align-items: center; gap: 6px; }
-.media-section, .process-section, .action-bar {
-  margin-top: 24px;
-  border-top: 1px solid #e9ecef;
-  padding-top: 16px;
+
+function filterStatus(status) {
+  statusFilter.value = status
+  currentPage.value = 1
+  const first = filteredCases.value[0]
+  if (first) {
+    selectCase(first)
+  } else {
+    selectedCase.value = null
+  }
 }
 }
-.media-gallery { display: flex; gap: 12px; flex-wrap: wrap; }
-.media-img { width: 100px; height: 80px; border-radius: 8px; object-fit: cover; cursor: pointer; }
-.video-preview { background: #f0f0f0; padding: 8px 16px; border-radius: 20px; cursor: pointer; display: inline-block; }
-.action-buttons { display: flex; gap: 12px; margin-top: 12px; }
-.empty-panel { flex: 1; display: flex; align-items: center; justify-content: center; background: white; border-radius: 20px; }
-.operator { font-size: 12px; color: #909399; margin-left: 12px; }
-.pagination-area { padding: 12px; text-align: center; border-top: 1px solid #eee; }
-</style>
+
+onMounted(() => {
+  loadCases()
+})
+</script>