This commit is contained in:
fanjia 2026-04-17 14:50:28 +08:00
commit 5e90481aaa
31 changed files with 3271 additions and 1248 deletions

View File

@ -190,7 +190,6 @@ const formData = ref({
district: '', // district: '', //
endStakeNo: '', // endStakeNo: '', //
estimatedRecoveryCost: '', // estimatedRecoveryCost: '', //
inspectionMileage: '', //
isBlocked: '', // isBlocked: '', //
needsRecovery: '', // needsRecovery: '', //
repairProgress: '', // repairProgress: '', //

View File

@ -102,12 +102,6 @@
<span class="info-value">{{ detailData.event?.district || '-' }}</span> <span class="info-value">{{ detailData.event?.district || '-' }}</span>
</div> </div>
<!-- 巡查里程 -->
<div class="info-row">
<span class="info-label">巡查里程</span>
<span class="info-value">{{ detailData.event?.inspectionMileage ? detailData.event.inspectionMileage + '公里' : '-' }}</span>
</div>
<!-- 是否恢复重建 --> <!-- 是否恢复重建 -->
<div class="info-row"> <div class="info-row">
<span class="info-label">是否恢复重建</span> <span class="info-label">是否恢复重建</span>

View File

@ -12,7 +12,6 @@
"district": "武侯区", "district": "武侯区",
"endStakeNo": "K2251+200", "endStakeNo": "K2251+200",
"estimatedRecoveryCost": 120.5, "estimatedRecoveryCost": 120.5,
"inspectionMileage": 25.6,
"isBlocked": true, "isBlocked": true,
"needsRecovery": true, "needsRecovery": true,
"repairProgress": "抢险中", "repairProgress": "抢险中",

View File

@ -8,7 +8,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, watch, ref, provide } from 'vue' import { onMounted, watch, ref, provide, computed } from 'vue'
import DynamicFormItem from './DynamicFormItem.vue' import DynamicFormItem from './DynamicFormItem.vue'
const props = defineProps({ const props = defineProps({
configList: { configList: {

View File

@ -46,7 +46,7 @@ export default () => {
const firstMenuItem = menuList.value[0]?.children?.[0]; const firstMenuItem = menuList.value[0]?.children?.[0];
if (firstMenuItem) { if (firstMenuItem) {
// 注释掉进入页面点击第一项的逻辑 后续记得打开 // 注释掉进入页面点击第一项的逻辑 后续记得打开
// handleMenuClick(firstMenuItem); handleMenuClick(firstMenuItem);
} }
} }
}, { immediate: true }); }, { immediate: true });

View File

@ -0,0 +1,5 @@
import hasPermi from './permission/hasPermi'
export default function directive(app){
app.directive('hasPermi', hasPermi)
}

View File

@ -0,0 +1,27 @@
/**
* v-hasPermi 操作权限处理
* Copyright (c) 2019 ruoyi
*/
import useUserStore from '@flightweb/store/modules/user'
export default {
mounted(el, binding, vnode) {
const { value } = binding
const all_permission = "*:*:*"
const permissions = useUserStore().permissions
if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value
const hasPermissions = permissions.some(permission => {
return all_permission === permission || permissionFlag.includes(permission)
})
if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`请设置操作权限标签值`)
}
}
}

View File

@ -190,6 +190,17 @@ const routes = [
parentRoute: 'warningManagement3' parentRoute: 'warningManagement3'
} }
}, },
{
path: '/messageManagement',
name: 'messageManagement',
component: () => import('../views/WarningManagement/law/messageManagement/index.vue'),
meta: {
title: '消息推送设置',
breadcrumb: true,
parentRoute: 'warningManagement3'
}
},
// 项目管理 - 区县 // 项目管理 - 区县
{ {

View File

@ -0,0 +1,13 @@
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: {
},
getters: {
},
actions: {
}
})

View File

