feat: h5灾毁管理

This commit is contained in:
niedongsheng 2026-04-15 09:08:41 +08:00
parent 3af90b27da
commit 4832facbf9
11 changed files with 1242 additions and 306 deletions

View File

@ -15,6 +15,7 @@
<!-- 弹出层选择器 -->
<van-popup v-model:show="showPicker" position="bottom" round>
<van-picker
:modelValue="getPickerValue()"
:columns="columns"
:title="pickerTitle"
:loading="loading"
@ -91,12 +92,32 @@ const emit = defineEmits(['update:modelValue', 'change'])
//
const showPicker = ref(false)
const getPickerValue = () => {
// picker,
if(props.modelValue === true) return [1]
if(props.modelValue === false) return [0]
return [props.modelValue]
}
// Picker options Picker
const columns = computed(() => {
return props.options.map(item => {
let value = item[props.valueKey]
let isBoolean = false
// vant
if(value === true) {
value = 1
isBoolean = true
}
if(value === false) {
value = 0
isBoolean = true
}
return {
text: item[props.labelKey],
value: item[props.valueKey]
value,
isBoolean
}
})
})
@ -119,7 +140,11 @@ const openPicker = () => {
//
const onConfirm = ({ selectedValues, selectedOptions }) => {
const value = selectedOptions[0][props.valueKey]
let value = selectedOptions[0][props.valueKey]
//
if(selectedOptions[0].isBoolean) {
value = value === 1 ? true : false
}
const label = selectedOptions[0][props.labelKey]
emit('update:modelValue', value)
emit('change', { value, label })

View File

@ -45,17 +45,7 @@ const props = defineProps({
//
tags: {
type: Array,
default: () => [
{ label: '全部', value: 'all' },
{ label: '边坡坍塌', value: '边坡坍塌' },
{ label: '泥石流', value: '泥石流' },
{ label: '路基沉陷', value: '路基沉陷' },
{ label: '山体滑坡', value: '山体滑坡' },
{ label: '行道树倒塌', value: '行道树倒塌' },
{ label: '积水', value: '积水' },
{ label: '积雪', value: '积雪' },
{ label: '其他', value: '其他' }
]
default: () => []
}
})
@ -86,7 +76,8 @@ const selectTag = (value) => {
//
const resetFilter = () => {
tempSelectedTag.value = 'all'
tempSelectedTag.value = null
emit('update:modelValue', tempSelectedTag.value)
}
//

View File

@ -62,7 +62,7 @@ const routes = [
component: () => import('../views/IceEvent/IceEventAdd.vue')
},
{
path: '/iceEventDetail/:data',
path: '/iceEventDetail',
name: 'IceEventDetail',
component: () => import('../views/IceEvent/IceEventDetails.vue')
},
@ -107,9 +107,8 @@ const routes = [
component: () => import('../views/DisasterManagement/DisasterReport.vue')
},
{
path: '/disasterDetail',
name: 'DisasterDetail',
component: () => import('../views/DisasterManagement/DisasterDetail.vue')
path: '/waterDisasterDetail',
component: () => import('../views/DisasterManagement/WaterDisasterDetail.vue')
}
]

View File

@ -1,7 +1,16 @@
<template>
<PageContainer title="灾害管理" @click-back="handleClickBack">
<div class="filter-wrapper">
<SearchInput style="margin-top: 0" v-model="searchValue" placeholder="请输入地点关键词" @search="handleSearch" />
<div class="filter-btn-block">
<van-button @click="showFilter = true">
<div class="filter-btn">
<span style="white-space: nowrap">{{ getShortTypeName(selectedDisasterType) }}</span> <van-icon name="filter-o" />
</div>
</van-button>
</div>
</div>
<CurrentSite />
<div class="list-panel">
<CardItem v-for="(item, index) in list" :key="index" :title="getEventTitle(item)" @click="handleClickItem(item)">
<template #headerExtra>
@ -37,18 +46,13 @@
<van-button type="primary" class="footer-btn" @click="handleAdd">灾害填报</van-button>
<!-- 筛选组件v-model 绑定选中的值visible 控制显示隐藏 -->
<TagFilter
v-model="selectedDisasterType"
:visible="showFilter"
@update:visible="showFilter = $event"
@confirm="handleFilterConfirm"
/>
<TagFilter v-model="selectedDisasterType" :visible="showFilter" :tags="disasterTypes" @update:visible="showFilter = $event" @confirm="handleFilterConfirm" />
</PageContainer>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { RouterLink, useRouter } from 'vue-router'
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'
@ -56,7 +60,7 @@ import CardItem from '@/components/CardItem.vue'
import EmptyBox from '@/components/EmptyBox.vue'
import CurrentSite from '@/components/CurrentSite.vue'
import TagFilter from '@/components/TagFilter.vue'
import { request } from "@shared/utils/request";
import { request } from '@shared/utils/request'
const router = useRouter()
@ -76,11 +80,11 @@ const emptyText = ref('暂无相关灾毁信息')
const showFilter = ref(false)
// v-model TagFilter
const selectedDisasterType = ref('all')
const selectedDisasterType = ref(null)
//
const disasterTypes = [
{ label: '全部', value: 'all' },
{ label: '全部', value: null },
{ label: '边坡坍塌', value: '边坡坍塌' },
{ label: '泥石流', value: '泥石流' },
{ label: '路基沉陷', value: '路基沉陷' },
@ -93,14 +97,11 @@ const disasterTypes = [
//
const getEventTitle = (item) => {
// 使 eventName使+线
if (item.eventName) {
return item.eventName
}
const parts = []
if (item.routeNo) parts.push(item.routeNo)
if (item.occurLocation) parts.push(item.occurLocation)
if (item.startStakeNo) parts.push(item.startStakeNo)
if (item.routeNo) parts.push(item.routeNo)
// if(item.disasterType) parts.push(item.disasterType == 'WATER_DAMAGE' ? '' : '')
if (item.roadConditionType) parts.push('发生' + item.roadConditionType)
if (parts.length > 0) {
return parts.join('')
@ -121,72 +122,59 @@ const getEventStatusType = (status) => {
//
const getDisasterTypeText = (item) => {
// roadConditionTyperepairProgress
//
// 1
if (item.repairProgress) {
return item.repairProgress
}
// 2
if (item.roadConditionType) {
return item.roadConditionType
}
// 3""
return '水毁灾害'
}
//
const getShortTypeName = (type) => {
const typeMap = {
'边坡坍塌': '边坡',
'泥石流': '泥石',
'路基沉陷': '路基',
'山体滑坡': '滑坡',
'行道树倒塌': '树倒',
'积水': '积水',
'积雪': '积雪',
'其他': '其他',
'未抢险': '待抢险',
'抢险中': '抢险中',
'已完成': '已完成',
'高速公路': '高速',
'国道': '国道',
'省道': '省道'
边坡坍塌: '边坡',
泥石流: '泥石',
路基沉陷: '路基',
山体滑坡: '滑坡',
行道树倒塌: '树倒',
积水: '积水',
积雪: '积雪',
其他: '其他',
未抢险: '待抢险',
抢险中: '抢险中',
已完成: '已完成',
高速公路: '高速',
国道: '国道',
省道: '省道'
}
return typeMap[type] || (type ? type.substring(0, 2) : '灾害')
return typeMap[type] || (type ? type.substring(0, 2) : '全部')
}
//
const getDisasterList = async (keyword = '', disasterType = 'all') => {
const getDisasterList = async (keyword = '', disasterType = null) => {
loading.value = true
try {
const result = await request({
url: '/snow-ops-platform/water-damage/list',
url: '/snow-ops-platform/unified-disaster/list',
method: 'get',
params: {
pageSize: 999,
keyword: keyword.trim(),
disasterType: disasterType === 'all' ? '' : disasterType
roadConditionType: disasterType || undefined
}
})
if (result?.data?.records) {
list.value = result.data.records.map(item => ({
list.value = result.data.records.map((item) => ({
...item,
// 使
title: getEventTitle(item),
status: getEventStatusText(item.eventStatus),
occurTime: item.occurTime,
estimateRecoverTime: item.expectRecoverTime,
disasterType: getDisasterTypeText(item)
estimateRecoverTime: item.expectRecoverTime
}))
} else {
showToast(result.message || '获取数据失败')
list.value = []
}
} catch (error) {
console.error('获取灾毁列表失败:', error)
showToast('获取数据失败,请稍后重试')
@ -201,14 +189,6 @@ const handleSearch = () => {
getDisasterList(searchValue.value, selectedDisasterType.value)
}
//
const handleAllClick = () => {
if (selectedDisasterType.value !== 'all') {
selectedDisasterType.value = 'all'
getDisasterList(searchValue.value, 'all')
}
}
//
const handleFilterConfirm = (type) => {
// selectedDisasterType v-model
@ -222,13 +202,23 @@ const handleClickBack = () => {
//
const handleClickItem = (item) => {
if (item.disasterType === 'WATER_DAMAGE') {
router.push({
path: '/disasterDetail',
path: '/waterDisasterDetail',
query: {
id: item.id
}
})
}
if (item.disasterType === 'ICE_SNOW') {
router.push({
name: 'IceEventDetail',
query: {
id: item.relationId
}
})
}
}
//
const handleAdd = () => {
@ -249,6 +239,23 @@ onMounted(() => {
padding-bottom: 80px;
}
.filter-wrapper {
display: flex;
gap: 10px;
align-items: center;
margin-top: 10px;
.filter-btn-block {
display: flex;
flex-wrap: nowrap;
.filter-btn {
display: flex;
flex-wrap: nowrap;
align-items: center;
}
}
}
:deep(.card-item) {
position: relative;
}

View File

@ -5,22 +5,16 @@
<!-- 事件类型 -->
<PanelItem title="事件类型" style="margin-bottom: 10px" v-if="!isContinue">
<van-radio-group v-model="eventType" direction="horizontal" class="event-type-group">
<van-radio name="water">水毁灾害</van-radio>
<van-radio name="ice">冰雪灾害</van-radio>
</van-radio-group>
<van-row gutter="10">
<van-col v-for="(item, index) in eventTypeOptions" :span="24 / eventTypeOptions.length" :key="index">
<van-button block plain :type="item.value === eventType ? 'primary' : 'default'" @click="eventType = item.value"> {{ item.label }} </van-button>
</van-col>
</van-row>
</PanelItem>
<!-- 根据事件类型渲染不同表单 -->
<WaterDisaster v-if="eventType === 'water'" ref="waterDisasterRef" />
<!-- 冰雪灾害表单待实现 -->
<div v-else class="coming-soon">
<van-empty description="冰雪灾害表单开发中..." />
</div>
<!-- 提交按钮 -->
<van-button type="primary" class="footer-btn" @click="handleSubmit" :loading="submitting"> 提交 </van-button>
<WaterDisaster v-if="eventType === 'water'" />
<IceDisaster v-if="eventType === 'ice'" />
</PageContainer>
</template>
@ -32,8 +26,8 @@ import PageContainer from '@/components/PageContainer.vue'
import CurrentSite from '@/components/CurrentSite.vue'
import PanelItem from '@/components/PanelItem.vue'
import WaterDisaster from './WaterDisaster/WaterDisaster.vue'
import IceDisaster from './IceDisaster.vue'
import { request } from '@shared/utils/request'
import mockFormData from './waterDisasterFormData.json'
const router = useRouter()
const route = useRoute()
@ -45,118 +39,27 @@ const title = ref(!isContinue ? '灾毁填报' : '灾毁续报')
//
const eventType = ref('water')
//
const formData = ref(route.query.mock ? mockFormData : {})
const waterDisasterRef = ref(null)
const submitting = ref(false)
const eventTypeOptions = [
{
label: '水毁灾害',
value: 'water'
},
{
label: '冰雪灾害',
value: 'ice'
}
]
//
const handleClickBack = () => {
router.replace('/disasterManagement')
}
//
const handleSubmit = async () => {
//
if (eventType.value === 'water') {
if (!waterDisasterRef.value.validate()) {
return
}
}
submitting.value = true
try {
//
let formData = {}
if (eventType.value === 'water') {
formData = waterDisasterRef.value.getFormData()
}
//
const submitData = {
...formData
//
}
const res = await request({
url: '/snow-ops-platform/water-damage/addOrUpdate',
method: 'post',
data: submitData
})
if (res?.code === '00000') {
showSuccessToast('提交成功')
if (submitData.event.needsRecovery) {
router.replace({
name: 'RebuildAdd',
params: {
data: res.data.id
}
})
} else {
//
setTimeout(() => {
router.replace('/disasterManagement')
}, 500)
}
} else {
showFailToast(res.message)
}
} catch (error) {
showFailToast('提交失败,请重试')
console.error('提交失败:', error)
} finally {
submitting.value = false
}
}
//
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
const newFormData = {
...data,
lossList: null,
report: formData.value.report,
fileList: null
}
waterDisasterRef.value.initFormData(newFormData)
} else {
showToast(result.message || '获取详情失败')
}
} catch (error) {
console.error('获取灾毁详情失败:', error)
showToast('获取详情失败,请稍后重试')
}
}
onMounted(() => {
if (route.query?.id) {
getDisasterDetail()
} else {
waterDisasterRef.value.initFormData(formData.value)
}
})
</script>
<style lang="scss" scoped>
.page-container {
padding-bottom: 80px;
background-color: #f5f7fa;
padding-bottom: 80px;
}
.event-type-group {
@ -170,26 +73,4 @@ onMounted(() => {
.coming-soon {
padding: 40px 20px;
}
.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>

View File

@ -0,0 +1,524 @@
<template>
<div class="home">
<div class="content">
<PanelItem title="基本信息">
<van-form class="IceEventAddForm" label-align="left" colon>
<van-field v-model="form.event.occurTime" label="发生时间" center>
<template #button>
<van-button plain round type="primary" size="mini" @click="getCurrentTime">校准时间</van-button>
</template>
</van-field>
<van-field v-model="form.event.occurLocation" label="发生地点" center placeholder="请填写" />
<van-field v-model="form.event.routeNo" label="线路编号" center placeholder="请填写" />
<van-field v-model="form.event.startStakeNo" label="起点桩号" center placeholder="请填写" />
<van-field v-model="form.event.endStakeNo" label="止点桩号" center placeholder="请填写" />
<van-field v-model="form.event.disasterMileage" label="受灾里程" center type="number" placeholder="请填写" />
</van-form>
</PanelItem>
<PanelItem title="处置情况">
<van-form class="IceEventAddForm" label-align="left" colon>
<van-field label="处置措施" center>
<template #input>
<div class="disposal-buttons">
<van-button plain :type="form.event.disposalMeasures === '限速通行' ? 'primary' : 'default'" size="small" @click="toggleDisposal('限速通行')">
限速通行
</van-button>
<van-button plain :type="form.event.disposalMeasures === '封闭交通' ? 'primary' : 'default'" size="small" @click="toggleDisposal('封闭交通')" class="last-button">
封闭交通
</van-button>
</div>
</template>
</van-field>
<van-field v-model="form.event.expectRecoverTime" label="预计恢复时间" center placeholder="请选择" readonly clickable @click="showExpectPicker = true" />
<van-popup :show="showExpectPicker" round position="bottom" close-on-click-overlay @close="showExpectPicker = false">
<van-picker-group title="选择日期时间" :tabs="['选择日期', '选择时间']" @confirm="handleConfirmExpectTime" @cancel="showExpectPicker = false">
<van-date-picker v-model="expectDate" :min-date="minDate" :max-date="maxDate" />
<van-time-picker v-model="expectTime" />
</van-picker-group>
</van-popup>
</van-form>
</PanelItem>
<PanelItem title="实施情况">
<van-form class="IceEventAddForm" label-align="left" colon>
<van-field v-model="form.material.inputManpower" type="number" label="投入人力" center placeholder="请填写">
<template #extra> 人次 </template>
</van-field>
<van-field v-model="form.material.inputFunds" type="number" label="投入资金" center placeholder="请填写">
<template #extra> 万元 </template>
</van-field>
<van-field v-model="form.material.inputEquipment" type="number" label="投入设备" center placeholder="请填写">
<template #extra> 台班 </template>
</van-field>
<!-- 选择物资列表 -->
<van-field
v-for="(material, index) in form.yhzMaterialList"
:key="material.rid"
v-model="material.usageAmount"
type="number"
@input="checkMaterialAmount(material, index)"
:label="material.wzmc"
center
:placeholder="`余额: ${material.ye} `"
>
<template #extra>
<span style="margin-right: 10px">{{ material.dw }}</span>
<van-button size="small" type="danger" @click.stop="form.yhzMaterialList.splice(index, 1)"> 删除 </van-button>
</template>
</van-field>
<van-button class="add-wzbtn" type="primary" icon="plus" plain @click="handleOpenAddMaterial">添加物资 </van-button>
<van-popup :show="showAddMaterialPopup" position="bottom" close-on-click-overlay @close="showAddMaterialPopup = false">
<div style="padding: 16px">
<h3 style="text-align: center; margin-bottom: 16px">添加物资</h3>
<!-- 搜索框 -->
<van-field v-model="searchText" placeholder="输入物资名称搜索" clearable @update:model-value="handleSearch"> </van-field>
<van-checkbox-group v-model="checked">
<van-cell-group inset style="margin: 16px 0">
<div style="display: flex; justify-content: space-between; padding: 8px 16px">
<span> {{ materialList.length }} </span>
<van-button size="mini" @click="toggleSelectAll" :type="isAllSelected ? 'primary' : 'default'">
{{ isAllSelected ? '取消全选' : '全选' }}
</van-button>
</div>
<van-cell v-for="(item, index) in materialList" clickable :key="item.rid" :title="item.wzmc" @click="toggle(index)">
<template #right-icon>
<van-checkbox :name="item.rid" :ref="(el) => (checkboxRefs[index] = el)" @click.stop />
</template>
</van-cell>
</van-cell-group>
</van-checkbox-group>
<van-button type="primary" block @click="addSelectedMaterials" style="margin-top: 10px"> 确认添加 </van-button>
</div>
</van-popup>
<van-field label="当前通行情况" center>
<template #input>
<div class="disposal-buttons">
<van-button plain :type="form.traffic.currentStatus === 1 ? 'primary' : 'default'" size="small" @click="form.traffic.currentStatus = 1"> 正常通行 </van-button>
<van-button plain :type="form.traffic.currentStatus === 2 ? 'primary' : 'default'" size="small" @click="form.traffic.currentStatus = 2"> 限速通行 </van-button>
<van-button plain :type="form.traffic.currentStatus === 3 ? 'primary' : 'default'" size="small" @click="form.traffic.currentStatus = 3" class="last-button">
封闭交通
</van-button>
</div>
</template>
</van-field>
<van-field label="有无车辆滞留" center>
<template #input>
<div class="disposal-buttons">
<van-button plain :type="form.traffic.hasStrandedVehicles === 1 ? 'primary' : 'default'" size="small" @click="form.traffic.hasStrandedVehicles = 1">
有滞留
</van-button>
<van-button
plain
:type="form.traffic.hasStrandedVehicles === 0 ? 'primary' : 'default'"
size="small"
@click="
() => {
form.traffic.hasStrandedVehicles = 0
form.traffic.strandedVehicleCount = null
}
"
class="last-button"
>
无滞留
</van-button>
</div>
</template>
</van-field>
<van-field v-if="form.traffic.hasStrandedVehicles === 1" v-model="form.traffic.strandedVehicleCount" type="number" label="滞留车辆数" center placeholder="请填写" />
<van-field v-model="form.traffic.actualRecoverTime" label="实际恢复时间" center placeholder="请选择" readonly clickable @click="showActualPicker = true" />
<van-popup :show="showActualPicker" round position="bottom" close-on-click-overlay @close="showActualPicker = false">
<van-picker-group title="选择日期时间" :tabs="['选择日期', '选择时间']" @confirm="handleConfirmActualTime" @cancel="showActualPicker = false">
<van-date-picker v-model="actualDate" :min-date="minDate" :max-date="maxDate" />
<van-time-picker v-model="actualTime" />
</van-picker-group>
</van-popup>
<van-field label="附件" center>
<template #input>
<van-uploader
v-model="fileList"
@delete="handleDelete"
name="photos"
:file-list="fileList"
:file-type="['image/jpeg', 'image/png']"
:after-read="afterRead"
multiple
:max-count="6"
/>
</template>
</van-field>
</van-form>
</PanelItem>
</div>
<van-button type="primary" class="add-btn" @click="handleAdd"> 提交 </van-button>
</div>
</template>
<script setup>
import { ref, onMounted, reactive, toRaw, watch, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { showToast, showLoadingToast } from 'vant'
import PanelItem from '@/components/PanelItem.vue'
import { request } from '../../../../shared/utils/request'
const router = useRouter()
const route = useRoute()
//
const yhzDetail = ref({}) //
const INIT_FORM = reactive({
event: {
occurLocation: '', //
routeNo: '', // 线
occurTime: '', //
startStakeNo: '', //
endStakeNo: '', //
disasterMileage: '', //
expectRecoverTime: '', //
actualRecoverTime: '', //
serviceStationId: '', // ID
district: '', //
reportTime: '', //
reporterPhone: '', //
disposalMeasures: '', //
createTime: '', //
updateTime: '', //
isDeleted: '' // 0- 1-
},
material: {
inputManpower: null, //
inputFunds: null, //
inputEquipment: null, //
createTime: '', //
updateTime: '' //
},
traffic: {
currentStatus: 0, // 1- 2- 3-
hasStrandedVehicles: 0, // 0- 1-
strandedVehicleCount: null, //
actualRecoverTime: '', //
createTime: '', //
updateTime: '' //
},
yhzMaterialList: [], //
photos: []
})
const form = reactive({ ...INIT_FORM })
const fileList = ref([])
//
const formatTime = (date = new Date()) => {
const pad = (n) => n.toString().padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
const getCurrentTime = () => {
form.event.occurTime = formatTime()
}
const toggleDisposal = (type) => {
form.event.disposalMeasures = form.event.disposalMeasures === type ? '' : type
}
//
onMounted(() => {
form.event.occurTime = formatTime() //
})
//
const showAddMaterialPopup = ref(false)
const materialList = ref([])
const checkboxRefs = ref([])
const checked = ref([])
const toggle = (index) => {
checkboxRefs.value[index].toggle()
}
const searchText = ref('')
const handleSearch = () => {
getMaterialList(searchText.value)
}
//
const toggleSelectAll = () => {
if (isAllSelected.value) {
checked.value = []
} else {
checked.value = materialList.value.map((item) => item.rid)
}
}
//
const isAllSelected = computed(() => {
return materialList.value.length > 0 && materialList.value.every((item) => checked.value.includes(item.rid))
})
//
const addSelectedMaterials = () => {
checked.value.forEach((rid) => {
const material = materialList.value.find((m) => m.rid === rid)
if (material && !form.yhzMaterialList.some((m) => m.rid === rid)) {
form.yhzMaterialList.push({
rid: rid,
wzmc: material.wzmc,
usageAmount: null,
dw: material.dw,
ye: material.ye
})
}
})
showAddMaterialPopup.value = false
checked.value = []
}
//
const checkMaterialAmount = (material, index) => {
if (material.usageAmount > material.ye) {
showToast({
type: 'fail',
message: '输入数量不能超过物资余额'
})
//
form.yhzMaterialList[index].usageAmount = material.ye
}
}
//
const getMaterialList = async (wzmc) => {
try {
const data = {
yhzid: yhzDetail.value.id,
wzmc,
pageNum: 1,
pageSize: 9999
}
const res = await request({
url: '/snow-ops-platform/yjwz/list',
method: 'GET',
params: data
})
if (res.code === '00000') {
materialList.value = res.data.records
} else {
throw new Error(res.message)
}
} catch (error) {
showToast({
type: 'fail',
message: error.message
})
}
}
//
const handleOpenAddMaterial = async () => {
await getMaterialList()
showAddMaterialPopup.value = true
}
const handleAdd = async () => {
try {
const toast = showLoadingToast({
message: '上报中...',
forbidClick: true,
duration: 0 // 0
})
form.event.serviceStationId = yhzDetail.value.id
form.event.district = yhzDetail.value.qxmc
console.log('yhzDetail', toRaw(yhzDetail.value))
console.log('form', toRaw(form))
const res = await request({
url: '/snow-ops-platform/event/add',
method: 'POST',
data: form
})
if (res.code === '00000') {
toast.close()
showToast({
type: 'success',
message: '上报成功'
})
setTimeout(() => {
router.replace('/disasterManagement')
}, 500)
} else {
toast.close()
throw new Error(res.message)
}
} catch (error) {
showToast({
type: 'fail',
message: error.message
})
}
}
const expectDate = ref([])
const expectTime = ref([])
const actualDate = ref([])
const actualTime = ref([])
//
const showExpectPicker = ref(false)
const minDate = new Date()
const maxDate = new Date(2050, 11, 31)
const handleConfirmExpectTime = () => {
const [year, month, day] = expectDate.value
const [hour, minute] = expectTime.value
form.event.expectRecoverTime =
`${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ` + `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00`
showExpectPicker.value = false
}
//
const showActualPicker = ref(false)
const handleConfirmActualTime = () => {
const [year, month, day] = actualDate.value
const [hour, minute] = actualTime.value
form.traffic.actualRecoverTime =
`${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ` + `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00`
showActualPicker.value = false
}
//
watch(showExpectPicker, (val) => {
if (val) {
const current = form.event.expectRecoverTime ? new Date(form.event.expectRecoverTime) : new Date()
expectDate.value = [current.getFullYear(), current.getMonth() + 1, current.getDate()]
expectTime.value = [current.getHours(), current.getMinutes()]
}
})
watch(showActualPicker, (val) => {
if (val) {
const current = form.traffic.actualRecoverTime ? new Date(form.traffic.actualRecoverTime) : new Date()
actualDate.value = [current.getFullYear(), current.getMonth() + 1, current.getDate()]
actualTime.value = [current.getHours(), current.getMinutes()]
}
})
//
const afterRead = async (file) => {
try {
const toast = showLoadingToast({
message: '上传中...',
forbidClick: true,
duration: 0 // 0
})
const formData = new FormData()
formData.append('file', file.file)
const res = await request({
url: '/snow-ops-platform/file/upload',
method: 'post',
data: formData
})
toast.close()
if (res.code === '00000') {
form.photos.push({ photoUrl: res.data })
const index = fileList.value.findIndex((f) => f.file === file.file)
if (index !== -1) {
fileList.value[index].serverUrl = res.data
}
console.log('form.photos', toRaw(form.photos))
console.log('fileList.value', fileList.value)
} else {
throw new Error(res.message)
}
} catch (error) {
toast.close()
showToast({
type: 'fail',
message: error.message
})
}
}
//
const handleDelete = (file) => {
if (file.serverUrl) {
const index = form.photos.findIndex((p) => p.photoUrl === file.serverUrl)
if (index !== -1) {
form.photos.splice(index, 1)
}
}
}
</script>
<style scoped>
.home {
/* 自动匹配导航栏高度 */
}
.content {
}
.content .van-cell-group .van-cell {
margin-bottom: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.add-wzbtn {
width: calc(100% - 32px);
margin: 10px 16px;
}
.add-btn {
position: fixed;
bottom: 20px;
left: 16px;
right: 16px;
width: calc(100% - 32px);
margin: 0 auto;
border-radius: 24px;
font-size: 16px;
height: 44px;
z-index: 999;
}
.grid {
margin-top: 16px;
}
.btn {
margin-top: 24px;
}
.status-tag {
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
color: white;
font-size: 12px;
}
.status-good {
background-color: #07c160;
}
.status-warning {
background-color: #ff976a;
}
.status-danger {
background-color: #ee0a24;
}
.disposal-buttons {
display: flex;
gap: 10px;
padding: 8px 0;
}
.disposal-buttons .van-button {
flex: 1;
}
.last-button {
margin-right: 16px;
}
</style>

View File

@ -0,0 +1,313 @@
<template>
<div class="mobile-selector">
<!-- 只读输入框点击打开选择器 -->
<!-- <div class="selector-input" @click="openPicker">
<span class="selector-value" :class="{ 'placeholder': !displayValue }">
{{ displayValue || placeholder }}
</span>
<van-icon name="arrow-down" class="selector-icon" />
</div> -->
<van-field :modelValue="modelValue" is-link readonly :label="label" :placeholder="placeholder" @click="showPicker = true" />
<!-- 移动端弹出层使用 van-action-sheet + 内部列表 -->
<van-action-sheet v-model:show="showPicker" :title="dialogTitle" :close-on-click-action="false" round :style="{ maxHeight: '80vh' }" @close="handlePickerClose">
<div class="picker-content">
<!-- 已选择提示 -->
<div class="selected-tip" v-if="tempSelectedItem">已选择路线{{ tempSelectedItem.routeCode }}</div>
<!-- 搜索框 -->
<van-search v-model="searchKeyword" :placeholder="searchPlaceholder" clearable @input="handleSearchInput" @clear="handleSearchClear" />
<!-- 列表区域 -->
<van-list v-model:loading="listLoading" :finished="finished" finished-text="没有更多了" @load="onLoadMore" :immediate-check="false">
<van-cell
v-for="item in dataList"
:key="item.routeCode"
:title="item.routeCode"
:class="{ 'is-selected': tempSelectedItem?.routeCode === item.routeCode }"
@click="handleItemClick(item)"
/>
<van-empty v-if="!listLoading && dataList.length === 0" description="暂无数据" />
</van-list>
</div>
</van-action-sheet>
</div>
</template>
<script setup>
import { ref, computed, watch, onUnmounted } from 'vue'
import { showToast } from 'vant'
import { request } from '@shared/utils/request'
// ==================== Props ====================
const props = defineProps({
// v-model 线
modelValue: {
type: [String, Number],
default: ''
},
label: {
type: String,
default: ''
},
//
placeholder: {
type: String,
default: '请选择路线'
},
//
dialogTitle: {
type: String,
default: '选择地点路线'
},
//
searchPlaceholder: {
type: String,
default: '请输入路线编号'
},
//
apiUrl: {
type: String,
default: '/snow-ops-platform/infrastructure-asset/routes'
},
//
pageSize: {
type: Number,
default: 20
},
//
extraParams: {
type: Object,
default: () => ({})
},
// (ms)
searchDelay: {
type: Number,
default: 500
}
})
const emit = defineEmits(['update:modelValue', 'change'])
// ==================== ====================
const showPicker = ref(false) //
const searchKeyword = ref('') //
const dataList = ref([]) //
const listLoading = ref(false) //
const finished = ref(false) //
const currentPage = ref(1) //
const total = ref(0) //
//
const tempSelectedItem = ref(null)
//
let searchTimer = null
// routeCode
const displayValue = computed(() => {
return props.modelValue || ''
})
// modelValue
watch(
() => props.modelValue,
(newVal) => {
if (tempSelectedItem.value && tempSelectedItem.value.routeCode !== newVal) {
tempSelectedItem.value = null
}
}
)
// ==================== API ====================
//
const fetchData = async (isReset = true) => {
if (isReset) {
//
currentPage.value = 1
dataList.value = []
finished.value = false
}
if (finished.value && !isReset) return
listLoading.value = true
try {
const params = {
pageNum: currentPage.value,
pageSize: props.pageSize,
lxbh: searchKeyword.value || props.modelValue || undefined,
xzdj: props.extraParams?.xzdj,
qxid: props.extraParams?.qxid
}
const response = await request({
url: props.apiUrl,
method: 'get',
params
})
if (response?.code === '00000') {
const records = response.data.records || []
total.value = response.data.total || 0
if (isReset) {
dataList.value = records
} else {
dataList.value = [...dataList.value, ...records]
}
//
finished.value = dataList.value.length >= total.value
} else {
showToast(response.message || '加载失败')
if (isReset) dataList.value = []
finished.value = true
}
} catch (error) {
console.error('请求失败:', error)
showToast('加载失败,请重试')
if (isReset) dataList.value = []
finished.value = true
} finally {
listLoading.value = false
}
}
// van-list load
const onLoadMore = () => {
if (finished.value) return
currentPage.value++
fetchData(false)
}
//
const debouncedSearch = () => {
if (searchTimer) clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
//
currentPage.value = 1
fetchData(true)
}, props.searchDelay)
}
// ==================== ====================
const openPicker = () => {
showPicker.value = true
//
if (props.modelValue) {
tempSelectedItem.value = { routeCode: props.modelValue }
} else {
tempSelectedItem.value = null
}
//
searchKeyword.value = ''
//
currentPage.value = 1
fetchData(true)
}
const handlePickerClose = () => {
showPicker.value = false
//
if (searchTimer) clearTimeout(searchTimer)
}
const handleSearchInput = () => {
debouncedSearch()
}
const handleSearchClear = () => {
searchKeyword.value = ''
debouncedSearch()
}
//
const handleItemClick = (item) => {
tempSelectedItem.value = item
// v-model
emit('update:modelValue', item.routeCode)
emit('change', item)
//
showPicker.value = false
}
// ==================== ====================
onUnmounted(() => {
if (searchTimer) clearTimeout(searchTimer)
})
</script>
<style scoped lang="scss">
.mobile-selector {
width: 100%;
.selector-input {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background-color: #f7f8fa;
border-radius: 8px;
font-size: 14px;
line-height: 1.4;
cursor: pointer;
transition: background-color 0.2s;
min-height: 44px; //
&:active {
background-color: #ebedf0;
}
.selector-value {
flex: 1;
color: #323233;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.placeholder {
color: #c8c9cc;
}
}
.selector-icon {
color: #969799;
font-size: 16px;
margin-left: 8px;
}
}
}
.picker-content {
display: flex;
flex-direction: column;
height: 100%;
max-height: 80vh;
.selected-tip {
padding: 12px 16px;
background-color: #f0f9eb;
color: #67c23a;
font-size: 13px;
border-bottom: 1px solid #e9e9e9;
}
// van-list
:deep(.van-list) {
flex: 1;
overflow-y: auto;
}
.van-cell {
font-size: 14px;
padding: 12px 16px;
&.is-selected {
color: #1989fa;
background-color: #ecf5ff;
}
}
}
</style>

View File

@ -4,13 +4,13 @@
<PanelItem title="基本信息" v-if="!isContinue">
<van-form>
<!-- 路况类别 -->
<BasePicker v-model="formData.roadConditionType" :options="roadConditionOptions" label="路况类别" placeholder="请选择" />
<BasePicker v-model="formData.roadConditionType" :options="options['roadConditionType']" label="路况类别" placeholder="请选择" />
<!-- 是否阻断 (event.isBlocked) -->
<BasePicker v-model="formData.event.isBlocked" :options="blockedOptions" label="是否阻断" placeholder="请选择" />
<BasePicker v-model="formData.event.isBlocked" :options="options['yesNoBool']" label="是否阻断" placeholder="请选择" />
<!-- 抢险进度 (event.repairProgress) -->
<BasePicker v-model="formData.event.repairProgress" :options="repairProgressOptions" label="抢险进度" placeholder="请选择" />
<BasePicker v-model="formData.event.repairProgress" :options="options['repairProgress']" label="抢险进度" placeholder="请选择" />
<!-- 水毁处数 (event.damageCount) -->
<van-field v-model="formData.event.damageCount" label="水毁处数" placeholder="请填写" type="number" />
@ -30,7 +30,7 @@
</div>
<!-- 线路编号 (顶层 routeNo) -->
<van-field v-model="formData.routeNo" label="线路编号" placeholder="请填写" />
<RoadRoutesPicker v-model="formData.routeNo" label="线路编号" placeholder="请线路" @change="handleRouteNoChange" />
<!-- 起点桩号 (event.startStakeNo) -->
<van-field v-model="formData.event.startStakeNo" label="起点桩号(K)" placeholder="请填写" />
@ -38,6 +38,10 @@
<!-- 止点桩号 (event.endStakeNo) -->
<van-field v-model="formData.event.endStakeNo" label="止点桩号(K)" placeholder="请填写" />
<van-field v-model="formData.event.longitude" label="经度" placeholder="请填写" />
<van-field v-model="formData.event.latitude" label="纬度" placeholder="请填写" />
<!-- 路况位置 (occurLocation) -->
<van-field v-model="formData.occurLocation" label="路况位置" placeholder="请填写" />
@ -51,13 +55,13 @@
<div class="disposal-measures">
<span class="measures-label">处置措施</span>
<div class="measures-options">
<!-- 改为单选使用 van-radio-group -->
<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>
<van-radio name="正常通行">正常通行</van-radio>
</van-radio-group>
<van-row gutter="10">
<van-col v-for="(item, index) in options['disposalMeasures']" :span="24 / options['disposalMeasures'].length" :key="index">
<van-button block plain :type="item.value === formData.report.disposalMeasures ? 'primary' : 'default'" @click="formData.report.disposalMeasures = item.value">
{{ item.label }}
</van-button>
</van-col>
</van-row>
</div>
</div>
@ -130,44 +134,49 @@
<van-field v-model="formData.report.siteDescription" label="现场描述" placeholder="请填写" type="textarea" rows="2" autosize />
<van-field label="附件" center>
<template #input>
<van-uploader accept="video/*,image/*" :modelValue="getFileList()" :after-read="afterRead" multiple :max-count="6" @delete="removeFile" />
<van-uploader accept="video/*,image/*" :modelValue="getFileList()" :after-read="afterRead" multiple :max-count="7" @delete="removeFile" />
</template>
</van-field>
</PanelItem>
<PanelItem>
<PanelItem v-if="!isContinue || (isContinue && !detail?.event.needsRecovery)">
<!-- 是否需要恢复重建 (event.needsRecovery) -->
<BasePicker v-model="formData.event.needsRecovery" :options="needsRecoveryOptions" label="是否需要恢复重建" placeholder="请选择" />
<BasePicker v-model="formData.event.needsRecovery" :options="options['yesNoBool']" label="是否需要恢复重建" placeholder="请选择" />
<!-- 恢复重建预估费用 (event.estimatedRecoveryCost) -->
<van-field v-model="formData.event.estimatedRecoveryCost" v-if="!isContinue" label="恢复重建预估费用" placeholder="请填写" type="digit">
<van-field v-model="formData.event.estimatedRecoveryCost" v-if="formData?.event.needsRecovery" label="恢复重建预估费用" placeholder="请填写" type="digit">
<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 } from 'vue'
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 RoadRoutesPicker from '../RoadRoutesPicker.vue'
import LossList from './LossList.vue'
import { useRouter, useRoute } from 'vue-router'
import { request } from '@shared/utils/request'
import { useOptions } from '@shared/composables/useOptions'
import mockFormData from '../waterDisasterFormData.json'
const route = useRoute()
const { options } = useOptions()
//
const isContinue = computed(() => route.query.isContinue)
// - Request 使 ref
const formData = ref({
//
occurLocation: '', //
occurTime: '', //
occurTime: null, //
roadConditionType: '', //
routeNo: '', // 线
@ -225,31 +234,7 @@ const getFileList = () => {
return fileList
}
// BasePicker
const roadConditionOptions = [
{ label: '高速公路', value: '高速公路' },
{ label: '国道', value: '国道' },
{ label: '省道', value: '省道' },
{ label: '县道', value: '县道' },
{ label: '乡道', value: '乡道' },
{ label: '村道', value: '村道' }
]
const blockedOptions = [
{ label: '是', value: true },
{ label: '否', value: false }
]
const repairProgressOptions = [
{ label: '未抢险', value: '未抢险' },
{ label: '抢险中', value: '抢险中' },
{ label: '已完成', value: '已完成' }
]
const needsRecoveryOptions = [
{ label: '是', value: true },
{ label: '否', value: false }
]
const submitting = ref(false)
//
const minDate = new Date(2020, 0, 1)
@ -260,7 +245,7 @@ const initFormData = (newVal) => {
// -
formData.value = {
occurLocation: newVal.occurLocation || '',
occurTime: newVal.occurTime || '',
occurTime: newVal.occurTime || formatTime(),
roadConditionType: newVal.roadConditionType || '',
routeNo: newVal.routeNo || '',
event: { ...formData.value.event, ...(newVal.event || {}) },
@ -311,6 +296,11 @@ const isVideoFile = (file) => {
if (file.fileUrl && videoExtensions.test(file.fileUrl)) return true
}
const handleRouteNoChange = (item) => {
formData.value.event.startStakeNo = item.startStakeNo
formData.value.event.endStakeNo = item.endStakeNo
}
/**
* 判断图片是否可以上传
* @param {File} file - 图片文件
@ -371,11 +361,23 @@ const isValidVideo = (file) => {
const checkFile = (file) => {
//
if (file.type.startsWith('image/')) {
const imageCount = formData.value.fileList?.filter((item) => item.fileType === 1).length
if (imageCount == 6) {
showFailToast('只能上传六张图片!')
return false
}
return isValidImage(file)
}
//
if (file.type.startsWith('video/')) {
const videoCount = formData.value.fileList?.filter((item) => item.fileType === 2).length
if (videoCount == 1) {
showFailToast('只能上传一个视频文件!')
return false
}
return isValidVideo(file)
}
@ -402,9 +404,8 @@ const afterRead = async (options) => {
toast.close()
if (res.code === '00000') {
const name = file.name
let type = isImageFile(file) ? 1 : isVideoFile(file) ? 2 : 3
const url = res.data
const type = isImageFile({ fileUrl: url}) ? 1 : isVideoFile({ fileUrl: url}) ? 2 : 3
const fileData = {
fileName: name,
fileUrl: url,
@ -413,7 +414,6 @@ const afterRead = async (options) => {
}
if (!formData.value.fileList) formData.value.fileList = []
formData.value.fileList.push(fileData)
} else {
throw new Error(res.message)
}
@ -431,14 +431,78 @@ const removeFile = (file, index) => {
formData.value.fileList.splice(index, 1)
}
//
//
const isEmpty = (value) => {
return value === null || value === undefined || value === ''
}
const validate = () => {
if (!formData.value.occurTime) {
showToast('请填写发生时间')
if (isEmpty(formData.value.roadConditionType)) {
showToast('请选择路况类别')
return false
}
if (!formData.value.routeNo) {
showToast('请填写线路编号')
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.occurTime)) {
showToast('请选择发生时间')
return false
}
if (isEmpty(formData.value.report?.expectRecoverTime)) {
showToast('请输入预计恢复时间')
return false
}
if (isEmpty(formData.value.routeNo)) {
showToast('请输入线路编号')
return false
}
if (isEmpty(formData.value.event?.startStakeNo)) {
showToast('请输入起点桩号')
return false
}
if (isEmpty(formData.value.event?.endStakeNo)) {
showToast('请输入止点桩号')
return false
}
if (isEmpty(formData.value.occurLocation)) {
showToast('请输入路况位置')
return false
}
if (isEmpty(formData.value.event?.blockedPointName)) {
showToast('请输入阻断点小地名')
return false
}
if (isEmpty(formData.value.event?.longitude)) {
showToast('请输入经度')
return false
}
if (isEmpty(formData.value.event?.latitude)) {
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
@ -449,6 +513,110 @@ const getFormData = () => {
return { ...formData.value }
}
//
const handleSubmit = async () => {
//
if (!validate()) return
submitting.value = true
try {
//
let formData = getFormData()
//
const submitData = {
...formData
//
}
const res = await request({
url: '/snow-ops-platform/water-damage/addOrUpdate',
method: 'post',
data: submitData
})
if (res?.code === '00000') {
showSuccessToast('提交成功')
let isRebuilded = false
if (isContinue && detail.value.event.needsRecovery) {
//
isRebuilded = true
}
if (!isRebuilded && submitData.event.needsRecovery) {
router.replace({
name: 'RebuildAdd',
params: {
data: res.data.id
}
})
} else {
//
setTimeout(() => {
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: null,
report: route.query?.mock ? mockFormData : {},
fileList: null
}
initFormData(newFormData)
} else {
showToast(result.message || '获取详情失败')
}
} catch (error) {
console.error('获取灾毁详情失败:', error)
showToast('获取详情失败,请稍后重试')
}
}
//
const formatTime = (date = new Date()) => {
const pad = (n) => n.toString().padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` + `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
onMounted(() => {
formData.value.occurTime = formatTime()
if (route.query.id) {
getDisasterDetail()
} else {
initFormData(route.query?.mock ? mockFormData : {})
}
})
//
defineExpose({
validate,
@ -543,4 +711,25 @@ defineExpose({
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>

View File

@ -4,7 +4,7 @@
<CurrentSite />
<!-- 基本信息 -->
<PanelItem title="基本信息">
<PanelItem title="基本信息" v-if="!loading">
<template #headerExtra>
<div class="status-wrapper">
<van-tag :type="getEventStatusType()" size="medium" plain>
@ -140,7 +140,7 @@
</PanelItem>
<!-- 填报信息 -->
<PanelItem title="填报信息">
<PanelItem title="填报信息" v-if="!loading">
<!-- 遍历所有填报记录首报 + 续报 -->
<div v-for="(report, index) in allReports" :key="index" class="report-section">
<div class="report-header">
@ -206,10 +206,12 @@
</PanelItem>
<!-- 底部按钮未解除状态显示续报按钮 -->
<div class="footer-buttons">
<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>
@ -241,6 +243,8 @@ const detailData = ref({
routeNo: ''
})
const loading = ref(true)
const allReports = computed(() => {
const reports =
detailData.value.report?.map((item, index) => {
@ -314,6 +318,8 @@ const getDisasterDetail = async () => {
return
}
loading.value = true
try {
const result = await request({
url: `/snow-ops-platform/water-damage/getById`,
@ -341,6 +347,7 @@ const getDisasterDetail = async () => {
console.error('获取灾毁详情失败:', error)
showToast('获取详情失败,请稍后重试')
}
loading.value = false
}
//
@ -524,4 +531,11 @@ onMounted(() => {
font-size: 16px;
}
}
.loading-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

View File

@ -92,14 +92,14 @@ const gridItems = [
params: { data: encodeURIComponent(JSON.stringify(yhzinfo.value)) },
},
},
{
icon: group106Icon,
text: "冰雪灾害",
to: {
name: "IceEventManage",
params: { data: encodeURIComponent(JSON.stringify(yhzinfo.value)) },
},
},
// {
// icon: group106Icon,
// text: "",
// to: {
// name: "IceEventManage",
// params: { data: encodeURIComponent(JSON.stringify(yhzinfo.value)) },
// },
// },
{
icon: group106Icon,
text: "灾害管理",

View File

@ -1,10 +1,6 @@
<template>
<div class="home">
<van-nav-bar title="冰雪灾害" fixed left-arrow @click-left="onClickLeft" />
<van-cell-group>
<van-cell title="当前站点" :value="yhzDetail.mc" />
</van-cell-group>
<PageContainer title="冰雪灾害" @click-back="onClickLeft">
<CurrentSite />
<div class="content" v-if="eventDetailData">
<van-cell-group>
@ -276,19 +272,22 @@
</van-button>
</div>
</van-popup>
</div>
</PageContainer>
</template>
<script setup>
import { ref, onMounted, toRaw, reactive, watch, computed } from "vue";
import { useRouter, useRoute } from "vue-router";
import { showToast, showLoadingToast, showImagePreview } from "vant";
import PageContainer from '@/components/PageContainer.vue'
import { request } from "../../../../shared/utils/request";
import CurrentSite from '@/components/CurrentSite.vue'
import { useYHZStore } from '@/stores/yhzStore';
const router = useRouter();
const route = useRoute();
const yhzStore = useYHZStore()
const yhzDetail = ref({});
const event = ref();
const eventDetailData = ref(); //
@ -296,7 +295,7 @@ const eventDetailData = ref(); // 冰雪事件详情数据
const getEventDetailData = async () => {
try {
const res = await request({
url: `/snow-ops-platform/event/getById?id=${event.value.id}`,
url: `/snow-ops-platform/event/getById?id=${route.query.id}`,
method: "GET",
});
if (res.code === "00000") {
@ -313,17 +312,11 @@ const getEventDetailData = async () => {
};
onMounted(() => {
const data = JSON.parse(decodeURIComponent(route.params.data));
yhzDetail.value = data.yhzDetail;
event.value = data.event;
getEventDetailData();
});
const onClickLeft = () => {
router.push({
name: "IceEventManage",
params: { data: encodeURIComponent(JSON.stringify(yhzDetail.value)) },
});
router.replace('/disasterManagement')
};
const showImage = (url) => {
@ -574,7 +567,7 @@ const checkMaterialAmount = (material, index) => {
const getMaterialList = async (wzmc) => {
try {
const data = {
yhzid: yhzDetail.value.id,
yhzid: yhzStore.getYHZInfo?.id,
wzmc,
pageNum: 1,
pageSize: 9999,