|
|
@@ -3,16 +3,11 @@
|
|
|
<!-- 搜索区 -->
|
|
|
<div class="filter-panel">
|
|
|
<div class="filter-form">
|
|
|
- <el-select v-model="searchForm.eventStatus" placeholder="事件状态" clearable style="width:140px">
|
|
|
- <el-option label="处置中" value="处置中" />
|
|
|
- <el-option label="已解除" value="已解除" />
|
|
|
- <el-option label="已关闭" value="已关闭" />
|
|
|
+ <el-select v-model="searchForm.status" placeholder="事件状态" clearable style="width:140px">
|
|
|
+ <el-option v-for="d in trackStatusOptions" :key="d.value" :label="d.label" :value="d.value" />
|
|
|
</el-select>
|
|
|
- <el-select v-model="searchForm.category" placeholder="事件类别" clearable style="width:140px">
|
|
|
- <el-option label="管道泄漏" value="管道泄漏" />
|
|
|
- <el-option label="塌陷" value="塌陷" />
|
|
|
- <el-option label="积水" value="积水" />
|
|
|
- <el-option label="设备故障" value="设备故障" />
|
|
|
+ <el-select v-model="searchForm.eventCategory" placeholder="事件类别" clearable style="width:140px">
|
|
|
+ <el-option v-for="d in dis_event_category" :key="d.value" :label="d.label" :value="d.value" />
|
|
|
</el-select>
|
|
|
<el-date-picker
|
|
|
v-model="searchForm.dateRange"
|
|
|
@@ -32,27 +27,42 @@
|
|
|
<div class="table-panel">
|
|
|
<el-table :data="tableData" border stripe style="width:100%" v-loading="loading">
|
|
|
<el-table-column prop="eventNo" label="事件编号" width="140" />
|
|
|
- <el-table-column prop="category" label="事件类别" width="110" />
|
|
|
- <el-table-column prop="level" label="事件等级" width="100">
|
|
|
+ <el-table-column prop="eventType" label="管网类型" width="100">
|
|
|
+ <template #default="{ row }">{{ dictLabel(dis_event_type, row.eventType) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="eventCategory" label="事件类别" width="110">
|
|
|
+ <template #default="{ row }">{{ dictLabel(dis_event_category, row.eventCategory) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="eventLevel" label="事件等级" width="100">
|
|
|
<template #default="{ row }">
|
|
|
- <el-tag :type="levelTagType(row.level)" size="small">{{ row.level }}</el-tag>
|
|
|
+ <el-tag v-if="row.eventLevel" :type="levelTagType(row.eventLevel)" size="small">{{ dictLabel(dis_event_level, row.eventLevel) }}</el-tag>
|
|
|
+ <span v-else>-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="status" label="当前状态" width="100">
|
|
|
+ <el-table-column prop="location" label="发生地点" min-width="150" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">{{ row.location || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="statusName" label="当前状态" width="100">
|
|
|
<template #default="{ row }">
|
|
|
- <el-tag :type="statusTagType(row.status)" size="small">{{ row.status }}</el-tag>
|
|
|
+ <el-tag :type="statusTagType(row.status)" size="small">{{ row.statusName }}</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="lastVisitTime" label="最近回访时间" min-width="150" />
|
|
|
- <el-table-column prop="nextVisitTime" label="下次回访时间" min-width="150" />
|
|
|
- <el-table-column prop="handler" label="负责人" min-width="100" />
|
|
|
+ <el-table-column prop="handler" label="负责人" width="100">
|
|
|
+ <template #default="{ row }">{{ row.handler || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="lastVisitTime" label="最近回访" width="150">
|
|
|
+ <template #default="{ row }">{{ row.lastVisitTime || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="nextVisitTime" label="下次回访" min-width="150">
|
|
|
+ <template #default="{ row }">{{ row.nextVisitTime || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
<el-table-column label="操作" width="260" fixed="right">
|
|
|
<template #default="{ row }">
|
|
|
- <el-button v-if="row.status === '处置中'" type="primary" link size="small" @click="handleTrack(row)">跟踪</el-button>
|
|
|
- <el-button v-if="row.status === '处置中'" type="primary" link size="small" @click="handleClose(row)">解除</el-button>
|
|
|
- <el-button v-if="row.status === '已解除'" type="primary" link size="small" @click="handleClose(row)">关闭</el-button>
|
|
|
+ <el-button v-if="row.status === 1" type="primary" link size="small" @click="handleStart(row)">开始处置</el-button>
|
|
|
+ <el-button v-if="row.status === 2" type="primary" link size="small" @click="handleTrack(row)">跟踪</el-button>
|
|
|
+ <el-button v-if="row.status === 2" type="primary" link size="small" @click="handleRelease(row)">解除</el-button>
|
|
|
<el-button type="primary" link size="small" @click="handleDetail(row)">详情</el-button>
|
|
|
- <el-button type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
|
|
|
+ <el-button v-if="row.editable" type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
@@ -72,6 +82,31 @@
|
|
|
<!-- 跟踪弹窗 -->
|
|
|
<el-dialog v-model="trackVisible" title="事件跟踪" width="700px" :close-on-click-modal="false">
|
|
|
<div v-if="currentRow">
|
|
|
+ <div class="section-title">事件信息</div>
|
|
|
+ <el-form label-width="100px" size="default" style="margin-bottom:12px">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="事件编号">{{ currentRow.eventNo }}</el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="管网类型">{{ dictLabel(dis_event_type, currentRow.eventType) }}</el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="当前状态">
|
|
|
+ <el-tag :type="statusTagType(currentRow.status)" size="small">{{ currentRow.statusName }}</el-tag>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="负责人">{{ currentRow.handler || '-' }}</el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-form-item label="发生地点">
|
|
|
+ <div class="desc-text">{{ currentRow.location || '-' }}</div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
<div class="section-title">事件进展时间线</div>
|
|
|
<el-timeline>
|
|
|
<el-timeline-item
|
|
|
@@ -87,20 +122,17 @@
|
|
|
|
|
|
<div class="section-title">新增跟踪记录</div>
|
|
|
<el-form :model="trackForm" :rules="trackRules" ref="trackFormRef" label-width="120px">
|
|
|
- <el-form-item label="跟踪内容" prop="content">
|
|
|
- <el-input v-model="trackForm.content" type="textarea" :rows="3" placeholder="请输入跟踪内容" />
|
|
|
+ <el-form-item label="跟踪内容" prop="operDesc">
|
|
|
+ <el-input v-model="trackForm.operDesc" type="textarea" :rows="3" placeholder="请输入跟踪内容" />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="当前态势评估" prop="assessment">
|
|
|
- <el-radio-group v-model="trackForm.assessment">
|
|
|
- <el-radio label="趋于平稳">趋于平稳</el-radio>
|
|
|
- <el-radio label="仍在发展">仍在发展</el-radio>
|
|
|
- <el-radio label="明显恶化">明显恶化</el-radio>
|
|
|
+ <el-form-item label="当前态势评估" prop="trend">
|
|
|
+ <el-radio-group v-model="trackForm.trend">
|
|
|
+ <el-radio v-for="d in dis_event_trend" :key="d.value" :label="d.value">{{ d.label }}</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="是否需要升级" prop="needUpgrade">
|
|
|
<el-radio-group v-model="trackForm.needUpgrade">
|
|
|
- <el-radio label="否">否</el-radio>
|
|
|
- <el-radio label="是">是</el-radio>
|
|
|
+ <el-radio v-for="d in dis_yes_no" :key="d.value" :label="Number(d.value)">{{ d.label }}</el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="下次回访时间" prop="nextVisitTime">
|
|
|
@@ -114,28 +146,68 @@
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <!-- 解除/关闭弹窗 -->
|
|
|
- <el-dialog v-model="closeVisible" :title="closeTitle" width="500px" :close-on-click-modal="false">
|
|
|
- <el-form :model="closeForm" :rules="closeRules" ref="closeFormRef" label-width="100px">
|
|
|
- <el-form-item label="事件编号">{{ closeForm.eventNo }}</el-form-item>
|
|
|
- <el-form-item label="事件摘要">{{ closeForm.summary }}</el-form-item>
|
|
|
- <el-form-item label="解除原因" prop="reason">
|
|
|
- <el-select v-model="closeForm.reason" placeholder="请选择" style="width:100%">
|
|
|
- <el-option label="事件不存在" value="事件不存在" />
|
|
|
- <el-option label="处置完成" value="处置完成" />
|
|
|
- <el-option label="误报" value="误报" />
|
|
|
+ <!-- 解除弹窗 -->
|
|
|
+ <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-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="remark">
|
|
|
- <el-input v-model="closeForm.remark" type="textarea" :rows="4" placeholder="请输入解除说明" />
|
|
|
+ <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="closeForm.lossAmount" :min="0" :precision="2" style="width:100%" />
|
|
|
+ <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>
|
|
|
<template #footer>
|
|
|
- <el-button @click="closeVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" @click="confirmClose">确认</el-button>
|
|
|
+ <el-button @click="releaseVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmRelease">确认</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
@@ -144,39 +216,96 @@
|
|
|
<div v-if="currentRow" class="detail-content">
|
|
|
<el-form label-width="100px" size="default">
|
|
|
<el-row :gutter="20">
|
|
|
- <el-col :span="8">
|
|
|
+ <el-col :span="12">
|
|
|
<el-form-item label="事件编号">{{ currentRow.eventNo }}</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="8">
|
|
|
- <el-form-item label="事件类别">{{ currentRow.category }}</el-form-item>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="事件来源">{{ dictLabel(dis_event_source, currentRow.eventSource) }}</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="8">
|
|
|
- <el-form-item label="事件等级">
|
|
|
- <el-tag v-if="currentRow.level" :type="levelTagType(currentRow.level)" size="small">{{ currentRow.level }}</el-tag>
|
|
|
- </el-form-item>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="管网类型">{{ dictLabel(dis_event_type, currentRow.eventType) }}</el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="事件类别">{{ dictLabel(dis_event_category, currentRow.eventCategory) }}</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
<el-row :gutter="20">
|
|
|
- <el-col :span="8">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="事件等级">
|
|
|
+ <el-tag v-if="currentRow.eventLevel" :type="levelTagType(currentRow.eventLevel)" size="small">{{ dictLabel(dis_event_level, currentRow.eventLevel) }}</el-tag>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
<el-form-item label="当前状态">
|
|
|
- <el-tag :type="statusTagType(currentRow.status)" size="small">{{ currentRow.status }}</el-tag>
|
|
|
+ <el-tag :type="statusTagType(currentRow.status)" size="small">{{ currentRow.statusName }}</el-tag>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="8">
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
<el-form-item label="负责人">{{ currentRow.handler || '-' }}</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="8">
|
|
|
+ <el-col :span="12">
|
|
|
<el-form-item label="发生地点">{{ currentRow.location || '-' }}</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
<el-row :gutter="20">
|
|
|
- <el-col :span="8">
|
|
|
+ <el-col :span="12">
|
|
|
<el-form-item label="最近回访">{{ currentRow.lastVisitTime || '-' }}</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="8">
|
|
|
+ <el-col :span="12">
|
|
|
<el-form-item label="下次回访">{{ currentRow.nextVisitTime || '-' }}</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="解除时间">{{ currentRow.releaseTime || '-' }}</el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="损失(万元)">{{ formatLoss(currentRow.lossAmount) }}</el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-form-item v-if="currentRow.beforeUrl" label="处置前佐证">
|
|
|
+ <template v-for="(item, i) in attachItems(currentRow.beforeUrl)" :key="'b' + i">
|
|
|
+ <el-image
|
|
|
+ v-if="isImage(item.name)"
|
|
|
+ :src="item.url"
|
|
|
+ :preview-src-list="[item.url]"
|
|
|
+ preview-teleported
|
|
|
+ fit="cover"
|
|
|
+ :initial-index="0"
|
|
|
+ style="width:64px;height:48px;border-radius:4px;border:1px solid #ebeef5;margin:0 8px 6px 0;cursor:zoom-in"
|
|
|
+ />
|
|
|
+ <el-link
|
|
|
+ v-else
|
|
|
+ type="primary"
|
|
|
+ style="margin-right:16px;font-size:13px"
|
|
|
+ @click="downloadAttach(item.url, item.name)"
|
|
|
+ >{{ item.name }}(点击下载)</el-link>
|
|
|
+ </template>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="currentRow.afterUrl" label="处置后佐证">
|
|
|
+ <template v-for="(item, i) in attachItems(currentRow.afterUrl)" :key="'a' + i">
|
|
|
+ <el-image
|
|
|
+ v-if="isImage(item.name)"
|
|
|
+ :src="item.url"
|
|
|
+ :preview-src-list="[item.url]"
|
|
|
+ preview-teleported
|
|
|
+ fit="cover"
|
|
|
+ :initial-index="0"
|
|
|
+ style="width:64px;height:48px;border-radius:4px;border:1px solid #ebeef5;margin:0 8px 6px 0;cursor:zoom-in"
|
|
|
+ />
|
|
|
+ <el-link
|
|
|
+ v-else
|
|
|
+ type="primary"
|
|
|
+ style="margin-right:16px;font-size:13px"
|
|
|
+ @click="downloadAttach(item.url, item.name)"
|
|
|
+ >{{ item.name }}(点击下载)</el-link>
|
|
|
+ </template>
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="事件描述">
|
|
|
<div class="desc-text">{{ currentRow.description || '-' }}</div>
|
|
|
</el-form-item>
|
|
|
@@ -202,24 +331,116 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup name="Gzjc">
|
|
|
-import { ref, onMounted } from 'vue'
|
|
|
+import { ref, nextTick, computed, onMounted } from 'vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
-import { getEventPage, deleteEventInfo, trackEvent, releaseEventInfo, closeEventInfo, getTrackLogs } from '@/api/dispatch'
|
|
|
+import { useDict } from '@/utils/dict'
|
|
|
+import { getToken } from '@/utils/auth'
|
|
|
+import {
|
|
|
+ getEventPage, deleteEventInfo, startDisposeEvent, trackEvent,
|
|
|
+ releaseEventInfo, getEventLogs, getEventStatusOptions
|
|
|
+} from '@/api/dispatch'
|
|
|
+
|
|
|
+// ==================== 字典 ====================
|
|
|
+const { dis_event_source, dis_event_type, dis_event_category, dis_event_level, dis_event_trend, dis_event_release_reason, dis_yes_no } = useDict(
|
|
|
+ 'dis_event_source', 'dis_event_type', 'dis_event_category', 'dis_event_level', 'dis_event_trend', 'dis_event_release_reason', 'dis_yes_no'
|
|
|
+)
|
|
|
+
|
|
|
+// 损失金额展示:空显示 -,有值保留 2 位小数
|
|
|
+const formatLoss = (val) => (val === null || val === undefined || val === '') ? '-' : Number(val).toFixed(2)
|
|
|
+
|
|
|
+// ---------- 处置佐证附件(URL 逗号分隔 → {url,name} 列表;图片预览 / 视频下载) ----------
|
|
|
+const IMAGE_EXT = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
|
|
|
+
|
|
|
+function attachUrls(attachUrl) {
|
|
|
+ return String(attachUrl || '').split(',').map(s => s.trim()).filter(Boolean)
|
|
|
+}
|
|
|
+
|
|
|
+function attachItems(attachUrl) {
|
|
|
+ return attachUrls(attachUrl).map(url => {
|
|
|
+ let name = url.substring(url.lastIndexOf('/') + 1)
|
|
|
+ try { name = decodeURIComponent(name) } catch { /* 保留原值 */ }
|
|
|
+ const underscore = name.indexOf('_')
|
|
|
+ return { url, name: underscore > 0 ? name.substring(underscore + 1) : name }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const isImage = (name) => IMAGE_EXT.includes(String(name).split('.').pop().toLowerCase())
|
|
|
+
|
|
|
+// 视频强制下载:fetch 转 blob 后用 a[download] 触发;CORS 拦截时兜底新窗口打开
|
|
|
+async function downloadAttach(url, name) {
|
|
|
+ if (!url) return
|
|
|
+ try {
|
|
|
+ const res = await fetch(url)
|
|
|
+ if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
|
+ const blob = await res.blob()
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.href = URL.createObjectURL(blob)
|
|
|
+ link.download = name || '视频'
|
|
|
+ link.click()
|
|
|
+ URL.revokeObjectURL(link.href)
|
|
|
+ } catch {
|
|
|
+ window.open(url, '_blank')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function dictLabel(list, value) {
|
|
|
+ if (value === null || value === undefined || value === '') return '-'
|
|
|
+ const hit = (list || []).find(item => String(item.value) === String(value))
|
|
|
+ return hit ? hit.label : value
|
|
|
+}
|
|
|
+
|
|
|
+// 本页覆盖「已核实(1) 待开始处置 / 处置中(2) 待跟踪解除 / 已解除(3) 终态查看」
|
|
|
+const trackStatusOptions = ref([])
|
|
|
+
|
|
|
+async function loadStatusOptions() {
|
|
|
+ try {
|
|
|
+ const res = await getEventStatusOptions()
|
|
|
+ trackStatusOptions.value = (res.data || []).filter(item => [1, 2, 3].includes(item.value))
|
|
|
+ } catch {
|
|
|
+ trackStatusOptions.value = []
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 状态码 -> 标签样式(0 待核实 1 已核实 2 处置中 3 已解除 4 不实事件)
|
|
|
+const STATUS_TAG = { 0: 'warning', 1: 'success', 2: 'primary', 3: 'warning', 4: 'info' }
|
|
|
+function statusTagType(status) {
|
|
|
+ return STATUS_TAG[status] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+// 等级字典值 -> 标签样式(1 Ⅰ级紧急 2 Ⅱ级较大 3 Ⅲ级一般)
|
|
|
+const LEVEL_TAG = { 1: 'danger', 2: 'warning', 3: 'info' }
|
|
|
+function levelTagType(level) {
|
|
|
+ return LEVEL_TAG[level] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+// 操作日志 -> 时间线节点
|
|
|
+const LOG_TYPE = { 0: 'primary', 1: 'success', 2: 'info', 3: 'primary', 4: 'primary', 5: 'warning', 6: '', 7: 'success', 8: 'warning', 9: 'info' }
|
|
|
+function toTimeline(logs) {
|
|
|
+ return (logs || []).map(log => ({
|
|
|
+ type: LOG_TYPE[log.operType] ?? '',
|
|
|
+ time: log.operTime,
|
|
|
+ title: log.operTypeName || '操作',
|
|
|
+ content: log.operDesc || log.targets || '-'
|
|
|
+ }))
|
|
|
+}
|
|
|
|
|
|
// ==================== 数据状态 ====================
|
|
|
const tableData = ref([])
|
|
|
const total = ref(0)
|
|
|
const loading = ref(false)
|
|
|
-const searchForm = ref({ eventStatus: '', category: '', dateRange: null })
|
|
|
+const searchForm = ref({ status: '', eventCategory: '', dateRange: null })
|
|
|
const pagination = ref({ page: 1, pageSize: 10 })
|
|
|
|
|
|
+// 下拉清空后可能是 '' / null / undefined,统一判定,避免拼出空查询条件
|
|
|
+const hasValue = (val) => val !== '' && val !== null && val !== undefined
|
|
|
+
|
|
|
// ==================== 加载数据 ====================
|
|
|
async function loadData() {
|
|
|
loading.value = true
|
|
|
try {
|
|
|
- const params = { statusList: ['处置中', '已解除', '已关闭'] }
|
|
|
- if (searchForm.value.eventStatus) params.statusList = [searchForm.value.eventStatus]
|
|
|
- if (searchForm.value.category) params.category = searchForm.value.category
|
|
|
+ const params = { statusList: [1, 2, 3] }
|
|
|
+ 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) {
|
|
|
params.startDate = searchForm.value.dateRange[0]
|
|
|
params.endDate = searchForm.value.dateRange[1]
|
|
|
@@ -235,6 +456,7 @@ async function loadData() {
|
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
|
+ loadStatusOptions()
|
|
|
loadData()
|
|
|
})
|
|
|
|
|
|
@@ -245,7 +467,7 @@ function handleSearch() {
|
|
|
}
|
|
|
|
|
|
function handleReset() {
|
|
|
- searchForm.value = { eventStatus: '', category: '', dateRange: null }
|
|
|
+ searchForm.value = { status: '', eventCategory: '', dateRange: null }
|
|
|
pagination.value.page = 1
|
|
|
loadData()
|
|
|
}
|
|
|
@@ -262,52 +484,50 @@ function handleCurrentChange(val) {
|
|
|
loadData()
|
|
|
}
|
|
|
|
|
|
-// ==================== 标签样式 ====================
|
|
|
-function statusTagType(status) {
|
|
|
- if (status === '处置中') return 'primary'
|
|
|
- if (status === '已解除') return 'warning'
|
|
|
- if (status === '已关闭') return 'success'
|
|
|
- return 'info'
|
|
|
-}
|
|
|
-
|
|
|
-function levelTagType(level) {
|
|
|
- if (level === '特别重大') return 'danger'
|
|
|
- if (level === '重大') return 'danger'
|
|
|
- if (level === '较大') return 'warning'
|
|
|
- return 'info'
|
|
|
+// ==================== 开始处置 ====================
|
|
|
+async function handleStart(row) {
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确认开始处置【' + (row.eventNo || '') + '】吗?', '开始处置', { type: 'warning' })
|
|
|
+ } catch {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await startDisposeEvent({ eventId: row.id, operDesc: '开始处置' })
|
|
|
+ ElMessage.success('已开始处置')
|
|
|
+ loadData()
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('操作失败:' + (error.message || '未知错误'))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// ==================== 跟踪 ====================
|
|
|
const trackVisible = ref(false)
|
|
|
const trackFormRef = ref(null)
|
|
|
+const currentRow = ref(null)
|
|
|
const currentTrackId = ref(null)
|
|
|
-const trackForm = ref({ eventNo: '', content: '', assessment: '趋于平稳', needUpgrade: '否', nextVisitTime: '' })
|
|
|
+const trackForm = ref({ operDesc: '', trend: '', needUpgrade: 0, nextVisitTime: '' })
|
|
|
const trackRules = {
|
|
|
- content: [{ required: true, message: '请输入跟踪内容', trigger: 'blur' }],
|
|
|
- assessment: [{ required: true, message: '请选择态势评估', trigger: 'change' }],
|
|
|
+ operDesc: [{ required: true, message: '请输入跟踪内容', trigger: 'blur' }],
|
|
|
+ trend: [{ required: true, message: '请选择态势评估', trigger: 'change' }],
|
|
|
needUpgrade: [{ required: true, message: '请选择是否升级', trigger: 'change' }],
|
|
|
nextVisitTime: [{ required: true, message: '请选择下次回访时间', trigger: 'change' }]
|
|
|
}
|
|
|
|
|
|
-function handleTrack(row) {
|
|
|
- currentRow.value = row
|
|
|
+async function handleTrack(row) {
|
|
|
+ currentRow.value = { ...row, timeline: [] }
|
|
|
currentTrackId.value = row.id
|
|
|
- trackForm.value = { eventNo: row.eventNo, content: '', assessment: '趋于平稳', needUpgrade: '否', nextVisitTime: '' }
|
|
|
- // 加载跟踪日志(按最近跟踪记录ID)
|
|
|
- if (row.trackId) {
|
|
|
- loadTrackLogs(row.trackId)
|
|
|
- }
|
|
|
+ trackForm.value = { operDesc: '', trend: '1', needUpgrade: 0, nextVisitTime: '' }
|
|
|
trackVisible.value = true
|
|
|
+ nextTick(() => trackFormRef.value?.clearValidate())
|
|
|
+ loadTimeline(row.id)
|
|
|
}
|
|
|
|
|
|
-async function loadTrackLogs(trackId) {
|
|
|
+async function loadTimeline(eventId) {
|
|
|
try {
|
|
|
- const res = await getTrackLogs(trackId)
|
|
|
- if (res.data && currentRow.value) {
|
|
|
- currentRow.value = { ...currentRow.value, timeline: res.data || [] }
|
|
|
- }
|
|
|
+ const res = await getEventLogs(eventId)
|
|
|
+ currentRow.value = { ...currentRow.value, timeline: toTimeline(res.data) }
|
|
|
} catch {
|
|
|
- // 使用行内已有数据
|
|
|
+ // 时间线加载失败时不影响跟踪录入
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -319,9 +539,9 @@ async function confirmTrack() {
|
|
|
}
|
|
|
try {
|
|
|
await trackEvent({
|
|
|
- relEventId: currentTrackId.value,
|
|
|
- description: trackForm.value.content,
|
|
|
- assessment: trackForm.value.assessment,
|
|
|
+ eventId: currentTrackId.value,
|
|
|
+ operDesc: trackForm.value.operDesc,
|
|
|
+ trend: trackForm.value.trend,
|
|
|
needUpgrade: trackForm.value.needUpgrade,
|
|
|
nextVisitTime: trackForm.value.nextVisitTime
|
|
|
})
|
|
|
@@ -333,44 +553,95 @@ async function confirmTrack() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// ==================== 解除/关闭 ====================
|
|
|
-const closeVisible = ref(false)
|
|
|
-const closeFormRef = ref(null)
|
|
|
-const currentCloseRow = ref(null)
|
|
|
-const closeForm = ref({ eventNo: '', summary: '', reason: '', remark: '', lossAmount: undefined })
|
|
|
-const closeRules = {
|
|
|
- reason: [{ required: true, message: '请选择解除原因', trigger: 'change' }],
|
|
|
- remark: [{ required: true, message: '请输入解除说明', trigger: 'blur' }]
|
|
|
+// ==================== 解除 ====================
|
|
|
+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
|
|
|
}
|
|
|
-const closeTitle = ref('事件解除')
|
|
|
|
|
|
-function handleClose(row) {
|
|
|
- currentCloseRow.value = row
|
|
|
- closeTitle.value = row.status === '处置中' ? '事件解除' : '事件关闭'
|
|
|
- closeForm.value = { eventNo: row.eventNo, summary: row.description || '', reason: '', remark: '', lossAmount: row.lossAmount ? Number(row.lossAmount) : undefined }
|
|
|
- closeVisible.value = true
|
|
|
+function handleAttachSuccess(res, slot) {
|
|
|
+ if (res.code === 200 && res.url) {
|
|
|
+ releaseForm.value[slot + 'Urls'].push(res.url)
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '上传失败')
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-async function confirmClose() {
|
|
|
+function handleAttachError() {
|
|
|
+ ElMessage.error('文件上传失败')
|
|
|
+}
|
|
|
+
|
|
|
+function handleRelease(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()
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+async function confirmRelease() {
|
|
|
try {
|
|
|
- await closeFormRef.value.validate()
|
|
|
+ await releaseFormRef.value.validate()
|
|
|
} catch {
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
- if (currentCloseRow.value.status === '处置中') {
|
|
|
- await releaseEventInfo(currentCloseRow.value.id)
|
|
|
- ElMessage.success('事件已解除')
|
|
|
- } else if (currentCloseRow.value.status === '已解除') {
|
|
|
- await closeEventInfo({
|
|
|
- id: currentCloseRow.value.id,
|
|
|
- closeReason: closeForm.value.reason,
|
|
|
- closeRemark: closeForm.value.remark,
|
|
|
- lossAmount: closeForm.value.lossAmount
|
|
|
- })
|
|
|
- ElMessage.success('事件已关闭')
|
|
|
- }
|
|
|
- closeVisible.value = false
|
|
|
+ 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()
|
|
|
+ })
|
|
|
loadData()
|
|
|
} catch (error) {
|
|
|
ElMessage.error('操作失败:' + (error.message || '未知错误'))
|
|
|
@@ -380,7 +651,7 @@ async function confirmClose() {
|
|
|
// ==================== 删除 ====================
|
|
|
async function handleDelete(row) {
|
|
|
try {
|
|
|
- await ElMessageBox.confirm('确定要删除该跟踪记录吗?', '删除确认', { type: 'warning' })
|
|
|
+ await ElMessageBox.confirm('确定要删除该事件吗?', '删除确认', { type: 'warning' })
|
|
|
await deleteEventInfo(row.id)
|
|
|
ElMessage.success('删除成功')
|
|
|
loadData()
|
|
|
@@ -393,21 +664,15 @@ async function handleDelete(row) {
|
|
|
|
|
|
// ==================== 详情 ====================
|
|
|
const detailVisible = ref(false)
|
|
|
-const currentRow = ref(null)
|
|
|
|
|
|
async function handleDetail(row) {
|
|
|
- currentRow.value = row
|
|
|
+ currentRow.value = { ...row, timeline: [] }
|
|
|
detailVisible.value = true
|
|
|
- // 加载最新的跟踪日志(按最近跟踪记录ID)
|
|
|
- if (row.trackId) {
|
|
|
- try {
|
|
|
- const res = await getTrackLogs(row.trackId)
|
|
|
- if (res.data && res.data.length) {
|
|
|
- currentRow.value = { ...row, timeline: res.data }
|
|
|
- }
|
|
|
- } catch {
|
|
|
- // 使用行内已有数据
|
|
|
- }
|
|
|
+ try {
|
|
|
+ const res = await getEventLogs(row.id)
|
|
|
+ currentRow.value = { ...currentRow.value, timeline: toTimeline(res.data) }
|
|
|
+ } catch {
|
|
|
+ // 加载失败时保留列表行数据
|
|
|
}
|
|
|
}
|
|
|
</script>
|