@ -166,7 +166,6 @@ const formData = reactive({
district: '', district: '',
endStakeNo: '', endStakeNo: '',
estimatedRecoveryCost: '', estimatedRecoveryCost: '',
inspectionMileage: '',
isBlocked: '', isBlocked: '',
needsRecovery: '', needsRecovery: '',
repairProgress: '', repairProgress: '',
@ -325,7 +324,6 @@ const resetForm = () => {
district: '', district: '',
endStakeNo: '', endStakeNo: '',
estimatedRecoveryCost: '', estimatedRecoveryCost: '',
inspectionMileage: '',
isBlocked: '', isBlocked: '',
needsRecovery: '', needsRecovery: '',
repairProgress: '', repairProgress: '',

View File

@ -1,9 +1,64 @@
<template> <template>
<div class="web-detail-container"> <div class="web-detail-container">
<div class="content-container"> <div class="content-container">
<div class="left-panel"> <div class="left-panel">
<DynamicDetail v-model="detailData" :config-list="detailConfig" /> <!-- 基本信息卡片 -->
<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">{{}}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">线路编号</span>
<span class="info-value">{{}}</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">冰雪事件</span>
</div>
</el-col>
<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">{{}}</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>
</el-card>
</div> </div>
<!-- <div class="right-panel" v-if="isEdit"> <!-- <div class="right-panel" v-if="isEdit">
<ContinueReport ref="continueReport" @refresh="getDisasterDetail" /> <ContinueReport ref="continueReport" @refresh="getDisasterDetail" />
@ -20,8 +75,6 @@ import { ArrowLeft, Picture, VideoCamera } from '@element-plus/icons-vue'
import ContinueReport from './IceDisasterContinueReportPC.vue' import ContinueReport from './IceDisasterContinueReportPC.vue'
import { request } from '@shared/utils/request' import { request } from '@shared/utils/request'
import FileUpload from '@/component/FileUpload/FileUpload.vue' import FileUpload from '@/component/FileUpload/FileUpload.vue'
import detailConfig from './detailConfig'
import DynamicDetail from '@/component/DynamicDetail/DynamicDetail.vue'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
@ -68,7 +121,6 @@ const hasReportData = computed(() => {
return allReports.value.length > 0 return allReports.value.length > 0
}) })
// //
const getDisasterDetail = async () => { const getDisasterDetail = async () => {
const id = route.query.id const id = route.query.id
@ -80,7 +132,7 @@ const getDisasterDetail = async () => {
try { try {
const result = await request({ const result = await request({
url: `/snow-ops-platform/event/getById?id=${route.query.id}`, url: `/snow-ops-platform/event/getById?id=${route.query.id}`,
method: 'get', method: 'get'
}) })
if (result?.data) { if (result?.data) {

View File

@ -1,111 +0,0 @@
export default [
{
type: 'card',
label: '基础信息',
children: [
{
type: 'row',
children: [
{
span: 8,
label: '事件类型',
value: '冰雪事件',
},
{
span: 8,
label: '路况类别',
prop: 'event.routeNo'
},
{
span: 8,
label: '处理措施',
prop: 'event.disposalMeasures'
}
]
},
{
type: 'row',
children: [
{
span: 8,
label: '地点路线',
prop: 'event.occurLocation'
},
{
span: 8,
label: '起点桩号',
prop: 'event.startStakeNo'
},
{
span: 8,
label: '止点桩号',
prop: 'event.endStakeNo'
}
]
},
{
type: 'row',
children: [
{
span: 8,
label: '路况位置',
prop: 'event.occurLocation'
},
{
span: 8,
label: '阻断点小地名',
prop: 'event.occurLocation'
},
{
span: 8,
label: '地点路线',
prop: 'event.occurLocation'
}
]
},
{
type: 'row',
children: [
{
span: 24,
label: '受灾里程',
prop: 'event.disasterMileage'
}
]
},
{
type: 'row',
children: [
{
span: 8,
label: '所属区县',
prop: 'event.district'
},
{
span: 8,
label: '发现时间',
prop: 'event.occurTime'
}
]
},
{
type: 'row',
children: [
{
span: 8,
label: '是否需要恢复重建',
prop: 'event.actualRecoverTime'
},
{
show: (formData) => {
return formData.event?.actualRecoverTime
},
span: 8,
label: '恢复重建预估费用(万元)',
prop: 'event.estimatedRecoveryCost'
}
]
}
]
}
]

View File

@ -1,83 +1,50 @@
<template> <template>
<div class="disaster-form-page"> <div class="disaster-form-page">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px" class="disaster-form" @submit.prevent> <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" class="disaster-form" @submit.prevent>
<!-- 基本信息区块 -->
<el-card class="form-section" shadow="never"> <el-card class="form-section" shadow="never">
<template #header> <template #header>
<div class="section-header"> <div class="section-header">
<span class="section-title">信息</span> <span class="section-title">信息</span>
</div> </div>
</template> </template>
<BlockItem title="填报人员信息"> <BlockItem title="填报人员信息">
<el-row :gutter="24"> <el-row :gutter="24">
<!-- 填报单位 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="填报单位" prop="event.reporterUnit"> <el-form-item label="填报单位" prop="event.reporterUnit">
<el-input v-model="formData.event.reporterUnit" placeholder="请填写" /> <el-input v-model="formData.event.reporterUnit" disabled />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 联系人 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="联系人员" prop="event.contactPerson"> <el-form-item label="联系人员" prop="event.contactPerson">
<el-input v-model="formData.event.contactPerson" placeholder="请填写" /> <el-input v-model="formData.event.contactPerson" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 联系电话 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="联系电话" prop="event.contactPhone"> <el-form-item label="联系电话" prop="event.contactPhone">
<el-input v-model="formData.event.contactPhone" placeholder="请填写" /> <el-input v-model="formData.event.contactPhone" maxlength="11" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</BlockItem> </BlockItem>
<BlockItem title="路况事件信息"> <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-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="事件类型"> <el-form-item label="事件类型">
<el-select v-model="eventType" placeholder="请选择" style="width: 100%" @change="handleEventTypeChange"> <el-select v-model="eventType" style="width: 100%" @change="handleEventTypeChange">
<el-option v-for="(option, idx) in options['eventType']" :label="option.label" :value="option.value" :key="idx" /> <el-option v-for="(option, idx) in options['eventType']" :key="idx" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 预计恢复时间 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="预计恢复时间" prop="report.expectRecoverTime"> <el-form-item label="填报站点" prop="event.serviceStationId">
<el-date-picker v-model="formData.report.expectRecoverTime" type="datetime" placeholder="请选择时间" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" /> <YHZSelect v-model="formData.event.serviceStationId" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> <el-col :span="8">
<el-row :gutter="24"> </el-row> <el-form-item label="发生时间" prop="occurTime">
<el-row :gutter="24"> <el-date-picker v-model="formData.occurTime" type="datetime" placeholder="请选择日期时间" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" />
<!-- 现场描述 -->
<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-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -85,90 +52,85 @@
<BlockItem title="位置信息"> <BlockItem title="位置信息">
<el-row :gutter="24"> <el-row :gutter="24">
<!-- 路线类型 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="路线类型"> <el-form-item label="路线类型">
<el-select v-model="filterForm.routeType" placeholder="请选择" style="width: 100%"> <el-input :model-value="routeTypeLabel" readonly />
<el-option v-for="(option, idx) in options['roadType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 所属区县 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="所属区县" prop="event.district"> <el-form-item label="所属区县" prop="event.district">
<el-select v-model="formData.event.district" placeholder="请选择" style="width: 100%" @change="handleDistrictChange"> <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-option v-for="(option, idx) in options['area']" :key="idx" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 受灾里程 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="受灾里程" prop="event.disasterMileage"> <el-form-item label="线路编号" prop="routeNo">
<el-input v-model="formData.event.disasterMileage" placeholder="请填写" /> <RoadRoutesSelect v-model="formData.routeNo" :extra-params="{ xzdj: filterForm.routeType, qxid: formData.event.district }" @change="handleRouteNoChange" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="地点路线" prop="routeNo"> <el-form-item label="发生地点" prop="event.occurLocation">
<RoadRoutesSelect v-model="formData.routeNo" @change="handleRouteNoChange" :extra-params="{ xzdj: filterForm.routeType, qxid: formData.event.district }" /> <el-input v-model="formData.event.occurLocation" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 起点桩号 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="起点桩号(K)" prop="event.startStakeNo"> <el-form-item label="路况位置" prop="occurLocation">
<el-input v-model="formData.occurLocation" placeholder="请选择">
<template #suffix>
<el-icon class="location-icon"><LocationFilled /></el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="起点桩号" prop="event.startStakeNo">
<el-input v-model="formData.event.startStakeNo" placeholder="请填写"> <el-input v-model="formData.event.startStakeNo" placeholder="请填写">
<template #prepend>K</template> <template #prepend>K</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 止点桩号 --> </el-row>
<el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="止点桩号(K)" prop="event.endStakeNo"> <el-form-item label="起点桩经度" prop="event.startStakeLongitude">
<el-input v-model="formData.event.startStakeLongitude" placeholder="请填写" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="起点桩纬度" prop="event.startStakeLatitude">
<el-input v-model="formData.event.startStakeLatitude" placeholder="请填写" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="止点桩号" prop="event.endStakeNo">
<el-input v-model="formData.event.endStakeNo" placeholder="请填写"> <el-input v-model="formData.event.endStakeNo" placeholder="请填写">
<template #prepend>K</template> <template #prepend>K</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </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-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="图片上传" prop="fileList"> <el-form-item label="止点桩经度" prop="event.endStakeLongitude">
<FileUpload type="image" :limit="9" v-model="formData.fileList" /> <el-input v-model="formData.event.endStakeLongitude" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="视频上传" prop="fileList"> <el-form-item label="止点桩纬度" prop="event.endStakeLatitude">
<FileUpload type="video" :limit="9" v-model="formData.fileList" /> <el-input v-model="formData.event.endStakeLatitude" placeholder="请填写" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="受灾里程" prop="event.disasterMileage">
<el-input v-model="formData.event.disasterMileage" placeholder="请填写">
<template #append>公里</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -178,440 +140,151 @@
<el-card class="form-section" shadow="never"> <el-card class="form-section" shadow="never">
<template #header> <template #header>
<div class="section-header"> <div class="section-header">
<span class="section-title">灾毁损失</span> <span class="section-title">处置情况</span>
</div> </div>
</template> </template>
<BlockItem title="路况事件信息">
<BlockItem>
<el-row :gutter="24"> <el-row :gutter="24">
<!-- 受伤人员 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="受伤人员" prop="report.injuredCount"> <el-form-item label="处理措施" prop="report.disposalMeasures">
<el-input-number v-model="formData.report.injuredCount" :min="0" :step="1" style="width: 100%"> <el-select v-model="formData.report.disposalMeasures" placeholder="请选择" style="width: 100%">
<template #suffix> <el-option v-for="(option, idx) in options['iceDisposalMeasures']" :key="idx" :label="option.label" :value="option.value" />
<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-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8">
<!-- 恢复重建预估费用 --> <el-form-item label="预计恢复时间" prop="report.expectRecoverTime">
<el-col :span="8" v-if="formData?.event.needsRecovery"> <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 label="恢复重建预估费用" prop="event.estimatedRecoveryCost"> </el-form-item>
<el-input-number v-model="formData.event.estimatedRecoveryCost" :min="0" :precision="2" style="width: 100%" placeholder="请填写"> </el-col>
<template #suffix> <el-col :span="8">
<span class="unit-text">万元</span> <el-form-item label="实际预计恢复时间" prop="report.actualRecoverTime">
</template> <el-date-picker v-model="formData.report.actualRecoverTime" type="datetime" placeholder="请选择日期时间" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" />
</el-input-number> </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>
<MaterialList v-model="formData.yhzMaterialList" :yhzId="formData.event.serviceStationId" />
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="投入人力" prop="material.inputManpower">
<el-input-number v-model="formData.material.inputManpower" :min="0" :step="1" :controls="false" placeholder="请填写" 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="material.inputFunds">
<el-input-number v-model="formData.material.inputFunds" :min="0" :precision="2" :controls="false" placeholder="请填写" 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="material.inputEquipment">
<el-input-number v-model="formData.material.inputEquipment" :min="0" :precision="1" :controls="false" placeholder="请填写" style="width: 100%">
<template #suffix>
<span class="unit-text">台班</span>
</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="有无滞留车辆" prop="traffic.hasStrandedVehicles">
<el-select v-model="formData.traffic.hasStrandedVehicles" placeholder="请选择" style="width: 100%" @change="handleHasStrandedVehiclesChange">
<el-option v-for="option in strandedVehicleOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="滞留车辆" prop="traffic.strandedVehicleCount">
<el-input-number v-model="formData.traffic.strandedVehicleCount" :min="0" :step="1" :controls="false" placeholder="请填写" style="width: 100%">
<template #suffix>
<span class="unit-text"></span>
</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="18">
<el-form-item label="现场情况描述" prop="report.siteDescription">
<el-input v-model="formData.report.siteDescription" type="textarea" :rows="3" placeholder="请填写" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="16">
<el-form-item label="附件上传" prop="fileList">
<div class="upload-wrapper">
<div class="upload-title">图片上传</div>
<FileUpload v-model="formData.fileList" type="image" :limit="9" />
</div>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</BlockItem> </BlockItem>
</el-card> </el-card>
<!-- 提交按钮 -->
<div class="form-actions"> <div class="form-actions">
<el-button @click="handleBack">取消</el-button> <el-button @click="handleBack">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">提交</el-button> <el-button type="primary" :loading="submitting" @click="handleSubmit">提交</el-button>
</div> </div>
</el-form> </el-form>
<!-- 图片预览对话框 -->
<el-dialog v-model="previewDialogVisible" title="图片预览" width="600px">
<img :src="previewImageUrl" style="width: 100%" alt="预览图片" />
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, computed, watch, onMounted } from 'vue' import { LocationFilled } from '@element-plus/icons-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 BlockItem from '@/component/BlockItem.vue'
import FileUpload from '@/component/FileUpload/FileUpload.vue' import FileUpload from '@/component/FileUpload/FileUpload.vue'
import { useOptions } from '@shared/composables/useOptions'
import RoadRoutesSelect from '../components/RoadRoutesSelect.vue' import RoadRoutesSelect from '../components/RoadRoutesSelect.vue'
import YHZSelect from '../components/YHZSelect.vue'
import MaterialList from '../components/MaterialList.vue'
import { useIceDisasterReport } from './useIceDisasterReport'
const router = useRouter() const {
const route = useRoute() eventType,
const { options, getAreaOptions } = useOptions() filterForm,
const formRef = ref(null) formData,
const submitting = ref(false) formRef,
formRules,
handleBack,
handleDistrictChange,
handleEventTypeChange,
handleRouteNoChange,
handleSubmit,
initFormData,
getFormData,
options,
routeTypeLabel,
strandedVehicleOptions,
submitting,
handleHasStrandedVehiclesChange,
validate
} = useIceDisasterReport()
//
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({ defineExpose({
validate, validate,
initFormData, initFormData,
@ -625,74 +298,65 @@ defineExpose({
background-color: #f5f7fa; background-color: #f5f7fa;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
}
.disaster-form { .form-section {
.form-section { margin-bottom: 20px;
margin-bottom: 20px;
:deep(.el-card__header) { :deep(.el-card__header) {
padding: 12px 20px; padding: 12px 20px;
background-color: #fafafa; background-color: #fafafa;
border-bottom: 1px solid #ebeef5; border-bottom: 1px solid #ebeef5;
} }
:deep(.el-card__body) { :deep(.el-card__body) {
padding: 20px; 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;
}
} }
} }
.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;
border-radius: 2px;
background-color: #409eff;
}
}
.location-icon {
color: #909399;
}
.unit-text {
color: #606266;
font-size: 14px;
}
.upload-wrapper {
width: 100%;
}
.upload-title {
margin-bottom: 12px;
color: #303133;
font-size: 14px;
}
.form-actions {
display: flex;
justify-content: center;
gap: 16px;
padding: 8px 0 32px;
}
</style> </style>

View File

@ -0,0 +1,336 @@
import { computed, onMounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { request } from '@/utils/request'
import { useOptions } from '@shared/composables/useOptions'
const DEFAULT_REPORTER_UNIT = '万州区公路中心'
const createDefaultFormData = () => ({
occurLocation: '',
occurTime: '',
routeNo: '',
event: {
contactPerson: '',
contactPhone: '',
disasterMileage: null,
district: '',
endStakeLatitude: null,
endStakeLongitude: null,
endStakeNo: '',
occurLocation: '',
reporterUnit: DEFAULT_REPORTER_UNIT,
serviceStationId: '',
startStakeLatitude: null,
startStakeLongitude: null,
startStakeNo: ''
},
material: {
inputEquipment: null,
inputFunds: null,
inputManpower: null
},
report: {
actualRecoverTime: '',
disposalMeasures: '',
expectRecoverTime: '',
siteDescription: ''
},
traffic: {
hasStrandedVehicles: null,
strandedVehicleCount: null
},
fileList: []
})
const mergeFormData = (source = {}) => {
const defaults = createDefaultFormData()
const merged = {
...defaults,
...source,
event: {
...defaults.event,
...(source.event || {})
},
material: {
...defaults.material,
...(source.material || {})
},
report: {
...defaults.report,
...(source.report || {})
},
traffic: {
...defaults.traffic,
...(source.traffic || {})
}
}
merged.fileList = Array.isArray(source.fileList) ? source.fileList : defaults.fileList
if (!merged.event.reporterUnit) {
merged.event.reporterUnit = DEFAULT_REPORTER_UNIT
}
return merged
}
const parsePointValue = (point) => {
if (!point) {
return { longitude: null, latitude: null }
}
if (Array.isArray(point) && point.length >= 2) {
return {
longitude: point[0] ?? null,
latitude: point[1] ?? null
}
}
if (typeof point === 'string') {
try {
const parsed = JSON.parse(point)
if (Array.isArray(parsed) && parsed.length >= 2) {
return {
longitude: parsed[0] ?? null,
latitude: parsed[1] ?? null
}
}
} catch (_error) {
return { longitude: null, latitude: null }
}
}
return { longitude: null, latitude: null }
}
export const useIceDisasterReport = () => {
const router = useRouter()
const route = useRoute()
const { options, getAreaOptions } = useOptions()
const formRef = ref(null)
const submitting = ref(false)
const eventType = ref('冰雪事件')
const filterForm = reactive({
routeType: ''
})
const formData = reactive(createDefaultFormData())
const strandedVehicleOptions = [
{ label: '有', value: 1 },
{ label: '无', value: 0 }
]
const routeTypeLabel = computed(() => {
const matched = options.value.roadType?.find((item) => item.value === filterForm.routeType)
return matched?.label || '国省道'
})
const formRules = {
'event.contactPerson': [{ required: true, message: '请输入联系人员', trigger: 'blur' }],
'event.contactPhone': [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的联系电话', trigger: 'blur' }
],
'event.serviceStationId': [{ required: true, message: '请选择填报站点', trigger: 'change' }],
occurTime: [{ required: true, message: '请选择发生时间', trigger: 'change' }],
'event.district': [{ required: true, message: '请选择所属区县', trigger: 'change' }],
routeNo: [{ required: true, message: '请选择线路编号', trigger: 'change' }],
'event.occurLocation': [{ required: true, message: '请输入发生地点', trigger: 'blur' }],
occurLocation: [{ required: true, message: '请选择路况位置', trigger: 'blur' }],
'event.startStakeNo': [{ required: true, message: '请输入起点桩号', trigger: 'blur' }],
'event.startStakeLongitude': [{ required: true, message: '请输入起点桩经度', trigger: 'blur' }],
'event.startStakeLatitude': [{ required: true, message: '请输入起点桩纬度', trigger: 'blur' }],
'event.endStakeNo': [{ required: true, message: '请输入止点桩号', trigger: 'blur' }],
'event.endStakeLongitude': [{ required: true, message: '请输入止点桩经度', trigger: 'blur' }],
'event.endStakeLatitude': [{ required: true, message: '请输入止点桩纬度', trigger: 'blur' }],
'event.disasterMileage': [{ required: true, message: '请输入受灾里程', trigger: 'blur' }],
'report.disposalMeasures': [{ required: true, message: '请选择处理措施', trigger: 'change' }],
'report.siteDescription': [{ required: true, message: '请输入现场情况描述', trigger: 'blur' }],
fileList: [
{
validator: (_rule, value, callback) => {
if (!Array.isArray(value) || value.length === 0) {
callback(new Error('请上传附件'))
return
}
callback()
},
trigger: 'change'
}
]
}
const initFormData = (data = {}) => {
Object.assign(formData, mergeFormData(data))
}
const handleEventTypeChange = (value) => {
if (value === '水毁事件') {
router.replace({ path: '/waterDisasterReport' })
}
}
const handleDistrictChange = () => {
formData.routeNo = ''
formData.event.startStakeNo = ''
formData.event.endStakeNo = ''
formData.event.startStakeLongitude = null
formData.event.startStakeLatitude = null
formData.event.endStakeLongitude = null
formData.event.endStakeLatitude = null
}
const handleRouteNoChange = (item = {}) => {
formData.routeNo = item.routeCode || formData.routeNo
formData.event.startStakeNo = item.startStakeNo
formData.event.endStakeNo = item.endStakeNo
const startPoint = parsePointValue(item.startPoint ?? item.startpoint)
const endPoint = parsePointValue(item.endPoint ?? item.endpoint)
formData.event.startStakeLongitude = startPoint.longitude
formData.event.startStakeLatitude = startPoint.latitude
formData.event.endStakeLongitude = endPoint.longitude
formData.event.endStakeLatitude = endPoint.latitude
}
const handleHasStrandedVehiclesChange = (value) => {
if (value !== 1) {
formData.traffic.strandedVehicleCount = null
}
}
const buildSubmitData = () => {
const payload = mergeFormData(formData)
payload.event.routeNo = payload.routeNo
payload.event.occurTime = payload.occurTime
payload.event.eventType = eventType.value
payload.event.roadType = filterForm.routeType
payload.event.roadConditionLocation = payload.occurLocation
if (payload.traffic.hasStrandedVehicles !== 1) {
payload.traffic.strandedVehicleCount = null
}
return payload
}
const getFormData = () => buildSubmitData()
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 res = await request({
url: '/snow-ops-platform/event/addOrUpdate',
method: 'post',
data: buildSubmitData()
})
if (res?.code === '00000') {
ElMessage.success('提交成功')
setTimeout(() => {
router.replace('/disasterManagement')
}, 500)
} else {
ElMessage.error(res?.message || '提交失败')
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('提交失败,请重试')
} finally {
submitting.value = false
}
}
const getDisasterDetail = async () => {
if (!route.query.id) {
return
}
try {
const result = await request({
url: `/snow-ops-platform/event/getById?id=${route.query.id}`,
method: 'get'
})
if (result?.data) {
const data = result.data
initFormData({
occurLocation: data.occurLocation || data.event?.roadConditionLocation || '',
occurTime: data.occurTime || data.event?.occurTime || '',
routeNo: data.routeNo || data.event?.routeNo || '',
event: {
...(data.event || {})
},
material: {
...(data.material || {})
},
report: {
...(data.report || {})
},
traffic: {
...(data.traffic || {})
}
})
} else {
ElMessage.warning(result?.message || '获取详情失败')
}
} catch (error) {
console.error('获取冰雪详情失败:', error)
ElMessage.error('获取详情失败,请稍后重试')
}
}
const handleBack = () => {
router.back()
}
onMounted(async () => {
await getAreaOptions()
if (route.query.id) {
await getDisasterDetail()
return
}
initFormData({})
})
return {
eventType,
filterForm,
formData,
formRef,
formRules,
handleBack,
handleDistrictChange,
handleEventTypeChange,
handleHasStrandedVehiclesChange,
handleRouteNoChange,
handleSubmit,
initFormData,
getFormData,
options,
routeTypeLabel,
strandedVehicleOptions,
submitting,
validate
}
}

View File

@ -166,7 +166,6 @@ const formData = reactive({
district: '', district: '',
endStakeNo: '', endStakeNo: '',
estimatedRecoveryCost: '', estimatedRecoveryCost: '',
inspectionMileage: '',
isBlocked: '', isBlocked: '',
needsRecovery: '', needsRecovery: '',
repairProgress: '', repairProgress: '',
@ -317,7 +316,6 @@ const resetForm = () => {
district: '', district: '',
endStakeNo: '', endStakeNo: '',
estimatedRecoveryCost: '', estimatedRecoveryCost: '',
inspectionMileage: '',
isBlocked: '', isBlocked: '',
needsRecovery: '', needsRecovery: '',
repairProgress: '', repairProgress: '',

View File

@ -68,6 +68,11 @@ const getLossDict = async () => {
} }
} }
const changeValue = (config, event) => {
const item = getValueItem(config)
item.totalAmount = event
}
onMounted(async () => { onMounted(async () => {
await getLossDict() await getLossDict()
}) })

View File

@ -1,32 +1,28 @@
<template> <template>
<div class="disaster-form-page"> <div class="disaster-form-page">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px" class="disaster-form" @submit.prevent> <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" class="disaster-form" @submit.prevent>
<!-- 基本信息区块 -->
<el-card class="form-section" shadow="never"> <el-card class="form-section" shadow="never">
<template #header> <template #header>
<div class="section-header"> <div class="section-header">
<span class="section-title">信息</span> <span class="section-title">信息</span>
</div> </div>
</template> </template>
<BlockItem title="填报人员信息"> <BlockItem title="填报人员信息">
<el-row :gutter="24"> <el-row :gutter="24">
<!-- 填报单位 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="填报单位" prop="event.reporterUnit"> <el-form-item label="填报单位" prop="event.reporterUnit">
<el-input v-model="formData.event.reporterUnit" placeholder="请填写" /> <el-input v-model="formData.event.reporterUnit" disabled />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 联系人 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="联系人员" prop="event.contactPerson"> <el-form-item label="联系人员" prop="event.contactPerson">
<el-input v-model="formData.event.contactPerson" placeholder="请填写" /> <el-input v-model="formData.event.contactPerson" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 联系电话 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="联系电话" prop="event.contactPhone"> <el-form-item label="联系电话" prop="event.contactPhone">
<el-input v-model="formData.event.contactPhone" placeholder="请填写" /> <el-input v-model="formData.event.contactPhone" maxlength="11" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -36,87 +32,60 @@
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="事件类型"> <el-form-item label="事件类型">
<el-select v-model="eventType" placeholder="请选择" style="width: 100%" @change="handleEventTypeChange"> <el-select v-model="eventType" style="width: 100%" @change="handleEventTypeChange">
<el-option v-for="(option, idx) in options['eventType']" :label="option.label" :value="option.value" :key="idx" /> <el-option v-for="(option, idx) in options['eventType']" :key="idx" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 路况类别 --> <el-col :span="8">
<el-form-item label="填报站点" prop="event.serviceStationId">
<YHZSelect v-model="formData.event.serviceStationId" />
</el-form-item>
</el-col>
<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-row>
<el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="路况类别" prop="roadConditionType"> <el-form-item label="路况类别" prop="roadConditionType">
<el-select v-model="formData.roadConditionType" placeholder="请选择" style="width: 100%"> <el-select v-model="formData.roadConditionType" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['waterRoadConditionType']" :label="option.label" :value="option.value" :key="idx" /> <el-option v-for="(option, idx) in options['waterRoadConditionType']" :key="idx" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 是否阻断 -->
<el-col :span="8">
<el-form-item label="是否阻断" prop="event.isBlocked">
<el-select v-model="formData.event.isBlocked" 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-row>
<el-row :gutter="24">
<!-- 抢险进度 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="抢险进度" prop="event.repairProgress"> <el-form-item label="抢险进度" prop="event.repairProgress">
<el-select v-model="formData.event.repairProgress" placeholder="请选择" style="width: 100%"> <el-select v-model="formData.event.repairProgress" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['repairProgress']" :label="option.label" :value="option.value" :key="idx" /> <el-option v-for="(option, idx) in options['repairProgress']" :key="idx" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 处理措施-->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="处理措施" prop="report.disposalMeasures"> <el-form-item label="是否阻断" prop="event.isBlocked">
<el-select v-model="formData.report.disposalMeasures" placeholder="请选择" style="width: 100%"> <el-select v-model="formData.event.isBlocked" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['disposalMeasures']" :label="option.label" :value="option.value" :key="idx" /> <el-option v-for="(option, idx) in options['yesNoBool']" :key="idx" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 水毁处数 --> </el-row>
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="受灾里程" prop="event.blockedMileage">
<el-input v-model="formData.event.blockedMileage" placeholder="请填写">
<template #append>公里</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="水毁处数" prop="event.damageCount"> <el-form-item label="水毁处数" prop="event.damageCount">
<el-input-number v-model="formData.event.damageCount" :min="0" :step="1" style="width: 100%" placeholder="请填写"> <el-input v-model="formData.event.damageCount" placeholder="请填写">
<template #suffix> <template #append></template>
<span class="unit-text"></span> </el-input>
</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<!-- 阻断里程 -->
<el-col :span="8">
<el-form-item label="阻断里程" prop="event.blockedMileage">
<el-input-number v-model="formData.event.blockedMileage" :min="0" :precision="3" style="width: 100%" placeholder="请填写">
<template #suffix>
<span class="unit-text">公里</span>
</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<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="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-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-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -124,84 +93,78 @@
<BlockItem title="位置信息"> <BlockItem title="位置信息">
<el-row :gutter="24"> <el-row :gutter="24">
<!-- 路线类型 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="路线类型"> <el-form-item label="路线类型">
<el-select v-model="filterForm.routeType" placeholder="请选择" style="width: 100%"> <el-input :model-value="routeTypeLabel" readonly />
<el-option v-for="(option, idx) in options['roadType']" :label="option.label" :value="option.value" :key="idx" />
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 所属区县 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="所属区县" prop="event.district"> <el-form-item label="所属区县" prop="event.district">
<el-select v-model="formData.event.district" placeholder="请选择" style="width: 100%" @change="handleDistrictChange"> <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-option v-for="(option, idx) in options['area']" :key="idx" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="地点路线" prop="routeNo"> <el-form-item label="线路编号" prop="routeNo">
<RoadRoutesSelect v-model="formData.routeNo" @change="handleRouteNoChange" :extra-params="{ xzdj: filterForm.routeType, qxid: formData.event.district }" /> <RoadRoutesSelect v-model="formData.routeNo" :extra-params="{ xzdj: filterForm.routeType, qxid: formData.event.district }" @change="handleRouteNoChange" />
</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 #append>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 #append>K</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="24"> <el-row :gutter="24">
<!-- 路况位置 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="路况位置" prop="occurLocation"> <el-form-item label="路况位置" prop="occurLocation">
<el-input v-model="formData.occurLocation" placeholder="请填写" /> <el-input v-model="formData.occurLocation" placeholder="请选择">
<template #suffix>
<el-icon class="location-icon"><LocationFilled /></el-icon>
</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 阻断点小地名 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="阻断点小地名" prop="event.blockedPointName"> <el-form-item label="阻断点小地名" prop="event.blockedPointName">
<el-input v-model="formData.event.blockedPointName" placeholder="请填写" /> <el-input v-model="formData.event.blockedPointName" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
<el-row :gutter="24">
<!-- 经度 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="经度" prop="event.longitude"> <el-form-item label="起点桩号" prop="event.startStakeNo">
<el-input v-model="formData.event.longitude" placeholder="经度"> </el-input> <el-input v-model="formData.event.startStakeNo" placeholder="请填写">
<template #prepend>K</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
<!-- 纬度 --> <el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="纬度" prop="event.latitude"> <el-form-item label="起点桩经度" prop="event.startStakeLongitude">
<el-input v-model="formData.event.latitude" placeholder="纬度"> </el-input> <el-input v-model="formData.event.startStakeLongitude" placeholder="请填写" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="起点桩纬度" prop="event.startStakeLatitude">
<el-input v-model="formData.event.startStakeLatitude" placeholder="请填写" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="止点桩号" prop="event.endStakeNo">
<el-input v-model="formData.event.endStakeNo" placeholder="请填写">
<template #prepend>K</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="图片上传" prop="fileList"> <el-form-item label="止点桩经度" prop="event.endStakeLongitude">
<FileUpload type="image" :limit="9" v-model="formData.fileList" /> <el-input v-model="formData.event.endStakeLongitude" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="视频上传" prop="fileList"> <el-form-item label="止点桩纬度" prop="event.endStakeLatitude">
<FileUpload type="video" :limit="9" v-model="formData.fileList" /> <el-input v-model="formData.event.endStakeLatitude" placeholder="请填写" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -211,12 +174,42 @@
<el-card class="form-section" shadow="never"> <el-card class="form-section" shadow="never">
<template #header> <template #header>
<div class="section-header"> <div class="section-header">
<span class="section-title">灾毁损失</span> <span class="section-title">处置情况</span>
</div> </div>
</template> </template>
<BlockItem title="路况事件信息">
<BlockItem>
<el-row :gutter="24">
<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']" :key="idx" :label="option.label" :value="option.value" />
</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-col :span="8">
<el-form-item label="实际恢复时间" prop="report.actualRecoverTime">
<el-date-picker v-model="formData.report.actualRecoverTime" type="datetime" placeholder="请选择时间" style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" />
</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-row :gutter="24">
<!-- 受伤人员 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="受伤人员" prop="report.injuredCount"> <el-form-item label="受伤人员" prop="report.injuredCount">
<el-input-number v-model="formData.report.injuredCount" :min="0" :step="1" style="width: 100%"> <el-input-number v-model="formData.report.injuredCount" :min="0" :step="1" style="width: 100%">
@ -226,8 +219,6 @@
</el-input-number> </el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 死亡人员 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="死亡人员" prop="report.deadCount"> <el-form-item label="死亡人员" prop="report.deadCount">
<el-input-number v-model="formData.report.deadCount" :min="0" :step="1" style="width: 100%"> <el-input-number v-model="formData.report.deadCount" :min="0" :step="1" style="width: 100%">
@ -237,8 +228,6 @@
</el-input-number> </el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 滞留人员 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="滞留人员" prop="report.strandedPersonCount"> <el-form-item label="滞留人员" prop="report.strandedPersonCount">
<el-input-number v-model="formData.report.strandedPersonCount" :min="0" :step="1" style="width: 100%"> <el-input-number v-model="formData.report.strandedPersonCount" :min="0" :step="1" style="width: 100%">
@ -248,8 +237,9 @@
</el-input-number> </el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
<!-- 损坏车辆 --> <el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="损坏车辆" prop="report.damagedVehicleCount"> <el-form-item label="损坏车辆" prop="report.damagedVehicleCount">
<el-input-number v-model="formData.report.damagedVehicleCount" :min="0" :step="1" style="width: 100%"> <el-input-number v-model="formData.report.damagedVehicleCount" :min="0" :step="1" style="width: 100%">
@ -259,8 +249,6 @@
</el-input-number> </el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 滞留车辆 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="滞留车辆" prop="report.strandedVehicleCount"> <el-form-item label="滞留车辆" prop="report.strandedVehicleCount">
<el-input-number v-model="formData.report.strandedVehicleCount" :min="0" :step="1" style="width: 100%"> <el-input-number v-model="formData.report.strandedVehicleCount" :min="0" :step="1" style="width: 100%">
@ -272,390 +260,110 @@
</el-col> </el-col>
</el-row> </el-row>
</BlockItem> </BlockItem>
<BlockItem title="道路损失及其他">
<LossList v-model:model-value="formData.report.lossList" /> <BlockItem title="灾毁损失">
<LossList v-model:model-value="formData.lossList" />
<el-row :gutter="24"> <el-row :gutter="24">
<!-- 投入机械 -->
<el-col :span="8"> <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="请填写"> <el-input v-model="formData.report.investedMachinery" placeholder="请填写">
<template #suffix> <template #append>/</template>
<span class="unit-text">/</span> </el-input>
</template>
</el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 投入人力 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="投入人力" prop="report.investedManpower"> <el-form-item label="投入人力" prop="report.investedManpower">
<el-input-number v-model="formData.report.investedManpower" :min="0" :step="1" style="width: 100%" placeholder="请填写"> <el-input-number v-model="formData.report.investedManpower" :min="0" :step="1" style="width: 100%">
<template #suffix> <template #suffix>
<span class="unit-text">人次</span> <span class="unit-text">人次</span>
</template> </template>
</el-input-number> </el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 投入资金 -->
<el-col :span="8"> <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="请填写"> <el-input v-model="formData.report.investedFunds" placeholder="请填写">
<template #suffix> <template #append>万元</template>
<span class="unit-text">万元</span> </el-input>
</template> </el-form-item>
</el-input-number> </el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="现场情况描述" prop="report.siteDescription">
<el-input v-model="formData.report.siteDescription" type="textarea" :rows="3" placeholder="请填写" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="16">
<el-form-item label="附件">
<FileUpload v-model="formData.fileList" type="image" :limit="9" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</BlockItem> </BlockItem>
<BlockItem title="恢复重建预估费用"> <BlockItem title="恢复重建预估费用">
<el-row :gutter="24"> <el-row :gutter="24">
<!-- 是否需要恢复重建 -->
<el-col :span="8"> <el-col :span="8">
<el-form-item label="是否需要恢复重建" prop="event.needsRecovery"> <el-form-item label="是否需要恢复重建" prop="event.needsRecovery">
<el-select v-model="formData.event.needsRecovery" placeholder="请选择" style="width: 100%"> <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-option v-for="(option, idx) in options['yesNoBool']" :key="idx" :label="option.label" :value="option.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-if="showEstimatedRecoveryCost" :span="8">
<!-- 恢复重建预估费用 -->
<el-col :span="8" v-if="formData?.event.needsRecovery">
<el-form-item label="恢复重建预估费用" prop="event.estimatedRecoveryCost"> <el-form-item label="恢复重建预估费用" prop="event.estimatedRecoveryCost">
<el-input-number v-model="formData.event.estimatedRecoveryCost" :min="0" :precision="2" style="width: 100%" placeholder="请填写"> <el-input v-model="formData.event.estimatedRecoveryCost" placeholder="请填写">
<template #suffix> <template #append>万元</template>
<span class="unit-text">万元</span> </el-input>
</template>
</el-input-number>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</BlockItem> </BlockItem>
</el-card> </el-card>
<!-- 提交按钮 -->
<div class="form-actions"> <div class="form-actions">
<el-button @click="handleBack">取消</el-button> <el-button @click="handleBack">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitting">提交</el-button> <el-button type="primary" :loading="submitting" @click="handleSubmit">提交</el-button>
</div> </div>
</el-form> </el-form>
<!-- 图片预览对话框 -->
<el-dialog v-model="previewDialogVisible" title="图片预览" width="600px">
<img :src="previewImageUrl" style="width: 100%" alt="预览图片" />
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, computed, watch, onMounted } from 'vue' import { LocationFilled } from '@element-plus/icons-vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Plus, Upload } from '@element-plus/icons-vue'
import mockData from './waterMockJson.json'
import { request } from '@/utils/request'
import LossList from './WaterDisasterLossListPC.vue'
import BlockItem from '@/component/BlockItem.vue' import BlockItem from '@/component/BlockItem.vue'
import FileUpload from '@/component/FileUpload/FileUpload.vue' import FileUpload from '@/component/FileUpload/FileUpload.vue'
import { useOptions } from '@shared/composables/useOptions'
import RoadRoutesSelect from '../components/RoadRoutesSelect.vue' import RoadRoutesSelect from '../components/RoadRoutesSelect.vue'
import YHZSelect from '../components/YHZSelect.vue'
import LossList from './WaterDisasterLossListPC.vue'
import { useWaterDisasterReport } from './useWaterDisasterReport'
const router = useRouter() const {
const route = useRoute() eventType,
const { options, getAreaOptions } = useOptions() filterForm,
const formRef = ref(null) formData,
const submitting = ref(false) formRef,
formRules,
handleBack,
handleDistrictChange,
handleEventTypeChange,
handleRouteNoChange,
handleSubmit,
initFormData,
getFormData,
options,
routeTypeLabel,
showEstimatedRecoveryCost,
submitting,
validate
} = useWaterDisasterReport()
//
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, //
damageCount: null, //
district: null, //
endStakeNo: null, //
estimatedRecoveryCost: null, //
inspectionMileage: null, //
isBlocked: null, //
needsRecovery: null, //
repairProgress: null, //
reporterUnit: null, //
startStakeNo: 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: '/iceDisasterReport' })
}
// 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' }],
'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.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 () => {
if (route.query.mock) {
initFormData(mockData)
} else {
initFormData({})
}
}
onMounted(() => {
//
getAreaOptions()
loadEditData()
})
//
defineExpose({ defineExpose({
validate, validate,
initFormData, initFormData,
@ -669,74 +377,55 @@ defineExpose({
background-color: #f5f7fa; background-color: #f5f7fa;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
}
.disaster-form { .form-section {
.form-section { margin-bottom: 20px;
margin-bottom: 20px;
:deep(.el-card__header) { :deep(.el-card__header) {
padding: 12px 20px; padding: 12px 20px;
background-color: #fafafa; background-color: #fafafa;
border-bottom: 1px solid #ebeef5; border-bottom: 1px solid #ebeef5;
} }
:deep(.el-card__body) { :deep(.el-card__body) {
padding: 20px; 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;
}
} }
} }
.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;
border-radius: 2px;
background-color: #409eff;
}
}
.location-icon {
color: #909399;
}
.unit-text {
color: #909399;
font-size: 12px;
}
.form-actions {
display: flex;
justify-content: center;
gap: 16px;
padding: 8px 0 32px;
}
</style> </style>

View File

@ -0,0 +1,742 @@
<template>
<div class="disaster-form-page">
<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="事件类型">
<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="roadConditionType">
<el-select v-model="formData.roadConditionType" placeholder="请选择" style="width: 100%">
<el-option v-for="(option, idx) in options['waterRoadConditionType']" :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.isBlocked">
<el-select v-model="formData.event.isBlocked" 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-row>
<el-row :gutter="24">
<!-- 抢险进度 -->
<el-col :span="8">
<el-form-item label="抢险进度" prop="event.repairProgress">
<el-select v-model="formData.event.repairProgress" placeholder="请选择" style="width: 100%">
<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="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-col :span="8">
<el-form-item label="水毁处数" prop="event.damageCount">
<el-input-number v-model="formData.event.damageCount" :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-row>
<el-row :gutter="24">
<!-- 阻断里程 -->
<el-col :span="8">
<el-form-item label="阻断里程" prop="event.blockedMileage">
<el-input-number v-model="formData.event.blockedMileage" :min="0" :precision="3" style="width: 100%" placeholder="请填写">
<template #suffix>
<span class="unit-text">公里</span>
</template>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<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="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-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-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 #append>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 #append>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 mockData from './waterMockJson.json'
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)
//
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, //
damageCount: null, //
district: null, //
endStakeNo: null, //
estimatedRecoveryCost: null, //
inspectionMileage: null, //
isBlocked: null, //
needsRecovery: null, //
repairProgress: null, //
reporterUnit: null, //
startStakeNo: 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: '/iceDisasterReport' })
}
// 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' }],
'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.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 () => {
if (route.query.mock) {
initFormData(mockData)
} else {
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

@ -0,0 +1,292 @@
import { computed, onMounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { request } from '@/utils/request'
import { useOptions } from '@shared/composables/useOptions'
import mockData from './waterMockJson.json'
const DEFAULT_REPORTER_UNIT = '万州区公路中心'
const createDefaultFormData = () => ({
occurLocation: '',
occurTime: '',
roadConditionType: '',
routeNo: '',
event: {
blockedMileage: null,
blockedPointName: '',
contactPerson: '',
contactPhone: '',
damageCount: null,
district: '',
endStakeLatitude: null,
endStakeLongitude: null,
endStakeNo: '',
estimatedRecoveryCost: null,
isBlocked: null,
latitude: null,
longitude: null,
needsRecovery: null,
repairProgress: '抢险中',
reportSite: '',
reporterUnit: DEFAULT_REPORTER_UNIT,
serviceStationId: '',
startStakeLatitude: null,
startStakeLongitude: null,
startStakeNo: ''
},
report: {
actualRecoverTime: '',
damagedVehicleCount: null,
deadCount: null,
disposalMeasures: '',
expectRecoverTime: '',
injuredCount: null,
investedFunds: null,
investedMachinery: null,
investedManpower: null,
remark: '',
siteDescription: '',
strandedPersonCount: null,
strandedVehicleCount: null,
totalLossAmount: null
},
lossList: [],
fileList: []
})
const mergeFormData = (source = {}) => {
const defaults = createDefaultFormData()
const merged = {
...defaults,
...source,
event: {
...defaults.event,
...(source.event || {})
},
report: {
...defaults.report,
...(source.report || {})
},
lossList: Array.isArray(source.lossList) ? source.lossList : defaults.lossList,
fileList: Array.isArray(source.fileList) ? source.fileList : defaults.fileList
}
if (!merged.event.reporterUnit) {
merged.event.reporterUnit = DEFAULT_REPORTER_UNIT
}
if (!merged.event.repairProgress) {
merged.event.repairProgress = '抢险中'
}
if (!merged.event.serviceStationId && merged.event.reportSite) {
merged.event.serviceStationId = merged.event.reportSite
}
return merged
}
export const useWaterDisasterReport = () => {
const router = useRouter()
const route = useRoute()
const { options, getAreaOptions } = useOptions()
const formRef = ref(null)
const submitting = ref(false)
const eventType = ref('水毁事件')
const filterForm = reactive({
routeType: ''
})
const formData = reactive(createDefaultFormData())
const routeTypeLabel = computed(() => {
const matched = options.value.roadType?.find((item) => item.value === filterForm.routeType)
return matched?.label || '国省道'
})
const showEstimatedRecoveryCost = computed(() => formData.event.needsRecovery === true)
const formRules = {
'event.contactPerson': [{ required: true, message: '请输入联系人员', trigger: 'blur' }],
'event.contactPhone': [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的联系电话', trigger: 'blur' }
],
'event.serviceStationId': [{ required: true, message: '请选择填报站点', trigger: 'change' }],
occurTime: [{ required: true, message: '请选择发生时间', trigger: 'change' }],
roadConditionType: [{ required: true, message: '请选择路况类别', trigger: 'change' }],
'event.repairProgress': [{ required: true, message: '请选择抢险进度', trigger: 'change' }],
'event.isBlocked': [{ required: true, message: '请选择是否阻断', trigger: 'change' }],
'event.blockedMileage': [{ required: true, message: '请输入受灾里程', trigger: 'blur' }],
'event.damageCount': [{ required: true, message: '请输入水毁处数', trigger: 'blur' }],
'event.district': [{ required: true, message: '请选择所属区县', trigger: 'change' }],
routeNo: [{ required: true, message: '请选择线路编号', trigger: 'change' }],
occurLocation: [{ required: true, message: '请输入路况位置', trigger: 'blur' }],
'event.blockedPointName': [{ required: true, message: '请输入阻断点小地名', trigger: 'blur' }],
'event.startStakeNo': [{ required: true, message: '请输入起点桩号', trigger: 'blur' }],
'event.startStakeLongitude': [{ required: true, message: '请输入起点桩经度', trigger: 'blur' }],
'event.startStakeLatitude': [{ required: true, message: '请输入起点桩纬度', trigger: 'blur' }],
'event.endStakeNo': [{ required: true, message: '请输入止点桩号', trigger: 'blur' }],
'event.endStakeLongitude': [{ required: true, message: '请输入止点桩经度', trigger: 'blur' }],
'event.endStakeLatitude': [{ required: true, message: '请输入止点桩纬度', trigger: 'blur' }],
'report.disposalMeasures': [{ required: true, message: '请选择处置措施', trigger: 'change' }],
'report.expectRecoverTime': [{ required: true, message: '请选择预计恢复时间', trigger: 'change' }],
'event.needsRecovery': [{ required: true, message: '请选择是否需要恢复重建', trigger: 'change' }],
'event.estimatedRecoveryCost': [
{
validator: (_rule, value, callback) => {
if (showEstimatedRecoveryCost.value && (value === '' || value === null || value === undefined)) {
callback(new Error('请输入恢复重建预估费用'))
return
}
callback()
},
trigger: 'blur'
}
]
}
const initFormData = (data = {}) => {
Object.assign(formData, mergeFormData(data))
}
const handleEventTypeChange = (value) => {
if (value === '冰雪事件') {
router.replace({ path: '/iceDisasterReport' })
}
}
const handleDistrictChange = () => {
// formData.routeNo = ''
// formData.occurLocation = ''
// formData.event.startStakeNo = ''
// formData.event.endStakeNo = ''
}
const parsePointValue = (point) => {
if (!point) {
return { longitude: null, latitude: null }
}
if (Array.isArray(point) && point.length >= 2) {
return {
longitude: point[0] ?? null,
latitude: point[1] ?? null
}
}
if (typeof point === 'string') {
try {
const parsed = JSON.parse(point)
if (Array.isArray(parsed) && parsed.length >= 2) {
return {
longitude: parsed[0] ?? null,
latitude: parsed[1] ?? null
}
}
} catch (_error) {
return { longitude: null, latitude: null }
}
}
return { longitude: null, latitude: null }
}
const handleRouteNoChange = (item = {}) => {
formData.routeNo = item.routeCode
formData.event.startStakeNo = item.startStakeNo
formData.event.endStakeNo = item.endStakeNo
const startPoint = parsePointValue(item.startPoint)
const endPoint = parsePointValue(item.endPoint)
formData.event.startStakeLongitude = startPoint.longitude
formData.event.startStakeLatitude = startPoint.latitude
formData.event.endStakeLongitude = endPoint.longitude
formData.event.endStakeLatitude = endPoint.latitude
}
const buildSubmitData = () => {
const payload = mergeFormData(formData)
if (!payload.event.reportSite && payload.event.serviceStationId) {
payload.event.reportSite = payload.event.serviceStationId
}
return payload
}
const getFormData = () => buildSubmitData()
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 res = await request({
url: '/snow-ops-platform/water-damage/addOrUpdate',
method: 'post',
data: buildSubmitData()
})
if (res?.code === '00000') {
ElMessage.success('提交成功')
setTimeout(() => {
router.replace('/disasterManagement')
}, 500)
} else {
ElMessage.error(res?.message || '提交失败')
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('提交失败,请重试')
} finally {
submitting.value = false
}
}
const handleBack = () => {
router.back()
}
onMounted(async () => {
await getAreaOptions()
initFormData(route.query.mock ? mockData : {})
})
return {
eventType,
filterForm,
formData,
formRef,
formRules,
handleBack,
handleDistrictChange,
handleEventTypeChange,
handleRouteNoChange,
handleSubmit,
initFormData,
getFormData,
options,
routeTypeLabel,
showEstimatedRecoveryCost,
submitting,
validate
}
}

View File

@ -12,7 +12,6 @@
"district": "武侯区", "district": "武侯区",
"endStakeNo": "K2251+200", "endStakeNo": "K2251+200",
"estimatedRecoveryCost": 120.5, "estimatedRecoveryCost": 120.5,
"inspectionMileage": 25.6,
"isBlocked": true, "isBlocked": true,
"needsRecovery": true, "needsRecovery": true,
"repairProgress": "抢险中", "repairProgress": "抢险中",

View File

@ -0,0 +1,170 @@
<template>
<el-row class="material-list-pc" :gutter="24">
<template v-for="(item, index) in configs" :key="index">
<el-col :span="colSpan">
<el-form-item :label="item.wzmc">
<el-input :modelValue="getValue(item)" :placeholder="`余额: ${item.ye} `" @update:modelValue="(event) => changeValue(item, event)">
<template #suffix>
<span>{{ item.dw }}</span>
</template>
</el-input>
</el-form-item>
</el-col>
</template>
</el-row>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Delete, Plus } from '@element-plus/icons-vue'
import { request } from '@shared/utils/request'
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
yhzId: {
type: [Number, null, String],
default: null
},
modelValue: {
type: Array,
default: () => []
},
colSpan: {
type: Number,
default: 8
}
})
watch(()=> props.yhzId, ()=> {
emit('update:modelValue', [])
getDict()
})
const getValue = (config) => {
const item = getValueItem(config)
return item?.usageAmount || 0
}
const getValueItem = (config) => {
let item = props.modelValue.find((v) => v.yhzid === config.yhzid)
if (item == null) {
item = { ...config }
props.modelValue.push(item)
}
return item
}
const changeValue = (item, value) => {
const index = props.modelValue.findIndex((v) => v.yhzid === item.yhzid)
if (index > -1) {
props.modelValue[index].usageAmount = value
}
}
const configs = ref([])
//
const getDict = async () => {
if(!props.yhzId) return []
try {
const res = await request({
url: `/snow-ops-platform/yjwz/list?yhzid=${props.yhzId}&pageNum=1&pageSize=9999`,
method: 'get'
})
configs.value = res.data?.records
} catch (error) {
console.error('获取物质列表失败:', error)
}
}
</script>
<style scoped lang="scss">
.material-list-pc {
width: 100%;
.loss-table {
margin-bottom: 16px;
:deep(.el-table) {
.amount-cell {
display: flex;
align-items: center;
gap: 8px;
.unit-text {
color: #909399;
font-size: 14px;
white-space: nowrap;
}
}
}
}
.add-button-wrapper {
display: flex;
justify-content: flex-start;
}
.calculate-form {
padding: 8px 0;
:deep(.el-form-item) {
margin-bottom: 20px;
}
}
.calculation-preview {
background-color: #f5f7fa;
border-radius: 8px;
padding: 16px;
margin-top: 16px;
.preview-item {
display: flex;
justify-content: space-between;
align-items: center;
.preview-label {
color: #606266;
font-size: 14px;
}
.preview-value {
color: #f56c6c;
font-size: 16px;
font-weight: 500;
}
}
}
.loss-picker-content {
max-height: 400px;
overflow-y: auto;
padding: 8px 0;
.loss-radio-group {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
.loss-radio-item {
margin: 0;
padding: 10px 12px;
border-radius: 6px;
width: 100%;
box-sizing: border-box;
:deep(.el-radio__label) {
width: calc(100% - 22px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
</style>

View File

@ -6,7 +6,7 @@
</template> </template>
</el-input> </el-input>
<el-dialog v-model="dialogVisible" :title="dialogTitle" :width="dialogWidth" :append-to-body="true" :modal="true" @close="handleDialogClose"> <el-dialog v-model="dialogVisible" :header="dialogTitle" :width="dialogWidth" :append-to-body="true" :modal="true" @close="handleDialogClose">
<div class="dialog-content"> <div class="dialog-content">
<div class="title">已选择地点路线{{ tempSelectedItem?.routeCode }}</div> <div class="title">已选择地点路线{{ tempSelectedItem?.routeCode }}</div>

View File

@ -0,0 +1,253 @@
<template>
<div class="selector-component">
<el-input :modelValue="tempSelectedItem?.mc" :placeholder="placeholder" readonly @click="openDialog">
<template #suffix>
<el-icon><ArrowDown /></el-icon>
</template>
</el-input>
<el-dialog v-model="dialogVisible" :header="dialogTitle" :width="dialogWidth" :append-to-body="true" :modal="true" @close="handleDialogClose">
<div class="dialog-content">
<div class="title">已选择养护站{{ tempSelectedItem?.mc }}</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="mc" label="服务站名称" />
<el-table-column prop="qxmc" 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/yhz/list'
},
//
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?.id) {
tempSelectedItem.value = null
}
})
// ==================== API ====================
const fetchData = async () => {
loading.value = true
try {
const params = {
pageNum: currentPage.value,
pageSize: pageSize.value,
}
//
if (searchKeyword.value) {
params.mc = 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?.mc || ''
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.id
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

@ -1,7 +1,7 @@
<template> <template>
<div class="detail-container"> <div class="detail-container">
<el-form ref="formRef" :model="form" label-position="right" label-width="auto" <el-form ref="formRef" :model="form" label-position="right" label-width="auto"
style="max-height: 60vh; overflow-y: auto; padding-right: 50px" :rules="rules"> style="max-height: 60vh; overflow-y: hidden; padding-right: 50px" :rules="rules">
<el-form-item label="" prop="schedules"> <el-form-item label="" prop="schedules">
<div class="tree-transfer-container"> <div class="tree-transfer-container">
@ -11,24 +11,45 @@
children: 'children', children: 'children',
label: 'orgName' label: 'orgName'
}" node-key="orgId" :expand-on-click-node="false" :default-expand-all="false" highlight-current }" node-key="orgId" :expand-on-click-node="false" :default-expand-all="false" highlight-current
style="height: 400px; overflow-y: auto;" style="height: 100%; overflow-y: auto;" @node-click="handleNodeClick">
@node-click="handleNodeClick"> <template #default="{ node: _node }">
<template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<span>{{ node.label }}</span> <span>{{ _node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</div> </div>
<!-- 中间空白区域 --> <!-- 中间用户列表 -->
<div class="middle-panel"> <div class="middle-panel">
<!-- 暂留空后续使用 --> <div class="panel-title">用户列表</div>
<div class="user-list">
<div v-for="user in currentUsers" :key="user.userId" class="user-item"
:class="{ 'selected': isUserSelected(user) }" @click="toggleUserSelection(user)">
<span class="user-name">{{ user.realName || user.nickName || user.account }}</span>
<span class="user-position">{{ user.positionName }}</span>
</div>
</div>
</div> </div>
<!-- 右侧空白区域 --> <!-- 右侧已选择用户 -->
<div class="right-panel"> <div class="right-panel">
<!-- 暂留空后续使用 --> <div class="panel-title">已选择用户 ({{ selectedUsers.length }})</div>
<div class="selected-user-list">
<div v-for="user in selectedUsers" :key="user.userId" class="selected-user-item">
<div class="user-info">
<span class="user-name">{{ user.realName || user.nickName || user.account }}</span>
<span class="user-position">{{ user.positionName }}</span>
</div>
<div class="time-picker-container">
<el-date-picker v-model="user.timeRange" type="datetimerange" range-separator=""
start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss" size="small" :shortcuts="dateShortcuts"
@change="handleTimeChange(user)" />
</div>
<el-button type="danger" size="small" icon="Delete" circle @click.stop="removeSelectedUser(user)" />
</div>
</div>
</div> </div>
</div> </div>
</el-form-item> </el-form-item>
@ -38,13 +59,31 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted, watch } from "vue";
import { request } from "@/utils/request"; import { request } from "@/utils/request";
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
const formRef = ref(null); const formRef = ref(null);
defineExpose({ formRef }); //
const getFormattedSelectedUsers = () => {
return selectedUsers.value.map(user => ({
userId: user.userId,
userName: user.realName || user.nickName || user.account,
orgId: user.orgId,
startTime: user.timeRange ? user.timeRange[0] : '',
endTime: user.timeRange ? user.timeRange[1] : ''
}));
}
//
const currentUsers = ref([]);
//
const selectedUsers = ref([]);
defineExpose({
formRef,
getFormattedSelectedUsers
});
const props = defineProps({ const props = defineProps({
form: { form: {
type: Object, type: Object,
@ -52,6 +91,13 @@ const props = defineProps({
}, },
}); });
// form
watch(selectedUsers, (newSelectedUsers) => {
// form
props.form.schedules = getFormattedSelectedUsers();
}, { deep: true, immediate: true });
const rules = computed(() => { const rules = computed(() => {
@ -122,14 +168,14 @@ const buildOrgTree = (orgList) => {
// ID-1 // ID-1
const parentIds = pidMatches.filter(id => id !== '-1'); const parentIds = pidMatches.filter(id => id !== '-1');
if (parentIds.length > 0) { if (parentIds.length > 0) {
parentId = parseInt(parentIds[parentIds.length - 1]); parentId = parentIds[parentIds.length - 1]; //
} }
} }
} }
// 2orgPids使orgParentId // 2orgPids使orgParentId
if (parentId === null && org.orgParentId && org.orgParentId !== -1) { if (parentId === null && org.orgParentId && org.orgParentId !== -1) {
parentId = org.orgParentId; parentId = String(org.orgParentId);
} }
// children // children
@ -156,15 +202,66 @@ const buildOrgTree = (orgList) => {
return sortTree(tree); return sortTree(tree);
} }
// //
const handleNodeClick = (data, node) => { const isUserSelected = (user) => {
// return selectedUsers.value.some(selected => selected.userId === user.userId);
if (!node.childNodes || node.childNodes.length === 0) { };
//
getUsersByOrgId(data.orgId); //
const toggleUserSelection = (user) => {
if (isUserSelected(user)) {
removeSelectedUser(user);
} else {
// orgId
selectedUsers.value.push({
...user,
orgId: user.orgId, // orgId
timeRange: null
});
} }
};
//
const removeSelectedUser = (user) => {
const index = selectedUsers.value.findIndex(selected => selected.userId === user.userId);
if (index > -1) {
selectedUsers.value.splice(index, 1);
}
};
//
const handleNodeClick = async (_data) => {
//
await getUsersByOrgId(_data.orgId);
} }
//
const dateShortcuts = [
{
text: '今天',
value: [new Date(), new Date()],
},
{
text: '明天',
value: [
new Date(new Date().getTime() + 86400000),
new Date(new Date().getTime() + 86400000)
],
},
{
text: '最近一周',
value: [new Date(), new Date(new Date().getTime() + 604800000)],
},
];
//
const handleTimeChange = (user) => {
//
console.log('时间范围变化:', user.userId, user.timeRange);
};
// ID // ID
const getUsersByOrgId = async (orgId) => { const getUsersByOrgId = async (orgId) => {
try { try {
@ -176,7 +273,8 @@ const getUsersByOrgId = async (orgId) => {
} }
}) })
if (res.code === '00000') { if (res.code === '00000') {
console.log('@@@@@@用户列表',res.data); currentUsers.value = res.data || [];
// console.log('@@@@@', res.data);
} else { } else {
throw new Error(res.message) throw new Error(res.message)
} }
@ -205,6 +303,7 @@ onMounted(() => {
display: flex; display: flex;
gap: 20px; gap: 20px;
width: 100%; width: 100%;
height: 60vh;
} }
.tree-panel { .tree-panel {
@ -213,10 +312,7 @@ onMounted(() => {
border-radius: 4px; border-radius: 4px;
padding: 10px; padding: 10px;
background-color: #fff; background-color: #fff;
} height: 100%;
.empty-panel {
} }
.middle-panel { .middle-panel {
@ -225,6 +321,9 @@ onMounted(() => {
border-radius: 4px; border-radius: 4px;
padding: 10px; padding: 10px;
background-color: #fff; background-color: #fff;
display: flex;
flex-direction: column;
height: 100%;
} }
.right-panel { .right-panel {
@ -233,6 +332,99 @@ onMounted(() => {
border-radius: 4px; border-radius: 4px;
padding: 10px; padding: 10px;
background-color: #fff; background-color: #fff;
display: flex;
flex-direction: column;
height: 100%;
}
.panel-title {
font-weight: bold;
font-size: 14px;
color: #303133;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.user-list {
flex: 1;
overflow-y: auto;
}
.user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 4px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
border: 1px solid transparent;
}
.user-item:hover {
background-color: #f5f7fa;
}
.user-item.selected {
background-color: #409eff;
color: white;
border-color: #409eff;
}
.user-name {
font-size: 14px;
font-weight: 500;
}
.user-position {
font-size: 12px;
opacity: 0.7;
}
.selected-user-list {
flex: 1;
overflow-y: auto;
}
.selected-user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 4px;
border-radius: 4px;
background-color: #f0f9ff;
border: 1px solid #e1f5fe;
gap: 12px;
}
.time-picker-container {
flex: 1.2;
min-width: 280px;
}
.time-picker-container :deep(.el-date-editor) {
width: 100%;
}
.user-info {
display: flex;
flex-direction: column;
min-width: 120px;
flex: 0.8;
}
.selected-user-item .user-name {
font-size: 14px;
font-weight: 500;
color: #303133;
}
.selected-user-item .user-position {
font-size: 12px;
color: #606266;
} }
.custom-tree-node { .custom-tree-node {

View File

@ -68,7 +68,7 @@ const pagination = reactive({
}, },
}); });
// 获取预警列表 // 获取值班列表
const getTableData = async (filterData = {}) => { const getTableData = async (filterData = {}) => {
try { try {
// 过滤空字符串属性 // 过滤空字符串属性
@ -100,7 +100,28 @@ const getTableData = async (filterData = {}) => {
} }
} }
// 打开发布预警弹窗 // 排班提交
const addSchedule = async (form) => {
try {
const res = await request({
url: '/snow-ops-platform/law-duty/schedule',
method: "POST",
data: form
})
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 openAddDialog = () => { const openAddDialog = () => {
model.title = '立即排班'; model.title = '立即排班';
Object.assign(form, INIT_FORM); Object.assign(form, INIT_FORM);
@ -113,7 +134,7 @@ const openAddDialog = () => {
}; };
model.onConfirm = async () => { model.onConfirm = async () => {
await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(async () => { await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(async () => {
console.log('@@@@@立即排班', form); await addSchedule(form)
// await publishWarning(form) // await publishWarning(form)
}) })
.catch((err) => { .catch((err) => {

View File

@ -2,6 +2,7 @@ import { h, ref, onMounted, reactive, watch, toRaw, nextTick } from "vue";
import { request } from "@/utils/request"; import { request } from "@/utils/request";
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import AddDialog from "./addDialog.vue"; import AddDialog from "./addDialog.vue";
import ScheduleDiaog from "../law/dutyManagement/addDialog.vue"
import DetailDrawer from "./detailDrawer.vue"; import DetailDrawer from "./detailDrawer.vue";
const tableData = ref([]); // 表格数据 const tableData = ref([]); // 表格数据
@ -305,6 +306,53 @@ const uploadFile = async (file) => {
} }
} }
const form2 = reactive({});
const INIT_FORM2 = {};
// 排班提交
const addSchedule = async (form) => {
try {
const res = await request({
url: '/snow-ops-platform/law-duty/schedule',
method: "POST",
data: form
})
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 openScheduleDiaog = () => {
model.title = '立即排班';
Object.assign(form2, INIT_FORM2);
model.props = {
form: form2,
};
model.content = ScheduleDiaog;
model.onCancel = () => {
modelVisible.value = false;
};
model.onConfirm = async () => {
await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(async () => {
await addSchedule(form)
// await publishWarning(form)
})
.catch((err) => {
ElMessage.error('请处理表单中的错误项');
});
};
model.width = "70%"
modelVisible.value = true;
}
@ -324,6 +372,12 @@ export default () => {
path: '/dutyManagement' path: '/dutyManagement'
}); });
}; };
// 跳转到消息推送设置
const gotoMessagePage = () => {
router.push({
path: '/messageManagement'
});
}
onMounted(() => { onMounted(() => {
getTableData(); getTableData();
@ -348,6 +402,7 @@ export default () => {
columns, columns,
gotoLedgerPage, gotoLedgerPage,
gotoDutyPage, gotoDutyPage,
gotoMessagePage,
modelVisible, modelVisible,
model, model,
@ -356,6 +411,7 @@ export default () => {
dialogRef, dialogRef,
drawerRef, drawerRef,
openAddDialog, openAddDialog,
openScheduleDiaog,
uploadFile, uploadFile,
} }

View File

@ -15,8 +15,9 @@
<el-button type="primary" @click="script.openAddDialog">发布预警</el-button> <el-button type="primary" @click="script.openAddDialog">发布预警</el-button>
<el-button type="primary" @click="triggerFileSelect">上传线下帮扶</el-button> <el-button type="primary" @click="triggerFileSelect">上传线下帮扶</el-button>
<el-button type="primary" @click="script.gotoLedgerPage">线下帮扶台账</el-button> <el-button type="primary" @click="script.gotoLedgerPage">线下帮扶台账</el-button>
<el-button type="primary" @click="">立即排班</el-button> <el-button type="primary" @click="script.openScheduleDiaog">立即排班</el-button>
<el-button type="primary" @click="script.gotoDutyPage">值班管理</el-button> <el-button type="primary" @click="script.gotoDutyPage">值班管理</el-button>
<el-button type="primary" @click="script.gotoMessagePage">消息推送设置</el-button>
<el-button type="primary" color="#952DE6" @click="">导出</el-button> <el-button type="primary" color="#952DE6" @click="">导出</el-button>
<input type="file" ref="fileInput" style="display: none" @change="handleFileSelect" accept=".*"></input> <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.gotoLedgerPage">驻地台账</el-button> -->

View File

@ -0,0 +1,267 @@
<template>
<div class="detail-container">
<el-form ref="formRef" :model="form" label-position="right" label-width="auto"
style="max-height: 60vh; overflow-y: hidden; padding-right: 50px" :rules="rules">
<el-form-item label="" prop="schedules">
<div class="user-select-container">
<!-- 左侧用户列表 -->
<div class="user-list-panel">
<div class="panel-title">用户列表</div>
<div class="user-list">
<div v-for="user in currentUsers" :key="user.userId" class="user-item"
:class="{ 'selected': isUserSelected(user) }" @click="toggleUserSelection(user)">
<span class="user-name">{{ user.realName || user.nickName || user.account }}</span>
<span class="user-position">{{ user.positionName }}</span>
</div>
</div>
</div>
<!-- 右侧已选择用户 -->
<div class="selected-panel">
<div class="panel-title">已选择用户 ({{ selectedUsers.length }})</div>
<div class="selected-user-list">
<div v-for="user in selectedUsers" :key="user.userId" class="selected-user-item">
<div class="user-info">
<span class="user-name">{{ user.realName || user.nickName || user.account }}</span>
<span class="user-position">{{ user.positionName }}</span>
</div>
<el-button type="danger" size="small" icon="Delete" circle @click.stop="removeSelectedUser(user)" />
</div>
</div>
</div>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from "vue";
import { request } from "@/utils/request";
import { ElMessage } from 'element-plus';
const formRef = ref(null);
//
const currentUsers = ref([]);
//
const selectedUsers = ref([]);
defineExpose({
formRef,
});
const props = defineProps({
form: {
type: Object,
default: () => ({}),
},
orgId: {
type: String,
default: ''
},
orgName: {
type: String,
default: ''
}
});
const rules = computed(() => {
return {
};
});
//
const isUserSelected = (user) => {
return selectedUsers.value.some(selected => selected.userId === user.userId);
};
//
const toggleUserSelection = (user) => {
if (isUserSelected(user)) {
removeSelectedUser(user);
} else {
// orgId
selectedUsers.value.push({
// ...user,
// orgId: user.orgId, // orgId
// timeRange: null
...user,
orgId: user.orgId,
orgName: props.orgName,
userId: user.userId,
userAccount: user.account,
userPhone: user.phone
});
}
};
//
const removeSelectedUser = (user) => {
const index = selectedUsers.value.findIndex(selected => selected.userId === user.userId);
if (index > -1) {
selectedUsers.value.splice(index, 1);
}
};
// ID
const getUsersByOrgId = async (orgId) => {
// console.log('@@@@@', orgId);
try {
const res = await request({
url: '/snow-ops-platform/user/orgUsers',
method: 'GET',
params: {
orgId
}
})
if (res.code === '00000') {
currentUsers.value = res.data || [];
// console.log('@@@@@', res.data);
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('获取用户失败');
console.error('获取用户失败:', error);
}
}
onMounted(() => {
getUsersByOrgId(props.orgId);
})
watch(selectedUsers.value, (val) => {
props.form.data = val;
}, { deep: true })
</script>
<style scoped>
.form-part {
padding: 20px;
}
.text-center {
text-align: center;
}
.user-select-container {
display: flex;
gap: 20px;
width: 100%;
height: 60vh;
}
.user-list-panel {
flex: 1;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 10px;
background-color: #fff;
height: 100%;
}
.selected-panel {
flex: 1;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 10px;
background-color: #fff;
display: flex;
flex-direction: column;
height: 100%;
}
.user-list {
flex: 1;
overflow-y: auto;
}
.user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 4px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
border: 1px solid transparent;
}
.user-item:hover {
background-color: #f5f7fa;
}
.user-item.selected {
background-color: #409eff;
color: white;
border-color: #409eff;
}
.user-name {
font-size: 14px;
font-weight: 500;
}
.user-position {
font-size: 12px;
opacity: 0.7;
}
.selected-user-list {
flex: 1;
overflow-y: auto;
}
.selected-user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 4px;
border-radius: 4px;
background-color: #f0f9ff;
border: 1px solid #e1f5fe;
gap: 12px;
}
.time-picker-container {
flex: 1.2;
min-width: 280px;
}
.time-picker-container :deep(.el-date-editor) {
width: 100%;
}
.user-info {
display: flex;
flex-direction: column;
min-width: 120px;
flex: 0.8;
}
.selected-user-item .user-name {
font-size: 14px;
font-weight: 500;
color: #303133;
}
.selected-user-item .user-position {
font-size: 12px;
color: #606266;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
font-size: 14px;
padding-right: 8px;
}
</style>

View File

@ -0,0 +1,224 @@
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 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); // 抽屉实例
// 消息推送组织列表固定六个增加personList存储该组织的人员数组
const messageOrgList = ref([
{ title: '中心领导', orgName: '中心领导', personList: [] },
{ title: '法规处', orgName: '法规处', personList: [] },
{ title: '养护处', orgName: '养护处', personList: [] },
{ title: '农村公路处', orgName: '农村公路处', personList: [] },
{ title: '建设处', orgName: '建设处', personList: [] },
{ title: '市公路应急中心/管理段', orgName: '公路管理段', personList: [] },
])
const messagePushPerson = ref([])
const userOrgsList = ref([])
// 查询所有消息推送人员
const getAllMessagePushPerson = async () => {
try {
const res = await request({
url: '/snow-ops-platform/messagePushPerson/listAll',
method: 'GET',
})
if (res.code === '00000') {
messagePushPerson.value = res.data
// 按orgId分组填充人员数据到对应组织
messageOrgList.value.forEach(org => {
org.personList = messagePushPerson.value.filter(person => person.orgId === org.orgId)
});
// console.log('@@@@@@', messageOrgList.value);
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('获取消息推送人员失败');
console.error('获取消息推送人员失败:', error);
}
}
// 查询所有组织
const getUserOrgs = async () => {
try {
const res = await request({
url: '/snow-ops-platform/user/userOrgs',
method: 'GET',
})
if (res.code === '00000') {
userOrgsList.value = res.data.data
// 遍历后端返回的组织数据与固定组织列表匹配并赋值orgId
messageOrgList.value.forEach(fixedOrg => {
const matchedOrg = userOrgsList.value.find(org =>
org.orgName === fixedOrg.orgName
);
if (matchedOrg) {
fixedOrg.orgId = matchedOrg.orgId;
}
});
// 在获取到orgId后立即根据orgId填充对应的人员数据
messageOrgList.value.forEach(org => {
org.personList = messagePushPerson.value.filter(person => person.orgId === org.orgId)
});
// console.log('@@@@',messageOrgList.value);
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('获取组织失败');
console.error('获取组织失败:', error);
}
}
// 添加消息推送人
const handelAdd = 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/messagePushPerson/add',
method: 'POST',
data: data
})
loading.close();
if (res.code === '00000') {
ElMessage.success('添加成功');
modelVisible.value = false;
await getAllMessagePushPerson();
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error('添加失败');
console.error('添加失败:', error);
}
}
// 打开添加人员弹窗
const openAddDialog = (orgId, orgName) => {
model.title = '添加人员';
Object.assign(form, INIT_FORM);
model.props = {
orgId: orgId,
orgName: orgName,
form: form,
};
model.content = AddDialog;
model.onCancel = () => {
modelVisible.value = false;
};
model.onConfirm = async () => {
await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(async () => {
// console.log('@@@@@@form',form);
await handelAdd(form.data)
// await addSchedule(form)
// await publishWarning(form)
})
.catch((err) => {
ElMessage.error('请处理表单中的错误项');
});
};
model.width = "70%"
modelVisible.value = true;
}
// 删除消息推送人
const deletePushPerson = async (person) => {
try {
await ElMessageBox.confirm(
`确定要删除【${person.realName}】吗?`,
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
const loading = ElLoading.service({
lock: true,
text: '操作中',
background: 'rgba(0, 0, 0, 0.7)',
})
const res = await request({
url: '/snow-ops-platform/messagePushPerson/deleteByUserId',
method: 'POST',
data: {
userId: person.userId
}
})
loading.close();
if (res.code === '00000') {
ElMessage.success('删除成功');
await getAllMessagePushPerson();
} else {
throw new Error(res.message)
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败');
console.error('删除失败:', error);
}
}
}
export default () => {
const router = useRouter();
onMounted(async () => {
await getUserOrgs();
await getAllMessagePushPerson();
})
return {
modelVisible,
model,
drawerVisible,
drawer,
dialogRef,
drawerRef,
openAddDialog,
deletePushPerson,
messageOrgList,
}
}

View File

@ -0,0 +1,121 @@
<template>
<div class="root">
<div class="content-box">
<el-card shadow="never" v-for="org in script.messageOrgList.value" class="org-box">
<template #header>
<div class="card-header">
<span>{{ org.title }}</span>
<el-button type="primary" size="small" text
@click="script.openAddDialog(org.orgId, org.orgName)">添加</el-button>
</div>
</template>
<!-- 人员列表 -->
<div class="person-list">
<div v-if="org.personList.length === 0" class="empty-text">暂无人员</div>
<div v-else class="person-item" v-for="person in org.personList" :key="person.userId">
<div class="person-info">
<span class="name">{{ person.realName }}</span>
<!-- <span class="account">({{ person.userAccount }})</span> -->
<span class="phone" v-if="person.userPhone">{{ person.userPhone }}</span>
</div>
<el-button type="danger" size="small" text @click="script.deletePushPerson(person)">删除</el-button>
</div>
</div>
</el-card>
</div>
<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;
}
.content-box {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
gap: 20px;
}
.org-box {
flex: 1;
height: 100%;
min-width: fit-content;
}
.card-header {
display: flex;
flex-direction: row;
justify-content: space-between;
white-space: nowrap;
align-items: center;
}
.person-list {
margin-top: 10px;
}
.empty-text {
color: #999;
text-align: center;
padding: 20px 0;
}
.person-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.person-item:last-child {
border-bottom: none;
}
.person-info {
display: flex;
gap: 10px;
align-items: center;
}
.name {
font-weight: bold;
}
.account {
color: #666;
font-size: 0.9em;
}
.phone {
color: #666;
font-size: 0.9em;
}
</style>

View File

@ -48,6 +48,12 @@ export function useOptions() {
{ label: '限制通行', value: '限制通行' } { label: '限制通行', value: '限制通行' }
] ]
// 冰灾 处理措施
options.value['iceDisposalMeasures'] = [
{ label: '封闭交通', value: '封闭交通' },
{ label: '限速通行', value: '限速通行' },
]
// 路线类型 // 路线类型
options.value['roadType'] = [ options.value['roadType'] = [
{ label: '国道', value: 'G' }, { label: '国道', value: 'G' },