This commit is contained in:
fanjia 2026-04-15 16:40:39 +08:00
commit 09693b4a80
39 changed files with 4587 additions and 912 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,17 +97,14 @@ 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(' ')
return parts.join('')
}
return '未命名事件'
}
@ -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,12 +202,22 @@ const handleClickBack = () => {
//
const handleClickItem = (item) => {
router.push({
path: '/disasterDetail',
query: {
id: item.id
}
})
if (item.disasterType === 'WATER_DAMAGE') {
router.push({
path: '/waterDisasterDetail',
query: {
id: item.id
}
})
}
if (item.disasterType === 'ICE_SNOW') {
router.push({
name: 'IceEventDetail',
query: {
id: item.relationId
}
})
}
}
//
@ -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;
}
@ -330,4 +337,4 @@ onMounted(() => {
:deep(.van-tag) {
white-space: nowrap;
}
</style>
</style>

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,526 @@
<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'
import { useYHZStore } from '@/stores/yhzStore';
const router = useRouter()
const route = useRoute()
const yhzStore = useYHZStore()
//
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: yhzStore.getYHZInfo.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 = yhzStore.getYHZInfo.id
form.event.district = yhzStore.getYHZInfo.qxmc
console.log('yhzDetail', toRaw(yhzStore.getYHZInfo))
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,323 @@
<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" :rules="rules" :required="required" @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
},
//
rules: {
type: Array,
default: () => []
},
//
required: {
type: Boolean,
default: false
}
})
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['waterRoadConditionType']" 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>
@ -112,17 +116,17 @@
<!-- 投入资源 (report) -->
<PanelItem>
<van-field v-model="formData.report.investedMachinery" label="投机械" placeholder="请填写" type="digit">
<van-field v-model="formData.report.investedMachinery" label="机械" placeholder="请填写" type="digit">
<template #button>
<span class="field-unit">/</span>
</template>
</van-field>
<van-field v-model="formData.report.investedManpower" label="投入力" placeholder="请填写" type="number">
<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="digit">
<van-field v-model="formData.report.investedFunds" label="资金" placeholder="请填写" type="digit">
<template #button>
<span class="field-unit">万元</span>
</template>
@ -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;
}
}
</style>
.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,

View File

