feat: 水毁细节微调

This commit is contained in:
niedongsheng 2026-04-10 17:41:23 +08:00
parent 145a0b1e33
commit 94bb7b62c2
11 changed files with 274 additions and 138 deletions

View File

@ -150,16 +150,18 @@
<div class="report-content">
<div class="info-row">
<span class="info-label">处置措施</span>
<span class="info-value">{{ formatDisposalMeasures(report.disposalMeasures) || '-' }}</span>
</div>
<div class="info-row">
<span class="info-label">塌方及损失</span>
<span class="info-value">{{ getLossDescription(report) }}</span>
</div>
<div class="info-row">
<span class="info-label">路产损失</span>
<span class="info-value">{{ report.totalLossAmount ? report.totalLossAmount + '万元' : '-' }}</span>
<span class="info-value">{{ report.disposalMeasures || '-' }}</span>
</div>
<template v-for="(lossItem, idx) of report.lossList" :key="idx">
<div class="info-row">
<span class="info-label">{{ lossItem.lossCategory }}</span>
<span class="info-value">{{ lossItem.totalAmount }}{{ lossItem.unit }}</span>
</div>
<div class="info-row" v-if="lossItem.lossCategory == '其他损失'">
<span class="info-label">其它损失描述</span>
<span class="info-value">{{ lossItem.remark }}</span>
</div>
</template>
<div class="info-row">
<span class="info-label">有无车辆滞留</span>
<span class="info-value">{{ getVehicleStrandedText(report) }}</span>
@ -181,13 +183,18 @@
<span class="info-value">{{ report.siteDescription || '-' }}</span>
</div>
<!-- 附件 -->
<div class="info-row" v-if="report.fileList && report.fileList.length > 0">
<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">
<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 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>
@ -200,8 +207,12 @@
<!-- 底部按钮未解除状态显示续报按钮 -->
<div class="footer-buttons">
<van-button type="primary" class="footer-btn" @click="handleContinueReport">续报</van-button>
<van-button type="primary" class="footer-btn" @click="handleContinueReport">续报</van-button>
</div>
<van-image-preview :startPosition="startPosition" v-model:show="previewImagesVisible" :images="imagesForPreview">
<template v-slot:index="{ index }">{{ index + 1 }}</template>
</van-image-preview>
</PageContainer>
</template>
@ -220,10 +231,10 @@ const route = useRoute()
// Data
const detailData = ref({
event: null, // Event
reportList: [], //
fileList: [], //
lossList: [], //
event: null, // Event
report: [], //
fileList: [], //
lossList: [], //
occurLocation: '',
occurTime: '',
roadConditionType: '',
@ -231,11 +242,17 @@ const detailData = ref({
})
const allReports = computed(() => {
const reports = detailData.value.reportList?.map((item, index)=>{
item.title = index == 0 ? '首报' : ('续报' + index)
return item
}) || []
return reports.reverse()
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()
})
//
@ -243,7 +260,6 @@ const hasReportData = computed(() => {
return allReports.value.length > 0
})
//
const getEventStatusText = () => {
return detailData.eventStatus === 1 ? '已解除' : '未解除'
@ -258,10 +274,10 @@ const getEventStatusType = () => {
const formatDisposalMeasures = (measures) => {
if (!measures) return ''
const measureMap = {
'半幅封闭': '半幅封闭',
'全副封闭': '全副封闭',
'便道通行': '便道通行',
'正常通行': '正常通行'
半幅封闭: '半幅封闭',
全副封闭: '全副封闭',
便道通行: '便道通行',
正常通行: '正常通行'
}
return measures
.split(',')
@ -287,7 +303,7 @@ const getLossDescription = (report) => {
//
const getVehicleStrandedText = (report) => {
const count = report?.strandedVehicleCount || 0
return count > 0 ? `有车滞留,共${count}` : '无车滞留'
return count > 0 ? `有车滞留` : '无车滞留'
}
//
@ -310,7 +326,7 @@ const getDisasterDetail = async () => {
const data = result.data
detailData.value = {
event: data.event || null,
reportList: data.report || [], // 使 reportList
report: data.report || [], // 使 report
fileList: data.fileList || [],
lossList: data.lossList || [],
occurLocation: data.occurLocation || '',
@ -344,10 +360,25 @@ const handleContinueReport = () => {
})
}
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 = (file) => {
console.log("🚀 ~ previewFile ~ file:", file)
// showImagePreview
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(() => {
@ -374,6 +405,13 @@ onMounted(() => {
margin-left: 20px;
margin-top: -8px;
}
&.column {
flex-direction: column;
.info-label {
margin-bottom: 10px;
}
}
}
.info-label {
@ -428,14 +466,17 @@ onMounted(() => {
}
.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;
padding: 8px;
background: #f7f8fa;
border-radius: 4px;
@ -450,11 +491,20 @@ onMounted(() => {
color: #323233;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.preview-image-block {
width: 60px;
height: 60px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.footer-buttons {
position: fixed;
bottom: 0;

View File

@ -49,7 +49,7 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { showToast, Tag as VanTag, Loading as VanLoading, Icon as VanIcon, Button as VanButton } from 'vant'
import { showToast, Tag as VanTag, Loading as VanLoading, Icon as VanIcon, Button as VanButton, showImagePreview } from 'vant'
import PageContainer from '@/components/PageContainer.vue'
import SearchInput from '@/components/SearchInput.vue'
import CardItem from '@/components/CardItem.vue'

View File

@ -87,10 +87,19 @@ const handleSubmit = async () => {
if (res?.code === '00000') {
showSuccessToast('提交成功')
//
setTimeout(() => {
router.replace('/disasterManagement')
}, 1000)
if (submitData.event.needsRecovery) {
router.replace({
name: 'RebuildAdd',
params: {
data: res.data.id
}
})
} else {
//
setTimeout(() => {
router.replace('/disasterManagement')
}, 500)
}
} else {
showFailToast(res.message)
}

View File

@ -1,54 +1,65 @@
<template>
<!-- 损失计算弹窗 -->
<van-dialog
v-model:show="visible"
:title="itemName + '损失信息'"
show-cancel-button
@confirm="confirm"
@cancel="cancelLoss"
confirm-button-text="确定"
cancel-button-text="取消"
>
<van-dialog v-model:show="visible" :title="lossItem?.title" show-cancel-button @confirm="confirm" @cancel="cancelLoss" confirm-button-text="确定" cancel-button-text="取消">
<div class="loss-dialog-content">
<!-- 塌方长 -->
<van-field v-model="formData.length" :label="itemName + '长'" placeholder="请填写长度" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<!-- 立方计算 -->
<template v-if="lossItem.calc == 'cube'">
<van-field v-model="formData.length" :label="lossItem?.itemName + '长'" placeholder="请填写长度" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<!-- 塌方宽 -->
<van-field v-model="formData.width" :label="itemName + '宽'" placeholder="请填写宽度" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.width" :label="lossItem?.itemName + '宽'" placeholder="请填写宽度" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<!-- 塌方高 -->
<van-field v-model="formData.height" :label="itemName + '高'" placeholder="请填写高度" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.height" :label="lossItem?.itemName + '高'" placeholder="请填写高度" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<!-- 单价 (0-2000) -->
<van-field v-model="formData.unitPrice" label="单价(0-2000元)" placeholder="请填写单价" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.unitPrice" label="单价" placeholder="请填写单价" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.totalAmount" :label="itemName + '损失金额'" placeholder="请填写金额" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.totalAmount" :label="lossItem?.itemName + '损失金额'" placeholder="请填写金额" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
</template>
<!-- 长度计算-->
<template v-else-if="lossItem.calc == 'length'">
<van-field v-model="formData.length" :label="lossItem?.itemName + '长度'" placeholder="请填写长度" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.unitPrice" label="单价" placeholder="请填写单价" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
<van-field v-model="formData.totalAmount" :label="lossItem?.itemName + '损失金额'" placeholder="请填写金额" type="digit" clearable>
<template #button>
<span class="field-unit"></span>
</template>
</van-field>
</template>
</div>
</van-dialog>
</template>
<script setup>
import { onMounted, ref, reactive, watch, computed } from 'vue'
import { showToast } from 'vant'
import { getLossItem } from './LossMap'
//
const visible = ref(false)
@ -61,9 +72,10 @@ const formData = ref({
unitPrice: ''
})
const itemName = ref('')
const lossItem = ref(null)
const totalAmount = computed(() => {
//
const cubeCalc = () => {
const l = parseFloat(formData.value.length)
const w = parseFloat(formData.value.width)
const h = parseFloat(formData.value.height)
@ -83,28 +95,45 @@ const totalAmount = computed(() => {
if (price > 2000) {
return 0
}
return (l * w * h * price)
return l * w * h * price
}
//
const lengthCalc = () => {
const l = parseFloat(formData.value.length)
const price = parseFloat(formData.value.unitPrice)
if (isNaN(l) || l <= 0) {
return 0
}
if (isNaN(price) || price < 0) {
return 0
}
if (price > 2000) {
return 0
}
return l * price
}
const totalAmount = computed(() => {
if(lossItem.value?.calc == 'cube') return cubeCalc()
if(lossItem.value?.calc == 'length') return lengthCalc()
return 0
})
watch(totalAmount, ()=>{
watch(totalAmount, () => {
formData.value.totalAmount = totalAmount.value
})
//
const show = (item) => {
console.log("🚀 ~ show ~ item:", item)
lossItem.value = getLossItem(item.lossTypeCode)
formData.value.length = item.length
formData.value.width = item.width
formData.value.height = item.height
formData.value.unitPrice = item.unitPrice
formData.value.totalAmount = item.totalAmount
itemName.value = getItemName(item)
visible.value = true
}
const getItemName = (item) => {
return item.lossTypeName
}
//
const validateForm = () => {
const l = parseFloat(formData.length)
@ -138,7 +167,7 @@ const validateForm = () => {
//
const confirm = () => {
if(!formData.value.totalAmount) {
if (!formData.value.totalAmount) {
showToast('请填写损失金额')
return
}

View File

@ -1,16 +1,25 @@
<template>
<div class="loss-list">
<template v-for="(item, index) in modelValue">
<van-field v-model="item.totalAmount" :label="getItemLabel(item)" placeholder="请填写" type="digit" @click="showCalculateDialog(item, index)">
<van-field v-if="getLossItem(item.lossTypeCode)" v-model="item.totalAmount" :label="getItemLabel(item)" placeholder="请填写" type="digit" @click="showCalculateDialog(item, index)">
<template #button>
<span class="field-unit">{{ item.unit }}</span>
<van-icon @click.stop="removeItem(index)" class="remove-icon" name="delete-o" />
</template>
</van-field>
<template v-else>
<van-field v-model="item.totalAmount" :label="getItemLabel(item)" placeholder="请填写" type="digit">
<template #button>
<span class="field-unit">{{ item.unit }}</span>
<van-icon @click.stop="removeItem(index)" class="remove-icon" name="delete-o" />
</template>
</van-field>
<van-field v-if="item.lossTypeCode == 'OTHER_LOSS'" v-model="item.remark" label="其它损失描述" placeholder="请填写"></van-field>
</template>
</template>
<van-button size="small" block type="primary" plain @click="addLoss">添加损失</van-button>
<CalculateDialog ref="cubeCalculateDialog" @confirm="confirmCalculate" />
<LossPicker ref="lossPicker" :options="options" @confirm="confirmAddLoss" />
<LossPicker ref="lossPicker" :options="getOptions()" @confirm="confirmAddLoss" />
</div>
</template>
@ -19,6 +28,7 @@ import { ref, reactive, computed, onMounted } from 'vue'
import CalculateDialog from './CalculateDialog.vue'
import { request } from '@shared/utils/request'
import LossPicker from './LossPicker.vue'
import { getLossItem } from './LossMap'
const emit = defineEmits(['update:modelValue'])
@ -33,6 +43,19 @@ const lossPicker = ref(null)
const options = ref({})
const getOptions = () => {
if(!options.value.loss) return []
const filteredOptions = options.value.loss.filter((item) => {
for(const valueItem of props.modelValue) {
if(item.value === valueItem.lossTypeId) {
return false
}
}
return true
})
return filteredOptions
}
const dialogItemIndex = ref(null)
//

View File

@ -0,0 +1,47 @@
const lossMap = {
COLLAPSE_LOSS: {
title: '塌方损失信息',
itemName: '塌方',
calc: 'cube',
},
RETAINING_WALL_LOSS: {
title: '挡墙损失信息',
itemName: '挡墙',
calc: 'cube',
},
ROADBED_LOSS: {
title: '路基损失信息',
itemName: '路基',
calc: 'cube',
},
PAVEMENT_LOSS: {
title: '路面损失信息',
itemName: '路面',
calc: 'cube',
},
SLOPE_PROTECTION_LOSS: {
title: '护坡损失信息',
itemName: '护坡',
calc: 'cube',
},
BRIDGE_LOSS: {
title: '桥梁损失信息',
itemName: '桥梁',
calc: 'cube',
},
TUNNEL_LOSS: {
title: '隧道损失信息',
itemName: '隧道',
calc: 'length'
},
CULVERT_LOSS: {
title: '涵洞损失信息',
itemName: '涵洞',
calc: 'length'
}
}
export const getLossItem = (code) => {
return lossMap[code]
}

View File

@ -19,7 +19,7 @@ const pickerTitle = ref("请选择损失类型")
const columns = computed(() => {
return props.options.loss || []
return props.options || []
})
const showPicker = ref(false)

View File

@ -52,7 +52,7 @@
<span class="measures-label">处置措施</span>
<div class="measures-options">
<!-- 改为单选使用 van-radio-group -->
<van-radio-group v-model="disposalMeasureValue" direction="horizontal">
<van-radio-group v-model="formData.report.disposalMeasures" direction="horizontal">
<van-radio name="半幅封闭">半幅封闭</van-radio>
<van-radio name="全副封闭">全副封闭</van-radio>
<van-radio name="便道通行">便道通行</van-radio>
@ -162,8 +162,6 @@ const route = useRoute()
//
const isContinue = computed(() => route.query.isContinue)
//
const disposalMeasureValue = ref('')
// - Request 使 ref
const formData = ref({
@ -216,22 +214,6 @@ const formData = ref({
fileList: []
})
// report.disposalMeasures
watch(disposalMeasureValue, (newVal) => {
formData.value.report.disposalMeasures = newVal
})
// report.disposalMeasures
watch(
() => formData.value.report.disposalMeasures,
(newVal) => {
if (newVal && typeof newVal === 'string') {
disposalMeasureValue.value = newVal
}
},
{ immediate: true }
)
const getFileList = () => {
const fileList = formData.value.fileList?.map((item) => {
return {
@ -286,11 +268,6 @@ const initFormData = (newVal) => {
lossList: newVal.lossList || [],
fileList: newVal.fileList || []
}
//
if (newVal.report?.disposalMeasures) {
disposalMeasureValue.value = newVal.report.disposalMeasures
}
}
}

View File

@ -24,7 +24,7 @@
"strandedPersonCount": 12,
"deadCount": 0,
"strandedVehicleCount": 12,
"disposalMeasures": "正常通行",
"disposalMeasures": null,
"actualRecoverTime": null,
"expectRecoverTime": null,
"injuredCount": 1,

View File

@ -166,10 +166,6 @@
<span class="info-label">处置措施</span>
<span class="info-value">{{ report.disposalMeasures || '-' }}</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">{{ report.actualRecoverTime || '-' }}</span>
@ -197,14 +193,6 @@
<div class="detal-info-wrapper">
<template v-if="report.showDetail">
<LossListDetail :modelValue="report.lossList" :col-span="8" />
<el-row :gutter="24">
<el-col :span="24">
<div class="info-item">
<span class="info-label">其它损失描述</span>
<span class="info-value">{{ '未对接' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="8">
<div class="info-item">
@ -294,7 +282,6 @@ const allReports = computed(() => {
} else {
item.title = '续报' + (detailData.value.report.length - 1 - index)
}
console.log(detailData.value.report.length - 1, ' ', index, ' -', item.title)
return item
}) || []
return reports

View File

@ -1,11 +1,19 @@
<template>
<el-row class="loss-list-detail" :gutter="24">
<el-col :span="colSpan" v-for="(item, index) in configs" :key="index">
<div class="info-item">
<span class="info-label">{{ item.lossTypeName }}</span>
<span class="info-value">{{ getValue(item) }}{{ item.unit }}</span>
</div>
</el-col>
<template v-for="(item, index) in configs" :key="index">
<el-col :span="colSpan">
<div class="info-item">
<span class="info-label">{{ item.lossTypeName }}</span>
<span class="info-value">{{ getValue(item) }}{{ item.unit }}</span>
</div>
</el-col>
<el-col :span="colSpan" v-if="item.lossTypeCode == 'OTHER_LOSS'">
<div class="info-item">
<span class="info-label">其它损失描述</span>
<span class="info-value">{{ getRemark(item) }}</span>
</div>
</el-col>
</template>
</el-row>
</template>
@ -34,6 +42,12 @@ const getValue = (config) => {
return value?.totalAmount || 0
}
const getRemark = (config) => {
const value = props.modelValue?.find((v) => v.lossTypeId === config.lossTypeId)
if (value == null) props.modelValue?.push({ ...config })
return value?.remark || ''
}
const configs = ref([])
//