feat: 灾毁

This commit is contained in:
niedongsheng 2026-04-09 09:35:05 +08:00
parent b24f315cbb
commit 13fc3a68c6
8 changed files with 1684 additions and 573 deletions

View File

@ -6,7 +6,6 @@
<!-- 基本信息 -->
<PanelItem title="基本信息">
<template #headerExtra>
<!-- 状态标签需要从续报中判断是否已解除或接口增加 eventStatus 字段 -->
<div class="status-wrapper">
<van-tag :type="getEventStatusType()" size="medium" plain>
{{ getEventStatusText() }}
@ -38,11 +37,10 @@
<span class="info-value">{{ detailData.event?.repairProgress || '-' }}</span>
</div>
<!-- 处理措施从首报中获取 -->
<div class="info-row">
<!-- <div class="info-row">
<span class="info-label">处理措施</span>
<span class="info-value">{{ formatDisposalMeasures(firstReport?.disposalMeasures) || '-' }}</span>
</div>
<span class="info-value">{{ formatDisposalMeasures(detailData.event?.disposalMeasures) || '-' }}</span>
</div> -->
<!-- 水毁处数 -->
<div class="info-row">
@ -159,63 +157,10 @@
<!-- 填报信息 -->
<PanelItem title="填报信息">
<!-- 首报信息 -->
<div class="report-section" v-if="firstReport">
<!-- 遍历所有填报记录首报 + 续报 -->
<div v-for="(report, index) in allReports" :key="index" class="report-section">
<div class="report-header">
<span class="report-title">首报</span>
<span class="report-meta">{{ firstReport.reporterName || '-' }} {{ firstReport.reportTime || '-' }}</span>
</div>
<div class="report-content">
<div class="info-row">
<span class="info-label">处置情况</span>
<span class="info-value">{{ formatDisposalMeasures(firstReport.disposalMeasures) || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">塌方及损失</span>
<span class="info-value">{{ getLossDescription(firstReport) }}</span>
</div>
<div class="info-row">
<span class="info-label">路产损失</span>
<span class="info-value">{{ firstReport.totalLossAmount ? firstReport.totalLossAmount + '万元' : '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">有无车辆滞留</span>
<span class="info-value">{{ getVehicleStrandedText(firstReport) }}</span>
</div>
<div class="info-row">
<span class="info-label">滞留车辆</span>
<span class="info-value">{{ firstReport.strandedVehicleCount || 0 }}</span>
</div>
<div class="info-row">
<span class="info-label">预计恢复时间</span>
<span class="info-value">{{ firstReport.expectRecoverTime || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">实际恢复时间</span>
<span class="info-value">{{ firstReport.actualRecoverTime || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">现场描述</span>
<span class="info-value">{{ firstReport.siteDescription || '-' }}</span>
</div>
<!-- 附件 -->
<div class="info-row" v-if="firstReport.fileList && firstReport.fileList.length > 0">
<span class="info-label">附件</span>
<div class="attachment-list">
<div v-for="(file, index) in firstReport.fileList" :key="index" class="attachment-item">
<van-icon :name="file.fileType === 1 ? 'photo-o' : 'video-o'" />
<span class="file-name">{{ file.fileName }}</span>
<van-button size="mini" type="primary" plain @click="previewFile(file)">预览</van-button>
</div>
</div>
</div>
</div>
</div>
<!-- 续报列表 -->
<div v-for="(report, index) in continueReports" :key="index" class="report-section">
<div class="report-header">
<span class="report-title">续报{{ index + 1 }}</span>
<span class="report-title">{{ report?.title }}</span>
<span class="report-meta">{{ report.reporterName || '-' }} {{ report.reportTime || '-' }}</span>
</div>
<div class="report-content">
@ -270,8 +215,8 @@
</PanelItem>
<!-- 底部按钮未解除状态显示续报按钮 -->
<div class="footer-buttons" v-if="!isEventResolved">
<van-button type="primary" class="footer-btn" @click="handleContinueReport">续报</van-button>
<div class="footer-buttons">
<van-button type="primary" class="footer-btn" @click="handleContinueReport">续报</van-button>
</div>
</PageContainer>
</template>
@ -292,8 +237,7 @@ const route = useRoute()
// Data
const detailData = ref({
event: null, // Event
report: null, // Report
reportList: [], //
reportList: [], //
fileList: [], //
lossList: [], //
occurLocation: '',
@ -302,42 +246,28 @@ const detailData = ref({
routeNo: ''
})
// 使 report reportList reportType === 1
const firstReport = computed(() => {
if (detailData.value.report) {
return detailData.value.report
}
return detailData.value.reportList?.find(r => r.reportType === 1)
})
// reportType === 2
const continueReports = computed(() => {
return detailData.value.reportList?.filter(r => r.reportType === 2) || []
const allReports = computed(() => {
const reports = detailData.value.reportList?.map((item, index)=>{
item.title = index == 0 ? '首报' : ('续报' + index)
return item
}) || []
return reports.reverse()
})
//
const hasReportData = computed(() => {
return firstReport.value || continueReports.value.length > 0
return allReports.value.length > 0
})
//
const isEventResolved = computed(() => {
//
// if (firstReport.value?.actualRecoverTime) {
// return true
// }
// //
// return continueReports.value.some(r => r.actualRecoverTime)
})
//
const getEventStatusText = () => {
return isEventResolved.value ? '已解除' : '未解除'
return detailData.eventStatus === 1 ? '已解除' : '未解除'
}
//
const getEventStatusType = () => {
return isEventResolved.value ? 'success' : 'danger'
return detailData.eventStatus === 1 ? 'success' : 'danger'
}
//
@ -396,8 +326,7 @@ const getDisasterDetail = async () => {
const data = result.data
detailData.value = {
event: data.event || null,
report: data.report || null,
reportList: data.reportList || [],
reportList: data.report || [], // 使 reportList
fileList: data.fileList || [],
lossList: data.lossList || [],
occurLocation: data.occurLocation || '',

View File

@ -58,7 +58,7 @@
<span>校准经纬度</span>
</div>
<!-- 路况位置 (event.endStakeNo) -->
<!-- 路况位置 (occurLocation) -->
<van-field v-model="formData.occurLocation" label="路况位置" placeholder="请填写" />
<!-- 阻断点小地名 (event.blockedPointName) -->
@ -71,12 +71,13 @@
<div class="disposal-measures">
<span class="measures-label">处置措施</span>
<div class="measures-options">
<van-checkbox-group v-model="disposalMeasuresArray" direction="horizontal">
<van-checkbox name="halfClose">半幅封闭</van-checkbox>
<van-checkbox name="fullClose">全副封闭</van-checkbox>
<van-checkbox name="bypass">便道通行</van-checkbox>
<van-checkbox name="normal">正常通行</van-checkbox>
</van-checkbox-group>
<!-- 改为单选使用 van-radio-group -->
<van-radio-group v-model="disposalMeasureValue" direction="horizontal">
<van-radio name="halfClose">半幅封闭</van-radio>
<van-radio name="fullClose">全副封闭</van-radio>
<van-radio name="bypass">便道通行</van-radio>
<van-radio name="normal">正常通行</van-radio>
</van-radio-group>
</div>
</div>
@ -198,8 +199,8 @@ const route = useRoute()
//
const isContinue = computed(() => route.query.isContinue)
//
const disposalMeasuresArray = ref([])
//
const disposalMeasureValue = ref('')
//
const imageFileList = ref([])
@ -240,7 +241,7 @@ const formData = reactive({
actualRecoverTime: '', //
damagedVehicleCount: '', //
deadCount: '', //
disposalMeasures: '', //
disposalMeasures: '', //
expectRecoverTime: '', //
injuredCount: '', //
investedFunds: '', //
@ -260,14 +261,10 @@ const formData = reactive({
fileList: []
})
// report.disposalMeasures
watch(
disposalMeasuresArray,
(newVal) => {
formData.report.disposalMeasures = newVal.join(',')
},
{ deep: true }
)
// report.disposalMeasures
watch(disposalMeasureValue, (newVal) => {
formData.report.disposalMeasures = newVal
})
// fileList
watch(
@ -313,12 +310,12 @@ watch(
{ deep: true }
)
// report.disposalMeasures
// report.disposalMeasures
watch(
() => formData.report.disposalMeasures,
(newVal) => {
if (newVal && typeof newVal === 'string') {
disposalMeasuresArray.value = newVal.split(',').filter(Boolean)
disposalMeasureValue.value = newVal
}
},
{ immediate: true }
@ -368,9 +365,9 @@ const initFormData = (newVal) => {
fileList: newVal.fileList || []
})
//
// split
if (newVal.report?.disposalMeasures) {
disposalMeasuresArray.value = newVal.report.disposalMeasures.split(',').filter(Boolean)
disposalMeasureValue.value = newVal.report.disposalMeasures
}
}
}
@ -480,12 +477,12 @@ defineExpose({
margin-bottom: 8px;
}
.measures-options {
:deep(.van-checkbox-group) {
:deep(.van-radio-group) {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
:deep(.van-checkbox) {
:deep(.van-radio) {
margin-right: 0;
}
}
@ -526,4 +523,4 @@ defineExpose({
width: 110px;
}
}
</style>
</style>

View File

@ -163,7 +163,16 @@ const routes = [
name: 'DisasterReport',
component: () => import('../views/DisasterManagement/DisasterReport/DisasterReport.vue'),
meta: {
title: '水毁事件填报',
title: '灾毁事件填报',
breadcrumb: true
}
},
{
path: '/waterDisasterDetail',
name: 'WaterDisasterDetail',
component: () => import('../views/DisasterManagement/DisasterDetail/WaterDisasterDetail.vue'),
meta: {
title: '水毁事件详情',
breadcrumb: true
}
}

View File

@ -0,0 +1,475 @@
<template>
<div class="water-disaster-pc">
<!-- 合并后的单个卡片 -->
<el-card class="form-card" shadow="never">
<div slot="header" class="card-header">
<span>续保信息</span>
</div>
<!-- 所有表单项合并到一个区域每行一个 -->
<el-form :model="formData" label-width="120px" size="small">
<!-- 处置措施 -->
<el-form-item label="处置措施">
<el-radio-group v-model="disposalMeasureValue">
<el-radio label="halfClose">半幅封闭</el-radio>
<el-radio label="fullClose">全副封闭</el-radio>
<el-radio label="bypass">便道通行</el-radio>
<el-radio label="normal">正常通行</el-radio>
</el-radio-group>
</el-form-item>
<!-- 预计恢复时间 -->
<el-form-item label="预计恢复时间">
<el-date-picker
v-model="formData.report.expectRecoverTime"
type="datetime"
placeholder="请选择时间"
value-format="yyyy-MM-dd HH:mm"
:picker-options="pickerOptions"
style="width: 100%"
/>
</el-form-item>
<!-- 实际恢复时间 -->
<el-form-item label="实际恢复时间">
<el-date-picker
v-model="formData.report.actualRecoverTime"
type="datetime"
placeholder="请选择时间"
value-format="yyyy-MM-dd HH:mm"
:picker-options="pickerOptions"
style="width: 100%"
/>
</el-form-item>
<!-- 受伤人员 -->
<el-form-item label="受伤人员">
<el-input-number v-model="formData.report.injuredCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 死亡人员 -->
<el-form-item label="死亡人员">
<el-input-number v-model="formData.report.deadCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 滞留人员 -->
<el-form-item label="滞留人员">
<el-input-number v-model="formData.report.strandedPersonCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 损坏车辆 -->
<el-form-item label="损坏车辆">
<el-input-number v-model="formData.report.damagedVehicleCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 滞留车辆 -->
<el-form-item label="滞留车辆">
<el-input-number v-model="formData.report.strandedVehicleCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 损失列表组件 -->
<el-form-item label="灾毁损失">
<loss-list v-model="formData.lossList" />
</el-form-item>
<!-- 处理情况 -->
<el-form-item label="处理情况">
<el-input v-model="formData.report.remark" type="textarea" :rows="2" placeholder="请填写(选填)" style="width: 60%" />
</el-form-item>
<!-- 损失总金额 -->
<el-form-item label="损失总金额">
<el-input v-model="formData.report.totalLossAmount" placeholder="请填写(选填)" style="width: 300px">
<template slot="append">万元</template>
</el-input>
</el-form-item>
<!-- 已投机械 -->
<el-form-item label="已投机械">
<el-input v-model="formData.report.investedMachinery" placeholder="请填写" style="width: 300px">
<template slot="append">/</template>
</el-input>
</el-form-item>
<!-- 已投入力 -->
<el-form-item label="已投入力">
<el-input-number v-model="formData.report.investedManpower" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append">人次</template>
</el-input-number>
</el-form-item>
<!-- 已投资金 -->
<el-form-item label="已投资金">
<el-input v-model="formData.report.investedFunds" placeholder="请填写" style="width: 300px">
<template slot="append">万元</template>
</el-input>
</el-form-item>
<!-- 现场描述 -->
<el-form-item label="现场描述">
<el-input v-model="formData.report.siteDescription" type="textarea" :rows="2" placeholder="请填写" style="width: 60%" />
</el-form-item>
<!-- 是否需要恢复重建 -->
<el-form-item label="是否需要恢复重建">
<el-radio-group v-model="formData.event.needsRecovery">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">追加记录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { ref, reactive, watch, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { request } from '@shared/utils/request'
// import LossList from './LossList.vue'
// Props
const props = defineProps({
value: {
type: Object,
default: () => ({})
}
})
// Emits
const emit = defineEmits(['input', 'change', 'submit'])
//
const formData = reactive({
occurLocation: '',
occurTime: '',
roadConditionType: '',
routeNo: '',
event: {
blockedMileage: '',
blockedPointName: '',
contactPerson: '',
contactPhone: '',
damageCount: '',
district: '',
endStakeLat: '',
endStakeLng: '',
endStakeNo: '',
estimatedRecoveryCost: '',
inspectionMileage: '',
isBlocked: '',
needsRecovery: '',
repairProgress: '',
reporterUnit: '',
startStakeLat: '',
startStakeLng: '',
startStakeNo: ''
},
report: {
actualRecoverTime: '',
damagedVehicleCount: '',
deadCount: '',
disposalMeasures: '',
expectRecoverTime: '',
injuredCount: '',
investedFunds: '',
investedMachinery: '',
investedManpower: '',
remark: '',
siteDescription: '',
strandedPersonCount: '',
strandedVehicleCount: '',
totalLossAmount: ''
},
lossList: [],
fileList: []
})
//
const disposalMeasureValue = ref('')
//
const roadConditionOptions = [
{ label: '高速公路', value: '高速公路' },
{ label: '国道', value: '国道' },
{ label: '省道', value: '省道' },
{ label: '县道', value: '县道' },
{ label: '乡道', value: '乡道' },
{ label: '村道', value: '村道' }
]
const blockedOptions = [
{ label: '是', value: true },
{ label: '否', value: false }
]
const repairProgressOptions = [
{ label: '未抢修', value: '未抢修' },
{ label: '抢修中', value: '抢修中' },
{ label: '已完成', value: '已完成' }
]
//
const pickerOptions = {
disabledDate(time) {
return time.getTime() < new Date(2020, 0, 1) || time.getTime() > new Date(2030, 11, 31)
}
}
//
watch(disposalMeasureValue, (newVal) => {
formData.report.disposalMeasures = newVal
})
//
watch(
() => props.value,
(newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
initFormData(newVal)
}
},
{ immediate: true, deep: true }
)
//
watch(
() => formData,
(newVal) => {
emit('input', newVal)
emit('change', newVal)
},
{ deep: true }
)
// report.disposalMeasures
watch(
() => formData.report.disposalMeasures,
(newVal) => {
if (newVal && typeof newVal === 'string') {
disposalMeasureValue.value = newVal
}
},
{ immediate: true }
)
//
const initFormData = (data) => {
Object.assign(formData, {
occurLocation: data.occurLocation || '',
occurTime: data.occurTime || '',
roadConditionType: data.roadConditionType || '',
routeNo: data.routeNo || '',
event: { ...formData.event, ...(data.event || {}) },
report: { ...formData.report, ...(data.report || {}) },
lossList: data.lossList || [],
fileList: data.fileList || []
})
if (data.report?.disposalMeasures) {
disposalMeasureValue.value = data.report.disposalMeasures
}
}
//
const calibrateTime = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
formData.occurTime = `${year}-${month}-${day} ${hours}:${minutes}`
ElMessage.success('时间已校准为当前时间')
}
//
const calibrateStartCoord = () => {
formData.event.startStakeLng = '108.41763025'
formData.event.startStakeLat = '108.41763025'
ElMessage.success('起点经纬度已校准')
}
//
const calibrateEndCoord = () => {
formData.event.endStakeLng = '108.41763025'
formData.event.endStakeLat = '108.41763025'
ElMessage.success('止点经纬度已校准')
}
//
const validate = () => {
if (!formData.occurTime) {
ElMessage.warning('请填写发生时间')
return false
}
if (!formData.routeNo) {
ElMessage.warning('请填写线路编号')
return false
}
return true
}
//
const getFormData = () => {
return { ...formData }
}
//
const resetForm = () => {
Object.assign(formData, {
occurLocation: '',
occurTime: '',
roadConditionType: '',
routeNo: '',
event: {
blockedMileage: '',
blockedPointName: '',
contactPerson: '',
contactPhone: '',
damageCount: '',
district: '',
endStakeLat: '',
endStakeLng: '',
endStakeNo: '',
estimatedRecoveryCost: '',
inspectionMileage: '',
isBlocked: '',
needsRecovery: '',
repairProgress: '',
reporterUnit: '',
startStakeLat: '',
startStakeLng: '',
startStakeNo: ''
},
report: {
actualRecoverTime: '',
damagedVehicleCount: '',
deadCount: '',
disposalMeasures: '',
expectRecoverTime: '',
injuredCount: '',
investedFunds: '',
investedMachinery: '',
investedManpower: '',
remark: '',
siteDescription: '',
strandedPersonCount: '',
strandedVehicleCount: '',
totalLossAmount: ''
},
lossList: [],
fileList: []
})
disposalMeasureValue.value = ''
}
//
const submit = () => {
if (validate()) {
emit('submit', getFormData())
}
}
const handleSubmit = async () => {
//
if (!validate()) {
return
}
try {
//
//
const submitData = {
...formData
//
}
const res = await request({
url: '/snow-ops-platform/water-damage/addOrUpdate',
method: 'post',
data: submitData
})
if (res?.code === '00000') {
ElMessage.success('提交成功')
emit("refresh")
} else {
ElMessage.error(res.message)
}
} catch (error) {
ElMessage.error('提交失败,请重试')
console.error('提交失败:', error)
} finally {
}
}
//
defineExpose({
validate,
initFormData,
getFormData,
resetForm,
calibrateTime,
calibrateStartCoord,
calibrateEndCoord
})
</script>
<style lang="scss" scoped>
.water-disaster-pc {
background-color: #f5f7fa;
height: 100%;
.form-card {
border-radius: 8px;
.card-header {
font-size: 16px;
font-weight: 600;
color: #303133;
border-left: 4px solid #409eff;
padding-left: 12px;
}
}
::v-deep .el-form-item {
margin-bottom: 22px;
}
::v-deep .el-input-group__append {
padding: 0 10px;
}
::v-deep .el-input-number {
width: 300px;
.el-input-group__append {
padding: 0 10px;
}
}
::v-deep .el-radio-group {
.el-radio {
margin-right: 20px;
}
}
}
</style>

View File

@ -0,0 +1,618 @@
<template>
<div class="web-detail-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<el-button :icon="ArrowLeft" @click="handleClickBack">返回</el-button>
<h2 class="page-title">灾毁详情</h2>
</div>
<div class="header-right">
<el-tag :type="getEventStatusType()" size="large">
{{ getEventStatusText() }}
</el-tag>
</div>
</div>
<div class="content-container">
<div class="left-panel">
<!-- 基本信息卡片 -->
<el-card class="info-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">基本信息</span>
</div>
</template>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">事件类型</span>
<span class="info-value">水毁事件</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">路况类别</span>
<span class="info-value">{{ detailData.roadConditionType || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">是否阻断</span>
<span class="info-value">{{ detailData.event?.isBlocked ? '是' : '否' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">抢修进度</span>
<span class="info-value">{{ detailData.event?.repairProgress || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">水毁处数</span>
<span class="info-value">{{ detailData.event?.damageCount || 0 }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">阻断里程</span>
<span class="info-value">{{ detailData.event?.blockedMileage ? detailData.event.blockedMileage + '公里' : '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">发生时间</span>
<span class="info-value">{{ detailData.occurTime || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">线路编号</span>
<span class="info-value">{{ detailData.routeNo || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">地点路线</span>
<span class="info-value">{{ detailData.occurLocation || '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">起点桩号</span>
<span class="info-value">{{ detailData.event?.startStakeNo || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">起点桩经度</span>
<span class="info-value">{{ detailData.event?.startStakeLng || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">起点桩纬度</span>
<span class="info-value">{{ detailData.event?.startStakeLat || '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">止点桩号</span>
<span class="info-value">{{ detailData.event?.endStakeNo || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">止点桩经度</span>
<span class="info-value">{{ detailData.event?.endStakeLng || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">止点桩纬度</span>
<span class="info-value">{{ detailData.event?.endStakeLat || '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">路况位置</span>
<span class="info-value">{{ detailData.event?.blockedPointName || detailData.occurLocation || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">阻断点小地名</span>
<span class="info-value">{{ detailData.event?.blockedPointName || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">上报区县</span>
<span class="info-value">{{ detailData.event?.district || '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">巡查里程</span>
<span class="info-value">{{ detailData.event?.inspectionMileage ? detailData.event.inspectionMileage + '公里' : '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">是否恢复重建</span>
<span class="info-value">{{ detailData.event?.needsRecovery ? '是' : '否' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">恢复重建预估费用</span>
<span class="info-value">{{ detailData.event?.estimatedRecoveryCost ? detailData.event.estimatedRecoveryCost + '万元' : '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">联系人</span>
<span class="info-value">{{ detailData.event?.contactPerson || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">联系电话</span>
<span class="info-value">{{ detailData.event?.contactPhone || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">填报单位</span>
<span class="info-value">{{ detailData.event?.reporterUnit || '-' }}</span>
</div>
</el-col>
</el-row>
</el-card>
<!-- 填报信息卡片 -->
<el-card class="info-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">填报信息</span>
</div>
</template>
<div v-if="hasReportData">
<div v-for="(report, index) in allReports" :key="index" class="report-section">
<div class="report-header">
<span class="report-title">{{ report?.title }}</span>
<span class="report-meta">{{ report.reporterName || '-' }} {{ report.reportTime || '-' }}</span>
</div>
<div class="info-list">
<div class="info-item">
<span class="info-label">处置情况</span>
<span class="info-value">{{ formatDisposalMeasures(report.disposalMeasures) || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">塌方及损失</span>
<span class="info-value">{{ getLossDescription(report) }}</span>
</div>
<div class="info-item">
<span class="info-label">路产损失</span>
<span class="info-value">{{ report.totalLossAmount ? report.totalLossAmount + '万元' : '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">有无车辆滞留</span>
<span class="info-value">{{ getVehicleStrandedText(report) }}</span>
</div>
<div class="info-item">
<span class="info-label">滞留车辆</span>
<span class="info-value">{{ report.strandedVehicleCount || 0 }}</span>
</div>
<div class="info-item">
<span class="info-label">预计恢复时间</span>
<span class="info-value">{{ report.expectRecoverTime || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">实际恢复时间</span>
<span class="info-value">{{ report.actualRecoverTime || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">现场描述</span>
<span class="info-value">{{ report.siteDescription || '-' }}</span>
</div>
<!-- 附件 -->
<div v-if="report.fileList && report.fileList.length > 0" class="info-item attachment-item">
<span class="info-label">附件</span>
<div class="attachment-list">
<el-link v-for="(file, fileIndex) in report.fileList" :key="fileIndex" :underline="false" @click="previewFile(file)" class="attachment-link">
<el-icon><Picture v-if="file.fileType === 1" /><VideoCamera v-else /></el-icon>
<span class="file-name">{{ file.fileName }}</span>
</el-link>
</div>
</div>
</div>
</div>
</div>
<el-empty v-else description="暂无填报信息" :image-size="100" />
</el-card>
<!-- 底部按钮 -->
<!-- <div class="footer-buttons">
<el-button type="primary" size="large" @click="handleContinueReport" class="footer-btn"> 续报 </el-button>
</div> -->
</div>
<div class="right-panel" v-if="isEdit">
<ContinueReport ref="continueReport" @refresh="getDisasterDetail" />
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ArrowLeft, Picture, VideoCamera } from '@element-plus/icons-vue'
import ContinueReport from './ContinueReport.vue'
import { request } from '@shared/utils/request'
import mockData from '../DisasterReport/waterMockJson.json'
const router = useRouter()
const route = useRoute()
//
const detailData = ref({
event: null,
report: [],
fileList: [],
lossList: [],
occurLocation: '',
occurTime: '',
roadConditionType: '',
routeNo: ''
})
//
const eventStatus = ref(0)
//
const isEdit = computed(() => {
return route.query.mode === 'edit'
})
const continueReport = ref(null)
// +
const allReports = computed(() => {
const reports =
detailData.value.report?.map((item, index) => {
item.title = index === 0 ? '首报' : '续报' + index
return item
}) || []
return reports.reverse()
})
//
const hasReportData = computed(() => {
return allReports.value.length > 0
})
//
const getEventStatusText = () => {
return eventStatus.value === 1 ? '已解除' : '未解除'
}
//
const getEventStatusType = () => {
return eventStatus.value === 1 ? 'success' : 'danger'
}
//
const formatDisposalMeasures = (measures) => {
if (!measures) return ''
const measureMap = {
halfClose: '半幅封闭',
fullClose: '全副封闭',
bypass: '便道通行',
normal: '正常通行'
}
return measures
.split(',')
.map((m) => measureMap[m.trim()] || m.trim())
.join('、')
}
//
const getLossDescription = (report) => {
const lossList = report?.lossList
if (!lossList || lossList.length === 0) return '-'
const totalVolume = lossList.reduce((sum, loss) => {
const volume = (loss.length || 0) * (loss.width || 0) * (loss.height || 0)
return sum + volume
}, 0)
const totalAmount = lossList.reduce((sum, loss) => sum + (loss.totalAmount || 0), 0)
return `${totalVolume}方,共损失${totalAmount}万元`
}
//
const getVehicleStrandedText = (report) => {
const count = report?.strandedVehicleCount || 0
return count > 0 ? `有车滞留,共${count}` : '无车滞留'
}
//
const getDisasterDetail = async () => {
const id = route.query.id
if (!id) {
ElMessage.warning('缺少灾毁ID')
return
}
try {
const result = await request({
url: `/snow-ops-platform/water-damage/getById`,
method: 'get',
params: { id }
})
if (result?.data) {
const data = result.data
detailData.value = {
event: data.event || null,
report: data.report || [],
fileList: data.fileList || [],
lossList: data.lossList || [],
occurLocation: data.occurLocation || '',
occurTime: data.occurTime || '',
roadConditionType: data.roadConditionType || '',
routeNo: data.routeNo || ''
}
eventStatus.value = data.eventStatus || 0
if (isEdit.value) {
const newFormData = {
...data,
lossList: null,
report: mockData.report,
fileList: null
}
continueReport.value?.initFormData(newFormData)
}
} else {
ElMessage.warning(result.message || '获取详情失败')
}
} catch (error) {
console.error('获取灾毁详情失败:', error)
ElMessage.error('获取详情失败,请稍后重试')
}
}
//
const handleClickBack = () => {
router.push('/disasterManagement')
}
//
const handleContinueReport = () => {
router.push({
path: '/disasterReport',
query: {
id: route.query.id,
eventId: detailData.value.event?.id,
isContinue: 'true'
}
})
}
//
const previewFile = (file) => {
if (file.fileUrl) {
window.open(file.fileUrl, '_blank')
}
}
onMounted(() => {
getDisasterDetail()
})
</script>
<style scoped lang="scss">
.web-detail-container {
padding: 20px;
background: #f5f7fa;
height: 100%;
overflow: auto;
padding-bottom: 100px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 20px;
.header-left {
display: flex;
align-items: center;
gap: 16px;
.page-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #303133;
}
}
}
.info-card {
margin-bottom: 20px;
border-radius: 8px;
:deep(.el-card__header) {
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
background: #fafafa;
}
.card-header {
.card-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
}
.info-row {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.info-item {
display: flex;
align-items: flex-start;
line-height: 1.5;
.info-label {
width: 120px;
flex-shrink: 0;
color: #909399;
font-size: 14px;
}
.info-value {
flex: 1;
color: #606266;
font-size: 14px;
word-break: break-all;
}
}
.report-section {
margin-bottom: 32px;
padding-bottom: 24px;
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
}
.report-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px dashed #dcdfe6;
.report-title {
font-size: 16px;
font-weight: 600;
color: #409eff;
}
.report-meta {
font-size: 13px;
color: #909399;
}
}
.attachment-wrapper {
margin-top: 12px;
padding-top: 8px;
border-top: 1px dashed #ebeef5;
.attachment-item {
.attachment-list {
display: flex;
flex-wrap: wrap;
gap: 16px;
.attachment-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: #f5f7fa;
border-radius: 4px;
cursor: pointer;
&:hover {
background: #ecf5ff;
color: #409eff;
}
.file-name {
font-size: 13px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
.footer-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
padding: 16px 24px;
background: #fff;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
z-index: 100;
.footer-btn {
width: 200px;
height: 44px;
font-size: 16px;
border-radius: 22px;
}
}
.content-container {
display: flex;
.left-panel {
flex: 1;
margin-right: 10px;
}
.right-panel {
width: 400px;
}
}
</style>

View File

@ -298,12 +298,12 @@ const resetFilters = () => {
//
const handleDetail = (row) => {
router.push({ path: '/disasterDetail', query: { id: row.id } })
router.push({ path: '/waterDisasterDetail', query: { id: row.id } })
}
//
const handleEdit = (row) => {
router.push({ path: '/disasterReport', query: { id: row.id, mode: 'edit' } })
router.push({ path: '/waterDisasterDetail', query: { id: row.id, mode: 'edit' } })
}
//

View File

@ -0,0 +1,45 @@
{
"occurLocation": "G108国道 K2250+300处",
"occurTime": "2024-07-15 14:30:00",
"roadConditionType": "国道",
"routeNo": "G108",
"event": {
"blockedMileage": 1.5,
"blockedPointName": "磨盘山隧道口",
"contactPerson": "张明",
"contactPhone": "13812345678",
"damageCount": 3,
"district": "武侯区",
"endStakeLat": "30.658712",
"endStakeLng": "104.082356",
"endStakeNo": "K2251+200",
"estimatedRecoveryCost": 120.5,
"inspectionMileage": 25.6,
"isBlocked": true,
"needsRecovery": true,
"repairProgress": "抢修中",
"reporterUnit": "武侯区交通运输局",
"startStakeLat": "30.652145",
"startStakeLng": "104.075632",
"startStakeNo": "K2250+300"
},
"report": {
"damagedVehicleCount": 2,
"strandedPersonCount": 12,
"deadCount": 0,
"strandedVehicleCount": 12,
"disposalMeasures": "halfClose,bypass",
"actualRecoverTime": "2024-07-17 12:00:00",
"expectRecoverTime": "2024-07-18 18:00:00",
"injuredCount": 1,
"investedFunds": 35.8,
"investedMachinery": 6,
"investedManpower": 45,
"remark": "已组织抢险队伍进行抢通,便道已修建完成",
"siteDescription": "因持续强降雨导致山体滑坡掩埋路面约50米边坡垮塌严重",
"totalLossAmount": 85.6
},
"lossList": [
],
"fileList": []
}