@ -5,12 +5,12 @@
<van-form ref="formRef" label-align="left" colon>
<van-field v-model="form.project.districtName" label="区县名称" center placeholder="请填写" required
:rules="[{ required: true, message: '请填写区县名称' }]" />
<van-field v-model="form.project.routeNo" label="线路编号" center placeholder="请填写" required
:rules="[{ required: true, message: '请填写线路编号' }]" />
<van-field v-model="form.project.startStakeNo" label="起点桩号" center placeholder="请填写" required
:rules="[{ required: true, message: '请填写起点桩号' }]" />
<van-field v-model="form.project.endStakeNo" label="止点桩号" center placeholder="请填写" required
:rules="[{ required: true, message: '请填写止点桩号' }]" />
<RoadRoutesPicker v-model="form.project.routeNo" label="线路编号" center placeholder="请选择" required
:rules="[{ required: true, message: '请选择线路编号' }]" @change="handleRouteNoChange" />
<van-field v-model="form.project.startStakeNo" label="起点桩号" disabled center placeholder="请选择线路编号"
required :rules="[{ required: true, message: '请填写起点桩号' }]" />
<van-field v-model="form.project.endStakeNo" label="止点桩号" disabled center placeholder="请选择线路编号"
required :rules="[{ required: true, message: '请填写止点桩号' }]" />
<van-field v-model="form.project.implementMileage" label="实施里程" center placeholder="单位:公里" required
type="number" :rules="[{ required: true, message: '请填写实施里程' }]">
<template #extra>
@ -59,6 +59,7 @@ import PageContainer from '@/components/PageContainer.vue'
import { showToast, showLoadingToast } from "vant";
import PanelItem from '@/components/PanelItem.vue'
import { request } from "../../../../shared/utils/request";
import RoadRoutesPicker from '../DisasterManagement/RoadRoutesPicker.vue'
const router = useRouter()
const route = useRoute()
@ -82,11 +83,47 @@ const form = reactive({
const fileList = ref([]);
const formRef = ref();
//
const getDetail = async (id) => {
try {
const res = await request({
url: '/snow-ops-platform/water-damage/getById',
method: 'GET',
params: {
id
}
})
if (res.code === '00000') {
form.project.districtName = res.data.event.district
form.project.routeNo = res.data.routeNo
form.project.startStakeNo = res.data.event.startStakeNo
form.project.endStakeNo = res.data.event.endStakeNo
form.project.implementMileage = res.data.event.blockedMileage
form.project.earthworkLoss = Array.isArray(res.data.lossList) ? res.data.lossList.reduce((sum, item) => sum + (item.totalAmount || 0), 0) : 0
form.project.disasterType = res.data.roadConditionType
form.project.locationRoute = res.data.occurLocation
form.project.roadLocation = res.data.event.blockedPointName
form.project.blockedPointName = res.data.event.blockedPointName
form.project.estimatedCost = res.data.event.estimatedRecoveryCost
form.fileList = res.data.fileList || []
fileList.value = res.data.fileList || []
} else {
throw new Error(res.message)
}
} catch (error) {
showToast({
type: "fail",
message: error.message,
});
}
}
onMounted(() => {
if (route.params.data) {
const data = JSON.parse(decodeURIComponent(route.params.data));
console.log('@@@@data', data);
// todo
// console.log('@@@@data', data);
//
getDetail(data);
} else {
// console.log('');
}
@ -94,7 +131,7 @@ onMounted(() => {
const handleClickBack = () => {
if (route.params.data) {
router.push('/')
} else {
router.push('/rebuild')
}
@ -180,10 +217,10 @@ const handleAdd = async () => {
data: form
})
toast.close();
if(res.code === '00000'){
if (res.code === '00000') {
showToast('提交成功');
handleClickBack();
}else{
} else {
showToast('提交失败, 请稍后重试或联系管理员');
}
} catch (error) {
@ -193,6 +230,12 @@ const handleAdd = async () => {
}
const handleRouteNoChange = (item) => {
form.project.startStakeNo = item.startStakeNo
form.project.endStakeNo = item.endStakeNo
}
</script>

View File

@ -170,17 +170,26 @@ const routes = [
}
},
// 线下帮扶台账 - 法规处
// {
// path: '/ledgerManagement3',
// name: 'ledgerManagement3',
// component: () => import('../views/LedgerManagement/index.vue'),
// meta: {
// title: '驻地台账',
// breadcrumb: true,
// parentRoute: 'warningManagement3'
// }
// },
{
path: '/ledgerManagement3',
name: 'ledgerManagement3',
component: () => import('../views/LedgerManagement/helpLedger/index.vue'),
meta: {
title: '线下帮扶台账',
breadcrumb: true,
parentRoute: 'warningManagement3'
}
},
{
path: '/dutyManagement',
name: 'dutyManagement',
component: () => import('../views/WarningManagement/law/dutyManagement/index.vue'),
meta: {
title: '值班管理',
breadcrumb: true,
parentRoute: 'warningManagement3'
}
},
// 项目管理 - 区县
{
@ -245,18 +254,33 @@ const routes = [
}
},
{
path: '/disasterReport',
name: 'DisasterReport',
component: () => import('../views/DisasterManagement/DisasterReport/DisasterReportPC.vue'),
path: '/iceDisasterReport',
component: () => import('../views/DisasterManagement/IceDisasterReport/IceDisasterReportPC.vue'),
meta: {
title: '灾毁事件填报',
title: '冰雪灾害上报',
breadcrumb: true
}
},
{
path: '/iceDisasterDetail',
component: () => import('../views/DisasterManagement/IceDisasterDetail/IceDisasterDetailPC.vue'),
meta: {
title: '冰雪灾害详情',
breadcrumb: true
}
},
{
path: '/waterDisasterReport',
component: () => import('../views/DisasterManagement/WaterDisasterReport/WaterDisasterReportPC.vue'),
meta: {
title: '水毁灾害上报',
breadcrumb: true
}
},
{
path: '/waterDisasterDetail',
name: 'WaterDisasterDetail',
component: () => import('../views/DisasterManagement/DisasterDetail/WaterDisasterDetailPC.vue'),
component: () => import('../views/DisasterManagement/WaterDisasterDetail/WaterDisasterDetailPC.vue'),
meta: {
title: '水毁事件详情',
breadcrumb: true

View File

@ -21,7 +21,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="事件类别">
<el-select v-model="filterForm.disasterType" placeholder="请选择" clearable>
<el-select v-model="filterForm.type" placeholder="请选择" clearable>
<el-option label="水毁事件" value="WATER_DAMAGE" />
<el-option label="冰雪灾害" value="ICE_SNOW" />
</el-select>
@ -37,18 +37,15 @@
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="是否阻断">
<el-select v-model="filterForm.isBlocked" placeholder="请选择" clearable>
<el-option label="是" value="是" />
<el-option label="否" value="否" />
<el-select v-model="filterForm.blocked" placeholder="请选择" clearable>
<el-option v-for="(option, idx) in options['yesNoBool']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6">
<el-form-item label="路线类型">
<el-select v-model="filterForm.roadConditionType" placeholder="请选择" clearable>
<el-option label="国省道" value="国省道" />
<el-option label="县乡道" value="县乡道" />
<el-option label="高速" value="高速" />
<el-select v-model="filterForm.routeType" placeholder="请选择" clearable>
<el-option v-for="(option, idx) in options['roadType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
@ -69,7 +66,7 @@
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="toReport">新增</el-button>
<el-button type="success" @click="handleExport">导出Excel</el-button>
<!-- <el-button type="success" @click="handleExport">导出Excel</el-button> -->
<el-button @click="resetFilters">重置</el-button>
</el-form-item>
</el-col>
@ -86,7 +83,11 @@
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="district" label="所属区县" min-width="120" />
<el-table-column prop="district" label="所属区县" min-width="120">
<template #default="{ row }">
<span>{{ getDistrictName(row.district) }}</span>
</template>
</el-table-column>
<el-table-column prop="routeNo" label="路线编号" min-width="120" />
<el-table-column prop="routeName" label="路线名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="startStakeNo" label="起点桩号" min-width="110" show-overflow-tooltip />
@ -120,13 +121,13 @@
<el-table-column prop="contactPhone" label="联系电话" width="120" />
<el-table-column label="图片" width="80" align="center">
<template #default="{ row }">
<el-icon style="font-size: 18px; cursor: pointer;" v-if="row.images && row.images.length > 0" @click="viewImages(row)"><Picture /></el-icon>
<el-icon style="font-size: 18px; cursor: pointer" v-if="row.images && row.images.length > 0" @click="viewImages(row)"><Picture /></el-icon>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="视频" width="80" align="center">
<template #default="{ row }">
<el-icon v-if="row.videos && row.videos.length > 0" style="font-size: 18px; cursor: pointer;" @click="viewVideos(row)"><VideoPlay /></el-icon>
<el-icon v-if="row.videos && row.videos.length > 0" style="font-size: 18px; cursor: pointer" @click="viewVideos(row)"><VideoPlay /></el-icon>
<span v-else></span>
</template>
</el-table-column>
@ -153,7 +154,7 @@
</div>
</el-card>
<el-image-viewer v-if="showPreviewImage && selectedRow" :url-list="selectedRow.images" show-progress @close="showPreviewImage = false" />
<el-image-viewer v-if="showPreviewImage && selectedRow" :url-list="selectedRow.images" show-progress @close="showPreviewImage = false" />
<VideoPreviewDialog ref="videoPreviewDialog" />
</div>
</template>
@ -165,18 +166,19 @@ import { useRouter } from 'vue-router'
import { request } from '@/utils/request'
import { Picture, VideoPlay } from '@element-plus/icons-vue'
import VideoPreviewDialog from '@/component/VideoPreviewDialog.vue'
import { useOptions } from '@shared/composables/useOptions'
const router = useRouter()
const { options, getAreaOptions } = useOptions()
//
const filterForm = reactive({
unit: '', //
occurLocation: '', //
routeNo: '', // 线
disasterType: '', // /
type: '', // /
eventStatus: '', // (0-, 1-)
isBlocked: '', //
roadConditionType: '', // 线
blocked: '', //
routeType: '', // 线
pushStatus: '' //
})
@ -199,6 +201,11 @@ const pagination = reactive({
total: 0
})
const getDistrictName = (districtId) => {
if (!options.value.area?.length) return '-'
return options.value.area.find((item) => item.value === districtId)?.label || '-'
}
//
const buildQueryParams = () => {
const params = {
@ -207,15 +214,15 @@ const buildQueryParams = () => {
unit: filterForm.unit,
occurLocation: filterForm.occurLocation,
routeNo: filterForm.routeNo,
disasterType: filterForm.disasterType,
type: filterForm.type,
eventStatus: filterForm.eventStatus,
isBlocked: filterForm.isBlocked,
roadConditionType: filterForm.roadConditionType
blocked: filterForm.blocked,
routeType: filterForm.routeType
}
if (dateRange.value && dateRange.value.length === 2) {
params.startDate = dateRange.value[0]
params.endDate = dateRange.value[1]
params.startTime = dateRange.value[0] + ' 00:00:00'
params.endTime = dateRange.value[1] + ' 23:59:59'
}
// null
@ -301,10 +308,10 @@ const resetFilters = () => {
filterForm.unit = ''
filterForm.occurLocation = ''
filterForm.routeNo = ''
filterForm.disasterType = ''
filterForm.type = ''
filterForm.eventStatus = ''
filterForm.isBlocked = ''
filterForm.roadConditionType = ''
filterForm.blocked = ''
filterForm.routeType = ''
filterForm.pushStatus = ''
dateRange.value = null
handleQuery()
@ -312,17 +319,27 @@ const resetFilters = () => {
//
const handleDetail = (row) => {
router.push({ path: '/waterDisasterDetail', query: { id: row.id } })
if (row.disasterType == 'WATER_DAMAGE') {
router.push({ path: '/waterDisasterDetail', query: { id: row.id } })
}
if (row.disasterType == 'ICE_SNOW') {
router.push({ path: '/iceDisasterDetail', query: { id: row.id } })
}
}
//
const handleEdit = (row) => {
router.push({ path: '/waterDisasterDetail', query: { id: row.id, mode: 'edit' } })
if (row.disasterType == 'WATER_DAMAGE') {
router.push({ path: '/waterDisasterDetail', query: { id: row.id, mode: 'edit' } })
}
if (row.disasterType == 'ICE_SNOW') {
router.push({ path: '/iceDisasterDetail', query: { id: row.id, mode: 'edit' } })
}
}
//
const toReport = () => {
router.push('/disasterReport')
router.push('/waterDisasterReport')
}
//
@ -353,6 +370,8 @@ const handleCurrentChange = (val) => {
//
onMounted(() => {
getAreaOptions()
fetchData()
})
</script>

View File

@ -1,115 +0,0 @@
<template>
<div class="disaster-report">
<el-page-header @back="handleBack" class="page-header">
<template #content>
<span class="title">{{ isEdit ? '灾毁填报' : '灾毁填报' }}</span>
</template>
</el-page-header>
<WaterDisasterReport />
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Upload } from '@element-plus/icons-vue'
import { request } from '@/utils/request'
import WaterDisasterReport from './WaterDisasterReportPC.vue'
const router = useRouter()
const route = useRoute()
const isEdit = computed(() => !!route.query.id)
const handleBack = () => {
router.back()
}
</script>
<style scoped lang="scss">
.disaster-report {
padding: 20px;
background-color: #f5f7fa;
height: 100%;
overflow: auto;
.page-header {
margin-bottom: 20px;
padding: 0 0 16px 0;
border-bottom: 1px solid #e4e7ed;
.title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
}
.disaster-form {
.form-section {
margin-bottom: 20px;
:deep(.el-card__header) {
padding: 12px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
:deep(.el-card__body) {
padding: 20px;
}
}
.section-header {
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
position: relative;
padding-left: 10px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 16px;
background-color: #409eff;
border-radius: 2px;
}
}
}
.sub-section-title {
font-size: 14px;
font-weight: 500;
color: #606266;
margin: 8px 0 16px 0;
padding-left: 8px;
border-left: 3px solid #409eff;
}
.unit-suffix {
margin-left: 8px;
color: #909399;
font-size: 12px;
}
.upload-tip {
font-size: 12px;
color: #909399;
margin-top: 8px;
}
.form-actions {
display: flex;
justify-content: center;
gap: 16px;
padding: 20px 0 40px;
}
}
}
</style>

View File

@ -0,0 +1,449 @@
<template>
<div class="water-disaster-pc">
<!-- 合并后的单个卡片 -->
<el-card class="form-card" shadow="never">
<div slot="header" class="card-header">
<span>续报信息</span>
</div>
<!-- 所有表单项合并到一个区域每行一个 -->
<el-form :model="formData" label-width="120px" size="small">
<!-- 处置措施 -->
<el-form-item label="处置措施">
<el-select v-model="formData.report.disposalMeasures">
<el-option label="半幅封闭" value="半幅封闭" />
<el-option label="全副封闭" value="全副封闭" />
<el-option label="便道通行" value="便道通行" />
<el-option label="正常通行" value="正常通行" />
</el-select>
</el-form-item>
<!-- 预计恢复时间 -->
<el-form-item label="预计恢复时间">
<el-date-picker
v-model="formData.report.expectRecoverTime"
type="datetime"
placeholder="请选择时间"
value-format="yyyy-MM-dd HH:mm"
:picker-options="pickerOptions"
style="width: 100%"
/>
</el-form-item>
<!-- 实际恢复时间 -->
<el-form-item label="实际恢复时间">
<el-date-picker
v-model="formData.report.actualRecoverTime"
type="datetime"
placeholder="请选择时间"
value-format="yyyy-MM-dd HH:mm"
:picker-options="pickerOptions"
style="width: 100%"
/>
</el-form-item>
<!-- 受伤人员 -->
<el-form-item label="受伤人员">
<el-input-number v-model="formData.report.injuredCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 死亡人员 -->
<el-form-item label="死亡人员">
<el-input-number v-model="formData.report.deadCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 滞留人员 -->
<el-form-item label="滞留人员">
<el-input-number v-model="formData.report.strandedPersonCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 损坏车辆 -->
<el-form-item label="损坏车辆">
<el-input-number v-model="formData.report.damagedVehicleCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 滞留车辆 -->
<el-form-item label="滞留车辆">
<el-input-number v-model="formData.report.strandedVehicleCount" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<!-- 损失列表组件 -->
<loss-list :col-span="24" v-model="formData.lossList" />
<!-- 处理情况 -->
<el-form-item label="处理情况">
<el-input v-model="formData.report.remark" type="textarea" :rows="2" placeholder="请填写(选填)" style="width: 60%" />
</el-form-item>
<!-- 损失总金额 -->
<el-form-item label="损失总金额">
<el-input v-model="formData.report.totalLossAmount" placeholder="请填写(选填)" style="width: 300px">
<template slot="append">万元</template>
</el-input>
</el-form-item>
<!-- 已投机械 -->
<el-form-item label="已投机械">
<el-input v-model="formData.report.investedMachinery" placeholder="请填写" style="width: 300px">
<template slot="append">/</template>
</el-input>
</el-form-item>
<!-- 投入人力 -->
<el-form-item label="投入人力">
<el-input-number v-model="formData.report.investedManpower" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append">人次</template>
</el-input-number>
</el-form-item>
<!-- 投入资金 -->
<el-form-item label="投入资金">
<el-input v-model="formData.report.investedFunds" placeholder="请填写" style="width: 300px">
<template slot="append">万元</template>
</el-input>
</el-form-item>
<!-- 现场描述 -->
<el-form-item label="现场描述">
<el-input v-model="formData.report.siteDescription" type="textarea" :rows="2" placeholder="请填写" style="width: 60%" />
</el-form-item>
<!-- 是否需要恢复重建 -->
<el-form-item label="是否需要恢复重建">
<el-radio-group v-model="formData.event.needsRecovery">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">追加记录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { ref, reactive, watch, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { request } from '@shared/utils/request'
import LossList from '../WaterDisasterReport/WaterDisasterLossListPC.vue'
// Props
const props = defineProps({
value: {
type: Object,
default: () => ({})
}
})
// Emits
const emit = defineEmits(['input', 'change', 'submit'])
//
const formData = reactive({
occurLocation: '',
occurTime: '',
roadConditionType: '',
routeNo: '',
event: {
blockedMileage: '',
blockedPointName: '',
contactPerson: '',
contactPhone: '',
damageCount: '',
district: '',
endStakeNo: '',
estimatedRecoveryCost: '',
inspectionMileage: '',
isBlocked: '',
needsRecovery: '',
repairProgress: '',
reporterUnit: '',
startStakeNo: ''
},
report: {
actualRecoverTime: '',
damagedVehicleCount: '',
deadCount: '',
disposalMeasures: '',
expectRecoverTime: '',
injuredCount: '',
investedFunds: '',
investedMachinery: '',
investedManpower: '',
remark: '',
siteDescription: '',
strandedPersonCount: '',
strandedVehicleCount: '',
totalLossAmount: ''
},
lossList: [],
fileList: []
})
//
const disposalMeasureValue = ref('')
//
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 pickerOptions = {
disabledDate(time) {
return time.getTime() < new Date(2020, 0, 1) || time.getTime() > new Date(2030, 11, 31)
}
}
//
watch(disposalMeasureValue, (newVal) => {
formData.report.disposalMeasures = newVal
})
//
watch(
() => props.value,
(newVal) => {
if (newVal && Object.keys(newVal).length > 0) {
initFormData(newVal)
}
},
{ immediate: true, deep: true }
)
//
watch(
() => formData,
(newVal) => {
emit('input', newVal)
emit('change', newVal)
},
{ deep: true }
)
// report.disposalMeasures
watch(
() => formData.report.disposalMeasures,
(newVal) => {
if (newVal && typeof newVal === 'string') {
disposalMeasureValue.value = newVal
}
},
{ immediate: true }
)
//
const initFormData = (data) => {
Object.assign(formData, {
occurLocation: data.occurLocation || '',
occurTime: data.occurTime || '',
roadConditionType: data.roadConditionType || '',
routeNo: data.routeNo || '',
event: { ...formData.event, ...(data.event || {}) },
report: { ...formData.report, ...(data.report || {}) },
lossList: data.lossList || [],
fileList: data.fileList || []
})
if (data.report?.disposalMeasures) {
disposalMeasureValue.value = data.report.disposalMeasures
}
}
//
const calibrateTime = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
formData.occurTime = `${year}-${month}-${day} ${hours}:${minutes}`
ElMessage.success('时间已校准为当前时间')
}
//
const validate = () => {
if (!formData.occurTime) {
ElMessage.warning('请填写发生时间')
return false
}
if (!formData.routeNo) {
ElMessage.warning('请填写线路编号')
return false
}
return true
}
//
const getFormData = () => {
return { ...formData }
}
//
const resetForm = () => {
Object.assign(formData, {
occurLocation: '',
occurTime: '',
roadConditionType: '',
routeNo: '',
event: {
blockedMileage: '',
blockedPointName: '',
contactPerson: '',
contactPhone: '',
damageCount: '',
district: '',
endStakeNo: '',
estimatedRecoveryCost: '',
inspectionMileage: '',
isBlocked: '',
needsRecovery: '',
repairProgress: '',
reporterUnit: '',
startStakeNo: ''
},
report: {
actualRecoverTime: '',
damagedVehicleCount: '',
deadCount: '',
disposalMeasures: '',
expectRecoverTime: '',
injuredCount: '',
investedFunds: '',
investedMachinery: '',
investedManpower: '',
remark: '',
siteDescription: '',
strandedPersonCount: '',
strandedVehicleCount: '',
totalLossAmount: ''
},
lossList: [],
fileList: []
})
disposalMeasureValue.value = ''
}
//
const submit = () => {
if (validate()) {
emit('submit', getFormData())
}
}
const handleSubmit = async () => {
//
if (!validate()) {
return
}
try {
//
//
const submitData = {
...formData
//
}
const res = await request({
url: '/snow-ops-platform/water-damage/addOrUpdate',
method: 'post',
data: submitData
})
if (res?.code === '00000') {
ElMessage.success('提交成功')
emit("refresh")
} else {
ElMessage.error(res.message)
}
} catch (error) {
ElMessage.error('提交失败,请重试')
console.error('提交失败:', error)
} finally {
}
}
//
defineExpose({
validate,
initFormData,
getFormData,
resetForm,
calibrateTime,
})
</script>
<style lang="scss" scoped>
.water-disaster-pc {
background-color: #f5f7fa;
height: 100%;
.form-card {
border-radius: 8px;
.card-header {
font-size: 16px;
font-weight: 600;
color: #303133;
border-left: 4px solid #409eff;
padding-left: 12px;
}
}
::v-deep .el-form-item {
margin-bottom: 22px;
}
::v-deep .el-input-group__append {
padding: 0 10px;
}
::v-deep .el-input-number {
width: 300px;
.el-input-group__append {
padding: 0 10px;
}
}
::v-deep .el-radio-group {
.el-radio {
margin-right: 20px;
}
}
}
</style>

View File

@ -0,0 +1,606 @@
<template>
<div class="web-detail-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<el-button :icon="ArrowLeft" @click="handleClickBack">返回</el-button>
<h2 class="page-title">冰雪事件详情</h2>
</div>
<div class="header-right">
<el-tag :type="getEventStatusType()" size="large">
{{ getEventStatusText() }}
</el-tag>
</div>
</div>
<div class="content-container">
<div class="left-panel">
<!-- 基本信息卡片 -->
<el-card class="info-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">基本信息</span>
</div>
</template>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">事件类型</span>
<span class="info-value">冰雪事件</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">路况类别</span>
<span class="info-value">{{ detailData.roadConditionType || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">是否阻断</span>
<span class="info-value">{{ detailData.event?.isBlocked ? '是' : '否' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">抢险进度</span>
<span class="info-value">{{ detailData.event?.repairProgress || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">处理措施</span>
<span class="info-value">{{ getBaseDisposalMeasures() }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">水毁处数</span>
<span class="info-value">{{ detailData.event?.damageCount || 0 }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">阻断里程</span>
<span class="info-value">{{ detailData.event?.blockedMileage ? detailData.event.blockedMileage + '公里' : '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">地点路线</span>
<span class="info-value">{{ detailData.occurLocation || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">起点桩号</span>
<span class="info-value">{{ detailData.event?.startStakeNo || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">止点桩号</span>
<span class="info-value">{{ detailData.event?.endStakeNo || '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">路况位置</span>
<span class="info-value">{{ detailData.event?.blockedPointName || detailData.occurLocation || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">阻断点小地名</span>
<span class="info-value">{{ detailData.event?.blockedPointName || '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">所属区县</span>
<span class="info-value">{{ detailData.event?.district || '-' }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">发生时间</span>
<span class="info-value">{{ detailData.occurTime || '-' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="info-row">
<el-col :span="8">
<div class="info-item">
<span class="info-label">是否恢复重建</span>
<span class="info-value">{{ detailData.event?.needsRecovery ? '是' : '否' }}</span>
</div>
</el-col>
<el-col :span="16" v-if="detailData.event?.needsRecovery">
<div class="info-item">
<span class="info-label">恢复重建预估费用</span>
<span class="info-value">{{ detailData.event?.estimatedRecoveryCost ? detailData.event.estimatedRecoveryCost + '万元' : '-' }}</span>
</div>
</el-col>
</el-row>
</el-card>
<!-- 填报信息卡片 -->
<el-card class="info-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">填报信息</span>
</div>
</template>
<div v-if="hasReportData">
<div v-for="(report, index) in allReports" :key="index" class="report-section">
<div class="report-header">
<span class="report-title">{{ report?.title }}</span>
<span class="report-meta">时间{{ report.reportTime || '-' }}</span>
</div>
<div class="content-wrapper">
<div class="basic-info-wrapper">
<div class="info-list">
<div class="info-item">
<span class="info-label">现场描述</span>
<span class="info-value">{{ report.siteDescription || '-' }}</span>
</div>
<div class="info-item">
<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.actualRecoverTime || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">预计恢复时间</span>
<span class="info-value">{{ report.expectRecoverTime || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">填报人</span>
<span class="info-value">{{ report.reporterName ? report.reporterName : '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">联系电话</span>
<span class="info-value">{{ report.phone ? report.phone : '-' }}</span>
</div>
</div>
<div class="file-list">
<FileUpload v-model="report.fileList" :readonly="!isEdit" />
</div>
</div>
<div class="detal-info-wrapper">
<template v-if="report.showDetail">
<LossListDetail :modelValue="report.lossList" :col-span="8" />
<el-row :gutter="24">
<el-col :span="8">
<div class="info-item margin">
<span class="info-label">投入机械</span>
<span class="info-value">{{ report.investedMachinery ? report.investedMachinery + '台/班' : '-'}}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item margin">
<span class="info-label">投入人力</span>
<span class="info-value">{{ report.investedManpower ? report.investedManpower + '人次' : '-'}}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item margin">
<span class="info-label">投入资金</span>
<span class="info-value">{{ report.investedFunds ? report.investedFunds + '万元' : '-'}}</span>
</div>
</el-col>
</el-row>
</template>
<el-button style="margin-top: 30px" type="primary" link @click="report.showDetail = !report.showDetail">
{{ report.showDetail ? '点击关闭详情' : '点击查看详情' }}
</el-button>
</div>
</div>
</div>
</div>
<el-empty v-else description="暂无填报信息" :image-size="100" />
</el-card>
<!-- 底部按钮 -->
<!-- <div class="footer-buttons">
<el-button type="primary" size="large" @click="handleContinueReport" class="footer-btn"> 续报 </el-button>
</div> -->
</div>
<div class="right-panel" v-if="isEdit">
<ContinueReport ref="continueReport" @refresh="getDisasterDetail" />
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ArrowLeft, Picture, VideoCamera } from '@element-plus/icons-vue'
import ContinueReport from './IceDisasterContinueReportPC.vue'
import { request } from '@shared/utils/request'
import FileUpload from '@/component/FileUpload/FileUpload.vue'
const router = useRouter()
const route = useRoute()
//
const detailData = ref({
event: null,
report: [],
fileList: [],
lossList: [],
occurLocation: '',
occurTime: '',
roadConditionType: '',
routeNo: ''
})
//
const eventStatus = ref(0)
//
const isEdit = computed(() => {
return route.query.mode === 'edit'
})
const continueReport = ref(null)
// +
const allReports = computed(() => {
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()
})
//
const hasReportData = computed(() => {
return allReports.value.length > 0
})
//
const getEventStatusText = () => {
return eventStatus.value === 1 ? '已解除' : '未解除'
}
//
const getEventStatusType = () => {
return eventStatus.value === 1 ? 'success' : 'danger'
}
const getBaseDisposalMeasures = () => {
const firstItem = allReports.value[0]
if (!firstItem) return '-'
return formatDisposalMeasures(firstItem.disposalMeasures || '') || '-'
}
//
const formatDisposalMeasures = (measures) => {
if (!measures) return ''
const measureMap = {
半幅封闭: '半幅封闭',
全副封闭: '全副封闭',
便道通行: '便道通行',
正常通行: '正常通行'
}
return measures
.split(',')
.map((m) => measureMap[m.trim()] || m.trim())
.join('、')
}
//
const getLossDescription = (report) => {
const lossList = report?.lossList
if (!lossList || lossList.length === 0) return '-'
const totalVolume = lossList.reduce((sum, loss) => {
const volume = (loss.length || 0) * (loss.width || 0) * (loss.height || 0)
return sum + volume
}, 0)
const totalAmount = lossList.reduce((sum, loss) => sum + (loss.totalAmount || 0), 0)
return `${totalVolume}方,共损失${totalAmount}万元`
}
//
const getVehicleStrandedText = (report) => {
const count = report?.strandedVehicleCount || 0
return count > 0 ? `有车滞留,共${count}` : '无车滞留'
}
//
const getDisasterDetail = async () => {
const id = route.query.id
if (!id) {
ElMessage.warning('缺少灾毁ID')
return
}
try {
const result = await request({
url: `/snow-ops-platform/water-damage/getById`,
method: 'get',
params: { id }
})
if (result?.data) {
const data = result.data
console.log('🚀 ~ getDisasterDetail ~ data:', data)
detailData.value = {
event: data.event || null,
report: data.report || [],
fileList: data.fileList || [],
lossList: data.lossList || [],
occurLocation: data.occurLocation || '',
occurTime: data.occurTime || '',
roadConditionType: data.roadConditionType || '',
routeNo: data.routeNo || ''
}
eventStatus.value = data.eventStatus || 0
if (isEdit.value) {
const newFormData = {
...data,
lossList: null,
report: {},
fileList: null
}
continueReport.value?.initFormData(newFormData)
}
} else {
ElMessage.warning(result.message || '获取详情失败')
}
} catch (error) {
console.error('获取灾毁详情失败:', error)
ElMessage.error('获取详情失败,请稍后重试')
}
}
//
const handleClickBack = () => {
router.push('/disasterManagement')
}
onMounted(() => {
getDisasterDetail()
})
</script>
<style scoped lang="scss">
.web-detail-container {
padding: 20px;
background: #f5f7fa;
height: 100%;
overflow: auto;
padding-bottom: 100px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 0 20px;
.header-left {
display: flex;
align-items: center;
gap: 16px;
.page-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #303133;
}
}
}
.info-card {
margin-bottom: 20px;
border-radius: 8px;
:deep(.el-card__header) {
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
background: #fafafa;
}
.card-header {
.card-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
}
.info-row {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.info-item {
display: flex;
align-items: flex-start;
line-height: 1.5;
& + .info-item {
margin-top: 10px;
}
&.margin {
margin-top: 10px;
}
.info-label {
white-space: nowrap;
flex-shrink: 0;
color: #909399;
font-size: 14px;
}
.info-value {
flex: 1;
color: #606266;
font-size: 14px;
word-break: break-all;
}
}
.report-section {
margin-bottom: 32px;
padding-bottom: 24px;
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
}
.report-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px dashed #dcdfe6;
.report-title {
font-size: 16px;
font-weight: 600;
color: #409eff;
}
.report-meta {
font-size: 13px;
color: #909399;
}
}
.attachment-wrapper {
margin-top: 12px;
padding-top: 8px;
border-top: 1px dashed #ebeef5;
.attachment-item {
.attachment-list {
display: flex;
flex-wrap: wrap;
gap: 16px;
.attachment-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: #f5f7fa;
border-radius: 4px;
cursor: pointer;
&:hover {
background: #ecf5ff;
color: #409eff;
}
.file-name {
font-size: 13px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
.footer-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
padding: 16px 24px;
background: #fff;
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
z-index: 100;
.footer-btn {
width: 200px;
height: 44px;
font-size: 16px;
border-radius: 22px;
}
}
.content-container {
display: flex;
.left-panel {
flex: 1;
margin-right: 10px;
}
.right-panel {
width: 300px;
}
}
.content-wrapper {
display: flex;
flex-direction: column;
}
.basic-info-wrapper {
display: flex;
}
.detal-info-wrapper {
margin-top: 10px;
border-top: 1px solid #efefef;
padding-top: 10px;
}
.info-list {
flex: 1;
overflow: hidden;
}
.file-list {
}
</style>

View File

@ -0,0 +1,704 @@
<template>
<div class="disaster-form-page">
<el-page-header style="margin-bottom: 10px;" @back="router.go(-1)" class="page-header">
<template #content>
<span class="title">{{ '冰雪灾害填报' }}</span>
</template>
</el-page-header>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px" class="disaster-form" @submit.prevent>
<!-- 基本信息区块 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-header">
<span class="section-title">基本信息</span>
</div>
</template>
<BlockItem title="填报人员信息">
<el-row :gutter="24">
<!-- 填报单位 -->
<el-col :span="8">
<el-form-item label="填报单位" prop="event.reporterUnit">
<el-input v-model="formData.event.reporterUnit" placeholder="请填写" />
</el-form-item>
</el-col>
<!-- 联系人 -->
<el-col :span="8">
<el-form-item label="联系人员" prop="event.contactPerson">
<el-input v-model="formData.event.contactPerson" placeholder="请填写" />
</el-form-item>
</el-col>
<!-- 联系电话 -->
<el-col :span="8">
<el-form-item label="联系电话" prop="event.contactPhone">
<el-input v-model="formData.event.contactPhone" placeholder="请填写" />
</el-form-item>
</el-col>
</el-row>
</BlockItem>
<BlockItem title="路况事件信息">
<el-row :gutter="24">
<!-- 发生时间 -->
<el-col :span="8">
<el-form-item label="发生时间" prop="occurTime">
<el-date-picker v-model="formData.occurTime" type="datetime" placeholder="请选择时间" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" />
</el-form-item>
</el-col>
<!-- 路况类别 -->
<el-col :span="8">
<el-form-item label="路况类别" prop="roadConditionType">
<el-select v-model="formData.roadConditionType" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['iceRoadConditionType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<!-- 处理措施-->
<el-col :span="8">
<el-form-item label="处理措施" prop="report.disposalMeasures">
<el-select v-model="formData.report.disposalMeasures" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['disposalMeasures']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="事件类型">
<el-select v-model="eventType" placeholder="请选择" style="width: 100%" @change="handleEventTypeChange">
<el-option v-for="(option, idx) in options['eventType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<!-- 预计恢复时间 -->
<el-col :span="8">
<el-form-item label="预计恢复时间" prop="report.expectRecoverTime">
<el-date-picker v-model="formData.report.expectRecoverTime" type="datetime" placeholder="请选择时间" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24"> </el-row>
<el-row :gutter="24">
<!-- 现场描述 -->
<el-col :span="16">
<el-form-item label="现场描述(绕行方案)" prop="report.siteDescription">
<el-input v-model="formData.report.siteDescription" type="textarea" :rows="2" placeholder="请填写" />
</el-form-item>
</el-col>
</el-row>
</BlockItem>
<BlockItem title="位置信息">
<el-row :gutter="24">
<!-- 路线类型 -->
<el-col :span="8">
<el-form-item label="路线类型">
<el-select v-model="filterForm.routeType" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['roadType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<!-- 所属区县 -->
<el-col :span="8">
<el-form-item label="所属区县" prop="event.district">
<el-select v-model="formData.event.district" placeholder="请选择" style="width: 100%" @change="handleDistrictChange">
<el-option v-for="(option, idx) in options['area']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<!-- 受灾里程 -->
<el-col :span="8">
<el-form-item label="受灾里程" prop="event.disasterMileage">
<el-input v-model="formData.event.disasterMileage" placeholder="请填写" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="地点路线" prop="routeNo">
<RoadRoutesSelect v-model="formData.routeNo" @change="handleRouteNoChange" :extra-params="{ xzdj: filterForm.routeType, qxid: formData.event.district }" />
</el-form-item>
</el-col>
<!-- 起点桩号 -->
<el-col :span="8">
<el-form-item label="起点桩号(K)" prop="event.startStakeNo">
<el-input v-model="formData.event.startStakeNo" placeholder="请填写">
<template #prepend>K</template>
</el-input>
</el-form-item>
</el-col>
<!-- 止点桩号 -->
<el-col :span="8">
<el-form-item label="止点桩号(K)" prop="event.endStakeNo">
<el-input v-model="formData.event.endStakeNo" placeholder="请填写">
<template #prepend>K</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<!-- 路况位置 -->
<el-col :span="8">
<el-form-item label="路况位置" prop="occurLocation">
<el-input v-model="formData.occurLocation" placeholder="请填写" />
</el-form-item>
</el-col>
<!-- 阻断点小地名 -->
<el-col :span="8">
<el-form-item label="阻断点小地名" prop="event.blockedPointName">
<el-input v-model="formData.event.blockedPointName" placeholder="请填写" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<!-- 经度 -->
<el-col :span="8">
<el-form-item label="经度" prop="event.longitude">
<el-input v-model="formData.event.longitude" placeholder="经度"> </el-input>
</el-form-item>
</el-col>
<!-- 纬度 -->
<el-col :span="8">
<el-form-item label="纬度" prop="event.latitude">
<el-input v-model="formData.event.latitude" placeholder="纬度"> </el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="图片上传" prop="fileList">
<FileUpload type="image" :limit="9" v-model="formData.fileList" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="视频上传" prop="fileList">
<FileUpload type="video" :limit="9" v-model="formData.fileList" />
</el-form-item>
</el-col>
</el-row>
</BlockItem>
</el-card>
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-header">
<span class="section-title">灾毁损失</span>
</div>
</template>
<BlockItem title="路况事件信息">
<el-row :gutter="24">
<!-- 受伤人员 -->
<el-col :span="8">
<el-form-item label="受伤人员" prop="report.injuredCount">
<el-input-number v-model="formData.report.injuredCount" :min="0" :step="1" style="width: 100%">
<template #suffix>
<span class="unit-text"></span>
</template>
</el-input-number>
</el-form-item>
</el-col>
<!-- 死亡人员 -->
<el-col :span="8">
<el-form-item label="死亡人员" prop="report.deadCount">
<el-input-number v-model="formData.report.deadCount" :min="0" :step="1" style="width: 100%">
<template #suffix>
<span class="unit-text"></span>
</template>
</el-input-number>
</el-form-item>
</el-col>
<!-- 滞留人员 -->
<el-col :span="8">
<el-form-item label="滞留人员" prop="report.strandedPersonCount">
<el-input-number v-model="formData.report.strandedPersonCount" :min="0" :step="1" style="width: 100%">
<template #suffix>
<span class="unit-text"></span>
</template>
</el-input-number>
</el-form-item>
</el-col>
<!-- 损坏车辆 -->
<el-col :span="8">
<el-form-item label="损坏车辆" prop="report.damagedVehicleCount">
<el-input-number v-model="formData.report.damagedVehicleCount" :min="0" :step="1" style="width: 100%">
<template #suffix>
<span class="unit-text"></span>
</template>
</el-input-number>
</el-form-item>
</el-col>
<!-- 滞留车辆 -->
<el-col :span="8">
<el-form-item label="滞留车辆" prop="report.strandedVehicleCount">
<el-input-number v-model="formData.report.strandedVehicleCount" :min="0" :step="1" style="width: 100%">
<template #suffix>
<span class="unit-text"></span>
</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
</BlockItem>
<BlockItem title="道路损失及其他">
<LossList v-model:model-value="formData.report.lossList" />
<el-row :gutter="24">
<!-- 投入机械 -->
<el-col :span="8">
<el-form-item label="投入机械" prop="report.investedMachinery">
<el-input-number v-model="formData.report.investedMachinery" :min="0" :precision="1" style="width: 100%" placeholder="请填写">
<template #suffix>
<span class="unit-text">/</span>
</template>
</el-input-number>
</el-form-item>
</el-col>
<!-- 投入人力 -->
<el-col :span="8">
<el-form-item label="投入人力" prop="report.investedManpower">
<el-input-number v-model="formData.report.investedManpower" :min="0" :step="1" style="width: 100%" placeholder="请填写">
<template #suffix>
<span class="unit-text">人次</span>
</template>
</el-input-number>
</el-form-item>
</el-col>
<!-- 投入资金 -->
<el-col :span="8">
<el-form-item label="投入资金" prop="report.investedFunds">
<el-input-number v-model="formData.report.investedFunds" :min="0" :precision="2" style="width: 100%" placeholder="请填写">
<template #suffix>
<span class="unit-text">万元</span>
</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
</BlockItem>
<BlockItem title="恢复重建预估费用">
<el-row :gutter="24">
<!-- 是否需要恢复重建 -->
<el-col :span="8">
<el-form-item label="是否需要恢复重建" prop="event.needsRecovery">
<el-select v-model="formData.event.needsRecovery" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['yesNoBool']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<!-- 恢复重建预估费用 -->
<el-col :span="8" v-if="formData?.event.needsRecovery">
<el-form-item label="恢复重建预估费用" prop="event.estimatedRecoveryCost">
<el-input-number v-model="formData.event.estimatedRecoveryCost" :min="0" :precision="2" style="width: 100%" placeholder="请填写">
<template #suffix>
<span class="unit-text">万元</span>
</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
</BlockItem>
</el-card>
<!-- 提交按钮 -->
<div class="form-actions">
<el-button @click="handleBack">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">提交</el-button>
</div>
</el-form>
<!-- 图片预览对话框 -->
<el-dialog v-model="previewDialogVisible" title="图片预览" width="600px">
<img :src="previewImageUrl" style="width: 100%" alt="预览图片" />
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Plus, Upload } from '@element-plus/icons-vue'
import { request } from '@/utils/request'
import BlockItem from '@/component/BlockItem.vue'
import FileUpload from '@/component/FileUpload/FileUpload.vue'
import { useOptions } from '@shared/composables/useOptions'
import RoadRoutesSelect from '../components/RoadRoutesSelect.vue'
const router = useRouter()
const route = useRoute()
const { options, getAreaOptions } = useOptions()
const formRef = ref(null)
const submitting = ref(false)
//
const isContinue = computed(() => route.query.isContinue === 'true')
//
const disposalMeasuresArray = ref([])
//
const imageFileList = ref([])
const videoFileList = ref([])
const eventType = ref('冰雪事件')
const filterForm = reactive({
routeType: ''
})
const formData = reactive({
//
occurLocation: null, // /
occurTime: null, //
roadConditionType: null, //
routeNo: null, // 线
// event
event: {
blockedMileage: null, //
blockedPointName: null, //
contactPerson: null, //
contactPhone: null, //
district: null, //
endStakeNo: null, //
estimatedRecoveryCost: null, //
inspectionMileage: null, //
needsRecovery: null, //
reporterUnit: null, //
startStakeNo: null, //
disasterMileage: null //
},
// report
report: {
actualRecoverTime: null, //
damagedVehicleCount: null, //
deadCount: null, //
disposalMeasures: null, //
expectRecoverTime: null, //
injuredCount: null, //
investedFunds: null, //
investedMachinery: null, //
investedManpower: null, //
remark: null, // /
siteDescription: null, //
strandedPersonCount: null, //
strandedVehicleCount: null, //
totalLossAmount: null //
},
// lossList
lossList: [],
// fileList
fileList: []
})
const handleEventTypeChange = () => {
router.replace({ path: '/waterDisasterReport' })
}
// report.disposalMeasures
watch(
disposalMeasuresArray,
(newVal) => {
formData.report.disposalMeasures = newVal.length ? newVal.join(',') : null
},
{ deep: true }
)
// fileList
watch(
imageFileList,
() => {
syncFileList()
},
{ deep: true }
)
// fileList
watch(
videoFileList,
() => {
syncFileList()
},
{ deep: true }
)
// fileList
const syncFileList = () => {
formData.fileList = [
...imageFileList.value.map((f) => ({
fileName: f.name || '',
fileSize: f.size || 0,
fileType: 1, // 1-
fileUrl: f.url || f.content || ''
})),
...videoFileList.value.map((f) => ({
fileName: f.name || '',
fileSize: f.size || 0,
fileType: 2, // 2-
fileUrl: f.url || f.content || ''
}))
]
}
// report.disposalMeasures
watch(
() => formData.report.disposalMeasures,
(newVal) => {
if (newVal && typeof newVal === 'string') {
disposalMeasuresArray.value = newVal.split(',').filter(Boolean)
} else {
disposalMeasuresArray.value = []
}
},
{ immediate: true }
)
//
const formRules = {
roadConditionType: [{ required: true, message: '请选择路况类别', trigger: 'change' }],
'report.disposalMeasures': [{ required: true, message: '请选择处置措施', trigger: 'change' }],
'event.blockedMileage': [{ required: true, message: '请输入阻断里程', trigger: 'blur' }],
occurTime: [{ required: true, message: '请选择发生时间', trigger: 'change' }],
'report.expectRecoverTime': [{ required: true, message: '请输入预计恢复时间', trigger: 'blur' }],
routeNo: [{ required: true, message: '请输入线路编号', trigger: 'blur' }],
'event.startStakeNo': [{ required: true, message: '请输入起点桩号', trigger: 'blur' }],
'event.endStakeNo': [{ required: true, message: '请输入止点桩号', trigger: 'blur' }],
occurLocation: [{ required: true, message: '请输入路况位置', trigger: 'blur' }],
'event.blockedPointName': [{ required: true, message: '请输入阻断点小地名', trigger: 'blur' }],
'event.longitude': [{ required: true, message: '请输入经度', trigger: 'blur' }],
'event.latitude': [{ required: true, message: '请输入纬度', trigger: 'blur' }],
'event.needsRecovery': [{ required: true, message: '请选择是否需要恢复重建', trigger: 'change' }],
'event.estimatedRecoveryCost': [{ required: true, message: '请输入恢复重建预估费用', trigger: 'blur' }]
// 'event.reporterUnit': [{ required: true, message: '', trigger: 'blur' }],
// 'event.contactPerson': [{ required: true, message: '', trigger: 'blur' }],
// 'event.contactPhone': [
// { required: true, message: '', trigger: 'blur' },
// { pattern: /^1[3-9]\d{9}$/, message: '', trigger: 'blur' }
// ]
}
//
const beforeImageUpload = (file) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'
const isLt500k = file.size / 1024 < 500
if (!isJpgOrPng) {
ElMessage.error('只能上传 JPG/PNG 格式的图片!')
return false
}
if (!isLt500k) {
ElMessage.error('图片大小不能超过 500KB!')
return false
}
return false // false
}
//
const beforeVideoUpload = (file) => {
const isLt20M = file.size / 1024 / 1024 < 20
if (!isLt20M) {
ElMessage.error('视频大小不能超过 20MB!')
return false
}
return false
}
//
const previewDialogVisible = ref(false)
const previewImageUrl = ref('')
const handlePicturePreview = (file) => {
previewImageUrl.value = file.url
previewDialogVisible.value = true
}
const handlePictureRemove = (file, fileList) => {
imageFileList.value = fileList
}
//
const handleBack = () => {
router.back()
}
// /
const initFormData = (data) => {
Object.assign(formData, data)
}
const handleDistrictChange = () => {
formData.routeNo = null
}
const handleRouteNoChange = (item) => {
formData.event.startStakeNo = item.startStakeNo
formData.event.endStakeNo = item.endStakeNo
}
//
const getFormData = () => {
return { ...formData }
}
//
const validate = async () => {
if (!formRef.value) return false
try {
await formRef.value.validate()
return true
} catch (error) {
ElMessage.warning('请完善表单信息')
return false
}
}
//
const handleSubmit = async () => {
//
if (!(await validate())) {
return
}
submitting.value = true
try {
//
//
const submitData = {
...formData
//
}
const res = await request({
url: '/snow-ops-platform/water-damage/addOrUpdate',
method: 'post',
data: submitData
})
if (res?.code === '00000') {
ElMessage.success('提交成功')
} else {
ElMessage.error(res.message)
}
//
setTimeout(() => {
router.replace('/disasterManagement')
}, 1000)
} catch (error) {
ElMessage.error('提交失败,请重试')
console.error('提交失败:', error)
} finally {
submitting.value = false
}
}
//
const loadEditData = async () => {
initFormData({})
}
onMounted(() => {
//
getAreaOptions()
loadEditData()
})
//
defineExpose({
validate,
initFormData,
getFormData
})
</script>
<style scoped lang="scss">
.disaster-form-page {
padding: 20px;
background-color: #f5f7fa;
height: 100%;
overflow: auto;
.disaster-form {
.form-section {
margin-bottom: 20px;
:deep(.el-card__header) {
padding: 12px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
:deep(.el-card__body) {
padding: 20px;
}
}
.section-header {
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
position: relative;
padding-left: 10px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 16px;
background-color: #409eff;
border-radius: 2px;
}
}
}
.sub-section-title {
font-size: 14px;
font-weight: 500;
color: #606266;
margin: 8px 0 16px 0;
padding-left: 8px;
border-left: 3px solid #409eff;
}
.unit-text {
color: #909399;
font-size: 12px;
}
.upload-tip {
font-size: 12px;
color: #909399;
margin-top: 8px;
}
.video-preview {
margin-top: 12px;
}
.form-actions {
display: flex;
justify-content: center;
gap: 16px;
padding: 20px 0 40px;
}
}
}
</style>

View File

@ -99,15 +99,15 @@
</el-input>
</el-form-item>
<!-- 投入力 -->
<el-form-item label="投入力">
<!-- 投入-->
<el-form-item label="投入力">
<el-input-number v-model="formData.report.investedManpower" :min="0" :controls="false" placeholder="请填写" style="width: 300px">
<template slot="append">人次</template>
</el-input-number>
</el-form-item>
<!-- 投资金 -->
<el-form-item label="投资金">
<!-- 资金 -->
<el-form-item label="资金">
<el-input v-model="formData.report.investedFunds" placeholder="请填写" style="width: 300px">
<template slot="append">万元</template>
</el-input>
@ -138,7 +138,7 @@
import { ref, reactive, watch, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { request } from '@shared/utils/request'
import LossList from '../DisasterReport/WaterDisasterLossListPC.vue'
import LossList from '../WaterDisasterReport/WaterDisasterLossListPC.vue'
// Props
const props = defineProps({

View File

@ -4,7 +4,7 @@
<div class="page-header">
<div class="header-left">
<el-button :icon="ArrowLeft" @click="handleClickBack">返回</el-button>
<h2 class="page-title">灾毁详情</h2>
<h2 class="page-title">水毁事件详情</h2>
</div>
<div class="header-right">
<el-tag :type="getEventStatusType()" size="large">
@ -132,7 +132,7 @@
<span class="info-value">{{ detailData.event?.needsRecovery ? '是' : '否' }}</span>
</div>
</el-col>
<el-col :span="16">
<el-col :span="16" v-if="detailData.event?.needsRecovery">
<div class="info-item">
<span class="info-label">恢复重建预估费用</span>
<span class="info-value">{{ detailData.event?.estimatedRecoveryCost ? detailData.event.estimatedRecoveryCost + '万元' : '-' }}</span>
@ -195,21 +195,21 @@
<LossListDetail :modelValue="report.lossList" :col-span="8" />
<el-row :gutter="24">
<el-col :span="8">
<div class="info-item">
<span class="info-label">投入机械</span>
<span class="info-value">{{ report.investedMachinery }}</span>
<div class="info-item margin">
<span class="info-label">投入机械</span>
<span class="info-value">{{ report.investedMachinery ? report.investedMachinery + '台/班' : '-'}}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">投入人力</span>
<span class="info-value">{{ report.investedManpower }}</span>
<div class="info-item margin">
<span class="info-label">投入人力</span>
<span class="info-value">{{ report.investedManpower ? report.investedManpower + '人次' : '-'}}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">投入资金</span>
<span class="info-value">{{ report.investedFunds }}</span>
<div class="info-item margin">
<span class="info-label">投入资金</span>
<span class="info-value">{{ report.investedFunds ? report.investedFunds + '万元' : '-'}}</span>
</div>
</el-col>
</el-row>
@ -246,7 +246,7 @@ import ContinueReport from './WaterDisasterContinueReportPC.vue'
import { request } from '@shared/utils/request'
import LossListDetail from './WaterDisasterLossListDetailPC.vue'
import FileUpload from '@/component/FileUpload/FileUpload.vue'
import mockData from '../DisasterReport/waterMockJson.json'
import mockData from '../WaterDisasterReport/waterMockJson.json'
const router = useRouter()
const route = useRoute()
@ -469,6 +469,10 @@ onMounted(() => {
margin-top: 10px;
}
&.margin {
margin-top: 10px;
}
.info-label {
white-space: nowrap;
flex-shrink: 0;

View File

@ -71,7 +71,6 @@ onMounted(async () => {
<style scoped lang="scss">
.loss-list-detail {
width: 100%;
.loss-table {
margin-bottom: 16px;

View File

@ -1,14 +1,21 @@
<template>
<el-row class="loss-list-pc" :gutter="24">
<el-col :span="colSpan" v-for="(item, index) in configs" :key="index">
<el-form-item :label="item.lossTypeName">
<el-input :modelValue="getValue(item)" @update:modelValue="(event) => changeValue(item, event)">
<template #suffix>
<span>{{ item.unit }}</span>
</template>
</el-input>
</el-form-item>
</el-col>
<template v-for="(item, index) in configs" :key="index">
<el-col :span="colSpan">
<el-form-item :label="item.lossTypeName">
<el-input :modelValue="getValue(item)" @update:modelValue="(event) => changeValue(item, event)">
<template #suffix>
<span>{{ item.unit }}</span>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24" v-if="item.lossTypeCode == 'OTHER_LOSS'">
<el-form-item label="其它损失描述">
<el-input v-model="getValueItem(item).remark" placeholder="请填写"> </el-input>
</el-form-item>
</el-col>
</template>
</el-row>
</template>
@ -32,9 +39,17 @@ const props = defineProps({
})
const getValue = (config) => {
const value = props.modelValue.find((v) => v.lossTypeId === config.lossTypeId)
if (value == null) props.modelValue.push({ ...config })
return value?.totalAmount || 0
const item = getValueItem(config)
return item?.totalAmount || 0
}
const getValueItem = (config) => {
let item = props.modelValue.find((v) => v.lossTypeId === config.lossTypeId)
if (item == null) {
item = { ...config }
props.modelValue.push(item)
}
return item
}
const configs = ref([])

View File

@ -1,5 +1,11 @@
<template>
<div class="disaster-form-page">
<el-page-header style="margin-bottom: 10px;" @back="router.go(-1)" class="page-header">
<template #content>
<span class="title">{{ '水毁灾害填报' }}</span>
</template>
</el-page-header>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px" class="disaster-form" @submit.prevent>
<!-- 基本信息区块 -->
<el-card class="form-section" shadow="never">
@ -36,9 +42,8 @@
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="事件类型">
<el-select v-model="eventType" placeholder="请选择" style="width: 100%">
<el-option label="水毁事件" value="水毁事件" />
<el-option label="冰雪事件" value="冰雪事件" />
<el-select v-model="eventType" placeholder="请选择" style="width: 100%" @change="handleEventTypeChange">
<el-option v-for="(option, idx) in options['eventType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
@ -46,12 +51,7 @@
<el-col :span="8">
<el-form-item label="路况类别" prop="roadConditionType">
<el-select v-model="formData.roadConditionType" placeholder="请选择" style="width: 100%">
<el-option label="高速公路" value="高速公路" />
<el-option label="国道" value="国道" />
<el-option label="省道" value="省道" />
<el-option label="县道" value="县道" />
<el-option label="乡道" value="乡道" />
<el-option label="村道" value="村道" />
<el-option v-for="(option, idx) in options['waterRoadConditionType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
@ -59,8 +59,7 @@
<el-col :span="8">
<el-form-item label="是否阻断" prop="event.isBlocked">
<el-select v-model="formData.event.isBlocked" placeholder="请选择" style="width: 100%">
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
<el-option v-for="(option, idx) in options['yesNoBool']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
@ -70,19 +69,15 @@
<el-col :span="8">
<el-form-item label="抢险进度" prop="event.repairProgress">
<el-select v-model="formData.event.repairProgress" placeholder="请选择" style="width: 100%">
<el-option label="未抢险" value="未抢险" />
<el-option label="抢险中" value="抢险中" />
<el-option v-for="(option, idx) in options['repairProgress']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<!-- 处理措施-->
<el-col :span="8">
<el-form-item label="处理措施" prop="event.disposalMeasures">
<el-select v-model="formData.event.disposalMeasures" placeholder="请选择" style="width: 100%">
<el-option label="全幅封闭" value="全幅封闭" />
<el-option label="半幅封闭" value="半幅封闭" />
<el-option label="正常通行" value="正常通行" />
<el-option label="限制通行" value="限制通行" />
<el-form-item label="处理措施" prop="report.disposalMeasures">
<el-select v-model="formData.report.disposalMeasures" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['disposalMeasures']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
@ -126,7 +121,7 @@
<el-row :gutter="24">
<!-- 现场描述 -->
<el-col :span="16">
<el-form-item label="现场描述" prop="report.siteDescription">
<el-form-item label="现场描述(绕行方案)" prop="report.siteDescription">
<el-input v-model="formData.report.siteDescription" type="textarea" :rows="2" placeholder="请填写" />
</el-form-item>
</el-col>
@ -135,20 +130,19 @@
<BlockItem title="位置信息">
<el-row :gutter="24">
<!-- 线路编号 -->
<!-- 路线类型 -->
<el-col :span="8">
<el-form-item label="路线类型" prop="routeNo">
<el-input v-model="formData.routeNo" placeholder="请填写" />
<el-form-item label="路线类型">
<el-select v-model="filterForm.routeType" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['roadType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<!-- 所属区县 -->
<el-col :span="8">
<el-form-item label="所属区县" prop="event.district">
<el-select v-model="formData.event.district" placeholder="请选择" style="width: 100%">
<el-option label="万州区" value="万州区" />
<el-option label="开州区" value="开州区" />
<el-option label="黔江区" value="黔江区" />
<el-option label="涪陵区" value="涪陵区" />
<el-select v-model="formData.event.district" placeholder="请选择" style="width: 100%" @change="handleDistrictChange">
<el-option v-for="(option, idx) in options['area']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
@ -156,7 +150,7 @@
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="地点路线" prop="routeNo">
<el-input v-model="formData.routeNo" placeholder="请填写" />
<RoadRoutesSelect v-model="formData.routeNo" @change="handleRouteNoChange" :extra-params="{ xzdj: filterForm.routeType, qxid: formData.event.district }" />
</el-form-item>
</el-col>
<!-- 起点桩号 -->
@ -193,15 +187,15 @@
<el-row :gutter="24">
<!-- 经度 -->
<el-col :span="8">
<el-form-item label="经度" prop="event.startStakeLng">
<el-input v-model="formData.event.startStakeLng" placeholder="经度"> </el-input>
<el-form-item label="经度" prop="event.longitude">
<el-input v-model="formData.event.longitude" placeholder="经度"> </el-input>
</el-form-item>
</el-col>
<!-- 纬度 -->
<el-col :span="8">
<el-form-item label="纬度" prop="event.startStakeLat">
<el-input v-model="formData.event.startStakeLat" placeholder="纬度"> </el-input>
<el-form-item label="纬度" prop="event.latitude">
<el-input v-model="formData.event.latitude" placeholder="纬度"> </el-input>
</el-form-item>
</el-col>
</el-row>
@ -213,14 +207,19 @@
</el-col>
<el-col :span="8">
<el-form-item label="视频上传" prop="fileList">
<FileUpload type="video" :limit="9" v-model="formData.fileList" />
<FileUpload type="video" :limit="9" v-model="formData.fileList" />
</el-form-item>
</el-col>
</el-row>
</BlockItem>
</el-card>
<el-card title="灾毁损失">
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-header">
<span class="section-title">灾毁损失</span>
</div>
</template>
<BlockItem title="路况事件信息">
<el-row :gutter="24">
<!-- 受伤人员 -->
@ -283,9 +282,9 @@
<LossList v-model:model-value="formData.report.lossList" />
<el-row :gutter="24">
<!-- 投入机械 -->
<!-- 投入机械 -->
<el-col :span="8">
<el-form-item label="投入机械" prop="report.investedMachinery">
<el-form-item label="投入机械" prop="report.investedMachinery">
<el-input-number v-model="formData.report.investedMachinery" :min="0" :precision="1" style="width: 100%" placeholder="请填写">
<template #suffix>
<span class="unit-text">/</span>
@ -294,7 +293,7 @@
</el-form-item>
</el-col>
<!-- 投入人力 -->
<!-- 投入人力 -->
<el-col :span="8">
<el-form-item label="投入人力" prop="report.investedManpower">
<el-input-number v-model="formData.report.investedManpower" :min="0" :step="1" style="width: 100%" placeholder="请填写">
@ -305,9 +304,9 @@
</el-form-item>
</el-col>
<!-- 投入资金 -->
<!-- 投入资金 -->
<el-col :span="8">
<el-form-item label="投入资金" prop="report.investedFunds">
<el-form-item label="投入资金" prop="report.investedFunds">
<el-input-number v-model="formData.report.investedFunds" :min="0" :precision="2" style="width: 100%" placeholder="请填写">
<template #suffix>
<span class="unit-text">万元</span>
@ -315,13 +314,6 @@
</el-input-number>
</el-form-item>
</el-col>
<!-- 其他损失描述 -->
<el-col :span="24">
<el-form-item label="其他损失描述" prop="report.siteDescription">
<el-input v-model="formData.report.siteDescription" type="textarea" :rows="2" placeholder="请填写" />
</el-form-item>
</el-col>
</el-row>
</BlockItem>
<BlockItem title="恢复重建预估费用">
@ -330,14 +322,13 @@
<el-col :span="8">
<el-form-item label="是否需要恢复重建" prop="event.needsRecovery">
<el-select v-model="formData.event.needsRecovery" placeholder="请选择" style="width: 100%">
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
<el-option v-for="(option, idx) in options['yesNoBool']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item>
</el-col>
<!-- 恢复重建预估费用 -->
<el-col :span="8" v-if="!isContinue">
<el-col :span="8" v-if="formData?.event.needsRecovery">
<el-form-item label="恢复重建预估费用" prop="event.estimatedRecoveryCost">
<el-input-number v-model="formData.event.estimatedRecoveryCost" :min="0" :precision="2" style="width: 100%" placeholder="请填写">
<template #suffix>
@ -374,9 +365,12 @@ import { request } from '@/utils/request'
import LossList from './WaterDisasterLossListPC.vue'
import BlockItem from '@/component/BlockItem.vue'
import FileUpload from '@/component/FileUpload/FileUpload.vue'
import { useOptions } from '@shared/composables/useOptions'
import RoadRoutesSelect from '../components/RoadRoutesSelect.vue'
const router = useRouter()
const route = useRoute()
const { options, getAreaOptions } = useOptions()
const formRef = ref(null)
const submitting = ref(false)
@ -392,6 +386,10 @@ const videoFileList = ref([])
const eventType = ref('水毁事件')
const filterForm = reactive({
routeType: ''
})
const formData = reactive({
//
occurLocation: null, // /
@ -442,6 +440,10 @@ const formData = reactive({
fileList: []
})
const handleEventTypeChange = () => {
router.replace({ path: '/iceDisasterReport' })
}
// report.disposalMeasures
watch(
disposalMeasuresArray,
@ -502,14 +504,29 @@ watch(
//
const formRules = {
roadConditionType: [{ required: true, message: '请选择路况类别', trigger: 'change' }],
'event.isBlocked': [{ required: true, message: '请选择是否阻断', trigger: 'change' }],
'event.repairProgress': [{ required: true, message: '请选择抢险进度', trigger: 'change' }],
'report.disposalMeasures': [{ required: true, message: '请选择处置措施', trigger: 'change' }],
'event.damageCount': [{ required: true, message: '请输入水毁处数', trigger: 'blur' }],
'event.blockedMileage': [{ required: true, message: '请输入阻断里程', trigger: 'blur' }],
occurTime: [{ required: true, message: '请选择发生时间', trigger: 'change' }],
'report.expectRecoverTime': [{ required: true, message: '请输入预计恢复时间', trigger: 'blur' }],
routeNo: [{ required: true, message: '请输入线路编号', trigger: 'blur' }],
'event.reporterUnit': [{ required: true, message: '请输入填报单位', trigger: 'blur' }],
'event.contactPerson': [{ required: true, message: '请输入联系人', trigger: 'blur' }],
'event.contactPhone': [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
'event.startStakeNo': [{ required: true, message: '请输入起点桩号', trigger: 'blur' }],
'event.endStakeNo': [{ required: true, message: '请输入止点桩号', trigger: 'blur' }],
occurLocation: [{ required: true, message: '请输入路况位置', trigger: 'blur' }],
'event.blockedPointName': [{ required: true, message: '请输入阻断点小地名', trigger: 'blur' }],
'event.longitude': [{ required: true, message: '请输入经度', trigger: 'blur' }],
'event.latitude': [{ required: true, message: '请输入纬度', trigger: 'blur' }],
'event.needsRecovery': [{ required: true, message: '请选择是否需要恢复重建', trigger: 'change' }],
'event.estimatedRecoveryCost': [{ required: true, message: '请输入恢复重建预估费用', trigger: 'blur' }]
// 'event.reporterUnit': [{ required: true, message: '', trigger: 'blur' }],
// 'event.contactPerson': [{ required: true, message: '', trigger: 'blur' }],
// 'event.contactPhone': [
// { required: true, message: '', trigger: 'blur' },
// { pattern: /^1[3-9]\d{9}$/, message: '', trigger: 'blur' }
// ]
}
//
@ -561,6 +578,15 @@ const initFormData = (data) => {
Object.assign(formData, data)
}
const handleDistrictChange = () => {
formData.routeNo = null
}
const handleRouteNoChange = (item) => {
formData.event.startStakeNo = item.startStakeNo
formData.event.endStakeNo = item.endStakeNo
}
//
const getFormData = () => {
return { ...formData }
@ -621,8 +647,7 @@ const handleSubmit = async () => {
//
const loadEditData = async () => {
if(route.query.mock) {
if (route.query.mock) {
initFormData(mockData)
} else {
initFormData({})
@ -630,6 +655,9 @@ const loadEditData = async () => {
}
onMounted(() => {
//
getAreaOptions()
loadEditData()
})
@ -643,7 +671,10 @@ defineExpose({
<style scoped lang="scss">
.disaster-form-page {
padding: 20px;
background-color: #f5f7fa;
height: 100%;
overflow: auto;
.disaster-form {
.form-section {

View File

@ -0,0 +1,255 @@
<template>
<div class="selector-component">
<el-input :modelValue="modelValue" :placeholder="placeholder" readonly @click="openDialog">
<template #suffix>
<el-icon><ArrowDown /></el-icon>
</template>
</el-input>
<el-dialog v-model="dialogVisible" :title="dialogTitle" :width="dialogWidth" :append-to-body="true" :modal="true" @close="handleDialogClose">
<div class="dialog-content">
<div class="title">已选择地点路线{{ tempSelectedItem?.routeCode }}</div>
<el-input v-model="searchKeyword" :placeholder="searchPlaceholder" clearable @input="handleSearch" />
<el-table :data="dataList" v-loading="loading" height="350" @row-click="handleRowClick" highlight-current-row>
<el-table-column prop="routeCode" label="线路编号" />
</el-table>
<div class="pagination-wrapper" v-if="total > 0">
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
:page-sizes="pageSizes"
layout="prev, pager, next, sizes"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, onUnmounted, watch } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { request } from '@shared/utils/request'
// ==================== Props ====================
const props = defineProps({
// v-model
modelValue: {
type: [String, Number],
default: ''
},
//
placeholder: {
type: String,
default: '请选择'
},
//
dialogTitle: {
type: String,
default: '选择地点路线'
},
//
dialogWidth: {
type: String,
default: '500px'
},
//
searchPlaceholder: {
type: String,
default: '请输入关键词搜索'
},
//
apiUrl: {
type: String,
default: '/snow-ops-platform/infrastructure-asset/routes'
},
//
pageSize: {
type: Number,
default: 10
},
//
pageSizes: {
type: Array,
default: () => [10, 20, 50]
},
//
extraParams: {
type: Object,
default: () => ({})
},
// (ms)
searchDelay: {
type: Number,
default: 1000
}
})
const emit = defineEmits(['update:modelValue', 'change'])
// ==================== ====================
const dialogVisible = ref(false)
const searchKeyword = ref('')
const dataList = ref([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(props.pageSize)
const total = ref(0)
//
const tempSelectedItem = ref(null)
//
let searchTimer = null
//
const selectedValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
watch(() => props.modelValue, () => {
if(props.modelValue != tempSelectedItem.value?.routeCode) {
tempSelectedItem.value = null
}
})
// ==================== API ====================
const fetchData = async () => {
loading.value = true
try {
const params = {
pageNum: currentPage.value,
pageSize: pageSize.value,
lxbh: props.modelValue,
xzdj: props.extraParams?.xzdj, //
qxid: props.extraParams?.qxid //
}
//
if (searchKeyword.value) {
params.lxbh = searchKeyword.value
}
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
// 便使
dataList.value = records
} else {
ElMessage.error(response.message || '加载失败')
dataList.value = []
total.value = 0
}
} catch (error) {
console.error('请求失败:', error)
ElMessage.error('加载失败,请重试')
dataList.value = []
total.value = 0
} finally {
loading.value = false
}
}
//
const debouncedSearch = () => {
//
if (searchTimer) {
clearTimeout(searchTimer)
}
//
searchTimer = setTimeout(() => {
currentPage.value = 1
fetchData()
}, props.searchDelay)
}
// ==================== ====================
const openDialog = () => {
dialogVisible.value = true
//
searchKeyword.value = tempSelectedItem.value?.routeCode || ''
currentPage.value = 1
fetchData()
}
const handleDialogClose = () => {
dialogVisible.value = false
//
if (searchTimer) {
clearTimeout(searchTimer)
}
}
const handleSearch = () => {
// 使
debouncedSearch()
}
const handlePageChange = (page) => {
currentPage.value = page
fetchData()
}
const handleSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
fetchData()
}
const handleRowClick = (row) => {
tempSelectedItem.value = row
selectedValue.value = tempSelectedItem.value.routeCode
dialogVisible.value = false
emit('change', tempSelectedItem.value)
}
// ==================== ====================
onUnmounted(() => {
if (searchTimer) {
clearTimeout(searchTimer)
}
})
</script>
<style scoped lang="scss">
.selector-component {
width: 100%;
:deep(.el-input__wrapper) {
cursor: pointer;
.el-input__inner {
cursor: pointer;
}
}
}
.dialog-content {
display: flex;
flex-direction: column;
gap: 16px;
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 8px;
}
}
</style>

View File

@ -0,0 +1,254 @@
import { h, ref, onMounted, reactive, watch, toRaw, nextTick } from "vue";
import { request } from "@/utils/request";
import { useRoute, useRouter } from 'vue-router'
const tableData = ref([]); // 表格数据
const modelVisible = ref(false); // 弹窗状态
const drawerVisible = ref(false); // 抽屉状态
// 弹窗内容
const model = reactive({
title: '',
content: null,
props: {},
onCancel: null,
onConfirm: null,
width: '',
});
const form = reactive({
});
const INIT_FORM = {
};
// 抽屉内容
const drawer = reactive({
title: '',
content: null,
props: {},
onCancel: null,
onConfirm: null,
direction: 'rtl',
size: '50%'
});
const dialogRef = ref(null); // 弹窗实例
const drawerRef = ref(null); // 抽屉实例
const columns = [
{
prop: "helpTime",
label: "帮扶时间",
// formatter: (row) => `${row.helpTime || ''} - ${row.helpTimeEnd || ''}`,
},
{
prop: "helpCounty",
label: "帮扶区县",
},
{
prop: "helpPerson",
label: "帮扶人员",
},
{
prop: "helpUnit",
label: "帮扶单位",
},
{
prop: "helpStatus",
label: "帮扶情况",
},
{
prop: "attachmentUrl",
label: "附件",
render: (row) => {
if (row.attachmentUrl) {
return h(ElButton, {
text: 'true',
type: 'primary',
onClick: () => downloadAttachment(row.attachmentUrl)
}, '附件下载');
} else {
return h('span', {}, '---');
}
}
},
{
prop: "uploadTime",
label: "上传时间",
},
{
prop: "remark",
label: "备注",
},
]
// 过滤条件
const filterData = reactive({})
// 分页
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
pageSizes: [10, 20, 50],
layout: "prev, pager, next, jumper",
onChange: (page, pageSize) => {
pagination.current = page;
pagination.pageSize = pageSize;
getTableData(filterData);
},
});
// 获取列表
const getTableData = async (filterData = {}) => {
try {
// 过滤空字符串属性
const filteredParams = {};
Object.keys(filterData).forEach(key => {
if (filterData[key] !== '' && filterData[key] != null) {
filteredParams[key] = filterData[key];
}
});
const res = await request({
url: '/snow-ops-platform/fgc-xxbf/list',
method: "GET",
params: {
...filteredParams,
pageNum: pagination.current,
pageSize: pagination.pageSize,
}
})
if (res.code === '00000') {
tableData.value = res.data.records
pagination.total = res.data.total
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error(error.message)
console.error('获取列表失败:', error);
}
}
// 下载附件
const downloadAttachment = (url) => {
if (!url) {
ElMessage.warning('无附件地址');
return;
}
// 直接使用 window.open 触发下载
window.open(url, '_blank');
}
// 预警类型选项
const eventTypeOptions = [
{ label: "全部", value: "" },
{ label: "台风预警", value: "台风预警" },
{ label: "暴雨预警", value: "暴雨预警" },
{ label: "寒潮预警", value: "寒潮预警" },
{ label: "大雾预警", value: "大雾预警" },
{ label: "暴雪预警", value: "暴雪预警" },
{ label: "道路结冰预警", value: "道路结冰预警" },
{ label: "雷电预警", value: "雷电预警" },
{ label: "强对流预警", value: "强对流预警" },
];
// 预警级别选项
const severityOptions = [
{ label: "全部", value: "" },
{ label: "红色预警", value: "红色预警" },
{ label: "橙色预警", value: "橙色预警" },
{ label: "黄色预警", value: "黄色预警" },
{ label: "蓝色预警", value: "蓝色预警" },
]
// 响应情况选项
const responseOptions = [
{ label: "全部", value: "" },
{ label: "未响应", value: "未响应" },
{ label: "部分响应", value: "部分响应" },
{ label: "全部响应", value: "全部响应" },
]
const downloadFile = async () => {
window.open('/snow-ops-platform/fgc-xxbf/template', '_blank');
}
const uploadFile = async (file) => {
if (!file) {
ElMessage.warning('请选择要上传的文件');
return;
}
try {
// 创建FormData对象
const formData = new FormData();
formData.append('file', file);
const loading = ElLoading.service({
lock: true,
text: '操作中',
background: 'rgba(0, 0, 0, 0.7)',
})
// 发送POST请求
const res = await request({
url: '/snow-ops-platform/fgc-xxbf/upload',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
// 关闭加载提示
loading.close();
if (res.code === '00000') {
ElMessage.success('文件上传成功');
// 刷新表格数据
getTableData(filterData);
} else {
throw new Error(res.message || '上传失败');
}
} catch (error) {
ElMessage.error(error.message || '文件上传失败');
console.error('文件上传失败:', error);
}
}
export default () => {
onMounted(() => {
getTableData();
})
watch(filterData, (val) => {
getTableData(filterData);
}, { deep: true })
return {
modelVisible,
model,
drawerVisible,
drawer,
dialogRef,
drawerRef,
eventTypeOptions,
severityOptions,
responseOptions,
tableData,
filterData,
pagination,
columns,
downloadFile,
uploadFile,
}
}

View File

@ -0,0 +1,81 @@
<template>
<div class="root">
<div class="search-box">
<el-input v-model="script.filterData.headline" style="width: 240px; margin-right: 10px" size="large"
placeholder="预警标题" :suffix-icon="Search" />
<el-select v-model="script.filterData.eventType" style="width: 240px; margin-right: 10px" size="large"
placeholder="预警类型" :suffix-icon="ArrowDown" :options="script.eventTypeOptions" clearable />
<el-select v-model="script.filterData.severity" style="width: 240px; margin-right: 10px" size="large"
placeholder="预警级别" :suffix-icon="ArrowDown" :options="script.severityOptions" clearable />
<el-select v-model="script.filterData.responseStatus" style="width: 240px; margin-right: 10px" size="large"
placeholder="响应情况" :suffix-icon="ArrowDown" :options="script.responseOptions" clearable />
</div>
<div class="event-box">
<el-button type="primary" @click="triggerFileSelect">上传线下帮扶</el-button>
<el-button type="primary" @click="script.downloadFile">线下帮扶模板下载</el-button>
<input type="file" ref="fileInput" style="display: none" @change="handleFileSelect" accept=".*"></input>
</div>
<DynamicTable :dataSource="script.tableData.value" :columns="script.columns" :autoHeight="true"
:pagination="script.pagination">
</DynamicTable>
<div class="model-box">
<MyDialog v-model="script.modelVisible.value" :title="script.model?.title"
:dynamicComponent="script.model?.content" :component-props="script.model?.props"
:onConfirm="script.model?.onConfirm" :onCancel="script.model?.onCancel" ref="dialogRef"
:width="script.model?.width">
</MyDialog>
<MyDrawer v-model="script.drawerVisible.value" :title="script.drawer?.title"
:dynamicComponent="script.drawer?.content" :component-props="script.drawer?.props"
:onConfirm="script.drawer?.onConfirm" :onCancel="script.drawer?.onCancel" ref="drawerRef"
:direction="script.drawer?.direction" :size="script.drawer?.size"></MyDrawer>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import DynamicTable from "../../../component/DynamicTable";
import { Search, ArrowDown, CaretBottom } from "@element-plus/icons-vue";
import MyDialog from "../../../component/MyDialog";
import MyDrawer from "../../../component/MyDrawer";
import scriptFn from "./index.js";
const script = scriptFn();
const { dialogRef, drawerRef } = script;
const fileInput = ref(null);
//
const triggerFileSelect = () => {
fileInput.value.click();
};
//
const handleFileSelect = (event) => {
const file = event.target.files[0];
if (file) {
script.uploadFile(file);
}
// input
event.target.value = '';
};
</script>
<style scoped>
.search-box {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.root {
height: 100%;
padding: 25px;
}
.event-box {
margin: 20px 0;
display: flex;
justify-content: flex-start;
}
</style>

View File

@ -372,15 +372,15 @@ export default () => {
getTableData();
})
// 新增填报
const newAdd = () => {
router.push({
name: 'projectAdd',
params: {
mode: 'add'
// 新增填报
const newAdd = () => {
router.push({
name: 'projectAdd',
params: {
mode: 'add'
}
})
}
})
}
watch(filterData, (val) => {

View File

@ -128,6 +128,7 @@ const getTableData = async (filterData = {}) => {
...filteredParams,
pageNum: pagination.current,
pageSize: pagination.pageSize,
ledgerType: 1, // 1-建设处2-区县
}
})
if (res.code === '00000') {

View File

@ -43,6 +43,8 @@ import MyDialog from "../../../component/MyDialog/index.js";
import MyDrawer from "../../../component/MyDrawer/index.js";
import scriptFn from "./index.js";
const script = scriptFn();
const { dialogRef, drawerRef } = script;
</script>
<style scoped>

View File

@ -128,6 +128,7 @@ const getTableData = async (filterData = {}) => {
...filteredParams,
pageNum: pagination.current,
pageSize: pagination.pageSize,
ledgerType: 2, // 1-建设处2-区县
}
})
if (res.code === '00000') {

View File

@ -40,6 +40,7 @@ import MyDialog from "../../../component/MyDialog/index.js";
import MyDrawer from "../../../component/MyDrawer/index.js";
import scriptFn from "./index.js";
const script = scriptFn();
const { dialogRef, drawerRef } = script;
</script>
<style scoped>

View File

@ -4,206 +4,78 @@
style="max-height: 60vh; overflow-y: auto; padding-right: 50px" :rules="rules">
<div class="form-part">
<el-row>
<h4 style="margin: 20px 0;">项目信息</h4>
<h4 style="margin: 0 0 20px 0;">气象信息</h4>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="预警标题" prop="预警标题">
<el-input v-model="form.headline" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="工程状态" prop="工程状态">
<el-radio-group v-model="form.zt">
<el-radio value="1">在建</el-radio>
<el-radio value="2">停工</el-radio>
</el-radio-group>
<el-form-item label="预警时间" prop="预警时间">
<el-col :span="11">
<el-date-picker v-model="form.onset" type="date" placeholder="开始时间" style="width: 100%" />
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-date-picker v-model="form.expires" type="date" placeholder="过期时间" style="width: 100%" />
</el-col>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="所属区县" prop="所属区县">
<el-select v-model="qx" filterable remote reserve-keyword clearable placeholder="输入区县名称查询"
:remote-method="remoteMethod_qx" :loading="loading" @change="handleSelect_qx" value-key="index">
<el-option v-for="(item, index) in qxList" :key="index" :label="item.qxmc" :value="item.qxmc" />
<el-col :span="12">
<el-form-item label="气象类型" prop="气象类型">
<el-select v-model="form.eventType" clearable placeholder="请选择">
<el-option v-for="(item, index) in [
{ label: '道路结冰', value: '道路结冰' },
{ label: '暴雪', value: '暴雪' },
{ label: '暴雨', value: '暴雨' },
]" :key="index" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="项目名称" prop="项目名称">
<el-input />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="驻地名称" prop="驻地名称">
<el-input />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="驻地类型" prop="驻地类型">
<el-select>
<el-col :span="12">
<el-form-item label="气象来源" prop="气象来源">
<el-select v-model="form.weatherSource" clearable placeholder="请选择">
<el-option v-for="(item, index) in [
{ label: '市级', value: '1' },
{ label: '部级', value: '2' },
{ label: '区县', value: '3' },
]" :key="index" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="坐标点位" prop="坐标点位">
<el-row :gutter="10">
<el-col :span="12">
<el-input aria-label="经度" placeholder="经度" />
</el-col>
<el-col :span="12">
<el-input aria-label="纬度" placeholder="纬度" />
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="所属项目名称:">
<el-input />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="项目类型:">
<el-select>
<el-col :span="12">
<el-form-item label="预警等级" prop="预警等级">
<el-select v-model="form.urgency" clearable placeholder="请选择">
<el-option v-for="(item, index) in [
{ label: '1级红色预警', value: '1' },
{ label: '2级橙色预警', value: '2' },
{ label: '3级黄色预警', value: '3' },
{ label: '4级蓝色预警', value: '4' },
]" :key="index" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="建设单位:">
<el-input />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="施工单位:">
<el-input />
<el-col :span="12">
<el-form-item label="风险区县" prop="风险区县">
<el-select v-model="form.areaCodes" clearable placeholder="请选择" multiple>
<el-option v-for="(item, index) in areaList" :key="index" :label="item.qxmc" :value="item.xzdm" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="行政区域:">
<el-select></el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="驻地人数:">
<el-input />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="驻地风险等级:">
<el-select></el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="房建类型:">
<el-select></el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="搬迁状态:">
<el-select></el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="form-part">
<el-row>
<h4 style="margin: 20px 0;">项目联系人信息</h4>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="吹哨人姓名:">
<el-input />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="吹哨人电话:">
<el-input />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="建设单位包保责任人姓名:">
<el-input />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="建设单位包保责任人电话:">
<el-input />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="施工单位包保责任人姓名:">
<el-input />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="施工单位包保责任人电话:">
<el-input />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="驻地包保责任人姓名:">
<el-input />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="驻地包保责任人电话:">
<el-input />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="区县级包保责任人姓名:">
<el-input />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="区县级包保责任人电话:">
<el-input />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="市级包保责任人姓名:">
<el-input />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="市级包保责任人电话:">
<el-input />
</el-form-item>
</el-col>
</el-row>
</div>
<div class="form-part">
<el-row>
<h4 style="margin: 20px 0;">驻地情况</h4>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<FileUpload type="image" :limit="9" :fileType=6 />
</el-col>
</el-row>
</div>
<div class="form-part">
<el-row>
<h4 style="margin: 20px 0;">现场情况</h4>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-input></el-input>
</el-col>
</el-row>
</div>
@ -224,169 +96,56 @@ const props = defineProps({
default: () => ({}),
},
});
const sfjwd = ref("是");
const qx = ref("");
const loading = ref(false);
const selectOptions = ref([]);
const qxList = ref([]);
//
const getUserList = async (key) => {
const areaList = ref([])
//
const getAreaList = async () => {
try {
const keyword = key;
let url = "";
if (keyword) {
url = `/snow-ops-platform/yhzry/getUserByKey?key=${keyword}`;
} else {
url = `/snow-ops-platform/yhzry/getUserByKey?key=`;
}
const res = await request({
url: url,
method: "GET",
});
if (res.code === "00000") {
return res.data;
url: '/snow-ops-platform/infrastructure-asset/counties',
method: 'get',
})
if (res.code === '00000') {
areaList.value = res.data
} else {
throw new Error(res.message);
throw new Error(res.message)
}
} catch (error) {
ElMessage.error(error.message);
console.log(error);
ElMessage.error(error.message)
console.log(error)
}
};
}
//
const remoteMethod = async (query) => {
if (query === "") {
selectOptions.value = [];
return [];
}
loading.value = true;
const res = await getUserList(query);
if (res) {
selectOptions.value = res;
}
loading.value = false;
};
//
const getQxList = async (key) => {
try {
const keyword = key;
let url = "";
if (keyword) {
url = `/snow-ops-platform/district/listDistricts?qxmc=${keyword}`;
} else {
url = `/snow-ops-platform/district/listDistricts?qxmc=`;
}
const res = await request({
url: url,
method: "GET",
});
if (res.code === "00000") {
return res.data;
} else {
throw new Error(res.message);
}
} catch (error) { }
};
//
const remoteMethod_qx = async (query) => {
loading.value = true;
const res = await getQxList(query);
if (res) {
qxList.value = res;
}
loading.value = false;
};
//
const handleSelect_qx = (value) => {
props.form.qxmc = value;
};
//
const handleSelect = (value) => {
console.log("value", value);
props.form.fzrXm = value.realName;
props.form.fzrSjhm = value.phone;
props.form.fzrUserId = value.userId;
};
onMounted(() => {
getAreaList()
})
const rules = computed(() => {
return {
mc: [
{
required: true,
validator: (rule, value, callback) => {
if (props.form.mc) {
callback();
} else {
callback(new Error("请输入服务站名称"));
}
},
trigger: "blur",
},
],
qxmc: [
{
required: true,
validator: (rule, value, callback) => {
if (props.form.qxmc) {
callback();
} else {
callback(new Error("请选择所属区县"));
}
},
trigger: "blur",
},
],
fzr: [
{
required: true,
validator: (rule, value, callback) => {
if (props.form.fzrUserId && props.form.fzrXm && props.form.fzrSjhm) {
callback();
} else {
callback(new Error("请选择负责人"));
}
},
trigger: "blur",
},
],
jd: [
{
required: sfjwd.value === "否",
validator: (rule, value, callback) => {
if (props.form.jd) {
callback();
} else {
callback(new Error("请输入站点经度"));
}
},
trigger: "blur",
},
],
wd: [
{
required: sfjwd.value === "否",
validator: (rule, value, callback) => {
if (props.form.wd) {
callback();
} else {
callback(new Error("请输入站点纬度"));
}
},
trigger: "blur",
},
],
// mc: [
// {
// required: true,
// validator: (rule, value, callback) => {
// if (props.form.mc) {
// callback();
// } else {
// callback(new Error(""));
// }
// },
// trigger: "blur",
// },
// ],
};
});
</script>
<style scoped>
.form-part{
.form-part {
padding: 20px;
}
.text-center {
text-align: center;
}
</style>

View File

@ -0,0 +1,151 @@
<template>
<div class="detail-container">
<el-form ref="formRef" :model="form" label-position="right" label-width="auto"
style="max-height: 60vh; overflow-y: auto; padding-right: 50px" :rules="rules">
<div class="form-part">
<el-row>
<h4 style="margin: 0 0 20px 0;">气象信息</h4>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="预警标题" prop="预警标题">
<el-input v-model="form.headline" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="预警时间" prop="预警时间">
<el-col :span="11">
<el-date-picker v-model="form.onset" type="date" placeholder="开始时间" style="width: 100%" />
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-date-picker v-model="form.expires" type="date" placeholder="过期时间" style="width: 100%" />
</el-col>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="气象类型" prop="气象类型">
<el-select v-model="form.eventType" clearable placeholder="请选择">
<el-option v-for="(item, index) in [
{ label: '道路结冰', value: '道路结冰' },
{ label: '暴雪', value: '暴雪' },
{ label: '暴雨', value: '暴雨' },
]" :key="index" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="气象来源" prop="气象来源">
<el-select v-model="form.weatherSource" clearable placeholder="请选择">
<el-option v-for="(item, index) in [
{ label: '市级', value: '1' },
{ label: '部级', value: '2' },
{ label: '区县', value: '3' },
]" :key="index" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="预警等级" prop="预警等级">
<el-select v-model="form.urgency" clearable placeholder="请选择">
<el-option v-for="(item, index) in [
{ label: '1级红色预警', value: '1' },
{ label: '2级橙色预警', value: '2' },
{ label: '3级黄色预警', value: '3' },
{ label: '4级蓝色预警', value: '4' },
]" :key="index" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="风险区县" prop="风险区县">
<el-select v-model="form.areaCodes" clearable placeholder="请选择" multiple>
<el-option v-for="(item, index) in areaList" :key="index" :label="item.qxmc" :value="item.xzdm" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</div>
</template>
<script setup>
import { ref, onMounted, watch, computed } from "vue";
import { request } from "@/utils/request";
import FileUpload from '@/component/FileUpload/FileUpload.vue'
const formRef = ref(null);
defineExpose({ formRef });
const props = defineProps({
form: {
type: Object,
default: () => ({}),
},
});
const areaList = ref([])
//
const getAreaList = async () => {
try {
const res = await request({
url: '/snow-ops-platform/infrastructure-asset/counties',
method: 'get',
})
if (res.code === '00000') {
areaList.value = res.data
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error(error.message)
console.log(error)
}
}
onMounted(() => {
getAreaList()
})
const rules = computed(() => {
return {
// mc: [
// {
// required: true,
// validator: (rule, value, callback) => {
// if (props.form.mc) {
// callback();
// } else {
// callback(new Error(""));
// }
// },
// trigger: "blur",
// },
// ],
};
});
</script>
<style scoped>
.form-part {
padding: 20px;
}
.text-center {
text-align: center;
}
</style>

View File

@ -0,0 +1,157 @@
import { h, ref, onMounted, reactive, watch, toRaw, nextTick } from "vue";
import { request } from "@/utils/request";
import { useRoute, useRouter } from 'vue-router'
import AddDialog from "./addDialog.vue";
const tableData = ref([]); // 表格数据
const modelVisible = ref(false); // 弹窗状态
const drawerVisible = ref(false); // 抽屉状态
// 弹窗内容
const model = reactive({
});
const form = reactive({
});
const INIT_FORM = {
};
// 抽屉内容
const drawer = reactive({
title: '',
content: null,
props: {},
onCancel: null,
onConfirm: null,
direction: 'rtl',
size: '50%'
});
const dialogRef = ref(null); // 弹窗实例
const drawerRef = ref(null); // 抽屉实例
const columns = [
{
prop: "userCount",
label: "值班人数",
},
{
prop: "startTime",
label: "开始时间",
},
{
prop: "endTime",
label: "结束时间",
},
{
prop: "createTime",
label: "排班时间",
},
{
prop: "createBy",
label: "排班人",
},
]
// 过滤条件
const filterData = reactive({})
// 分页
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
pageSizes: [10, 20, 50],
layout: "prev, pager, next, jumper",
onChange: (page, pageSize) => {
pagination.current = page;
pagination.pageSize = pageSize;
getTableData(filterData);
},
});
// 获取预警列表
const getTableData = async (filterData = {}) => {
try {
// 过滤空字符串属性
const filteredParams = {};
Object.keys(filterData).forEach(key => {
if (filterData[key] !== '' && filterData[key] != null) {
filteredParams[key] = filterData[key];
}
});
const res = await request({
url: '/snow-ops-platform/law-duty/page',
method: "GET",
params: {
dutyStartTime: filteredParams.timeRange?.[0],
dutyEndTime: filteredParams.timeRange?.[1],
pageNum: pagination.current,
pageSize: pagination.pageSize,
}
})
if (res.code === '00000') {
tableData.value = res.data.records
pagination.total = res.data.total
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('获取排班列表失败');
console.error('获取排班列表失败:', error);
}
}
// 打开发布预警弹窗
const openAddDialog = () => {
model.title = '发布预警';
Object.assign(form, INIT_FORM);
model.props = {
form: form,
};
model.content = AddDialog;
model.onCancel = () => {
modelVisible.value = false;
};
model.onConfirm = async () => {
await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(async () => {
console.log('@@@@@发布预警', form);
// await publishWarning(form)
})
.catch((err) => {
ElMessage.error('请处理表单中的错误项');
});
};
model.width = "50%"
modelVisible.value = true;
}
export default () => {
const router = useRouter();
onMounted(() => {
getTableData();
})
watch(filterData, (val) => {
getTableData(filterData);
}, { deep: true })
return {
tableData,
filterData,
pagination,
columns,
modelVisible,
model,
drawerVisible,
drawer,
dialogRef,
drawerRef,
openAddDialog,
}
}

View File

@ -0,0 +1,54 @@
<template>
<div class="root">
<div class="search-box">
<el-date-picker v-model="script.filterData.timeRange" type="datetimerange" start-placeholder="值班开始时间"
end-placeholder="值班结束时间" style="width: 400px; margin-right: 10px" size="large"
format="YYYY-MM-DD HH:mm:ss" date-format="YYYY/MM/DD" time-format="hh:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss" />
</div>
<div class="event-box">
<el-button type="primary" @click="">立即排班</el-button>
<!-- <el-button type="primary" @click="script.gotoLedgerPage">驻地台账</el-button> -->
<!-- <el-button type="primary" @click="script.openAddDialog">上报项目</el-button> -->
</div>
<DynamicTable :dataSource="script.tableData.value" :columns="script.columns" :autoHeight="true"
:pagination="script.pagination">
</DynamicTable>
<div class="model-box">
<MyDialog v-model="script.modelVisible.value" :title="script.model?.title"
:dynamicComponent="script.model?.content" :component-props="script.model?.props"
:onConfirm="script.model?.onConfirm" :onCancel="script.model?.onCancel" ref="dialogRef"
:width="script.model?.width">
</MyDialog>
<MyDrawer v-model="script.drawerVisible.value" :title="script.drawer?.title"
:dynamicComponent="script.drawer?.content" :component-props="script.drawer?.props"
:onConfirm="script.drawer?.onConfirm" :onCancel="script.drawer?.onCancel" ref="drawerRef"
:direction="script.drawer?.direction" :size="script.drawer?.size">
</MyDrawer>
</div>
</div>
</template>
<script setup>
import DynamicTable from "@/component/DynamicTable/index.js";
import { Search, ArrowDown } from "@element-plus/icons-vue";
import MyDialog from "@/component/MyDialog/index.js";
import MyDrawer from "@/component/MyDrawer/index.js";
import scriptFn from "./index.js";
const script = scriptFn();
const { dialogRef, drawerRef } = script;
</script>
<style scoped>
.root {
height: 100%;
padding: 25px;
}
.event-box {
margin: 20px 0;
display: flex;
justify-content: flex-end;
}
</style>

View File

@ -17,9 +17,22 @@ const model = reactive({
width: '',
});
const form = reactive({
headline: '',
onset: '',
expires: '',
eventType: '',
weatherSource: '',
urgency: '',
areaCodes: [],
});
const INIT_FORM = {
headline: '',
onset: '',
expires: '',
eventType: '',
weatherSource: '',
urgency: '',
areaCodes: [],
};
// 抽屉内容
const drawer = reactive({
@ -67,8 +80,24 @@ const columns = [
label: "预警结束时间",
},
{
prop: "affectedSiteCount",
label: "影响数量",
prop: "affectedRoadSectionCount",
label: "影响路段",
},
{
prop: "affectedBridgeCount",
label: "影响桥梁",
},
{
prop: "affectedTunnelCount",
label: "影响隧道",
},
{
prop: "affectedSlopeCount",
label: "影响边坡",
},
{
prop: "affectedProjectCount",
label: "影响项目",
},
{
prop: "responseStatus",
@ -122,7 +151,7 @@ const getTableData = async (filterData = {}) => {
}
});
const res = await request({
url: '/snow-ops-platform/weatherWarning/response-list',
url: '/snow-ops-platform/lawWeatherWarning/response-list',
method: "GET",
params: {
...filteredParams,
@ -172,9 +201,9 @@ const responseOptions = [
{ label: "全部响应", value: "全部响应" },
]
// 打开填报项目弹窗
// 打开发布预警弹窗
const openAddDialog = () => {
model.title = '填报项目';
model.title = '发布预警';
Object.assign(form, INIT_FORM);
model.props = {
form: form,
@ -184,18 +213,46 @@ const openAddDialog = () => {
modelVisible.value = false;
};
model.onConfirm = async () => {
dialogType.value = '';
await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(() => {
console.log('@@@@@填报项目', form);
await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(async () => {
// console.log('@@@@@发布预警', form);
await publishWarning(form)
})
.catch((err) => {
ElMessage.error('请处理表单中的错误项');
});
};
model.width = "70%"
model.width = "50%"
modelVisible.value = true;
}
// 发布预警
const publishWarning = async (data) => {
try {
const loading = ElLoading.service({
lock: true,
text: '操作中',
background: 'rgba(0, 0, 0, 0.7)',
})
const res = await request({
url: '/snow-ops-platform/lawWeatherWarning/publish',
method: 'POST',
data
})
loading.close();
if (res.code === '00000') {
ElMessage.success('操作成功');
modelVisible.value = false;
getTableData(filterData);
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('操作失败');
console.error('操作失败:', error);
}
}
// 打开详情弹窗
const openDetailDrawer = (row) => {
drawer.title = '预警详情';
@ -206,14 +263,65 @@ const openDetailDrawer = (row) => {
drawerVisible.value = true;
}
// 上传线下帮扶
const uploadFile = async (file) => {
if (!file) {
ElMessage.warning('请选择要上传的文件');
return;
}
try {
// 创建FormData对象
const formData = new FormData();
formData.append('file', file);
const loading = ElLoading.service({
lock: true,
text: '操作中',
background: 'rgba(0, 0, 0, 0.7)',
})
// 发送POST请求
const res = await request({
url: '/snow-ops-platform/fgc-xxbf/upload',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
// 关闭加载提示
loading.close();
if (res.code === '00000') {
ElMessage.success('文件上传成功');
} else {
throw new Error(res.message || '上传失败');
}
} catch (error) {
ElMessage.error(error.message || '文件上传失败');
console.error('文件上传失败:', error);
}
}
export default () => {
const router = useRouter();
// 跳转到台账页面
const gotoLedgerPage = () => {
router.push({
path: '/ledgerManagement2'
path: '/ledgerManagement3'
});
};
// 跳转到值班管理
const gotoDutyPage = () => {
router.push({
path: '/dutyManagement'
});
};
@ -239,6 +347,7 @@ export default () => {
pagination,
columns,
gotoLedgerPage,
gotoDutyPage,
modelVisible,
model,
@ -247,5 +356,7 @@ export default () => {
dialogRef,
drawerRef,
openAddDialog,
uploadFile,
}
}

View File

@ -12,10 +12,13 @@
</div>
<div class="event-box">
<el-button type="primary" @click="">生成报告</el-button>
<el-button type="primary" @click="">发布预警</el-button>
<el-button type="primary" @click="">上传线下帮扶</el-button>
<el-button type="primary" @click="">线下帮扶台账</el-button>
<el-button type="primary" @click="script.openAddDialog">发布预警</el-button>
<el-button type="primary" @click="triggerFileSelect">上传线下帮扶</el-button>
<el-button type="primary" @click="script.gotoLedgerPage">线下帮扶台账</el-button>
<el-button type="primary" @click="">立即排班</el-button>
<el-button type="primary" @click="script.gotoDutyPage">值班管理</el-button>
<el-button type="primary" color="#952DE6" @click="">导出</el-button>
<input type="file" ref="fileInput" style="display: none" @change="handleFileSelect" accept=".*"></input>
<!-- <el-button type="primary" @click="script.gotoLedgerPage">驻地台账</el-button> -->
<!-- <el-button type="primary" @click="script.openAddDialog">上报项目</el-button> -->
</div>
@ -39,12 +42,30 @@
</template>
<script setup>
import { ref } from 'vue';
import DynamicTable from "../../../component/DynamicTable/index.js";
import { Search, ArrowDown } from "@element-plus/icons-vue";
import MyDialog from "../../../component/MyDialog/index.js";
import MyDrawer from "../../../component/MyDrawer/index.js";
import scriptFn from "./index.js";
const script = scriptFn();
const { dialogRef, drawerRef } = script;
const fileInput = ref(null);
//
const triggerFileSelect = () => {
fileInput.value.click();
};
//
const handleFileSelect = (event) => {
const file = event.target.files[0];
if (file) {
script.uploadFile(file);
}
// input
event.target.value = '';
};
</script>
<style scoped>

View File

@ -0,0 +1,100 @@
import { ref } from 'vue'
import { request } from '@shared/utils/request'
// 通用调用接口方法
const getOptionsByApi = async ({ url, params }) => {
const res = await request({
url,
method: 'get',
params,
timeout: 60000
})
if (res?.code === '00000') return res.data
else {
console.error('接口调用失败', res)
return []
}
}
// 公用下拉选项
export function useOptions() {
const options = ref({})
// 是否 布尔类型
options.value['yesNoBool'] = [
{ label: '是', value: true },
{ label: '否', value: false }
]
// 事件类型
options.value['eventType'] = [
{ label: '水毁事件', value: '水毁事件' },
{ label: '冰雪事件', value: '冰雪事件' }
]
// 抢险进度
options.value['repairProgress'] = [
{ label: '未抢险', value: '未抢险' },
{ label: '抢险中', value: '抢险中' }
]
// 处理措施
options.value['disposalMeasures'] = [
{ label: '全幅封闭', value: '全幅封闭' },
{ label: '半幅封闭', value: '半幅封闭' },
{ label: '正常通行', value: '正常通行' },
{ label: '限制通行', value: '限制通行' }
]
// 路线类型
options.value['roadType'] = [
{ label: '国道', value: 'G' },
{ label: '省道', value: 'S' },
{ label: '县道', value: 'X' },
{ label: '乡道', value: 'Y' },
{ label: '村道', value: 'C' }
]
// 路况类型 水灾 冰灾
// 雪灾
options.value['iceRoadConditionType'] = [
{ label: '积雪', value: '积雪' },
{ label: '积冰', value: '积冰' },
]
// 水灾
options.value['waterRoadConditionType'] = [
{ label: '山体滑坡', value: '山体滑坡' },
{ label: '泥石流', value: '泥石流' },
{ label: '边坡坍塌(上、下)', value: '边坡坍塌(上、下)' },
{ label: '路基沉降(垮塌)', value: '路基沉降(垮塌)' },
{ label: '行道树倒塌', value: '行道树倒塌' },
{ label: '积水', value: '积水' },
{ label: '其他', value: '其他' }
]
// 获取区县
const getAreaOptions = async (params) => {
let list = await getOptionsByApi({
url: '/snow-ops-platform/infrastructure-asset/counties',
params: {
xzdm: params?.xzdm // 区县代码 可选
}
})
list = list.filter((item) => {
return !!item.qxmc
})
options.value['area'] = list.map((item) => {
return {
label: item.qxmc,
value: item.xzdm
}
})
}
return {
options,
getAreaOptions,
}
}