feat: H5灾毁重构
This commit is contained in:
parent
d831a04968
commit
fd1dc1c993
@ -108,7 +108,11 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/waterDisasterDetail',
|
||||
component: () => import('../views/DisasterManagement/WaterDisasterDetail.vue')
|
||||
component: () => import('../views/DisasterManagement/WaterDisaster/WaterDisasterDetail.vue')
|
||||
},
|
||||
{
|
||||
path: '/iceDisasterDetail',
|
||||
component: () => import('../views/DisasterManagement/IceDisaster/IceDisasterDetail.vue')
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<div class="disaster-report">
|
||||
<!-- 基本信息 -->
|
||||
<PanelItem title="基本信息" v-if="!isContinue">
|
||||
<!-- 发生时间 (顶层 occurTime) -->
|
||||
<BaseDatePicker
|
||||
v-model="formData.occurTime"
|
||||
label="发生时间"
|
||||
placeholder="请选择时间"
|
||||
:columnsType="['year', 'month', 'day', 'hour', 'minute']"
|
||||
/>
|
||||
<div class="calibrate-time-btn" @click="calibrateTime">
|
||||
<van-icon name="replay" />
|
||||
<span>校准时间</span>
|
||||
</div>
|
||||
|
||||
<!-- 线路编号 (顶层 routeNo) -->
|
||||
<RoadRoutesPicker
|
||||
v-model="formData.routeNo"
|
||||
label="线路编号"
|
||||
placeholder="请线路"
|
||||
@change="handleRouteNoChange"
|
||||
/>
|
||||
|
||||
<!-- 发生地点 (occurLocation) -->
|
||||
<van-field v-model="formData.occurLocation" label="发生地点" placeholder="请填写" />
|
||||
|
||||
<!-- 起点桩号 (event.startStakeNo) -->
|
||||
<van-field v-model="formData.event.startStakeNo" label="起点桩号(K)" placeholder="请填写" />
|
||||
|
||||
<!-- 止点桩号 (event.endStakeNo) -->
|
||||
<van-field v-model="formData.event.endStakeNo" label="止点桩号(K)" placeholder="请填写" />
|
||||
|
||||
<!-- 受灾里程 (event.blockedMileage) -->
|
||||
<van-field
|
||||
v-model="formData.event.blockedMileage"
|
||||
label="受灾里程"
|
||||
placeholder="请填写"
|
||||
type="digit"
|
||||
>
|
||||
<template #button>
|
||||
<span class="field-unit">公里</span>
|
||||
</template>
|
||||
</van-field>
|
||||
</PanelItem>
|
||||
|
||||
<PanelItem title="处置情况">
|
||||
<van-field label="处置措施">
|
||||
<template #input>
|
||||
<van-col
|
||||
v-for="(item, index) in options['iceDisposalMeasures']"
|
||||
:span="24 / options['iceDisposalMeasures'].length"
|
||||
:key="index"
|
||||
>
|
||||
<van-button
|
||||
block
|
||||
plain
|
||||
:type="item.value === formData.report.disposalMeasures ? 'primary' : 'default'"
|
||||
@click="formData.report.disposalMeasures = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</van-button>
|
||||
</van-col>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<BaseDatePicker
|
||||
v-model="formData.report.expectRecoverTime"
|
||||
label="预计恢复时间"
|
||||
placeholder="请选择时间"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
type="datetime"
|
||||
/>
|
||||
</PanelItem>
|
||||
|
||||
<PanelItem title="实施情况">
|
||||
<van-field
|
||||
v-model="formData.report.investedMachinery"
|
||||
label="投入人力"
|
||||
placeholder="请填写"
|
||||
type="digit"
|
||||
>
|
||||
<template #button>
|
||||
<span class="field-unit">台/班</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
v-model="formData.report.investedManpower"
|
||||
label="投入资金"
|
||||
placeholder="请填写"
|
||||
type="number"
|
||||
>
|
||||
<template #button>
|
||||
<span class="field-unit">人次</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
v-model="formData.report.investedMachinery"
|
||||
label="投入设备"
|
||||
placeholder="请填写"
|
||||
type="digit"
|
||||
>
|
||||
<template #button>
|
||||
<span class="field-unit">万元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<!-- 物资选择 -->
|
||||
<MaterialPicker v-model="formData.yhzMaterialList" />
|
||||
|
||||
<BasePicker
|
||||
v-model="formData.event.test"
|
||||
:options="options['haveOrNot']"
|
||||
label="有无车辆滞留"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
|
||||
<van-field
|
||||
v-if="formData.event.hasStrandedVehicles === 1"
|
||||
v-model="form.event.strandedVehicleCount"
|
||||
type="number"
|
||||
label="滞留车辆数"
|
||||
center
|
||||
placeholder="请填写"
|
||||
/>
|
||||
|
||||
<BaseDatePicker
|
||||
v-model="formData.report.expectRecoverTime"
|
||||
label="实际恢复时间"
|
||||
placeholder="请选择时间"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
type="datetime"
|
||||
/>
|
||||
</PanelItem>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import PanelItem from '@/components/PanelItem.vue';
|
||||
import BasePicker from '@/components/BasePicker.vue';
|
||||
import BaseDatePicker from '@/components/BaseDatePicker.vue';
|
||||
import RoadRoutesPicker from '../components/RoadRoutesPicker.vue';
|
||||
import MaterialPicker from '../components/MaterialPicker.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { request } from '@shared/utils/request';
|
||||
import { useOptions } from '@shared/composables/useOptions';
|
||||
|
||||
const route = useRoute();
|
||||
const { options } = useOptions();
|
||||
|
||||
// 是否为续报
|
||||
const isContinue = computed(() => route.query.isContinue);
|
||||
|
||||
const minDate = new Date();
|
||||
const maxDate = new Date(2050, 11, 31);
|
||||
|
||||
const formData = ref({
|
||||
event: {},
|
||||
report: {},
|
||||
fileList: [],
|
||||
yhzMaterialList: [],
|
||||
});
|
||||
|
||||
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.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.startStakeLng = startPoint.longitude;
|
||||
formData.event.startStakeLat = startPoint.latitude;
|
||||
formData.event.endStakeLng = endPoint.longitude;
|
||||
formData.event.endStakeLat = endPoint.latitude;
|
||||
};
|
||||
|
||||
const getDisasterDetail = async () => {
|
||||
const id = route.query.id;
|
||||
if (!id) {
|
||||
ElMessage.warning('缺少灾毁ID');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await request({
|
||||
url: `/snow-ops-platform/event/getById`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (result?.data) {
|
||||
const data = result.data;
|
||||
detailData.value = data;
|
||||
} else {
|
||||
ElMessage.warning(result.message || '获取详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取灾毁详情失败:', error);
|
||||
ElMessage.error('获取详情失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (isContinue.value) {
|
||||
getDisasterDetail();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
@ -160,8 +160,8 @@ import { showToast, showFailToast, showLoadingToast } from 'vant'
|
||||
import PanelItem from '@/components/PanelItem.vue'
|
||||
import BasePicker from '@/components/BasePicker.vue'
|
||||
import BaseDatePicker from '@/components/BaseDatePicker.vue'
|
||||
import RoadRoutesPicker from '../RoadRoutesPicker.vue'
|
||||
import LossList from './components/LossList.vue'
|
||||
import RoadRoutesPicker from '../components/RoadRoutesPicker.vue'
|
||||
import LossList from '../components/LossList.vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { request } from '@shared/utils/request'
|
||||
import { useOptions } from '@shared/composables/useOptions'
|
||||
|
||||
@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<div class="material-picker">
|
||||
<van-field
|
||||
v-for="(material, index) in modelValue"
|
||||
:key="material.rid"
|
||||
:label="material.wzmc"
|
||||
:model-value="material.usageAmount"
|
||||
type="number"
|
||||
center
|
||||
:placeholder="`余额: ${material.ye}`"
|
||||
@update:model-value="value => updateUsageAmount(value, index)"
|
||||
>
|
||||
<template #extra>
|
||||
<span class="material-unit">{{ material.dw }}</span>
|
||||
<van-button size="small" type="danger" @click.stop="removeMaterial(index)">
|
||||
删除
|
||||
</van-button>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-button class="add-wzbtn" type="primary" icon="plus" block plain @click="handleOpen">
|
||||
添加物资
|
||||
</van-button>
|
||||
|
||||
<van-popup
|
||||
:show="showPopup"
|
||||
position="bottom"
|
||||
close-on-click-overlay
|
||||
@close="showPopup = false"
|
||||
>
|
||||
<div class="popup-content">
|
||||
<h3 class="popup-title">添加物资</h3>
|
||||
|
||||
<van-field
|
||||
v-model="searchText"
|
||||
placeholder="输入物资名称搜索"
|
||||
clearable
|
||||
@update:model-value="handleSearch"
|
||||
/>
|
||||
|
||||
<van-checkbox-group v-model="checked">
|
||||
<van-cell-group inset class="material-list">
|
||||
<div class="material-header">
|
||||
<span>共 {{ materialList.length }} 项</span>
|
||||
<van-button
|
||||
size="mini"
|
||||
:type="isAllSelected ? 'primary' : 'default'"
|
||||
@click="toggleSelectAll"
|
||||
>
|
||||
{{ isAllSelected ? '取消全选' : '全选' }}
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<van-cell
|
||||
v-for="(item, index) in materialList"
|
||||
:key="item.rid"
|
||||
clickable
|
||||
:title="item.wzmc"
|
||||
@click="toggle(index)"
|
||||
>
|
||||
<template #right-icon>
|
||||
<van-checkbox
|
||||
:ref="el => (checkboxRefs[index] = el)"
|
||||
:name="item.rid"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
|
||||
<van-button type="primary" block class="confirm-btn" @click="addSelectedMaterials">
|
||||
确认添加
|
||||
</van-button>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { showToast } from 'vant';
|
||||
import { request } from '@shared/utils/request';
|
||||
import { useYHZStore } from '@/stores/yhzStore';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const yhzStore = useYHZStore();
|
||||
|
||||
const showPopup = ref(false);
|
||||
const materialList = ref([]);
|
||||
const checkboxRefs = ref([]);
|
||||
const checked = ref([]);
|
||||
const searchText = ref('');
|
||||
|
||||
const isAllSelected = computed(() => {
|
||||
return (
|
||||
materialList.value.length > 0 &&
|
||||
materialList.value.every(item => checked.value.includes(item.rid))
|
||||
);
|
||||
});
|
||||
|
||||
const toggle = index => {
|
||||
checkboxRefs.value[index]?.toggle();
|
||||
};
|
||||
|
||||
const handleSearch = value => {
|
||||
getMaterialList(value);
|
||||
};
|
||||
|
||||
const toggleSelectAll = () => {
|
||||
checked.value = isAllSelected.value ? [] : materialList.value.map(item => item.rid);
|
||||
};
|
||||
|
||||
const updateUsageAmount = (value, index) => {
|
||||
const nextMaterials = [...props.modelValue];
|
||||
const currentMaterial = nextMaterials[index];
|
||||
|
||||
if (!currentMaterial) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value !== '' && Number(value) > Number(currentMaterial.ye)) {
|
||||
showToast({
|
||||
type: 'fail',
|
||||
message: '输入数量不能超过物资余额',
|
||||
});
|
||||
currentMaterial.usageAmount = currentMaterial.ye;
|
||||
} else {
|
||||
currentMaterial.usageAmount = value;
|
||||
}
|
||||
|
||||
emit('update:modelValue', nextMaterials);
|
||||
};
|
||||
|
||||
const removeMaterial = index => {
|
||||
const nextMaterials = props.modelValue.filter((_, currentIndex) => currentIndex !== index);
|
||||
emit('update:modelValue', nextMaterials);
|
||||
};
|
||||
|
||||
const addSelectedMaterials = () => {
|
||||
const nextMaterials = [...props.modelValue];
|
||||
|
||||
checked.value.forEach(rid => {
|
||||
const material = materialList.value.find(item => item.rid === rid);
|
||||
if (!material || nextMaterials.some(item => item.rid === rid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextMaterials.push({
|
||||
rid,
|
||||
wzmc: material.wzmc,
|
||||
usageAmount: null,
|
||||
dw: material.dw,
|
||||
ye: material.ye,
|
||||
});
|
||||
});
|
||||
|
||||
emit('update:modelValue', nextMaterials);
|
||||
checked.value = [];
|
||||
showPopup.value = false;
|
||||
};
|
||||
|
||||
const getMaterialList = async wzmc => {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/snow-ops-platform/yjwz/list',
|
||||
method: 'GET',
|
||||
params: {
|
||||
yhzid: yhzStore.getYHZInfo?.id,
|
||||
wzmc,
|
||||
pageNum: 1,
|
||||
pageSize: 9999,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.code === '00000') {
|
||||
materialList.value = res.data.records;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(res.message);
|
||||
} catch (error) {
|
||||
showToast({
|
||||
type: 'fail',
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpen = async () => {
|
||||
await getMaterialList();
|
||||
showPopup.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.popup-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.material-unit {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.material-list {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.material-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user