2026-04-24 13:20:44 +08:00

454 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<PageContainer title="冰毁详情" @click-back="handleClickBack" class="page-container">
<!-- 当前站点信息 -->
<CurrentSite />
<!-- 基本信息 -->
<PanelItem title="基本信息" v-if="!loading">
<template #headerExtra>
<div class="status-wrapper">
<van-tag :type="getEventStatusType()" size="medium" plain>
{{ getEventStatusText() }}
</van-tag>
</div>
</template>
<!-- 事件类型 -->
<div class="info-row">
<span class="info-label">事件类型:</span>
<span class="info-value">冰毁事件</span>
</div>
<!-- 所属服务站: -->
<div class="info-row">
<span class="info-label">所属服务站:</span>
<span class="info-value">{{ detailData.event?.stationName || '-' }}</span>
</div>
<!-- 线路编号: -->
<div class="info-row">
<span class="info-label">线路编号:</span>
<span class="info-value">{{ detailData.event?.routeNo || '-' }}</span>
</div>
<!-- 发生时间 -->
<div class="info-row">
<span class="info-label">发生时间:</span>
<span class="info-value">{{ detailData.event?.occurTime || '-' }}</span>
</div>
<!-- 地点路线 -->
<div class="info-row">
<span class="info-label">路况位置:</span>
<span class="info-value">{{ detailData.event?.occurLocation || '-' }}</span>
</div>
<!-- 发生地点 -->
<div class="info-row">
<span class="info-label">发生地点:</span>
<span class="info-value">{{ detailData.event?.occurLocation || '-' }}</span>
</div>
<!-- 起点桩号 -->
<div class="info-row">
<span class="info-label">起点桩号:</span>
<span class="info-value">{{ detailData.event?.startStakeNo || '-' }}</span>
</div>
<!-- 止点桩号 -->
<div class="info-row">
<span class="info-label">止点桩号:</span>
<span class="info-value">{{ detailData.event?.endStakeNo || '-' }}</span>
</div>
</PanelItem>
<!-- 填报信息 -->
<PanelItem title="填报信息" v-if="!loading">
<!-- 遍历所有填报记录(首报 + 续报) -->
<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="report-content">
<div class="info-row">
<span class="info-label">处置措施:</span>
<span class="info-value">{{ report.disposalMeasures || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">预计恢复时间:</span>
<span class="info-value">{{ report.expectRecoverTime || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">实际恢复时间:</span>
<span class="info-value">{{ report.actualRecoverTime || '-' }}</span>
</div>
<template v-for="(item, idx) of report.materialUsageList" :key="idx">
<div class="info-row">
<span class="info-label">{{ item.materialName }}</span>
<span class="info-value" v-if="item.usageAmount">{{ item.usageAmount + '' + item.materialUnit }}</span>
<span class="info-value" v-else>-</span>
</div>
</template>
<div class="info-row">
<span class="info-label">有无车辆滞留</span>
<span class="info-value">{{ getVehicleStrandedText(report) }}</span>
</div>
<div class="info-row">
<span class="info-label">滞留车辆</span>
<span class="info-value">{{ report.strandedVehicleCount || 0 }}</span>
</div>
<div class="info-row">
<span class="info-label">现场描述</span>
<span class="info-value">{{ report.siteDescription || '-' }}</span>
</div>
<!-- 附件 -->
<div class="info-row column" v-if="report.fileList && report.fileList.length > 0">
<span class="info-label">附件</span>
<div class="attachment-list">
<div
v-for="(file, fileIndex) in report.fileList"
:key="fileIndex"
class="attachment-item"
>
<div
class="preview-image-block"
v-if="file.fileType === 1"
@click="previewFile(report, file)"
>
<img :src="file.fileUrl" alt="" />
</div>
<div class="preview-video-block" v-else>
<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(report, file)">预览</van-button> -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 无填报信息时显示 -->
<EmptyBox v-if="!hasReportData" placeholder="暂无填报信息" />
</PanelItem>
<!-- 底部按钮未解除状态显示续报按钮 -->
<div class="footer-buttons" v-if="!loading">
<van-button type="primary" class="footer-btn" @click="handleContinueReport">续报</van-button>
</div>
<van-loading class="loading-icon" v-if="loading">加载中...</van-loading>
<van-image-preview
:startPosition="startPosition"
v-model:show="previewImagesVisible"
:images="imagesForPreview"
>
<template v-slot:index="{ index }">{{ index + 1 }}</template>
</van-image-preview>
</PageContainer>
</template>
<script setup>
import { onMounted, ref, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { showToast, showImagePreview } from 'vant';
import PageContainer from '@/components/PageContainer.vue';
import PanelItem from '@/components/PanelItem.vue';
import CurrentSite from '@/components/CurrentSite.vue';
import EmptyBox from '@/components/EmptyBox.vue';
import { request } from '@shared/utils/request';
const router = useRouter();
const route = useRoute();
// 详情数据(对应 Data 接口)
const detailData = ref({
event: null, // Event 对象
report: [], // 填报列表(包含首报和续报)
fileList: [], // 附件列表
lossList: [], // 损失列表
});
const loading = ref(true);
const allReports = computed(() => {
const reports =
detailData.value.report?.map((item, index) => {
if (index === detailData.value.report.length - 1) {
item.title = '首报';
} else {
item.title = '续报' + (detailData.value.report.length - 1 - index);
}
return item;
}) || [];
return reports;
// return reports.reverse()
});
// 是否有填报数据
const hasReportData = computed(() => {
return allReports.value.length > 0;
});
// 获取事件状态文本
const getEventStatusText = () => {
return detailData.eventStatus === 1 ? '已解除' : '未解除';
};
// 获取事件状态类型
const getEventStatusType = () => {
return detailData.eventStatus === 1 ? 'success' : 'danger';
};
// 格式化处置措施(逗号分隔转中文)
const formatDisposalMeasures = measures => {
if (!measures) return '';
const measureMap = {
半幅封闭: '半幅封闭',
全副封闭: '全副封闭',
便道通行: '便道通行',
正常通行: '正常通行',
};
return measures
.split(',')
.map(m => measureMap[m.trim()] || m.trim())
.join('、');
};
// 获取车辆滞留文本
const getVehicleStrandedText = report => {
const count = report?.strandedVehicleCount || 0;
return count > 0 ? `有车滞留` : '无车滞留';
};
// 获取灾毁详情
const getDisasterDetail = async () => {
const id = route.query.id;
if (!id) {
showToast('缺少灾毁ID');
return;
}
loading.value = true;
try {
const result = await request({
url: `/snow-ops-platform/event/getById`,
method: 'get',
params: { id },
});
if (result?.data) {
// 接口返回 Data 结构
const data = result.data;
detailData.value = {
event: data.event || null,
report: data.reportList?.reverse() || [], // 直接使用 report包含首报和续报
fileList: data.fileList || [],
lossList: data.lossList || [],
};
} else {
showToast(result.message || '获取详情失败');
}
} catch (error) {
console.error('获取灾毁详情失败:', error);
showToast('获取详情失败,请稍后重试');
}
loading.value = false;
};
// 点击返回
const handleClickBack = () => {
router.push('/disasterManagement');
};
// 续报
const handleContinueReport = () => {
router.push({
path: '/disasterReport',
query: {
id: route.query.id,
eventType: 'ice',
isContinue: 'true',
},
});
};
const isImageFile = file => {
// 根据url后缀判断
const imageExtensions = /\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i;
if (file.fileUrl && imageExtensions.test(file.fileUrl)) return true;
// 根据文件类型判断
if (file.type && file.type.startsWith('image/')) return true;
return false;
};
const imagesForPreview = ref([]);
const previewImagesVisible = ref(false);
const startPosition = ref(0);
// 预览附件
const previewFile = (report, file) => {
const images = report.fileList.filter(file => isImageFile(file));
imagesForPreview.value = images.map(item => item.fileUrl);
startPosition.value = imagesForPreview.value.indexOf(file.fileUrl);
previewImagesVisible.value = true;
};
onMounted(() => {
getDisasterDetail();
});
</script>
<style scoped lang="scss">
.page-container {
padding-bottom: 80px;
}
.status-wrapper {
border-bottom: 1px solid #ebedf0;
}
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
line-height: 1.4;
&.sub-row {
margin-left: 20px;
margin-top: -8px;
}
&.column {
flex-direction: column;
.info-label {
margin-bottom: 10px;
}
}
}
.info-label {
width: 110px;
flex-shrink: 0;
color: #969799;
font-size: 14px;
}
.info-value {
flex: 1;
color: #323233;
font-size: 14px;
word-break: break-all;
}
.report-section {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #ebedf0;
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
}
.report-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px dashed #ebedf0;
.report-title {
font-size: 16px;
font-weight: 500;
color: #1989fa;
}
.report-meta {
font-size: 12px;
color: #969799;
}
}
.report-content {
.info-row {
margin-bottom: 10px;
}
}
.attachment-list {
display: flex;
flex-wrap: wrap;
flex: 1;
gap: 10px;
overflow: hidden;
.attachment-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
background: #f7f8fa;
border-radius: 4px;
.van-icon {
font-size: 20px;
color: #1989fa;
}
.file-name {
flex: 1;
font-size: 13px;
color: #323233;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.preview-image-block {
width: 60px;
height: 60px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.footer-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
gap: 12px;
padding: 12px 16px;
background: #fff;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
z-index: 10;
.footer-btn {
flex: 1;
height: 44px;
border-radius: 22px;
font-size: 16px;
}
}
.loading-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>