2026-04-07 17:01:46 +08:00

533 lines
16 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>
<div class="water-disaster">
<!-- 基本信息 -->
<PanelItem title="基本信息">
<van-form>
<!-- 路况类别 -->
<BasePicker v-model="formData.roadCondition" :options="roadConditionOptions" label="路况类别" placeholder="请选择" />
<!-- 是否阻断 -->
<BasePicker v-model="formData.isBlocked" :options="blockedOptions" label="是否阻断" placeholder="请选择" />
<!-- 抢修进度 -->
<BasePicker v-model="formData.repairProgress" :options="repairProgressOptions" label="抢修进度" placeholder="请选择" />
<!-- 水毁处数 -->
<van-field v-model="formData.waterDamageCount" label="水毁处数" placeholder="请填写" type="number" />
<!-- 阻断里程 -->
<van-field v-model="formData.blockedMileage" label="阻断里程" placeholder="请填写" type="digit">
<template #button>
<span class="field-unit">公里</span>
</template>
</van-field>
<!-- 发生时间 -->
<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>
<!-- 线路编号 -->
<van-field v-model="formData.lineCode" label="线路编号" placeholder="请填写" />
<!-- 起点桩号 -->
<van-field v-model="formData.startPileNo" label="起点桩号(K)" placeholder="请填写" />
<!-- 起点桩经纬度 -->
<div class="coordinate-row">
<van-field v-model="formData.startLongitude" label="起点桩经度" placeholder="经度" class="coordinate-field" />
<van-field v-model="formData.startLatitude" label="起点桩纬度" placeholder="纬度" class="coordinate-field" />
</div>
<div class="calibrate-coord-btn" @click="calibrateStartCoord">
<van-icon name="location-o" />
<span>校准经纬度</span>
</div>
<!-- 止点桩号 -->
<van-field v-model="formData.endPileNo" label="止点桩号(K)" placeholder="请填写" />
<!-- 止点桩经纬度 -->
<div class="coordinate-row">
<van-field v-model="formData.endLongitude" label="止点桩经度" placeholder="经度" class="coordinate-field" />
<van-field v-model="formData.endLatitude" label="止点桩纬度" placeholder="纬度" class="coordinate-field" />
</div>
<div class="calibrate-coord-btn" @click="calibrateEndCoord">
<van-icon name="location-o" />
<span>校准经纬度</span>
</div>
<!-- 路况位置 -->
<van-field v-model="formData.roadLocation" label="路况位置" placeholder="请填写" />
<!-- 阻断点小地名 -->
<van-field v-model="formData.smallPlaceName" label="阻断点小地名" placeholder="请填写" />
</van-form>
</PanelItem>
<!-- 处置情况 -->
<PanelItem title="处置情况">
<div class="disposal-measures">
<span class="measures-label">处置措施</span>
<div class="measures-options">
<van-checkbox-group v-model="formData.disposalMeasures" 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>
</div>
</div>
<!-- 预计恢复时间 -->
<BaseDatePicker v-model="formData.estimatedRecoverTime" label="预计恢复时间" placeholder="请选择时间" :min-date="minDate" :max-date="maxDate" type="datetime" />
<!-- 实际恢复时间 -->
<BaseDatePicker v-model="formData.actualRecoverTime" label="实际恢复时间" placeholder="请选择时间" :min-date="minDate" :max-date="maxDate" type="datetime" />
</PanelItem>
<!-- 人员车辆 -->
<PanelItem title="人员车辆">
<van-form>
<van-field v-model="formData.injuredCount" label="受伤人员" placeholder="请填写" type="number">
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.deathCount" label="死亡人员" placeholder="请填写" type="number">
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.strandedPeople" label="滞留人员" placeholder="请填写" type="number">
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.damagedVehicles" label="损坏车辆" placeholder="请填写" type="number">
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.strandedVehicles" label="滞留车辆" placeholder="请填写" type="number">
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
</van-form>
</PanelItem>
<!-- 灾毁损失 -->
<PanelItem title="灾毁损失">
<div class="loss-row">
<span class="loss-label">塌方及损失</span>
<span class="loss-value">{{ formData.collapseLoss }}/万元</span>
</div>
<van-button size="small" block type="primary" plain @click="showLossDialog = true">添加损失</van-button>
<van-field v-model="formData.handlingSituation" label="处理情况" placeholder="请填写(选填)" />
<van-field v-model="formData.totalLossAmount" label="损失总金额" placeholder="请填写(选填)" type="digit">
<template #button>
<span class="field-unit">万元</span>
</template>
</van-field>
</PanelItem>
<PanelItem>
<van-field v-model="formData.machineryInput" label="已投机械" placeholder="请填写" type="digit">
<template #button>
<span class="field-unit">/</span>
</template>
</van-field>
<van-field v-model="formData.laborInput" label="已投入力" placeholder="请填写" type="number">
<template #button>
<span class="field-unit">人次</span>
</template>
</van-field>
<van-field v-model="formData.fundsInput" label="已投资金" placeholder="请填写" type="digit">
<template #button>
<span class="field-unit">万元</span>
</template>
</van-field>
<van-field v-model="formData.siteDescription" label="现场描述" placeholder="请填写" type="textarea" rows="2" autosize />
<van-field label="附件">
<template #input>
</template>
</van-field>
</PanelItem>
<!-- 附件 -->
<!-- <PanelItem title="附件">
<div class="attachment-tip">图片只能上传jpg/png文件且不超过500kb视频仅支持20s内的视频</div>
<div class="upload-area">
<van-uploader
v-model="formData.imageFiles"
:after-read="afterImageRead"
accept="image/jpeg,image/png"
:max-size="500 * 1024"
@oversize="onOversize"
multiple
:max-count="9"
>
<div class="upload-btn">
<van-icon name="photo-o" size="24" />
<span>上传图片</span>
</div>
</van-uploader>
<van-uploader v-model="formData.videoFile" :after-read="afterVideoRead" accept="video/*" :max-size="20 * 1024 * 1024" @oversize="onVideoOversize">
<div class="upload-btn">
<van-icon name="video-o" size="24" />
<span>上传视频</span>
</div>
</van-uploader>
</div>
<div v-if="formData.videoFile.length > 0 && formData.videoFile[0].content" class="video-preview">
<video :src="formData.videoFile[0].content" controls style="width: 100%; max-height: 200px"></video>
</div>
</PanelItem> -->
<!-- 损失计算弹窗 -->
<van-dialog v-model:show="showLossDialog" title="添加塌方损失" show-cancel-button @confirm="confirmLoss" @cancel="showLossDialog = false">
<div class="loss-dialog-content">
<van-field v-model="lossForm.length" label="长度(m)" type="number" placeholder="请输入长度" />
<van-field v-model="lossForm.width" label="宽度(m)" type="number" placeholder="请输入宽度" />
<van-field v-model="lossForm.height" label="高度(m)" type="number" placeholder="请输入高度" />
<van-field v-model="lossForm.unitPrice" label="单价(元/m³)" type="number" placeholder="请输入单价" />
<div class="loss-calc-result">预估塌方量{{ calculatedVolume }} </div>
<div class="loss-calc-result">预估损失{{ calculatedLoss }} 万元</div>
</div>
</van-dialog>
</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'
// 定义 props
const props = defineProps({
modelValue: {
type: Object,
default: () => ({})
}
})
// 定义 emits
const emit = defineEmits(['update:modelValue', 'submit'])
// 表单数据
const formData = reactive({
roadCondition: '',
isBlocked: '',
repairProgress: '',
waterDamageCount: '',
blockedMileage: '',
occurTime: '',
lineCode: '',
startPileNo: '',
startLongitude: '',
startLatitude: '',
endPileNo: '',
endLongitude: '',
endLatitude: '',
roadLocation: '',
smallPlaceName: '',
disposalMeasures: [],
estimatedRecoverTime: '',
actualRecoverTime: '',
injuredCount: '',
deathCount: '',
strandedPeople: '',
damagedVehicles: '',
strandedVehicles: '',
collapseLoss: '0',
handlingSituation: '',
totalLossAmount: '',
machineryInput: '',
laborInput: '',
fundsInput: '',
siteDescription: '',
imageFiles: [],
videoFile: []
})
// 弹窗显示状态
const showLossDialog = ref(false)
// BasePicker 选项数据
const roadConditionOptions = [
{ label: '高速公路', value: '高速公路' },
{ label: '国道', value: '国道' },
{ label: '省道', value: '省道' },
{ label: '县道', value: '县道' },
{ label: '乡道', value: '乡道' },
{ label: '村道', value: '村道' }
]
const blockedOptions = [
{ label: '是', value: '是' },
{ label: '否', value: '否' }
]
const repairProgressOptions = [
{ label: '未开始', value: '未开始' },
{ label: '进行中', value: '进行中' },
{ label: '已抢通', value: '已抢通' },
{ label: '已修复', value: '已修复' }
]
// 时间选择器范围
const minDate = new Date(2020, 0, 1)
const maxDate = new Date(2030, 11, 31)
// 损失计算表单
const lossForm = reactive({
length: '',
width: '',
height: '',
unitPrice: ''
})
// 计算塌方量
const calculatedVolume = computed(() => {
const l = parseFloat(lossForm.length) || 0
const w = parseFloat(lossForm.width) || 0
const h = parseFloat(lossForm.height) || 0
return (l * w * h).toFixed(2)
})
// 计算损失金额(万元)
const calculatedLoss = computed(() => {
const volume = parseFloat(calculatedVolume.value) || 0
const price = parseFloat(lossForm.unitPrice) || 0
const lossYuan = volume * price
return (lossYuan / 10000).toFixed(2)
})
// 监听父组件传入的数据
watch(
() => props.modelValue,
(newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
Object.assign(formData, newVal)
}
},
{ immediate: true, deep: true }
)
// 监听表单数据变化,同步到父组件
watch(
formData,
() => {
emit('update:modelValue', { ...formData })
},
{ deep: true }
)
// 校准时间
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 = () => {
formData.startLongitude = '108.41763025'
formData.startLatitude = '108.41763025'
showToast('起点经纬度已校准')
}
// 校准止点经纬度
const calibrateEndCoord = () => {
formData.endLongitude = '108.41763025'
formData.endLatitude = '108.41763025'
showToast('止点经纬度已校准')
}
// 确认损失计算
const confirmLoss = () => {
const lossValue = calculatedLoss.value
if (parseFloat(lossValue) > 0) {
formData.collapseLoss = lossValue
if (formData.totalLossAmount) {
const currentTotal = parseFloat(formData.totalLossAmount) || 0
formData.totalLossAmount = (currentTotal + parseFloat(lossValue)).toFixed(2)
} else {
formData.totalLossAmount = lossValue
}
} else {
showToast('请填写有效的长宽高和单价')
}
lossForm.length = ''
lossForm.width = ''
lossForm.height = ''
lossForm.unitPrice = ''
showLossDialog.value = false
}
// 图片上传处理
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
}
if (!formData.lineCode) {
showToast('请填写线路编号')
return false
}
return true
}
// 获取表单数据
const getFormData = () => {
return { ...formData }
}
// 暴露方法给父组件
defineExpose({
validate,
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;
}
}
}
.loss-row {
display: flex;
align-items: center;
justify-content: space-between;
background: #f8f9fa;
padding: 10px 12px;
border-radius: 8px;
margin: 12px 0;
.loss-label {
font-size: 14px;
color: #323233;
}
.loss-value {
font-size: 16px;
font-weight: 600;
color: #ee0a24;
}
}
.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;
}
.loss-dialog-content {
padding: 8px 16px 16px;
.loss-calc-result {
font-size: 14px;
color: #1989fa;
margin-top: 12px;
text-align: center;
}
}
:deep(.van-field__label) {
width: 90px;
}
}
</style>