530 lines
17 KiB
Vue
Raw Normal View History

2026-04-07 17:01:46 +08:00
<template>
<div class="water-disaster">
<!-- 基本信息 -->
2026-04-08 16:01:04 +08:00
<PanelItem title="基本信息" v-if="!isContinue">
2026-04-07 17:01:46 +08:00
<van-form>
<!-- 路况类别 -->
2026-04-08 11:12:41 +08:00
<BasePicker v-model="formData.roadConditionType" :options="roadConditionOptions" label="路况类别" placeholder="请选择" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 是否阻断 (event.isBlocked) -->
<BasePicker v-model="formData.event.isBlocked" :options="blockedOptions" label="是否阻断" placeholder="请选择" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 抢修进度 (event.repairProgress) -->
<BasePicker v-model="formData.event.repairProgress" :options="repairProgressOptions" label="抢修进度" placeholder="请选择" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 水毁处数 (event.damageCount) -->
<van-field v-model="formData.event.damageCount" label="水毁处数" placeholder="请填写" type="number" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 阻断里程 (event.blockedMileage) -->
<van-field v-model="formData.event.blockedMileage" label="阻断里程" placeholder="请填写" type="digit">
2026-04-07 17:01:46 +08:00
<template #button>
<span class="field-unit">公里</span>
</template>
</van-field>
2026-04-08 11:12:41 +08:00
<!-- 发生时间 (顶层 occurTime) -->
2026-04-07 17:01:46 +08:00
<BaseDatePicker v-model="formData.occurTime" label="发生时间" placeholder="请选择时间" :columnsType="['year', 'month', 'day', 'hour', 'minute']" />
<div class="calibrate-time-btn" @click="calibrateTime">
<van-icon name="replay" />
<span>校准时间</span>
</div>
2026-04-08 11:12:41 +08:00
<!-- 线路编号 (顶层 routeNo) -->
<van-field v-model="formData.routeNo" label="线路编号" placeholder="请填写" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 起点桩号 (event.startStakeNo) -->
<van-field v-model="formData.event.startStakeNo" label="起点桩号(K)" placeholder="请填写" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 起点桩经纬度 (event.startStakeLng / startStakeLat) -->
2026-04-07 17:01:46 +08:00
<div class="coordinate-row">
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.event.startStakeLng" label="起点桩经度" placeholder="经度" class="coordinate-field" />
<van-field v-model="formData.event.startStakeLat" label="起点桩纬度" placeholder="纬度" class="coordinate-field" />
2026-04-07 17:01:46 +08:00
</div>
<div class="calibrate-coord-btn" @click="calibrateStartCoord">
<van-icon name="location-o" />
<span>校准经纬度</span>
</div>
2026-04-08 11:12:41 +08:00
<!-- 止点桩号 (event.endStakeNo) -->
<van-field v-model="formData.event.endStakeNo" label="止点桩号(K)" placeholder="请填写" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 止点桩经纬度 (event.endStakeLng / endStakeLat) -->
2026-04-07 17:01:46 +08:00
<div class="coordinate-row">
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.event.endStakeLng" label="止点桩经度" placeholder="经度" class="coordinate-field" />
<van-field v-model="formData.event.endStakeLat" label="止点桩纬度" placeholder="纬度" class="coordinate-field" />
2026-04-07 17:01:46 +08:00
</div>
<div class="calibrate-coord-btn" @click="calibrateEndCoord">
<van-icon name="location-o" />
<span>校准经纬度</span>
</div>
2026-04-08 11:12:41 +08:00
<!-- 路况位置 (event.endStakeNo) -->
<van-field v-model="formData.occurLocation" label="路况位置" placeholder="请填写" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 阻断点小地名 (event.blockedPointName) -->
<van-field v-model="formData.event.blockedPointName" label="阻断点小地名" placeholder="请填写" />
2026-04-07 17:01:46 +08:00
</van-form>
</PanelItem>
2026-04-08 11:12:41 +08:00
<!-- 处置情况 (report) -->
2026-04-07 17:01:46 +08:00
<PanelItem title="处置情况">
<div class="disposal-measures">
<span class="measures-label">处置措施</span>
<div class="measures-options">
2026-04-08 11:12:41 +08:00
<van-checkbox-group v-model="disposalMeasuresArray" direction="horizontal">
2026-04-07 17:01:46 +08:00
<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>
</div>
</div>
2026-04-08 11:12:41 +08:00
<!-- 预计恢复时间 (report.expectRecoverTime) -->
<BaseDatePicker v-model="formData.report.expectRecoverTime" label="预计恢复时间" placeholder="请选择时间" :min-date="minDate" :max-date="maxDate" type="datetime" />
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
<!-- 实际恢复时间 (report.actualRecoverTime) -->
<BaseDatePicker v-model="formData.report.actualRecoverTime" label="实际恢复时间" placeholder="请选择时间" :min-date="minDate" :max-date="maxDate" type="datetime" />
2026-04-07 17:01:46 +08:00
</PanelItem>
2026-04-08 11:12:41 +08:00
<!-- 人员车辆 (report) -->
2026-04-07 17:01:46 +08:00
<PanelItem title="人员车辆">
<van-form>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.injuredCount" label="受伤人员" placeholder="请填写" type="number">
2026-04-07 17:01:46 +08:00
<template #button>
2026-04-08 11:12:41 +08:00
<span class="field-unit"></span>
2026-04-07 17:01:46 +08:00
</template>
</van-field>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.deadCount" label="死亡人员" placeholder="请填写" type="number">
2026-04-07 17:01:46 +08:00
<template #button>
2026-04-08 11:12:41 +08:00
<span class="field-unit"></span>
2026-04-07 17:01:46 +08:00
</template>
</van-field>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.strandedPersonCount" label="滞留人员" placeholder="请填写" type="number">
2026-04-07 17:01:46 +08:00
<template #button>
2026-04-08 11:12:41 +08:00
<span class="field-unit"></span>
2026-04-07 17:01:46 +08:00
</template>
</van-field>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.damagedVehicleCount" label="损坏车辆" placeholder="请填写" type="number">
2026-04-07 17:01:46 +08:00
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.strandedVehicleCount" label="滞留车辆" placeholder="请填写" type="number">
2026-04-07 17:01:46 +08:00
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
</van-form>
</PanelItem>
2026-04-08 11:12:41 +08:00
<!-- 灾毁损失 (lossList) -->
2026-04-07 17:01:46 +08:00
<PanelItem title="灾毁损失">
2026-04-08 09:21:47 +08:00
<LossList v-model="formData.lossList" />
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.remark" label="处理情况" placeholder="请填写(选填)" />
<van-field v-model="formData.report.totalLossAmount" label="损失总金额" placeholder="请填写(选填)" type="digit">
2026-04-07 17:01:46 +08:00
<template #button>
<span class="field-unit">万元</span>
</template>
</van-field>
</PanelItem>
2026-04-08 11:12:41 +08:00
<!-- 投入资源 (report) -->
2026-04-07 17:01:46 +08:00
<PanelItem>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.investedMachinery" label="已投机械" placeholder="请填写" type="digit">
2026-04-07 17:01:46 +08:00
<template #button>
<span class="field-unit">/</span>
</template>
</van-field>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.investedManpower" label="已投入力" placeholder="请填写" type="number">
2026-04-07 17:01:46 +08:00
<template #button>
<span class="field-unit">人次</span>
</template>
</van-field>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.investedFunds" label="已投资金" placeholder="请填写" type="digit">
2026-04-07 17:01:46 +08:00
<template #button>
<span class="field-unit">万元</span>
</template>
</van-field>
2026-04-08 11:12:41 +08:00
<van-field v-model="formData.report.siteDescription" label="现场描述" placeholder="请填写" type="textarea" rows="2" autosize />
2026-04-07 17:01:46 +08:00
</PanelItem>
2026-04-08 11:12:41 +08:00
<!-- 附件 (fileList) -->
2026-04-07 17:01:46 +08:00
<!-- <PanelItem title="附件">
<div class="attachment-tip">图片只能上传jpg/png文件且不超过500kb视频仅支持20s内的视频</div>
<div class="upload-area">
2026-04-08 11:12:41 +08:00
<van-uploader v-model="imageFileList" :after-read="afterImageRead" accept="image/jpeg,image/png" :max-size="500 * 1024" @oversize="onOversize" multiple :max-count="9">
2026-04-07 17:01:46 +08:00
<div class="upload-btn">
<van-icon name="photo-o" size="24" />
<span>上传图片</span>
</div>
</van-uploader>
2026-04-08 11:12:41 +08:00
<van-uploader v-model="videoFileList" :after-read="afterVideoRead" accept="video/*" :max-size="20 * 1024 * 1024" @oversize="onVideoOversize">
2026-04-07 17:01:46 +08:00
<div class="upload-btn">
<van-icon name="video-o" size="24" />
<span>上传视频</span>
</div>
</van-uploader>
</div>
2026-04-08 11:12:41 +08:00
<div v-if="videoFileList.length > 0 && videoFileList[0].content" class="video-preview">
<video :src="videoFileList[0].content" controls style="width: 100%; max-height: 200px"></video>
2026-04-07 17:01:46 +08:00
</div>
</PanelItem> -->
2026-04-08 11:12:41 +08:00
<PanelItem>
<!-- 是否需要恢复重建 (event.needsRecovery) -->
<BasePicker v-model="formData.event.needsRecovery" :options="needsRecoveryOptions" label="是否需要恢复重建" placeholder="请选择" />
<!-- 恢复重建预估费用 (event.estimatedRecoveryCost) -->
2026-04-08 16:01:04 +08:00
<van-field v-model="formData.event.estimatedRecoveryCost" v-if="!isContinue" label="恢复重建预估费用" placeholder="请填写" type="digit">
2026-04-08 11:12:41 +08:00
<template #button>
<span class="field-unit">万元</span>
</template>
</van-field>
</PanelItem>
2026-04-07 17:01:46 +08:00
</div>
</template>
<script setup>
import { ref, reactive, computed, watch } from 'vue'
import { showToast, showFailToast } from 'vant'
import PanelItem from '@/components/PanelItem.vue'
import BasePicker from '@/components/BasePicker.vue'
import BaseDatePicker from '@/components/BaseDatePicker.vue'
2026-04-08 09:21:47 +08:00
import LossList from './LossList.vue'
2026-04-08 16:01:04 +08:00
import { useRouter, useRoute } from 'vue-router'
2026-04-07 17:01:46 +08:00
2026-04-08 16:01:04 +08:00
const route = useRoute()
// 是否为续报
const isContinue = computed(() => route.query.isContinue)
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
// 处置措施数组(用于多选框组,需要转换为逗号分隔的字符串)
const disposalMeasuresArray = ref([])
// 附件列表
const imageFileList = ref([])
const videoFileList = ref([])
2026-04-07 17:01:46 +08:00
2026-04-08 11:12:41 +08:00
// 表单数据 - 按 Request 接口结构定义
2026-04-07 17:01:46 +08:00
const formData = reactive({
2026-04-08 11:12:41 +08:00
// 顶层字段
occurLocation: '', // 发生地点
occurTime: '', // 发生时间
roadConditionType: '', // 路况类别
routeNo: '', // 线路编号
// event 对象
event: {
blockedMileage: '', // 阻断里程
blockedPointName: '', // 阻断点小地名
contactPerson: '', // 联系人
contactPhone: '', // 联系电话
damageCount: '', // 水毁处数
district: '', // 上报区县
endStakeLat: '', // 止点纬度
endStakeLng: '', // 止点经度
endStakeNo: '', // 止点桩号
estimatedRecoveryCost: '', // 恢复重建预估费用
inspectionMileage: '', // 巡查里程
isBlocked: '', // 是否阻断
needsRecovery: '', // 是否需要恢复重建
repairProgress: '', // 抢修进度
reporterUnit: '', // 填报单位
startStakeLat: '', // 起点纬度
startStakeLng: '', // 起点经度
startStakeNo: '' // 起点桩号
},
// report 对象
report: {
actualRecoverTime: '', // 实际恢复时间
damagedVehicleCount: '', // 损坏车辆
deadCount: '', // 死亡人员
disposalMeasures: '', // 处置措施(逗号分隔)
expectRecoverTime: '', // 预计恢复时间
injuredCount: '', // 受伤人员
investedFunds: '', // 已投资金
investedMachinery: '', // 已投机械
investedManpower: '', // 已投人力
remark: '', // 处理情况/备注
siteDescription: '', // 现场描述
strandedPersonCount: '', // 滞留人员
strandedVehicleCount: '', // 滞留车辆
totalLossAmount: '' // 损失总金额
},
// lossList 数组
lossList: [],
// fileList 数组
fileList: []
2026-04-07 17:01:46 +08:00
})
2026-04-08 11:12:41 +08:00
// 监听处置措施数组变化,转换为逗号分隔的字符串存到 report.disposalMeasures
watch(
disposalMeasuresArray,
(newVal) => {
formData.report.disposalMeasures = newVal.join(',')
},
{ deep: true }
)
// 监听附件变化,同步到 fileList
watch(
imageFileList,
(newVal) => {
// 转换为接口需要的格式
formData.fileList = [
...imageFileList.value.map((f) => ({
fileName: f.file?.name || '',
fileSize: f.file?.size || 0,
fileType: 1, // 1-图片
fileUrl: f.content || ''
})),
...videoFileList.value.map((f) => ({
fileName: f.file?.name || '',
fileSize: f.file?.size || 0,
fileType: 2, // 2-视频
fileUrl: f.content || ''
}))
]
},
{ deep: true }
)
watch(
videoFileList,
(newVal) => {
formData.fileList = [
...imageFileList.value.map((f) => ({
fileName: f.file?.name || '',
fileSize: f.file?.size || 0,
fileType: 1,
fileUrl: f.content || ''
})),
...newVal.map((f) => ({
fileName: f.file?.name || '',
fileSize: f.file?.size || 0,
fileType: 2,
fileUrl: f.content || ''
}))
]
},
{ deep: true }
)
// 从 report.disposalMeasures 初始化处置措施数组
watch(
() => formData.report.disposalMeasures,
(newVal) => {
if (newVal && typeof newVal === 'string') {
disposalMeasuresArray.value = newVal.split(',').filter(Boolean)
}
},
{ immediate: true }
)
2026-04-07 17:01:46 +08:00
// BasePicker 选项数据
const roadConditionOptions = [
{ label: '高速公路', value: '高速公路' },
{ label: '国道', value: '国道' },
{ label: '省道', value: '省道' },
{ label: '县道', value: '县道' },
{ label: '乡道', value: '乡道' },
{ label: '村道', value: '村道' }
]
const blockedOptions = [
2026-04-08 11:12:41 +08:00
{ label: '是', value: true },
{ label: '否', value: false }
2026-04-07 17:01:46 +08:00
]
const repairProgressOptions = [
2026-04-08 11:12:41 +08:00
{ label: '未抢修', value: '未抢修' },
{ label: '抢修中', value: '抢修中' },
{ label: '已完成', value: '已完成' },
]
const needsRecoveryOptions = [
{ label: '是', value: true },
{ label: '否', value: false }
2026-04-07 17:01:46 +08:00
]
// 时间选择器范围
const minDate = new Date(2020, 0, 1)
const maxDate = new Date(2030, 11, 31)
2026-04-08 11:12:41 +08:00
const initFormData = (newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
// 深度合并数据
Object.assign(formData, {
occurLocation: newVal.occurLocation || '',
occurTime: newVal.occurTime || '',
roadConditionType: newVal.roadConditionType || '',
routeNo: newVal.routeNo || '',
event: { ...formData.event, ...(newVal.event || {}) },
report: { ...formData.report, ...(newVal.report || {}) },
lossList: newVal.lossList || [],
fileList: newVal.fileList || []
})
// 初始化处置措施数组
if (newVal.report?.disposalMeasures) {
disposalMeasuresArray.value = newVal.report.disposalMeasures.split(',').filter(Boolean)
2026-04-07 17:01:46 +08:00
}
2026-04-08 11:12:41 +08:00
}
}
2026-04-07 17:01:46 +08:00
// 校准时间
const calibrateTime = () => {
const now = new Date()
const formatted = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
formData.occurTime = formatted
showToast('时间已校准为当前时间')
}
// 校准起点经纬度
const calibrateStartCoord = () => {
2026-04-08 11:12:41 +08:00
formData.event.startStakeLng = '108.41763025'
formData.event.startStakeLat = '108.41763025'
2026-04-07 17:01:46 +08:00
showToast('起点经纬度已校准')
}
// 校准止点经纬度
const calibrateEndCoord = () => {
2026-04-08 11:12:41 +08:00
formData.event.endStakeLng = '108.41763025'
formData.event.endStakeLat = '108.41763025'
2026-04-07 17:01:46 +08:00
showToast('止点经纬度已校准')
}
// 图片上传处理
const afterImageRead = (file) => {
console.log('图片上传:', file)
}
const onOversize = () => {
showFailToast('图片大小不能超过500KB')
}
const afterVideoRead = (file) => {
console.log('视频上传:', file)
}
const onVideoOversize = () => {
showFailToast('视频大小不能超过20MB')
}
// 暴露验证方法
const validate = () => {
if (!formData.occurTime) {
showToast('请填写发生时间')
return false
}
2026-04-08 11:12:41 +08:00
if (!formData.routeNo) {
2026-04-07 17:01:46 +08:00
showToast('请填写线路编号')
return false
}
return true
}
// 获取表单数据
const getFormData = () => {
return { ...formData }
}
// 暴露方法给父组件
defineExpose({
validate,
2026-04-08 11:12:41 +08:00
initFormData,
2026-04-07 17:01:46 +08:00
getFormData
})
</script>
<style lang="scss" scoped>
.water-disaster {
.coordinate-row {
display: flex;
gap: 8px;
.coordinate-field {
flex: 1;
}
}
.calibrate-time-btn,
.calibrate-coord-btn {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #1989fa;
margin: 8px 0 8px 12px;
padding: 4px 8px;
background: #f0f7ff;
border-radius: 20px;
cursor: pointer;
width: fit-content;
}
.field-unit {
color: #969799;
font-size: 14px;
margin-left: 4px;
}
.disposal-measures {
margin-bottom: 16px;
.measures-label {
font-size: 14px;
color: #323233;
display: block;
margin-bottom: 8px;
}
.measures-options {
:deep(.van-checkbox-group) {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
:deep(.van-checkbox) {
margin-right: 0;
}
}
}
.attachment-tip {
font-size: 12px;
color: #969799;
margin-bottom: 12px;
}
.upload-area {
display: flex;
gap: 16px;
flex-wrap: wrap;
.upload-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
background: #f8f9fa;
border: 1px dashed #dcdee0;
border-radius: 8px;
gap: 4px;
font-size: 12px;
color: #969799;
cursor: pointer;
}
}
.video-preview {
margin-top: 12px;
}
:deep(.van-field__label) {
2026-04-08 11:12:41 +08:00
width: 110px;
2026-04-07 17:01:46 +08:00
}
}
</style>