611 lines
19 KiB
Vue
611 lines
19 KiB
Vue
<template>
|
|
<div class="water-disaster" >
|
|
<!-- 基本信息 -->
|
|
<PanelItem title="基本信息" v-if="!isContinue">
|
|
<van-form >
|
|
<!-- 路况类别 -->
|
|
<BasePicker v-model="formData.event.roadConditionType" :options="options['waterRoadConditionType']" label="路况类别" placeholder="请选择" />
|
|
|
|
<!-- 是否阻断 (event.isBlocked) -->
|
|
<BasePicker v-model="formData.event.isBlocked" :options="options['yesOrNoBool']" label="是否阻断" placeholder="请选择" />
|
|
|
|
<!-- 抢险进度 (event.repairProgress) -->
|
|
<BasePicker v-model="formData.event.repairProgress" :options="options['repairProgress']" label="抢险进度" placeholder="请选择" />
|
|
|
|
<!-- 水毁处数 (event.damageCount) -->
|
|
<van-field v-model="formData.event.damageCount" label="水毁处数" placeholder="请填写" type="number" />
|
|
|
|
<!-- 阻断里程 (event.blockedMileage) -->
|
|
<van-field v-model="formData.event.blockedMileage" label="阻断里程" placeholder="请填写" type="number">
|
|
<template #button>
|
|
<span class="field-unit">公里</span>
|
|
</template>
|
|
</van-field>
|
|
|
|
<!-- 发生时间 (顶层 occurTime) -->
|
|
<BaseDatePicker v-model="formData.event.occurTime" label="发生时间" placeholder="请选择时间" :columnsType="['year', 'month', 'day', 'hour', 'minute']" />
|
|
<div class="calibrate-time-btn" @click="calibrateTime">
|
|
<van-icon name="replay" />
|
|
<span>校准时间</span>
|
|
</div>
|
|
|
|
<BasePicker
|
|
v-model="routeFilterForm.routeType"
|
|
:options="options['roadType']"
|
|
label="路线类型"
|
|
placeholder="请选择"
|
|
@change="handleRouteTypeChange"
|
|
/>
|
|
|
|
<!-- 线路编号 (顶层 routeNo) -->
|
|
<RoadRoutesPicker
|
|
v-model="formData.event.routeNo"
|
|
label="线路编号"
|
|
placeholder="请选择"
|
|
:extra-params="{ xzdj: routeFilterForm.routeType }"
|
|
@change="handleRouteNoChange"
|
|
/>
|
|
|
|
<!-- 起点桩号 (event.startStakeNo) -->
|
|
<van-field v-model="formData.event.startStakeNo" label="起点桩号(K)" placeholder="请填写" />
|
|
|
|
<!-- 起点桩经度 -->
|
|
<van-field v-model="formData.event.startStakeLng" label="起点桩经度" placeholder="请填写" type="number" />
|
|
|
|
<!-- 起点桩纬度 -->
|
|
<van-field v-model="formData.event.startStakeLat" label="起点桩纬度" placeholder="请填写" type="number" />
|
|
|
|
<!-- 止点桩号 (event.endStakeNo) -->
|
|
<van-field v-model="formData.event.endStakeNo" label="止点桩号(K)" placeholder="请填写" />
|
|
|
|
<!-- 止点桩经度 -->
|
|
<van-field v-model="formData.event.endStakeLng" label="止点桩经度" placeholder="请填写" type="number" />
|
|
|
|
<!-- 止点桩纬度 -->
|
|
<van-field v-model="formData.event.endStakeLat" label="止点桩纬度" placeholder="请填写" type="number" />
|
|
|
|
<!-- 路况位置 (occurLocation) -->
|
|
<PositionPickerMobile
|
|
v-model="formData.event.occurLocation"
|
|
label="路况位置"
|
|
placeholder="请选择"
|
|
:initial-longitude="formData.event.startStakeLng"
|
|
:initial-latitude="formData.event.startStakeLat"
|
|
/>
|
|
|
|
<!-- 阻断点小地名 (event.blockedPointName) -->
|
|
<van-field v-model="formData.event.blockedPointName" label="阻断点小地名" placeholder="请填写" />
|
|
</van-form>
|
|
</PanelItem>
|
|
|
|
<!-- 处置情况 (report) -->
|
|
<PanelItem title="处置情况">
|
|
<DisposalMeasuresSelector
|
|
v-model="formData.report.disposalMeasures"
|
|
:options="options['disposalMeasures']"
|
|
label="处置措施"
|
|
/>
|
|
|
|
<!-- 预计恢复时间 (report.expectRecoverTime) -->
|
|
<BaseDatePicker v-model="formData.report.expectRecoverTime" label="预计恢复时间" placeholder="请选择时间" :min-date="minDate" :max-date="maxDate" type="datetime" />
|
|
|
|
<!-- 实际恢复时间 (report.actualRecoverTime) -->
|
|
<BaseDatePicker v-model="formData.report.actualRecoverTime" label="实际恢复时间" placeholder="请选择时间(非必填)" :min-date="minDate" :max-date="maxDate" type="datetime" />
|
|
</PanelItem>
|
|
|
|
<!-- 人员车辆 (report) -->
|
|
<PanelItem title="人员车辆">
|
|
<van-form>
|
|
<van-field v-model="formData.report.injuredCount" label="受伤人员" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">人</span>
|
|
</template>
|
|
</van-field>
|
|
<van-field v-model="formData.report.deadCount" label="死亡人员" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">人</span>
|
|
</template>
|
|
</van-field>
|
|
<van-field v-model="formData.report.strandedPersonCount" label="滞留人员" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">人</span>
|
|
</template>
|
|
</van-field>
|
|
<van-field v-model="formData.report.damagedVehicleCount" label="损坏车辆" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">辆</span>
|
|
</template>
|
|
</van-field>
|
|
<van-field v-model="formData.report.strandedVehicleCount" label="滞留车辆" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">辆</span>
|
|
</template>
|
|
</van-field>
|
|
</van-form>
|
|
</PanelItem>
|
|
|
|
<!-- 灾毁损失 (lossList) -->
|
|
<PanelItem title="灾毁损失">
|
|
<LossList v-model="formData.lossList" />
|
|
<van-field v-model="formData.report.remark" label="处理情况" placeholder="请填写(非必填)" />
|
|
<van-field v-model="formData.report.totalLossAmount" label="损失总金额" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">万元</span>
|
|
</template>
|
|
</van-field>
|
|
</PanelItem>
|
|
|
|
<!-- 投入资源 (report) -->
|
|
<PanelItem>
|
|
<van-field v-model="formData.report.investedMachinery" label="投入机械" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">台/班</span>
|
|
</template>
|
|
</van-field>
|
|
<van-field v-model="formData.report.investedManpower" label="投入人力" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">人次</span>
|
|
</template>
|
|
</van-field>
|
|
<van-field v-model="formData.report.investedFunds" label="投入资金" placeholder="请填写(非必填)" type="number">
|
|
<template #button>
|
|
<span class="field-unit">万元</span>
|
|
</template>
|
|
</van-field>
|
|
<van-field v-model="formData.report.siteDescription" label="现场描述" placeholder="请填写(非必填)" type="textarea" rows="2" autosize />
|
|
<!-- 文件上传 -->
|
|
<DisasterFileUpload label="附件上传" v-model="formData.fileList" />
|
|
</PanelItem>
|
|
<PanelItem v-if="!isContinue || (isContinue && !detail?.event.needsRecovery)">
|
|
<!-- 是否需要恢复重建 (event.needsRecovery) -->
|
|
<BasePicker v-model="formData.event.needsRecovery" :options="options['yesOrNoBool']" label="是否需要恢复重建" placeholder="请选择" />
|
|
<!-- 恢复重建预估费用 (event.estimatedRecoveryCost) -->
|
|
<van-field v-model="formData.event.estimatedRecoveryCost" v-if="formData?.event.needsRecovery" label="恢复重建预估费用" placeholder="请填写" type="number">
|
|
<template #button>
|
|
<span class="field-unit">万元</span>
|
|
</template>
|
|
</van-field>
|
|
</PanelItem>
|
|
|
|
<!-- 提交按钮 -->
|
|
<van-button type="primary" class="footer-btn" @click="handleSubmit" :loading="submitting"> 提交 </van-button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch, onMounted } from 'vue'
|
|
import { showToast, showFailToast, showLoadingToast } from 'vant'
|
|
import PanelItem from '@/components/PanelItem.vue'
|
|
import BasePicker from '@/components/BasePicker.vue'
|
|
import BaseDatePicker from '@/components/BaseDatePicker.vue'
|
|
import DisasterFileUpload from '../components/DisasterFileUpload.vue'
|
|
import DisposalMeasuresSelector from '../components/DisposalMeasuresSelector.vue'
|
|
import PositionPickerMobile from '../components/PositionPickerMobile.vue'
|
|
import RoadRoutesPicker from '../components/RoadRoutesPicker.vue'
|
|
import LossList from '../components/LossList.vue'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { request } from '@shared/utils/request'
|
|
import { useOptions } from '@shared/composables/useOptions'
|
|
import { formatDate } from '@shared/utils'
|
|
import { useYHZStore } from '@/stores/yhzStore';
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const { options } = useOptions()
|
|
const yhzStore = useYHZStore();
|
|
// 是否为续报
|
|
const isContinue = computed(() => route.query.isContinue)
|
|
|
|
// 表单数据 - 按 Request 接口结构定义,使用 ref 包装
|
|
const formData = ref({
|
|
// event 对象
|
|
event: {
|
|
startStakeNo: '',
|
|
startStakeLng: '',
|
|
startStakeLat: '',
|
|
endStakeNo: '',
|
|
endStakeLng: '',
|
|
endStakeLat: ''
|
|
},
|
|
|
|
// report 对象
|
|
report: {},
|
|
|
|
// lossList 数组
|
|
lossList: [],
|
|
|
|
// fileList 数组
|
|
fileList: []
|
|
})
|
|
|
|
const routeFilterForm = ref({
|
|
routeType: ''
|
|
})
|
|
|
|
const submitting = ref(false)
|
|
|
|
// 时间选择器范围
|
|
const minDate = new Date(2020, 0, 1)
|
|
const maxDate = new Date(2030, 11, 31)
|
|
|
|
const initFormData = (newVal) => {
|
|
formData.value = { ...newVal }
|
|
}
|
|
|
|
// 校准时间
|
|
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.value.event.occurTime = formatted
|
|
showToast('时间已校准为当前时间')
|
|
}
|
|
|
|
const parsePointValue = (point) => {
|
|
if (!point) {
|
|
return { longitude: null, latitude: null }
|
|
}
|
|
|
|
if (Array.isArray(point) && point.length >= 2) {
|
|
return {
|
|
longitude: point[0] ?? null,
|
|
latitude: point[1] ?? null
|
|
}
|
|
}
|
|
|
|
if (typeof point === 'string') {
|
|
try {
|
|
const parsed = JSON.parse(point)
|
|
if (Array.isArray(parsed) && parsed.length >= 2) {
|
|
return {
|
|
longitude: parsed[0] ?? null,
|
|
latitude: parsed[1] ?? null
|
|
}
|
|
}
|
|
} catch (_error) {
|
|
return { longitude: null, latitude: null }
|
|
}
|
|
}
|
|
|
|
return { longitude: null, latitude: null }
|
|
}
|
|
|
|
const resetRouteFields = () => {
|
|
formData.value.event.routeNo = ''
|
|
formData.value.event.startStakeNo = ''
|
|
formData.value.event.startStakeLng = ''
|
|
formData.value.event.startStakeLat = ''
|
|
formData.value.event.endStakeNo = ''
|
|
formData.value.event.endStakeLng = ''
|
|
formData.value.event.endStakeLat = ''
|
|
}
|
|
|
|
const handleRouteTypeChange = () => {
|
|
resetRouteFields()
|
|
}
|
|
|
|
const handleRouteNoChange = (item) => {
|
|
formData.value.event.routeNo = item.routeCode
|
|
formData.value.event.startStakeNo = item.startStakeNo
|
|
formData.value.event.endStakeNo = item.endStakeNo
|
|
|
|
const startPoint = parsePointValue(item.startPoint)
|
|
const endPoint = parsePointValue(item.endPoint)
|
|
|
|
formData.value.event.startStakeLng = startPoint.longitude
|
|
formData.value.event.startStakeLat = startPoint.latitude
|
|
formData.value.event.endStakeLng = endPoint.longitude
|
|
formData.value.event.endStakeLat = endPoint.latitude
|
|
}
|
|
|
|
// 简单的空值判断
|
|
const isEmpty = (value) => {
|
|
return value === null || value === undefined || value === ''
|
|
}
|
|
|
|
const validate = () => {
|
|
if (isEmpty(formData.value.event?.roadConditionType)) {
|
|
showToast('请选择路况类别')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.isBlocked)) {
|
|
showToast('请选择是否阻断')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.repairProgress)) {
|
|
showToast('请选择抢险进度')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.report?.disposalMeasures)) {
|
|
showToast('请选择处置措施')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.damageCount)) {
|
|
showToast('请输入水毁处数')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.blockedMileage)) {
|
|
showToast('请输入阻断里程')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.occurTime)) {
|
|
showToast('请选择发生时间')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.report?.expectRecoverTime)) {
|
|
showToast('请输入预计恢复时间')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.routeNo)) {
|
|
showToast('请输入线路编号')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.startStakeNo)) {
|
|
showToast('请输入起点桩号')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.startStakeLng)) {
|
|
showToast('请输入起点桩经度')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.startStakeLat)) {
|
|
showToast('请输入起点桩纬度')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.endStakeNo)) {
|
|
showToast('请输入止点桩号')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.endStakeLng)) {
|
|
showToast('请输入止点桩经度')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.endStakeLat)) {
|
|
showToast('请输入止点桩纬度')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.occurLocation)) {
|
|
showToast('请选择路况位置')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.blockedPointName)) {
|
|
showToast('请输入阻断点小地名')
|
|
return false
|
|
}
|
|
if (isEmpty(formData.value.event?.needsRecovery)) {
|
|
showToast('请选择是否需要恢复重建')
|
|
return false
|
|
}
|
|
if (formData.value.event?.needsRecovery && isEmpty(formData.value.event?.estimatedRecoveryCost)) {
|
|
showToast('请输入恢复重建预估费用')
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 获取表单数据 - 返回 formData.value 的副本
|
|
const getFormData = () => {
|
|
return { ...formData.value }
|
|
}
|
|
|
|
// 提交表单
|
|
const handleSubmit = async () => {
|
|
// 验证表单
|
|
if (!validate()) return
|
|
|
|
submitting.value = true
|
|
try {
|
|
// 获取表单数据
|
|
let formData = getFormData()
|
|
|
|
// 添加事件类型和站点信息
|
|
const submitData = {
|
|
...formData
|
|
// 可以在这里添加站点信息等其他数据
|
|
}
|
|
submitData.event.serviceStationId = yhzStore.getYHZInfo?.id
|
|
submitData.event.serviceStationName = yhzStore.getYHZInfo?.mc
|
|
|
|
// 调用接口提交数据
|
|
let apiUrl = formData.value.event?.id ? '/snow-ops-platform/water-damage/dispose' : '/snow-ops-platform/water-damage/report'
|
|
if(formData.value.event?.id && formData.value.report.disposalMeasures == '正常通行') {
|
|
apiUrl = '/snow-ops-platform/water-damage/release'
|
|
}
|
|
const res = await request({
|
|
url: apiUrl,
|
|
method: 'post',
|
|
data: submitData
|
|
})
|
|
|
|
if (res?.code === '00000') {
|
|
showSuccessToast('提交成功')
|
|
let isRebuilded = false
|
|
if (isContinue.value && detail.value.event.needsRecovery) {
|
|
// 如果之前已经进行了项目重建的流程,后续不再进行该流程
|
|
isRebuilded = true
|
|
}
|
|
if (!isRebuilded && submitData.event.needsRecovery) {
|
|
router.replace({
|
|
name: 'RebuildAdd',
|
|
params: {
|
|
data: res.data.id
|
|
}
|
|
})
|
|
} else {
|
|
// 提交成功后返回列表页
|
|
setTimeout(() => {
|
|
if(isContinue.value) router.go(-1)
|
|
else router.replace('/disasterManagement')
|
|
}, 500)
|
|
}
|
|
} else {
|
|
showFailToast(res.message)
|
|
}
|
|
} catch (error) {
|
|
showFailToast('提交失败,请重试')
|
|
console.error('提交失败:', error)
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
const detail = ref(null)
|
|
|
|
// 获取灾毁详情
|
|
const getDisasterDetail = async () => {
|
|
const id = route.query.id
|
|
if (!id) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
const result = await request({
|
|
url: `/snow-ops-platform/water-damage/getById`,
|
|
method: 'get',
|
|
params: { id }
|
|
})
|
|
|
|
if (result?.data) {
|
|
// 接口返回 Data 结构
|
|
const data = result.data
|
|
detail.value = result.data
|
|
const newFormData = {
|
|
...data,
|
|
lossList: [],
|
|
report: {},
|
|
fileList: []
|
|
}
|
|
initFormData(newFormData)
|
|
} else {
|
|
showToast(result.message || '获取详情失败')
|
|
}
|
|
} catch (error) {
|
|
console.error('获取灾毁详情失败:', error)
|
|
showToast('获取详情失败,请稍后重试')
|
|
}
|
|
}
|
|
|
|
|
|
onMounted(() => {
|
|
formData.value.event.occurTime = formatDate(Date.now())
|
|
if (route.query.id) {
|
|
getDisasterDetail()
|
|
}
|
|
})
|
|
|
|
// 暴露方法给父组件
|
|
defineExpose({
|
|
validate,
|
|
initFormData,
|
|
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-radio-group) {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 16px;
|
|
}
|
|
:deep(.van-radio) {
|
|
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):not(.van-field__label--top) {
|
|
width: 110px;
|
|
}
|
|
}
|
|
.footer-btn {
|
|
position: fixed;
|
|
bottom: 15px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: calc(100% - 32px);
|
|
max-width: 340px;
|
|
border-radius: 48px;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
z-index: 10;
|
|
|
|
&:active {
|
|
opacity: 0.9;
|
|
transform: translateX(-50%) scale(0.98);
|
|
}
|
|
}
|
|
</style>
|