Merge branch 'dev' of http://222.212.85.86:8222/bdzl2/bxztApp into dev
This commit is contained in:
commit
0d0a1d4899
434
packages/mobile/src/components/BaseDatePicker.vue
Normal file
434
packages/mobile/src/components/BaseDatePicker.vue
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
<template>
|
||||||
|
<div class="base-date-time-picker">
|
||||||
|
<!-- 使用 van-field 作为展示区域 - 修复 bug1:改为 modelValue -->
|
||||||
|
<van-field
|
||||||
|
v-model="displayValue"
|
||||||
|
:label="label"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
:readonly="true"
|
||||||
|
:right-icon="rightIcon"
|
||||||
|
:clickable="!disabled"
|
||||||
|
@click="openPicker"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 弹出层:同时包含日期和时间选择器 -->
|
||||||
|
<van-popup v-model:show="showPicker" position="bottom" round>
|
||||||
|
<div class="picker-container">
|
||||||
|
<div class="picker-header">
|
||||||
|
<span class="picker-cancel" @click="showPicker = false">取消</span>
|
||||||
|
<span class="picker-title">{{ pickerTitle }}</span>
|
||||||
|
<span class="picker-confirm" @click="onConfirm">确定</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="picker-wrapper" :class="{ 'has-time': hasTime }">
|
||||||
|
<!-- 日期选择器 -->
|
||||||
|
<van-date-picker
|
||||||
|
v-model="currentDate"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:columns-type="dateColumnsType"
|
||||||
|
:formatter="formatter"
|
||||||
|
:filter="filter"
|
||||||
|
:loading="loading"
|
||||||
|
:show-toolbar="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 时间选择器(当有小时列时显示) -->
|
||||||
|
<van-time-picker
|
||||||
|
v-if="hasTime"
|
||||||
|
v-model="currentTime"
|
||||||
|
:columns-type="timeColumnsType"
|
||||||
|
:min-hour="minHour"
|
||||||
|
:max-hour="maxHour"
|
||||||
|
:min-minute="minMinute"
|
||||||
|
:max-minute="maxMinute"
|
||||||
|
:formatter="formatter"
|
||||||
|
:show-toolbar="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</van-popup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { Field, Popup, DatePicker, TimePicker } from 'vant'
|
||||||
|
|
||||||
|
// Props 定义
|
||||||
|
const props = defineProps({
|
||||||
|
// 双向绑定值 (v-model)
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 左侧标签文字
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 占位符
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择日期时间'
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// Picker 顶部标题
|
||||||
|
pickerTitle: {
|
||||||
|
type: String,
|
||||||
|
default: '选择日期时间'
|
||||||
|
},
|
||||||
|
// 列类型:支持 'year', 'month', 'day', 'hour', 'minute', 'second'
|
||||||
|
columnsType: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ['year', 'month', 'day', 'hour', 'minute']
|
||||||
|
},
|
||||||
|
// 最小日期
|
||||||
|
minDate: {
|
||||||
|
type: Date,
|
||||||
|
default: () => new Date(1900, 0, 1)
|
||||||
|
},
|
||||||
|
// 最大日期
|
||||||
|
maxDate: {
|
||||||
|
type: Date,
|
||||||
|
default: () => new Date(2100, 11, 31)
|
||||||
|
},
|
||||||
|
// 最小小时
|
||||||
|
minHour: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 最大小时
|
||||||
|
maxHour: {
|
||||||
|
type: Number,
|
||||||
|
default: 23
|
||||||
|
},
|
||||||
|
// 最小分钟
|
||||||
|
minMinute: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 最大分钟
|
||||||
|
maxMinute: {
|
||||||
|
type: Number,
|
||||||
|
default: 59
|
||||||
|
},
|
||||||
|
// 加载状态
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 右侧图标
|
||||||
|
rightIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'arrow-down'
|
||||||
|
},
|
||||||
|
// 日期时间格式化函数
|
||||||
|
formatter: {
|
||||||
|
type: Function,
|
||||||
|
default: (type, option) => {
|
||||||
|
if (type === 'year') option.text += '年'
|
||||||
|
if (type === 'month') option.text += '月'
|
||||||
|
if (type === 'day') option.text += '日'
|
||||||
|
if (type === 'hour') option.text += '时'
|
||||||
|
if (type === 'minute') option.text += '分'
|
||||||
|
if (type === 'second') option.text += '秒'
|
||||||
|
return option
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 选项过滤函数
|
||||||
|
filter: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emits 定义
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
|
||||||
|
// 控制弹出层显示
|
||||||
|
const showPicker = ref(false)
|
||||||
|
|
||||||
|
// 当前选中的日期(数组格式,如 ['2024', '01', '01'])
|
||||||
|
const currentDate = ref([])
|
||||||
|
|
||||||
|
// 当前选中的时间(数组格式,如 ['14', '30', '00'])
|
||||||
|
const currentTime = ref([])
|
||||||
|
|
||||||
|
// 分离日期列类型和时间列类型
|
||||||
|
const dateColumnsType = computed(() => {
|
||||||
|
return props.columnsType.filter(col => ['year', 'month', 'day'].includes(col))
|
||||||
|
})
|
||||||
|
|
||||||
|
const timeColumnsType = computed(() => {
|
||||||
|
return props.columnsType.filter(col => ['hour', 'minute', 'second'].includes(col))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否有时间选择
|
||||||
|
const hasTime = computed(() => {
|
||||||
|
return timeColumnsType.value.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 格式化日期显示(修复 bug2:正确处理分钟显示)
|
||||||
|
const displayValue = computed(() => {
|
||||||
|
if (!props.modelValue) return ''
|
||||||
|
|
||||||
|
// 根据 columnsType 格式化显示
|
||||||
|
const hasYear = props.columnsType.includes('year')
|
||||||
|
const hasMonth = props.columnsType.includes('month')
|
||||||
|
const hasDay = props.columnsType.includes('day')
|
||||||
|
const hasHour = props.columnsType.includes('hour')
|
||||||
|
const hasMinute = props.columnsType.includes('minute')
|
||||||
|
const hasSecond = props.columnsType.includes('second')
|
||||||
|
|
||||||
|
// 解析日期时间
|
||||||
|
let datePart = ''
|
||||||
|
let timePart = ''
|
||||||
|
|
||||||
|
if (props.modelValue.includes(' ')) {
|
||||||
|
[datePart, timePart] = props.modelValue.split(' ')
|
||||||
|
} else if (props.modelValue.includes('-')) {
|
||||||
|
datePart = props.modelValue
|
||||||
|
} else if (props.modelValue.includes(':')) {
|
||||||
|
timePart = props.modelValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期部分
|
||||||
|
let result = ''
|
||||||
|
if (datePart && (hasYear || hasMonth || hasDay)) {
|
||||||
|
const dateArr = datePart.split('-')
|
||||||
|
if (hasYear && dateArr[0]) result += dateArr[0]
|
||||||
|
if (hasMonth && dateArr[1]) result += (result ? '-' : '') + dateArr[1]
|
||||||
|
if (hasDay && dateArr[2]) result += (result ? '-' : '') + dateArr[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间部分 - 修复 bug2:确保分钟正确显示
|
||||||
|
if (timePart && (hasHour || hasMinute || hasSecond)) {
|
||||||
|
const timeArr = timePart.split(':')
|
||||||
|
let timeResult = ''
|
||||||
|
if (hasHour && timeArr[0]) timeResult += timeArr[0]
|
||||||
|
if (hasMinute && timeArr[1]) timeResult += (timeResult ? ':' : '') + timeArr[1]
|
||||||
|
if (hasSecond && timeArr[2]) timeResult += (timeResult ? ':' : '') + timeArr[2]
|
||||||
|
|
||||||
|
if (timeResult) {
|
||||||
|
result += (result ? ' ' : '') + timeResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result || props.modelValue
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取默认时间值(根据列类型)
|
||||||
|
const getDefaultTime = () => {
|
||||||
|
const now = new Date()
|
||||||
|
const timeValues = []
|
||||||
|
|
||||||
|
if (timeColumnsType.value.includes('hour')) {
|
||||||
|
timeValues.push(String(now.getHours()).padStart(2, '0'))
|
||||||
|
}
|
||||||
|
if (timeColumnsType.value.includes('minute')) {
|
||||||
|
timeValues.push(String(now.getMinutes()).padStart(2, '0'))
|
||||||
|
}
|
||||||
|
if (timeColumnsType.value.includes('second')) {
|
||||||
|
timeValues.push(String(now.getSeconds()).padStart(2, '0'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化当前值
|
||||||
|
const initValue = () => {
|
||||||
|
// 初始化日期部分
|
||||||
|
if (props.modelValue) {
|
||||||
|
// 解析 YYYY-MM-DD HH:mm:ss 格式
|
||||||
|
let datePart = ''
|
||||||
|
let timePart = ''
|
||||||
|
|
||||||
|
if (props.modelValue.includes(' ')) {
|
||||||
|
[datePart, timePart] = props.modelValue.split(' ')
|
||||||
|
} else if (props.modelValue.includes('-')) {
|
||||||
|
datePart = props.modelValue
|
||||||
|
} else if (props.modelValue.includes(':')) {
|
||||||
|
timePart = props.modelValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (datePart) {
|
||||||
|
const dateArr = datePart.split('-')
|
||||||
|
currentDate.value = []
|
||||||
|
if (dateColumnsType.value.includes('year') && dateArr[0]) {
|
||||||
|
currentDate.value.push(dateArr[0])
|
||||||
|
}
|
||||||
|
if (dateColumnsType.value.includes('month') && dateArr[1]) {
|
||||||
|
currentDate.value.push(dateArr[1])
|
||||||
|
}
|
||||||
|
if (dateColumnsType.value.includes('day') && dateArr[2]) {
|
||||||
|
currentDate.value.push(dateArr[2])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const now = new Date()
|
||||||
|
currentDate.value = []
|
||||||
|
if (dateColumnsType.value.includes('year')) {
|
||||||
|
currentDate.value.push(String(now.getFullYear()))
|
||||||
|
}
|
||||||
|
if (dateColumnsType.value.includes('month')) {
|
||||||
|
currentDate.value.push(String(now.getMonth() + 1).padStart(2, '0'))
|
||||||
|
}
|
||||||
|
if (dateColumnsType.value.includes('day')) {
|
||||||
|
currentDate.value.push(String(now.getDate()).padStart(2, '0'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化时间部分(根据列类型)
|
||||||
|
if (hasTime.value) {
|
||||||
|
if (timePart) {
|
||||||
|
const timeArr = timePart.split(':')
|
||||||
|
const result = []
|
||||||
|
|
||||||
|
if (timeColumnsType.value.includes('hour')) {
|
||||||
|
result.push(timeArr[0] || '00')
|
||||||
|
}
|
||||||
|
if (timeColumnsType.value.includes('minute')) {
|
||||||
|
result.push(timeArr[1] || '00')
|
||||||
|
}
|
||||||
|
if (timeColumnsType.value.includes('second')) {
|
||||||
|
result.push(timeArr[2] || '00')
|
||||||
|
}
|
||||||
|
currentTime.value = result
|
||||||
|
} else {
|
||||||
|
currentTime.value = getDefaultTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 默认当前日期
|
||||||
|
const now = new Date()
|
||||||
|
currentDate.value = []
|
||||||
|
if (dateColumnsType.value.includes('year')) {
|
||||||
|
currentDate.value.push(String(now.getFullYear()))
|
||||||
|
}
|
||||||
|
if (dateColumnsType.value.includes('month')) {
|
||||||
|
currentDate.value.push(String(now.getMonth() + 1).padStart(2, '0'))
|
||||||
|
}
|
||||||
|
if (dateColumnsType.value.includes('day')) {
|
||||||
|
currentDate.value.push(String(now.getDate()).padStart(2, '0'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认当前时间(根据列类型)
|
||||||
|
if (hasTime.value) {
|
||||||
|
currentTime.value = getDefaultTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开选择器
|
||||||
|
const openPicker = () => {
|
||||||
|
if (!props.disabled) {
|
||||||
|
initValue()
|
||||||
|
showPicker.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期部分
|
||||||
|
const formatDatePart = () => {
|
||||||
|
if (!currentDate.value || currentDate.value.length === 0) return ''
|
||||||
|
let result = currentDate.value.join('-')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间部分
|
||||||
|
const formatTimePart = () => {
|
||||||
|
if (!hasTime.value) return ''
|
||||||
|
if (!currentTime.value || currentTime.value.length === 0) return ''
|
||||||
|
let result = currentTime.value.join(':')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认选择
|
||||||
|
const onConfirm = () => {
|
||||||
|
let finalValue = formatDatePart()
|
||||||
|
|
||||||
|
if (hasTime.value) {
|
||||||
|
const timePart = formatTimePart()
|
||||||
|
if (timePart) {
|
||||||
|
finalValue += ' ' + timePart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:modelValue', finalValue)
|
||||||
|
emit('change', finalValue)
|
||||||
|
showPicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听外部 modelValue 变化,重新初始化
|
||||||
|
watch(() => props.modelValue, () => {
|
||||||
|
initValue()
|
||||||
|
}, { immediate: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.base-date-time-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.van-field__control--readonly) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-container {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid #ebedf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-cancel {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #969799;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #323233;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-confirm {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1989fa;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-wrapper {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-wrapper .van-picker {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 只有日期选择器时占满宽度 */
|
||||||
|
.picker-wrapper:not(.has-time) .van-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 同时有日期和时间时调整宽度比例 */
|
||||||
|
.picker-wrapper.has-time .van-date-picker {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-wrapper.has-time .van-time-picker {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
139
packages/mobile/src/components/BasePicker.vue
Normal file
139
packages/mobile/src/components/BasePicker.vue
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<div class="base-picker">
|
||||||
|
<!-- 使用 van-field 作为展示区域 -->
|
||||||
|
<van-field
|
||||||
|
:modelValue="displayValue"
|
||||||
|
:label="label"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
:readonly="true"
|
||||||
|
:right-icon="rightIcon"
|
||||||
|
:clickable="!disabled"
|
||||||
|
@click="openPicker"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 弹出层选择器 -->
|
||||||
|
<van-popup v-model:show="showPicker" position="bottom" round>
|
||||||
|
<van-picker
|
||||||
|
:columns="columns"
|
||||||
|
:title="pickerTitle"
|
||||||
|
:loading="loading"
|
||||||
|
show-toolbar
|
||||||
|
@confirm="onConfirm"
|
||||||
|
@cancel="showPicker = false"
|
||||||
|
/>
|
||||||
|
</van-popup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { Field, Popup, Picker } from 'vant'
|
||||||
|
|
||||||
|
// Props 定义
|
||||||
|
const props = defineProps({
|
||||||
|
// 双向绑定值 (v-model)
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number, Boolean],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
// 选项数据,默认格式 [{ label: '显示名', value: '值' }]
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// 左侧标签文字
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 占位符
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择'
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// Picker 顶部标题
|
||||||
|
pickerTitle: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择'
|
||||||
|
},
|
||||||
|
// 加载状态
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 右侧图标(可自定义)
|
||||||
|
rightIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'arrow-down'
|
||||||
|
},
|
||||||
|
// 自定义选项的 label 字段名
|
||||||
|
labelKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'label'
|
||||||
|
},
|
||||||
|
// 自定义选项的 value 字段名
|
||||||
|
valueKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'value'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emits 定义
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
|
||||||
|
// 控制弹出层显示
|
||||||
|
const showPicker = ref(false)
|
||||||
|
|
||||||
|
// 处理 Picker 数据格式:将 options 转为 Picker 需要的文本数组
|
||||||
|
const columns = computed(() => {
|
||||||
|
return props.options.map(item => {
|
||||||
|
return {
|
||||||
|
text: item[props.labelKey],
|
||||||
|
value: item[props.valueKey]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 根据选中的值,获取显示文本
|
||||||
|
const displayValue = computed(() => {
|
||||||
|
if (props.modelValue === null || props.modelValue === undefined || props.modelValue === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const selected = props.options.find(item => item[props.valueKey] === props.modelValue)
|
||||||
|
return selected ? selected[props.labelKey] : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 打开选择器
|
||||||
|
const openPicker = () => {
|
||||||
|
if (!props.disabled) {
|
||||||
|
showPicker.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认选择
|
||||||
|
const onConfirm = ({ selectedValues, selectedOptions }) => {
|
||||||
|
const value = selectedOptions[0][props.valueKey]
|
||||||
|
const label = selectedOptions[0][props.labelKey]
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emit('change', { value, label })
|
||||||
|
showPicker.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.base-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可选:调整 Field 的只读样式 */
|
||||||
|
:deep(.van-field__control--readonly) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -39,7 +39,7 @@ const props = defineProps({
|
|||||||
.card-item {
|
.card-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 21px 50px 17px 10px;
|
padding: 20px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<van-nav-bar title="气象预警" fixed left-arrow @click-left="onClickLeft" />
|
<van-nav-bar :title="title" fixed left-arrow @click-left="onClickLeft" />
|
||||||
<div class="page-content-wrapper">
|
<div class="page-content-wrapper">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
@ -10,6 +10,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['back'])
|
const emit = defineEmits(['back'])
|
||||||
|
|
||||||
const onClickLeft = () => {
|
const onClickLeft = () => {
|
||||||
|
|||||||
50
packages/mobile/src/components/PanelItem.vue
Normal file
50
packages/mobile/src/components/PanelItem.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div class="panel-item">
|
||||||
|
<slot v-if="title" name="header">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-title">{{ title }}</div>
|
||||||
|
<div class="header-extra" v-if="$slots.headerExtra">
|
||||||
|
<slot name="headerExtra"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.panel-item {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
& + .panel-item {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
.header-title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #4a4a4a;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -3,18 +3,26 @@
|
|||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<div class="input-block">
|
<div class="input-block">
|
||||||
<van-icon class="search-icon" name="search" />
|
<van-icon class="search-icon" name="search" />
|
||||||
<input class="inner-input" v-model="modelValue" :placeholder="placeholder" />
|
<input
|
||||||
<van-icon class="close-icon" name="clear" v-if="modelValue !== ''" @click="modelValue = ''" />
|
class="inner-input"
|
||||||
|
v-model="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
/>
|
||||||
|
<van-icon
|
||||||
|
class="close-icon"
|
||||||
|
name="clear"
|
||||||
|
v-if="modelValue !== ''"
|
||||||
|
@click="clearInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 右侧插槽:用于放置全部和筛选按钮 -->
|
||||||
|
<div class="slot-wrapper" v-if="$slots.extra">
|
||||||
|
<slot name="extra"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 占位符 -->
|
|
||||||
<!-- <div class="placeholder-block" v-if="modelValue === '111'">
|
|
||||||
<van-icon class="search-icon" name="search" />
|
|
||||||
<span class="placeholder-text">{{ placeholder }}</span>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
@ -26,7 +34,13 @@ const props = defineProps({
|
|||||||
default: '请输入关键词'
|
default: '请输入关键词'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 清空输入框
|
||||||
|
const clearInput = () => {
|
||||||
|
modelValue.value = ''
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.search-input {
|
.search-input {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -37,19 +51,23 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
gap: 12px; // 输入框和按钮组间距
|
||||||
|
padding-right: 12px; // 右侧内边距
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-block {
|
.input-block {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0 20px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.search-icon, .close-icon {
|
.search-icon, .close-icon {
|
||||||
@ -59,10 +77,18 @@ const props = defineProps({
|
|||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #9b9b9b;
|
color: #9b9b9b;
|
||||||
|
pointer-events: none; // 搜索图标不阻挡点击
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-icon {
|
.close-icon {
|
||||||
left: unset;
|
left: unset;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
|
pointer-events: auto; // 清空图标可点击
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,28 +97,17 @@ const props = defineProps({
|
|||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-block {
|
.slot-wrapper {
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
pointer-events: none;
|
gap: 8px; // 按钮间距
|
||||||
|
flex-shrink: 0; // 防止压缩
|
||||||
.search-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-right: 3px;
|
|
||||||
color: #9b9b9b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-text {
|
|
||||||
color: #9b9b9b;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
223
packages/mobile/src/components/TagFilter.vue
Normal file
223
packages/mobile/src/components/TagFilter.vue
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tag-filter" v-show="visible">
|
||||||
|
<div class="filter-mask" @click="close"></div>
|
||||||
|
<div class="filter-container">
|
||||||
|
<div class="filter-header">
|
||||||
|
<span class="filter-title">类型筛选</span>
|
||||||
|
<van-icon name="close" class="close-icon" @click="close" />
|
||||||
|
</div>
|
||||||
|
<div class="filter-content">
|
||||||
|
<div class="tag-list">
|
||||||
|
<span
|
||||||
|
v-for="tag in tagList"
|
||||||
|
:key="tag.value"
|
||||||
|
class="tag-item"
|
||||||
|
:class="{ active: tempSelectedTag === tag.value }"
|
||||||
|
@click="selectTag(tag.value)"
|
||||||
|
>
|
||||||
|
{{ tag.label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="filter-footer">
|
||||||
|
<van-button class="reset-btn" size="small" @click="resetFilter">重置</van-button>
|
||||||
|
<van-button type="primary" class="confirm-btn" size="small" @click="confirmFilter">确定</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
import { Icon as VanIcon, Button as VanButton } from 'vant'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 筛选选中的值(支持 v-model)
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: 'all'
|
||||||
|
},
|
||||||
|
// 控制显示隐藏
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 标签列表
|
||||||
|
tags: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [
|
||||||
|
{ label: '全部', value: 'all' },
|
||||||
|
{ label: '边坡坍塌', value: '边坡坍塌' },
|
||||||
|
{ label: '泥石流', value: '泥石流' },
|
||||||
|
{ label: '路基沉陷', value: '路基沉陷' },
|
||||||
|
{ label: '山体滑坡', value: '山体滑坡' },
|
||||||
|
{ label: '行道树倒塌', value: '行道树倒塌' },
|
||||||
|
{ label: '积水', value: '积水' },
|
||||||
|
{ label: '积雪', value: '积雪' },
|
||||||
|
{ label: '其他', value: '其他' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'update:visible', 'confirm'])
|
||||||
|
|
||||||
|
// 临时选中的标签(用于确认前暂存)
|
||||||
|
const tempSelectedTag = ref(props.modelValue)
|
||||||
|
|
||||||
|
// 标签列表
|
||||||
|
const tagList = computed(() => props.tags)
|
||||||
|
|
||||||
|
// 监听 modelValue 变化,同步临时选中值
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
tempSelectedTag.value = newVal
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听 visible 变化,打开时同步临时选中值
|
||||||
|
watch(() => props.visible, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
tempSelectedTag.value = props.modelValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 选择标签(临时)
|
||||||
|
const selectTag = (value) => {
|
||||||
|
tempSelectedTag.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置筛选
|
||||||
|
const resetFilter = () => {
|
||||||
|
tempSelectedTag.value = 'all'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认筛选
|
||||||
|
const confirmFilter = () => {
|
||||||
|
emit('update:modelValue', tempSelectedTag.value)
|
||||||
|
emit('confirm', tempSelectedTag.value)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭筛选弹窗
|
||||||
|
const close = () => {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.tag-filter {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 0 0 16px 16px;
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 16px 12px;
|
||||||
|
border-bottom: 1px solid #ebedf0;
|
||||||
|
|
||||||
|
.filter-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #323233;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
font-size: 22px;
|
||||||
|
color: #969799;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-content {
|
||||||
|
padding: 16px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item {
|
||||||
|
padding: 6px 16px;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
border-radius: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #646566;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #1989fa;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px 20px;
|
||||||
|
border-top: 1px solid #ebedf0;
|
||||||
|
|
||||||
|
.reset-btn,
|
||||||
|
.confirm-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-btn {
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
border: none;
|
||||||
|
color: #646566;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -80,6 +80,31 @@ const routes = [
|
|||||||
path: '/warningMessageHandle',
|
path: '/warningMessageHandle',
|
||||||
name: 'WarningMessageHandle',
|
name: 'WarningMessageHandle',
|
||||||
component: () => import('../views/WarningMessage/WarningMessageHandle.vue')
|
component: () => import('../views/WarningMessage/WarningMessageHandle.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/rebuild',
|
||||||
|
name: 'Rebuild',
|
||||||
|
component: () => import('../views/Rebuild/Rebuild.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/rebuild-add/:data?',
|
||||||
|
name: 'RebuildAdd',
|
||||||
|
component: () => import('../views/Rebuild/RebuildAdd.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/rebuild-details/:data?',
|
||||||
|
name: 'RebuildDetails',
|
||||||
|
component: () => import('../views/Rebuild/RebuildDetails.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/disasterManagement',
|
||||||
|
name: 'DisasterManagement',
|
||||||
|
component: () => import('../views/DisasterManagement/DisasterManagement.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/disasterReport',
|
||||||
|
name: 'DisasterReport',
|
||||||
|
component: () => import('../views/DisasterManagement/DisasterReport.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,274 @@
|
|||||||
|
<template>
|
||||||
|
<PageContainer title="灾害管理" @click-back="handleClickBack">
|
||||||
|
<CurrentSite />
|
||||||
|
|
||||||
|
<div class="list-panel">
|
||||||
|
<CardItem v-for="(item, index) in list" :key="index" :title="item.title" @click="handleClickItem(item)">
|
||||||
|
<template #headerExtra>
|
||||||
|
<van-tag :type="item.status === '未解除' ? 'danger' : 'success'" plain size="medium">
|
||||||
|
{{ item.status }}
|
||||||
|
</van-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="info-block">
|
||||||
|
<div class="time-box">
|
||||||
|
<span class="info-label">发生时间:</span>
|
||||||
|
<span class="info-value">{{ item.occurTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="time-box">
|
||||||
|
<span class="info-label">预计恢复时间:</span>
|
||||||
|
<span class="info-value">{{ item.estimateRecoverTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="disaster-type-wrapper">
|
||||||
|
<van-tag type="primary" size="medium" plain>{{ item.disasterType }}</van-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-icon class="jump-icon-absolute" name="arrow" />
|
||||||
|
</CardItem>
|
||||||
|
|
||||||
|
<div v-if="loading" class="loading-wrapper">
|
||||||
|
<van-loading size="24px" vertical>加载中...</van-loading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EmptyBox v-if="!loading && list.length === 0" :placeholder="emptyText" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-button type="primary" class="footer-btn" @click="handleAdd"> 灾害填报 </van-button>
|
||||||
|
|
||||||
|
<!-- 筛选组件:v-model 绑定选中的值,visible 控制显示隐藏 -->
|
||||||
|
<TagFilter
|
||||||
|
v-model="selectedDisasterType"
|
||||||
|
:visible="showFilter"
|
||||||
|
@update:visible="showFilter = $event"
|
||||||
|
@confirm="handleFilterConfirm"
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { showToast, Tag as VanTag, Loading as VanLoading, Icon as VanIcon, Button as VanButton } from 'vant'
|
||||||
|
import PageContainer from '@/components/PageContainer.vue'
|
||||||
|
import SearchInput from '@/components/SearchInput.vue'
|
||||||
|
import CardItem from '@/components/CardItem.vue'
|
||||||
|
import EmptyBox from '@/components/EmptyBox.vue'
|
||||||
|
import CurrentSite from '@/components/CurrentSite.vue'
|
||||||
|
import TagFilter from '@/components/TagFilter.vue'
|
||||||
|
import { request } from "@shared/utils/request";
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
const searchValue = ref('')
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const list = ref([])
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 空状态文本
|
||||||
|
const emptyText = ref('暂无相关灾毁信息')
|
||||||
|
|
||||||
|
// 筛选弹窗显示状态
|
||||||
|
const showFilter = ref(false)
|
||||||
|
|
||||||
|
// 当前选中的灾毁类型(通过 v-model 传给 TagFilter)
|
||||||
|
const selectedDisasterType = ref('all')
|
||||||
|
|
||||||
|
// 灾毁类型列表
|
||||||
|
const disasterTypes = [
|
||||||
|
{ label: '全部', value: 'all' },
|
||||||
|
{ label: '边坡坍塌', value: '边坡坍塌' },
|
||||||
|
{ label: '泥石流', value: '泥石流' },
|
||||||
|
{ label: '路基沉陷', value: '路基沉陷' },
|
||||||
|
{ label: '山体滑坡', value: '山体滑坡' },
|
||||||
|
{ label: '行道树倒塌', value: '行道树倒塌' },
|
||||||
|
{ label: '积水', value: '积水' },
|
||||||
|
{ label: '积雪', value: '积雪' },
|
||||||
|
{ label: '其他', value: '其他' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 获取简短类型名称
|
||||||
|
const getShortTypeName = (type) => {
|
||||||
|
const typeMap = {
|
||||||
|
'边坡坍塌': '边坡',
|
||||||
|
'泥石流': '泥石',
|
||||||
|
'路基沉陷': '路基',
|
||||||
|
'山体滑坡': '滑坡',
|
||||||
|
'行道树倒塌': '树倒',
|
||||||
|
'积水': '积水',
|
||||||
|
'积雪': '积雪',
|
||||||
|
'其他': '其他'
|
||||||
|
}
|
||||||
|
return typeMap[type] || type.substring(0, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取灾毁列表数据
|
||||||
|
const getDisasterList = async (keyword = '', disasterType = 'all') => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await request({
|
||||||
|
url: '/snow-ops-platform/water-damage/list',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
keyword: keyword.trim(),
|
||||||
|
disasterType: disasterType === 'all' ? '' : disasterType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (result?.data?.records) {
|
||||||
|
list.value = result.data.records
|
||||||
|
} else {
|
||||||
|
showToast(result.message || '获取数据失败')
|
||||||
|
list.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取灾毁列表失败:', error)
|
||||||
|
showToast('获取数据失败,请稍后重试')
|
||||||
|
list.value = []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索处理
|
||||||
|
const handleSearch = () => {
|
||||||
|
getDisasterList(searchValue.value, selectedDisasterType.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击全部按钮
|
||||||
|
const handleAllClick = () => {
|
||||||
|
if (selectedDisasterType.value !== 'all') {
|
||||||
|
selectedDisasterType.value = 'all'
|
||||||
|
getDisasterList(searchValue.value, 'all')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选确认
|
||||||
|
const handleFilterConfirm = (type) => {
|
||||||
|
// selectedDisasterType 已经通过 v-model 更新,这里只需重新请求数据
|
||||||
|
getDisasterList(searchValue.value, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击返回
|
||||||
|
const handleClickBack = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击列表项
|
||||||
|
const handleClickItem = (item) => {
|
||||||
|
router.push({
|
||||||
|
path: '/disaster-detail',
|
||||||
|
query: {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
status: item.status,
|
||||||
|
occurTime: item.occurTime,
|
||||||
|
estimateRecoverTime: item.estimateRecoverTime,
|
||||||
|
disasterType: item.disasterType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 灾毁填报
|
||||||
|
const handleAdd = () => {
|
||||||
|
router.push('/disasterReport')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面初始化加载数据
|
||||||
|
onMounted(() => {
|
||||||
|
getDisasterList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.card-item) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jump-icon-absolute {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 16px;
|
||||||
|
color: rgba(102, 102, 102, 0.4);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disaster-type-wrapper {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btn {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: 340px;
|
||||||
|
border-radius: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.9;
|
||||||
|
transform: translateX(-50%) scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.van-button) {
|
||||||
|
&.active {
|
||||||
|
background-color: #1989fa;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #1989fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
148
packages/mobile/src/views/DisasterManagement/DisasterReport.vue
Normal file
148
packages/mobile/src/views/DisasterManagement/DisasterReport.vue
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<template>
|
||||||
|
<PageContainer title="灾毁填报" @click-back="handleClickBack" class="page-container">
|
||||||
|
<!-- 当前站点信息 -->
|
||||||
|
<CurrentSite />
|
||||||
|
|
||||||
|
<!-- 事件类型 -->
|
||||||
|
<PanelItem title="事件类型">
|
||||||
|
<van-radio-group v-model="eventType" direction="horizontal" class="event-type-group">
|
||||||
|
<van-radio name="water">水毁灾害</van-radio>
|
||||||
|
<van-radio name="ice">冰雪灾害</van-radio>
|
||||||
|
</van-radio-group>
|
||||||
|
</PanelItem>
|
||||||
|
|
||||||
|
<!-- 根据事件类型渲染不同表单 -->
|
||||||
|
<WaterDisaster
|
||||||
|
v-if="eventType === 'water'"
|
||||||
|
ref="waterDisasterRef"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 冰雪灾害表单(待实现) -->
|
||||||
|
<div v-else class="coming-soon">
|
||||||
|
<van-empty description="冰雪灾害表单开发中..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 提交按钮 -->
|
||||||
|
<van-button type="primary" class="footer-btn" @click="handleSubmit" :loading="submitting">
|
||||||
|
提交
|
||||||
|
</van-button>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { showToast, showSuccessToast, showFailToast } from 'vant'
|
||||||
|
import PageContainer from '@/components/PageContainer.vue'
|
||||||
|
import CurrentSite from '@/components/CurrentSite.vue'
|
||||||
|
import PanelItem from '@/components/PanelItem.vue'
|
||||||
|
import WaterDisaster from './WaterDisaster/WaterDisaster.vue'
|
||||||
|
import { request } from "@shared/utils/request";
|
||||||
|
import mockFormData from './waterDisasterFormData.json'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 事件类型
|
||||||
|
const eventType = ref('water')
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref(mockFormData)
|
||||||
|
const waterDisasterRef = ref(null)
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const handleClickBack = () => {
|
||||||
|
router.replace('/disasterManagement')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
// 验证表单
|
||||||
|
if (eventType.value === 'water') {
|
||||||
|
if (!waterDisasterRef.value.validate()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
// 获取表单数据
|
||||||
|
let formData = {}
|
||||||
|
if (eventType.value === 'water') {
|
||||||
|
formData = waterDisasterRef.value.getFormData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加事件类型和站点信息
|
||||||
|
const submitData = {
|
||||||
|
...formData,
|
||||||
|
// 可以在这里添加站点信息等其他数据
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await request({
|
||||||
|
url: '/snow-ops-platform/water-damage/addOrUpdate',
|
||||||
|
method: 'post',
|
||||||
|
data: submitData
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 模拟提交接口
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
showSuccessToast('提交成功')
|
||||||
|
|
||||||
|
// 提交成功后返回列表页
|
||||||
|
setTimeout(() => {
|
||||||
|
router.replace('/disasterManagement')
|
||||||
|
}, 1500)
|
||||||
|
} catch (error) {
|
||||||
|
showFailToast('提交失败,请重试')
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
waterDisasterRef.value.initFormData(formData.value)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-container {
|
||||||
|
padding-bottom: 80px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-type-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
:deep(.van-radio) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.coming-soon {
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btn {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 15px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: 340px;
|
||||||
|
border-radius: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.9;
|
||||||
|
transform: translateX(-50%) scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 损失计算弹窗 -->
|
||||||
|
<van-dialog
|
||||||
|
v-model:show="visible"
|
||||||
|
title="塌方损失信息"
|
||||||
|
show-cancel-button
|
||||||
|
@confirm="confirm"
|
||||||
|
@cancel="cancelLoss"
|
||||||
|
confirm-button-text="确定"
|
||||||
|
cancel-button-text="取消"
|
||||||
|
>
|
||||||
|
<div class="loss-dialog-content">
|
||||||
|
<!-- 塌方长 -->
|
||||||
|
<van-field v-model="formData.length" label="塌方长" placeholder="请填写长度" type="digit" clearable>
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">米</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<!-- 塌方宽 -->
|
||||||
|
<van-field v-model="formData.width" label="塌方宽" placeholder="请填写宽度" type="digit" clearable>
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">米</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<!-- 塌方高 -->
|
||||||
|
<van-field v-model="formData.height" label="塌方高" placeholder="请填写高度" type="digit" clearable>
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">米</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<!-- 单价 (0-2000元) -->
|
||||||
|
<van-field v-model="formData.unitPrice" label="单价(0-2000元)" placeholder="请填写单价" type="digit" clearable>
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">元</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<van-field v-model="formData.totalPrice" label="塌方损失金额" placeholder="请填写金额" type="digit" clearable>
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">元</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</div>
|
||||||
|
</van-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, reactive, watch, computed } from 'vue'
|
||||||
|
import { showToast } from 'vant'
|
||||||
|
|
||||||
|
// 弹窗显示状态
|
||||||
|
const visible = ref(false)
|
||||||
|
|
||||||
|
// 损失计算表单
|
||||||
|
const formData = ref({
|
||||||
|
length: '',
|
||||||
|
width: '',
|
||||||
|
height: '',
|
||||||
|
unitPrice: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalPrice = computed(() => {
|
||||||
|
const l = parseFloat(formData.value.length)
|
||||||
|
const w = parseFloat(formData.value.width)
|
||||||
|
const h = parseFloat(formData.value.height)
|
||||||
|
const price = parseFloat(formData.value.unitPrice)
|
||||||
|
if (isNaN(l) || l <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (isNaN(w) || w <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (isNaN(h) || h <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (isNaN(price) || price < 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if (price > 2000) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (l * w * h * price)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(totalPrice, ()=>{
|
||||||
|
formData.value.totalPrice = totalPrice.value
|
||||||
|
})
|
||||||
|
// 显示弹窗
|
||||||
|
const show = () => {
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证表单数据
|
||||||
|
const validateForm = () => {
|
||||||
|
const l = parseFloat(formData.length)
|
||||||
|
const w = parseFloat(formData.width)
|
||||||
|
const h = parseFloat(formData.height)
|
||||||
|
const price = parseFloat(formData.unitPrice)
|
||||||
|
|
||||||
|
if (isNaN(l) || l <= 0) {
|
||||||
|
showToast('请填写有效的塌方长度')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isNaN(w) || w <= 0) {
|
||||||
|
showToast('请填写有效的塌方宽度')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isNaN(h) || h <= 0) {
|
||||||
|
showToast('请填写有效的塌方高度')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isNaN(price) || price < 0) {
|
||||||
|
showToast('请填写有效的单价')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (price > 2000) {
|
||||||
|
showToast('单价不能超过2000元')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认损失计算
|
||||||
|
const confirm = () => {
|
||||||
|
if(!formData.value.totalPrice) {
|
||||||
|
showToast('请填写损失金额')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('confirm', formData.value.totalPrice)
|
||||||
|
// 重置表单
|
||||||
|
resetForm()
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消操作
|
||||||
|
const cancelLoss = () => {
|
||||||
|
resetForm()
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value.length = ''
|
||||||
|
formData.value.width = ''
|
||||||
|
formData.value.height = ''
|
||||||
|
formData.value.unitPrice = ''
|
||||||
|
formData.value.totalPrice
|
||||||
|
}
|
||||||
|
// 定义事件
|
||||||
|
const emit = defineEmits(['confirm'])
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<div class="loss-list">
|
||||||
|
<template v-for="item in modelValue">
|
||||||
|
<van-field v-model="item.totalAmount" :label="getItemLabel(item)" placeholder="请填写" type="digit" @click="cubeCalculateDialog.show()">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">方/万元</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</template>
|
||||||
|
<van-button size="small" block type="primary" plain @click="addLoss">添加损失</van-button>
|
||||||
|
<CubeCalculateDialog ref="cubeCalculateDialog" />
|
||||||
|
<LossPicker ref="lossPicker" :options="options" @confirm="confirmAddLoss" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
|
import CubeCalculateDialog from './CubeCalculateDialog.vue'
|
||||||
|
import { request } from '@shared/utils/request'
|
||||||
|
import LossPicker from './LossPicker.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const lossPicker = ref(null)
|
||||||
|
|
||||||
|
const options = ref({})
|
||||||
|
// 添加损失项
|
||||||
|
const addLoss = () => {
|
||||||
|
lossPicker.value?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmAddLoss = (item) => {
|
||||||
|
emit('update:modelValue', [...props.modelValue, item])
|
||||||
|
}
|
||||||
|
|
||||||
|
const cubeCalculateDialog = ref(null)
|
||||||
|
|
||||||
|
const getItemLabel = (item) => {
|
||||||
|
const loss = options.value.loss?.find((loss) => loss.value === item.lossTypeId)
|
||||||
|
return loss?.text
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLossDict = async (params) => {
|
||||||
|
const res = await request({
|
||||||
|
url: '/snow-ops-platform/water-damage/loss/typeAndInfo',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
options.value.loss = res.data.records.map((item)=>{
|
||||||
|
return {
|
||||||
|
text: item.lossTypeName,
|
||||||
|
value: item.lossTypeId,
|
||||||
|
item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getLossDict()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.field-unit {
|
||||||
|
color: #969799;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loss-dialog-content {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
:deep(.van-field) {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calculation-preview {
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
.preview-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-label {
|
||||||
|
color: #646566;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-value {
|
||||||
|
color: #ee0a24;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<van-popup v-model:show="showPicker" position="bottom" round>
|
||||||
|
<van-picker :columns="columns" :title="pickerTitle" show-toolbar @confirm="onConfirm" @cancel="showPicker = false" />
|
||||||
|
</van-popup>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, computed } from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['confirm'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const pickerTitle = ref("请选择损失类型")
|
||||||
|
|
||||||
|
|
||||||
|
const columns = computed(() => {
|
||||||
|
return props.options.loss || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const showPicker = ref(false)
|
||||||
|
|
||||||
|
const show = () => {
|
||||||
|
showPicker.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const clsoe = () => {
|
||||||
|
showPicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConfirm = ({ selectedValues, selectedOptions }) => {
|
||||||
|
|
||||||
|
emit('confirm', selectedOptions[0].item)
|
||||||
|
showPicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
clsoe
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@ -0,0 +1,524 @@
|
|||||||
|
<template>
|
||||||
|
<div class="water-disaster">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<PanelItem title="基本信息">
|
||||||
|
<van-form>
|
||||||
|
<!-- 路况类别 -->
|
||||||
|
<BasePicker v-model="formData.roadConditionType" :options="roadConditionOptions" label="路况类别" placeholder="请选择" />
|
||||||
|
|
||||||
|
<!-- 是否阻断 (event.isBlocked) -->
|
||||||
|
<BasePicker v-model="formData.event.isBlocked" :options="blockedOptions" label="是否阻断" placeholder="请选择" />
|
||||||
|
|
||||||
|
<!-- 抢修进度 (event.repairProgress) -->
|
||||||
|
<BasePicker v-model="formData.event.repairProgress" :options="repairProgressOptions" label="抢修进度" placeholder="请选择" />
|
||||||
|
|
||||||
|
<!-- 水毁处数 (event.damageCount) -->
|
||||||
|
<van-field v-model="formData.event.damageCount" label="水毁处数" placeholder="请填写" type="number" />
|
||||||
|
|
||||||
|
<!-- 阻断里程 (event.blockedMileage) -->
|
||||||
|
<van-field v-model="formData.event.blockedMileage" label="阻断里程" placeholder="请填写" type="digit">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">公里</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<!-- 发生时间 (顶层 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) -->
|
||||||
|
<van-field v-model="formData.routeNo" label="线路编号" placeholder="请填写" />
|
||||||
|
|
||||||
|
<!-- 起点桩号 (event.startStakeNo) -->
|
||||||
|
<van-field v-model="formData.event.startStakeNo" label="起点桩号(K)" placeholder="请填写" />
|
||||||
|
|
||||||
|
<!-- 起点桩经纬度 (event.startStakeLng / startStakeLat) -->
|
||||||
|
<div class="coordinate-row">
|
||||||
|
<van-field v-model="formData.event.startStakeLng" label="起点桩经度" placeholder="经度" class="coordinate-field" />
|
||||||
|
<van-field v-model="formData.event.startStakeLat" label="起点桩纬度" placeholder="纬度" class="coordinate-field" />
|
||||||
|
</div>
|
||||||
|
<div class="calibrate-coord-btn" @click="calibrateStartCoord">
|
||||||
|
<van-icon name="location-o" />
|
||||||
|
<span>校准经纬度</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 止点桩号 (event.endStakeNo) -->
|
||||||
|
<van-field v-model="formData.event.endStakeNo" label="止点桩号(K)" placeholder="请填写" />
|
||||||
|
|
||||||
|
<!-- 止点桩经纬度 (event.endStakeLng / endStakeLat) -->
|
||||||
|
<div class="coordinate-row">
|
||||||
|
<van-field v-model="formData.event.endStakeLng" label="止点桩经度" placeholder="经度" class="coordinate-field" />
|
||||||
|
<van-field v-model="formData.event.endStakeLat" label="止点桩纬度" placeholder="纬度" class="coordinate-field" />
|
||||||
|
</div>
|
||||||
|
<div class="calibrate-coord-btn" @click="calibrateEndCoord">
|
||||||
|
<van-icon name="location-o" />
|
||||||
|
<span>校准经纬度</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 路况位置 (event.endStakeNo) -->
|
||||||
|
<van-field v-model="formData.occurLocation" label="路况位置" placeholder="请填写" />
|
||||||
|
|
||||||
|
<!-- 阻断点小地名 (event.blockedPointName) -->
|
||||||
|
<van-field v-model="formData.event.blockedPointName" label="阻断点小地名" placeholder="请填写" />
|
||||||
|
</van-form>
|
||||||
|
</PanelItem>
|
||||||
|
|
||||||
|
<!-- 处置情况 (report) -->
|
||||||
|
<PanelItem title="处置情况">
|
||||||
|
<div class="disposal-measures">
|
||||||
|
<span class="measures-label">处置措施</span>
|
||||||
|
<div class="measures-options">
|
||||||
|
<van-checkbox-group v-model="disposalMeasuresArray" direction="horizontal">
|
||||||
|
<van-checkbox name="halfClose">半幅封闭</van-checkbox>
|
||||||
|
<van-checkbox name="fullClose">全副封闭</van-checkbox>
|
||||||
|
<van-checkbox name="bypass">便道通行</van-checkbox>
|
||||||
|
<van-checkbox name="normal">正常通行</van-checkbox>
|
||||||
|
</van-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 预计恢复时间 (report.expectRecoverTime) -->
|
||||||
|
<BaseDatePicker v-model="formData.report.expectRecoverTime" label="预计恢复时间" placeholder="请选择时间" :min-date="minDate" :max-date="maxDate" type="datetime" />
|
||||||
|
|
||||||
|
<!-- 实际恢复时间 (report.actualRecoverTime) -->
|
||||||
|
<BaseDatePicker v-model="formData.report.actualRecoverTime" label="实际恢复时间" placeholder="请选择时间" :min-date="minDate" :max-date="maxDate" type="datetime" />
|
||||||
|
</PanelItem>
|
||||||
|
|
||||||
|
<!-- 人员车辆 (report) -->
|
||||||
|
<PanelItem title="人员车辆">
|
||||||
|
<van-form>
|
||||||
|
<van-field v-model="formData.report.injuredCount" label="受伤人员" placeholder="请填写" type="number">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">人</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-field v-model="formData.report.deadCount" label="死亡人员" placeholder="请填写" type="number">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">人</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-field v-model="formData.report.strandedPersonCount" label="滞留人员" placeholder="请填写" type="number">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">人</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-field v-model="formData.report.damagedVehicleCount" label="损坏车辆" placeholder="请填写" type="number">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">辆</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-field v-model="formData.report.strandedVehicleCount" label="滞留车辆" placeholder="请填写" type="number">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">辆</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</van-form>
|
||||||
|
</PanelItem>
|
||||||
|
|
||||||
|
<!-- 灾毁损失 (lossList) -->
|
||||||
|
<PanelItem title="灾毁损失">
|
||||||
|
<LossList v-model="formData.lossList" />
|
||||||
|
<van-field v-model="formData.report.remark" label="处理情况" placeholder="请填写(选填)" />
|
||||||
|
<van-field v-model="formData.report.totalLossAmount" label="损失总金额" placeholder="请填写(选填)" type="digit">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">万元</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</PanelItem>
|
||||||
|
|
||||||
|
<!-- 投入资源 (report) -->
|
||||||
|
<PanelItem>
|
||||||
|
<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.investedFunds" label="已投资金" placeholder="请填写" type="digit">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">万元</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-field v-model="formData.report.siteDescription" label="现场描述" placeholder="请填写" type="textarea" rows="2" autosize />
|
||||||
|
</PanelItem>
|
||||||
|
|
||||||
|
<!-- 附件 (fileList) -->
|
||||||
|
<!-- <PanelItem title="附件">
|
||||||
|
<div class="attachment-tip">图片只能上传jpg/png文件,且不超过500kb;视频仅支持20s内的视频</div>
|
||||||
|
<div class="upload-area">
|
||||||
|
<van-uploader v-model="imageFileList" :after-read="afterImageRead" accept="image/jpeg,image/png" :max-size="500 * 1024" @oversize="onOversize" multiple :max-count="9">
|
||||||
|
<div class="upload-btn">
|
||||||
|
<van-icon name="photo-o" size="24" />
|
||||||
|
<span>上传图片</span>
|
||||||
|
</div>
|
||||||
|
</van-uploader>
|
||||||
|
<van-uploader v-model="videoFileList" :after-read="afterVideoRead" accept="video/*" :max-size="20 * 1024 * 1024" @oversize="onVideoOversize">
|
||||||
|
<div class="upload-btn">
|
||||||
|
<van-icon name="video-o" size="24" />
|
||||||
|
<span>上传视频</span>
|
||||||
|
</div>
|
||||||
|
</van-uploader>
|
||||||
|
</div>
|
||||||
|
<div v-if="videoFileList.length > 0 && videoFileList[0].content" class="video-preview">
|
||||||
|
<video :src="videoFileList[0].content" controls style="width: 100%; max-height: 200px"></video>
|
||||||
|
</div>
|
||||||
|
</PanelItem> -->
|
||||||
|
|
||||||
|
<PanelItem>
|
||||||
|
<!-- 是否需要恢复重建 (event.needsRecovery) -->
|
||||||
|
<BasePicker v-model="formData.event.needsRecovery" :options="needsRecoveryOptions" label="是否需要恢复重建" placeholder="请选择" />
|
||||||
|
<!-- 恢复重建预估费用 (event.estimatedRecoveryCost) -->
|
||||||
|
<van-field v-model="formData.event.estimatedRecoveryCost" label="恢复重建预估费用" placeholder="请填写" type="digit">
|
||||||
|
<template #button>
|
||||||
|
<span class="field-unit">万元</span>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</PanelItem>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
|
import { showToast, showFailToast } from 'vant'
|
||||||
|
import PanelItem from '@/components/PanelItem.vue'
|
||||||
|
import BasePicker from '@/components/BasePicker.vue'
|
||||||
|
import BaseDatePicker from '@/components/BaseDatePicker.vue'
|
||||||
|
import LossList from './LossList.vue'
|
||||||
|
|
||||||
|
|
||||||
|
// 处置措施数组(用于多选框组,需要转换为逗号分隔的字符串)
|
||||||
|
const disposalMeasuresArray = ref([])
|
||||||
|
|
||||||
|
// 附件列表
|
||||||
|
const imageFileList = ref([])
|
||||||
|
const videoFileList = ref([])
|
||||||
|
|
||||||
|
// 表单数据 - 按 Request 接口结构定义
|
||||||
|
const formData = reactive({
|
||||||
|
// 顶层字段
|
||||||
|
occurLocation: '', // 发生地点
|
||||||
|
occurTime: '', // 发生时间
|
||||||
|
roadConditionType: '', // 路况类别
|
||||||
|
routeNo: '', // 线路编号
|
||||||
|
|
||||||
|
// event 对象
|
||||||
|
event: {
|
||||||
|
blockedMileage: '', // 阻断里程
|
||||||
|
blockedPointName: '', // 阻断点小地名
|
||||||
|
contactPerson: '', // 联系人
|
||||||
|
contactPhone: '', // 联系电话
|
||||||
|
damageCount: '', // 水毁处数
|
||||||
|
district: '', // 上报区县
|
||||||
|
endStakeLat: '', // 止点纬度
|
||||||
|
endStakeLng: '', // 止点经度
|
||||||
|
endStakeNo: '', // 止点桩号
|
||||||
|
estimatedRecoveryCost: '', // 恢复重建预估费用
|
||||||
|
inspectionMileage: '', // 巡查里程
|
||||||
|
isBlocked: '', // 是否阻断
|
||||||
|
needsRecovery: '', // 是否需要恢复重建
|
||||||
|
repairProgress: '', // 抢修进度
|
||||||
|
reporterUnit: '', // 填报单位
|
||||||
|
startStakeLat: '', // 起点纬度
|
||||||
|
startStakeLng: '', // 起点经度
|
||||||
|
startStakeNo: '' // 起点桩号
|
||||||
|
},
|
||||||
|
|
||||||
|
// report 对象
|
||||||
|
report: {
|
||||||
|
actualRecoverTime: '', // 实际恢复时间
|
||||||
|
damagedVehicleCount: '', // 损坏车辆
|
||||||
|
deadCount: '', // 死亡人员
|
||||||
|
disposalMeasures: '', // 处置措施(逗号分隔)
|
||||||
|
expectRecoverTime: '', // 预计恢复时间
|
||||||
|
injuredCount: '', // 受伤人员
|
||||||
|
investedFunds: '', // 已投资金
|
||||||
|
investedMachinery: '', // 已投机械
|
||||||
|
investedManpower: '', // 已投人力
|
||||||
|
remark: '', // 处理情况/备注
|
||||||
|
siteDescription: '', // 现场描述
|
||||||
|
strandedPersonCount: '', // 滞留人员
|
||||||
|
strandedVehicleCount: '', // 滞留车辆
|
||||||
|
totalLossAmount: '' // 损失总金额
|
||||||
|
},
|
||||||
|
|
||||||
|
// lossList 数组
|
||||||
|
lossList: [],
|
||||||
|
|
||||||
|
// fileList 数组
|
||||||
|
fileList: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听处置措施数组变化,转换为逗号分隔的字符串存到 report.disposalMeasures
|
||||||
|
watch(
|
||||||
|
disposalMeasuresArray,
|
||||||
|
(newVal) => {
|
||||||
|
formData.report.disposalMeasures = newVal.join(',')
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听附件变化,同步到 fileList
|
||||||
|
watch(
|
||||||
|
imageFileList,
|
||||||
|
(newVal) => {
|
||||||
|
// 转换为接口需要的格式
|
||||||
|
formData.fileList = [
|
||||||
|
...imageFileList.value.map((f) => ({
|
||||||
|
fileName: f.file?.name || '',
|
||||||
|
fileSize: f.file?.size || 0,
|
||||||
|
fileType: 1, // 1-图片
|
||||||
|
fileUrl: f.content || ''
|
||||||
|
})),
|
||||||
|
...videoFileList.value.map((f) => ({
|
||||||
|
fileName: f.file?.name || '',
|
||||||
|
fileSize: f.file?.size || 0,
|
||||||
|
fileType: 2, // 2-视频
|
||||||
|
fileUrl: f.content || ''
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
videoFileList,
|
||||||
|
(newVal) => {
|
||||||
|
formData.fileList = [
|
||||||
|
...imageFileList.value.map((f) => ({
|
||||||
|
fileName: f.file?.name || '',
|
||||||
|
fileSize: f.file?.size || 0,
|
||||||
|
fileType: 1,
|
||||||
|
fileUrl: f.content || ''
|
||||||
|
})),
|
||||||
|
...newVal.map((f) => ({
|
||||||
|
fileName: f.file?.name || '',
|
||||||
|
fileSize: f.file?.size || 0,
|
||||||
|
fileType: 2,
|
||||||
|
fileUrl: f.content || ''
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从 report.disposalMeasures 初始化处置措施数组
|
||||||
|
watch(
|
||||||
|
() => formData.report.disposalMeasures,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal && typeof newVal === 'string') {
|
||||||
|
disposalMeasuresArray.value = newVal.split(',').filter(Boolean)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// BasePicker 选项数据
|
||||||
|
const roadConditionOptions = [
|
||||||
|
{ label: '高速公路', value: '高速公路' },
|
||||||
|
{ label: '国道', value: '国道' },
|
||||||
|
{ label: '省道', value: '省道' },
|
||||||
|
{ label: '县道', value: '县道' },
|
||||||
|
{ label: '乡道', value: '乡道' },
|
||||||
|
{ label: '村道', value: '村道' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const blockedOptions = [
|
||||||
|
{ label: '是', value: true },
|
||||||
|
{ label: '否', value: false }
|
||||||
|
]
|
||||||
|
|
||||||
|
const repairProgressOptions = [
|
||||||
|
{ label: '未抢修', value: '未抢修' },
|
||||||
|
{ label: '抢修中', value: '抢修中' },
|
||||||
|
{ label: '已完成', value: '已完成' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const needsRecoveryOptions = [
|
||||||
|
{ label: '是', value: true },
|
||||||
|
{ label: '否', value: false }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 时间选择器范围
|
||||||
|
const minDate = new Date(2020, 0, 1)
|
||||||
|
const maxDate = new Date(2030, 11, 31)
|
||||||
|
|
||||||
|
const initFormData = (newVal) => {
|
||||||
|
if (newVal && Object.keys(newVal).length > 0) {
|
||||||
|
// 深度合并数据
|
||||||
|
Object.assign(formData, {
|
||||||
|
occurLocation: newVal.occurLocation || '',
|
||||||
|
occurTime: newVal.occurTime || '',
|
||||||
|
roadConditionType: newVal.roadConditionType || '',
|
||||||
|
routeNo: newVal.routeNo || '',
|
||||||
|
event: { ...formData.event, ...(newVal.event || {}) },
|
||||||
|
report: { ...formData.report, ...(newVal.report || {}) },
|
||||||
|
lossList: newVal.lossList || [],
|
||||||
|
fileList: newVal.fileList || []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化处置措施数组
|
||||||
|
if (newVal.report?.disposalMeasures) {
|
||||||
|
disposalMeasuresArray.value = newVal.report.disposalMeasures.split(',').filter(Boolean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校准时间
|
||||||
|
const calibrateTime = () => {
|
||||||
|
const now = new Date()
|
||||||
|
const formatted = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
|
||||||
|
formData.occurTime = formatted
|
||||||
|
showToast('时间已校准为当前时间')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校准起点经纬度
|
||||||
|
const calibrateStartCoord = () => {
|
||||||
|
formData.event.startStakeLng = '108.41763025'
|
||||||
|
formData.event.startStakeLat = '108.41763025'
|
||||||
|
showToast('起点经纬度已校准')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校准止点经纬度
|
||||||
|
const calibrateEndCoord = () => {
|
||||||
|
formData.event.endStakeLng = '108.41763025'
|
||||||
|
formData.event.endStakeLat = '108.41763025'
|
||||||
|
showToast('止点经纬度已校准')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片上传处理
|
||||||
|
const afterImageRead = (file) => {
|
||||||
|
console.log('图片上传:', file)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOversize = () => {
|
||||||
|
showFailToast('图片大小不能超过500KB')
|
||||||
|
}
|
||||||
|
|
||||||
|
const afterVideoRead = (file) => {
|
||||||
|
console.log('视频上传:', file)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onVideoOversize = () => {
|
||||||
|
showFailToast('视频大小不能超过20MB')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露验证方法
|
||||||
|
const validate = () => {
|
||||||
|
if (!formData.occurTime) {
|
||||||
|
showToast('请填写发生时间')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!formData.routeNo) {
|
||||||
|
showToast('请填写线路编号')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取表单数据
|
||||||
|
const getFormData = () => {
|
||||||
|
return { ...formData }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
validate,
|
||||||
|
initFormData,
|
||||||
|
getFormData
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.water-disaster {
|
||||||
|
.coordinate-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
.coordinate-field {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calibrate-time-btn,
|
||||||
|
.calibrate-coord-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #1989fa;
|
||||||
|
margin: 8px 0 8px 12px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: #f0f7ff;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-unit {
|
||||||
|
color: #969799;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disposal-measures {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
.measures-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #323233;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.measures-options {
|
||||||
|
:deep(.van-checkbox-group) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
:deep(.van-checkbox) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #969799;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
.upload-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px dashed #dcdee0;
|
||||||
|
border-radius: 8px;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #969799;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.van-field__label) {
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
42
packages/mobile/src/views/DisasterManagement/mockData.json
Normal file
42
packages/mobile/src/views/DisasterManagement/mockData.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "G242金铃乡老窖坪发生积雪",
|
||||||
|
"status": "未解除",
|
||||||
|
"occurTime": "2025/10/10 20:29",
|
||||||
|
"estimateRecoverTime": "2025/10/10 20:29",
|
||||||
|
"disasterType": "积雪"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "S521白鹿镇X发生边坡坍塌",
|
||||||
|
"status": "已解除",
|
||||||
|
"occurTime": "2025/10/10 20:29",
|
||||||
|
"estimateRecoverTime": "2025/10/10 20:29",
|
||||||
|
"disasterType": "边坡坍塌"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "彭水S523发生边坡坍塌",
|
||||||
|
"status": "未解除",
|
||||||
|
"occurTime": "2025/10/10 20:29",
|
||||||
|
"estimateRecoverTime": "2025/10/10 20:29",
|
||||||
|
"disasterType": "路基沉陷"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "梁平蟠龙镇G318发生山体滑坡",
|
||||||
|
"status": "已解除",
|
||||||
|
"occurTime": "2025/10/10 20:29",
|
||||||
|
"estimateRecoverTime": "2025/10/10 20:29",
|
||||||
|
"disasterType": "山体滑坡"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "重庆市大足区XX县G201行道树倒塌",
|
||||||
|
"status": "已解除",
|
||||||
|
"occurTime": "2025/10/10 20:29",
|
||||||
|
"estimateRecoverTime": "2025/10/10 20:29",
|
||||||
|
"disasterType": "行道树倒塌"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"occurLocation": "G108国道 K2250+300处",
|
||||||
|
"occurTime": "2024-07-15 14:30:00",
|
||||||
|
"roadConditionType": "国道",
|
||||||
|
"routeNo": "G108",
|
||||||
|
"event": {
|
||||||
|
"blockedMileage": 1.5,
|
||||||
|
"blockedPointName": "磨盘山隧道口",
|
||||||
|
"contactPerson": "张明",
|
||||||
|
"contactPhone": "13812345678",
|
||||||
|
"damageCount": 3,
|
||||||
|
"district": "武侯区",
|
||||||
|
"endStakeLat": "30.658712",
|
||||||
|
"endStakeLng": "104.082356",
|
||||||
|
"endStakeNo": "K2251+200",
|
||||||
|
"estimatedRecoveryCost": 120.5,
|
||||||
|
"inspectionMileage": 25.6,
|
||||||
|
"isBlocked": true,
|
||||||
|
"needsRecovery": true,
|
||||||
|
"repairProgress": "抢修中",
|
||||||
|
"reporterUnit": "武侯区交通运输局",
|
||||||
|
"startStakeLat": "30.652145",
|
||||||
|
"startStakeLng": "104.075632",
|
||||||
|
"startStakeNo": "K2250+300"
|
||||||
|
},
|
||||||
|
"report": {
|
||||||
|
"damagedVehicleCount": 2,
|
||||||
|
"strandedPersonCount": 12,
|
||||||
|
"deadCount": 0,
|
||||||
|
"strandedVehicleCount": 12,
|
||||||
|
"disposalMeasures": "halfClose,bypass",
|
||||||
|
"actualRecoverTime": "2024-07-17 12:00:00",
|
||||||
|
"expectRecoverTime": "2024-07-18 18:00:00",
|
||||||
|
"injuredCount": 1,
|
||||||
|
"investedFunds": 35.8,
|
||||||
|
"investedMachinery": 6,
|
||||||
|
"investedManpower": 45,
|
||||||
|
"remark": "已组织抢险队伍进行抢通,便道已修建完成",
|
||||||
|
"siteDescription": "因持续强降雨导致山体滑坡,掩埋路面约50米,边坡垮塌严重",
|
||||||
|
"totalLossAmount": 85.6
|
||||||
|
},
|
||||||
|
"lossList": [
|
||||||
|
{
|
||||||
|
"length": 50,
|
||||||
|
"width": 8.5,
|
||||||
|
"height": 2.5,
|
||||||
|
"unitPrice": 380,
|
||||||
|
"totalAmount": 38.9,
|
||||||
|
"lossCategory": "路面损毁",
|
||||||
|
"remark": "沥青路面严重损坏"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"length": 30,
|
||||||
|
"width": 2.5,
|
||||||
|
"height": 6,
|
||||||
|
"unitPrice": 520,
|
||||||
|
"totalAmount": 23.4,
|
||||||
|
"lossCategory": "挡墙损毁",
|
||||||
|
"remark": "浆砌片石挡墙垮塌"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileList": []
|
||||||
|
}
|
||||||
@ -100,6 +100,13 @@ const gridItems = [
|
|||||||
params: { data: encodeURIComponent(JSON.stringify(yhzinfo.value)) },
|
params: { data: encodeURIComponent(JSON.stringify(yhzinfo.value)) },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: group106Icon,
|
||||||
|
text: "灾害管理",
|
||||||
|
to: {
|
||||||
|
name: "DisasterManagement",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: group105Icon,
|
icon: group105Icon,
|
||||||
text: "预警信息",
|
text: "预警信息",
|
||||||
@ -107,6 +114,13 @@ const gridItems = [
|
|||||||
name: "WarningMessage",
|
name: "WarningMessage",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: group106Icon,
|
||||||
|
text: '恢复重建',
|
||||||
|
to: {
|
||||||
|
name: 'Rebuild',
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 获取当前登录用于就职的养护站信息
|
// 获取当前登录用于就职的养护站信息
|
||||||
|
|||||||
169
packages/mobile/src/views/Rebuild/Rebuild.vue
Normal file
169
packages/mobile/src/views/Rebuild/Rebuild.vue
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<PageContainer title="恢复重建" @click-back="handleClickBack">
|
||||||
|
<SearchInput v-model="searchValue" />
|
||||||
|
|
||||||
|
<CurrentSite />
|
||||||
|
|
||||||
|
<div class="list-panel">
|
||||||
|
<CardItem v-for="(item, index) in list" :key="index" :title="`${item.area} ${item.rNumber} ${item.type}`"
|
||||||
|
@click="handleClickItem(item)">
|
||||||
|
<template #headerExtra>
|
||||||
|
<van-tag v-if="item.status === '审批通过'" type="success" plain size="medium">{{ item.status }}</van-tag>
|
||||||
|
<van-tag v-else-if="item.status === '审批驳回'" type="danger" plain size="medium">{{ item.status
|
||||||
|
}}</van-tag>
|
||||||
|
<van-tag v-else type="warning" plain size="medium">{{ item.status }}</van-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="left-info">
|
||||||
|
<div><span class="label">起止桩号:</span><span class="value">{{ item.stationNumber }}</span></div>
|
||||||
|
<div><span class="label">路况位置:</span><span class="value">{{ item.position }}</span></div>
|
||||||
|
<div><span class="label">提交日期:</span><span class="value">{{ item.publishTime }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="right-arrow" @click.stop="handleClickItem(item)">
|
||||||
|
<van-icon name="arrow" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardItem>
|
||||||
|
|
||||||
|
<!-- 空状态提示 -->
|
||||||
|
<EmptyBox v-if="list.length === 0" placeholder="暂无相关预警信息" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-button type="primary" class="add-btn" icon="plus" @click="handleAddDevice">
|
||||||
|
项目填报
|
||||||
|
</van-button>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import PageContainer from '@/components/PageContainer.vue'
|
||||||
|
import SearchInput from '@/components/SearchInput.vue'
|
||||||
|
import CurrentSite from '@/components/CurrentSite.vue'
|
||||||
|
import CardItem from '@/components/CardItem.vue'
|
||||||
|
import EmptyBox from '@/components/EmptyBox.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
const searchValue = ref('')
|
||||||
|
|
||||||
|
// 预警列表数据
|
||||||
|
const list = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
area: '彭水',
|
||||||
|
rNumber: 'G211',
|
||||||
|
type: '发生水毁道路崩塌恢复重建',
|
||||||
|
stationNumber: 'K1674.16-1678.84',
|
||||||
|
position: '徐家镇村口南分叉路口',
|
||||||
|
publishTime: '2025-05-20',
|
||||||
|
status: '审批通过'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
area: '巴南',
|
||||||
|
rNumber: 'S303',
|
||||||
|
type: '道路发生边坡坍塌',
|
||||||
|
stationNumber: 'K1674.16-1678.84',
|
||||||
|
position: '徐家镇村口南分叉路口',
|
||||||
|
publishTime: '2025-05-20',
|
||||||
|
status: '审批驳回'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
area: '彭水',
|
||||||
|
rNumber: 'G211',
|
||||||
|
type: '道路崩塌改造工程',
|
||||||
|
stationNumber: 'K1674.16-1678.84',
|
||||||
|
position: '徐家镇村口南分叉路口',
|
||||||
|
publishTime: '2025-05-20',
|
||||||
|
status: '审批通过'
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
const handleClickBack = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickItem = (item) => {
|
||||||
|
router.push({
|
||||||
|
name: 'RebuildDetails',
|
||||||
|
params: {
|
||||||
|
data: encodeURIComponent(JSON.stringify(item.id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddDevice = () => {
|
||||||
|
router.push('/rebuild-add')
|
||||||
|
// router.push({
|
||||||
|
// name: "RebuildAdd",
|
||||||
|
// params: {
|
||||||
|
// data: encodeURIComponent(JSON.stringify(11)),
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.list-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-arrow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 16px;
|
||||||
|
right: 16px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 44px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
154
packages/mobile/src/views/Rebuild/RebuildAdd.vue
Normal file
154
packages/mobile/src/views/Rebuild/RebuildAdd.vue
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<PageContainer title="项目填报" @click-back="handleClickBack">
|
||||||
|
<div class="content">
|
||||||
|
<PanelItem>
|
||||||
|
<van-form label-align="left" colon>
|
||||||
|
<van-field v-model="form.name" label="区县名称" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写区县名称' }]" />
|
||||||
|
<van-field v-model="form.name" label="线路编号" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写线路编号' }]" />
|
||||||
|
<van-field v-model="form.name" label="起点桩号" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写起点桩号' }]" />
|
||||||
|
<van-field v-model="form.name" label="止点桩号" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写止点桩号' }]" />
|
||||||
|
<van-field v-model="form.name" label="止点桩号" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写止点桩号' }]" />
|
||||||
|
<van-field v-model="form.number" label="实施里程" center placeholder="单位:公里" required type="number"
|
||||||
|
:rules="[{ required: true, message: '请填写实施里程' }]">
|
||||||
|
<template #extra>
|
||||||
|
公里
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-field v-model="form.name" label="塌方及损失" center placeholder="(方/万元)" required
|
||||||
|
:rules="[{ required: true, message: '请填写塌方及损失' }]" />
|
||||||
|
<van-field v-model="form.name" label="灾害类型" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写灾害类型' }]" />
|
||||||
|
<van-field v-model="form.name" label="地点路线" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写地点路线' }]" />
|
||||||
|
<van-field v-model="form.name" label="路况位置" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写路况位置' }]" />
|
||||||
|
<van-field v-model="form.name" label="阻断点小地名" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写阻断点小地名' }]" />
|
||||||
|
<van-field v-model="form.name" label="恢复重建预估费用" center placeholder="请填写" required
|
||||||
|
:rules="[{ required: true, message: '请填写恢复重建预估费用' }]">
|
||||||
|
<template #extra>
|
||||||
|
万元
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-field label="附件" center>
|
||||||
|
<template #input>
|
||||||
|
<van-uploader v-model="fileList" @delete="handleDelete" name="photos" :file-list="fileList"
|
||||||
|
:file-type="['image/jpeg', 'image/png']" :after-read="afterRead" multiple
|
||||||
|
:max-count="6" />
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</van-form>
|
||||||
|
|
||||||
|
</PanelItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-button type="primary" class="add-btn" icon="plus" @click="handleAdd">
|
||||||
|
提交
|
||||||
|
</van-button>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import PageContainer from '@/components/PageContainer.vue'
|
||||||
|
import { showToast, showLoadingToast } from "vant";
|
||||||
|
import PanelItem from '@/components/PanelItem.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const form = ref({})
|
||||||
|
const fileList = ref([]);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.params.data) {
|
||||||
|
const data = JSON.parse(decodeURIComponent(route.params.data));
|
||||||
|
console.log('@@@@data', data);
|
||||||
|
// todo 在有传参的时候 调用接口去获取数据 并且初始化表单
|
||||||
|
} else {
|
||||||
|
console.log('无传入数据');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClickBack = () => {
|
||||||
|
if (route.params.data) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
router.push('/rebuild')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件删除
|
||||||
|
const handleDelete = (file) => {
|
||||||
|
if (file.serverUrl) {
|
||||||
|
const index = form.photos.findIndex((p) => p.photoUrl === file.serverUrl);
|
||||||
|
if (index !== -1) {
|
||||||
|
form.photos.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文件上传
|
||||||
|
const afterRead = async (file) => {
|
||||||
|
try {
|
||||||
|
const toast = showLoadingToast({
|
||||||
|
message: "上传中...",
|
||||||
|
forbidClick: true,
|
||||||
|
duration: 0, // 设置为0表示不会自动关闭
|
||||||
|
});
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file.file);
|
||||||
|
const res = await request({
|
||||||
|
url: "/snow-ops-platform/file/upload",
|
||||||
|
method: "post",
|
||||||
|
data: formData,
|
||||||
|
});
|
||||||
|
toast.close();
|
||||||
|
if (res.code === "00000") {
|
||||||
|
form.photos.push({ photoUrl: res.data });
|
||||||
|
const index = fileList.value.findIndex((f) => f.file === file.file);
|
||||||
|
if (index !== -1) {
|
||||||
|
fileList.value[index].serverUrl = res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("form.photos", toRaw(form.photos));
|
||||||
|
console.log("fileList.value", fileList.value);
|
||||||
|
} else {
|
||||||
|
throw new Error(res.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.close();
|
||||||
|
showToast({
|
||||||
|
type: "fail",
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.content {
|
||||||
|
padding: 20px 0px 80px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 16px;
|
||||||
|
right: 16px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 44px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
104
packages/mobile/src/views/Rebuild/RebuildDetails.vue
Normal file
104
packages/mobile/src/views/Rebuild/RebuildDetails.vue
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<PageContainer title="项目填报" @click-back="handleClickBack">
|
||||||
|
<div class="content">
|
||||||
|
<PanelItem>
|
||||||
|
<div class="detail">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-title">{{ `${data.area} ${data.rNumber} ${data.type}` }}</div>
|
||||||
|
<div class="header-extra">
|
||||||
|
<van-tag v-if="data.status === '审批通过'" type="success" plain size="medium">{{ data.status
|
||||||
|
}}</van-tag>
|
||||||
|
<van-tag v-else-if="data.status === '审批驳回'" type="danger" plain size="medium">{{ data.status
|
||||||
|
}}</van-tag>
|
||||||
|
<van-tag v-else type="warning" plain size="medium">{{ data.status }}</van-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">区县名称: {{ data.area }}</div>
|
||||||
|
<div class="item">线路编号: {{ data.area }}</div>
|
||||||
|
<div class="item">起点桩号: {{ data.area }}</div>
|
||||||
|
<div class="item">止点桩号: {{ data.area }}</div>
|
||||||
|
<div class="item">实施里程: {{ data.area }}</div>
|
||||||
|
<div class="item">塌方及损失: {{ data.area }}</div>
|
||||||
|
<div class="item">灾害类型: {{ data.area }}</div>
|
||||||
|
<div class="item">地点路线: {{ data.area }}</div>
|
||||||
|
<div class="item">阻断点小地名: {{ data.area }}</div>
|
||||||
|
<div class="item">提交时间: {{ data.area }}</div>
|
||||||
|
<div class="item">恢复重建预估费用: {{ data.area }}</div>
|
||||||
|
</div>
|
||||||
|
</PanelItem>
|
||||||
|
<PanelItem title="附件">
|
||||||
|
<!-- 附件 -->
|
||||||
|
</PanelItem>
|
||||||
|
<PanelItem v-if="data.xxx">
|
||||||
|
<!-- 驳回理由 -->
|
||||||
|
</PanelItem>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import PageContainer from '@/components/PageContainer.vue'
|
||||||
|
import { showToast, showLoadingToast } from "vant";
|
||||||
|
import PanelItem from '@/components/PanelItem.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const data = ref({
|
||||||
|
area: '',
|
||||||
|
rNumber: '',
|
||||||
|
type: '',
|
||||||
|
status: '审批通过',
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.params.data) {
|
||||||
|
const data = JSON.parse(decodeURIComponent(route.params.data));
|
||||||
|
console.log('@@@@data', data);
|
||||||
|
// todo 在有传参的时候 调用接口去获取数据 并且初始化表单
|
||||||
|
} else {
|
||||||
|
console.log('无传入数据');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClickBack = () => {
|
||||||
|
router.push('/rebuild')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.content {
|
||||||
|
padding: 20px 0px 0px 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #4a4a4a;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -139,6 +139,16 @@ const routes = [
|
|||||||
breadcrumb: true,
|
breadcrumb: true,
|
||||||
parentRoute: 'warningManagement' // 用于在面包屑中建立父子关系
|
parentRoute: 'warningManagement' // 用于在面包屑中建立父子关系
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// 项目管理
|
||||||
|
{
|
||||||
|
path: '/projectManagement',
|
||||||
|
name: 'projectManagement',
|
||||||
|
component: () => import('../views/ProjectManagement_Rebuild/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '项目管理',
|
||||||
|
breadcrumb: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,372 @@
|
|||||||
|
<template>
|
||||||
|
<div class="detail-container">
|
||||||
|
<el-form ref="formRef" :model="form" label-position="right" label-width="auto"
|
||||||
|
style="max-height: 60vh; overflow-y: auto; padding-right: 50px" :rules="rules">
|
||||||
|
<el-row style="margin: 20px 0px;">
|
||||||
|
<h4>项目信息</h4>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="工程状态" prop="工程状态">
|
||||||
|
<el-radio-group v-model="form.zt">
|
||||||
|
<el-radio value="1">在建</el-radio>
|
||||||
|
<el-radio value="2">停工</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="所属区县" prop="所属区县">
|
||||||
|
<el-select v-model="qx" filterable remote reserve-keyword clearable placeholder="输入区县名称查询"
|
||||||
|
:remote-method="remoteMethod_qx" :loading="loading" @change="handleSelect_qx" value-key="index">
|
||||||
|
<el-option v-for="(item, index) in qxList" :key="index" :label="item.qxmc" :value="item.qxmc" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="项目名称" prop="项目名称">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="驻地名称" prop="驻地名称">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="驻地类型" prop="驻地类型">
|
||||||
|
<el-select>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="坐标点位" prop="坐标点位">
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-input aria-label="经度" placeholder="经度" />
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-input aria-label="纬度" placeholder="纬度" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="所属项目名称:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="项目类型:">
|
||||||
|
<el-select>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="建设单位:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="施工单位:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="行政区域:">
|
||||||
|
<el-select ></el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="驻地人数:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="驻地风险等级:">
|
||||||
|
<el-select ></el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="房建类型:">
|
||||||
|
<el-select ></el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="搬迁状态:">
|
||||||
|
<el-select ></el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row style="margin: 20px 0px;">
|
||||||
|
<h4>项目联系人信息</h4>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="吹哨人姓名:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="吹哨人电话:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="建设单位包保责任人姓名:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="建设单位包保责任人电话:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="施工单位包保责任人姓名:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="施工单位包保责任人电话:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="驻地包保责任人姓名:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="驻地包保责任人电话:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="区县级包保责任人姓名:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="区县级包保责任人电话:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="市级包保责任人姓名:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="市级包保责任人电话:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row style="margin: 20px 0px;">
|
||||||
|
<h4>其他信息</h4>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="备注:">
|
||||||
|
<el-input />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch, computed } from "vue";
|
||||||
|
import { request } from "@/utils/request";
|
||||||
|
const formRef = ref(null);
|
||||||
|
defineExpose({ formRef });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
form: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sfjwd = ref("是");
|
||||||
|
const qx = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
const selectOptions = ref([]);
|
||||||
|
const qxList = ref([]);
|
||||||
|
|
||||||
|
// 根据用户信息 查询角色列表
|
||||||
|
const getUserList = async (key) => {
|
||||||
|
try {
|
||||||
|
const keyword = key;
|
||||||
|
let url = "";
|
||||||
|
if (keyword) {
|
||||||
|
url = `/snow-ops-platform/yhzry/getUserByKey?key=${keyword}`;
|
||||||
|
} else {
|
||||||
|
url = `/snow-ops-platform/yhzry/getUserByKey?key=`;
|
||||||
|
}
|
||||||
|
const res = await request({
|
||||||
|
url: url,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
if (res.code === "00000") {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(res.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.message);
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择人员筛选
|
||||||
|
const remoteMethod = async (query) => {
|
||||||
|
if (query === "") {
|
||||||
|
selectOptions.value = [];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getUserList(query);
|
||||||
|
if (res) {
|
||||||
|
selectOptions.value = res;
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据区县名称 查询区县列表
|
||||||
|
const getQxList = async (key) => {
|
||||||
|
try {
|
||||||
|
const keyword = key;
|
||||||
|
let url = "";
|
||||||
|
if (keyword) {
|
||||||
|
url = `/snow-ops-platform/district/listDistricts?qxmc=${keyword}`;
|
||||||
|
} else {
|
||||||
|
url = `/snow-ops-platform/district/listDistricts?qxmc=`;
|
||||||
|
}
|
||||||
|
const res = await request({
|
||||||
|
url: url,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
if (res.code === "00000") {
|
||||||
|
return res.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(res.message);
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择区县筛选
|
||||||
|
const remoteMethod_qx = async (query) => {
|
||||||
|
loading.value = true;
|
||||||
|
const res = await getQxList(query);
|
||||||
|
if (res) {
|
||||||
|
qxList.value = res;
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择区县
|
||||||
|
const handleSelect_qx = (value) => {
|
||||||
|
props.form.qxmc = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择人员
|
||||||
|
const handleSelect = (value) => {
|
||||||
|
console.log("value", value);
|
||||||
|
props.form.fzrXm = value.realName;
|
||||||
|
props.form.fzrSjhm = value.phone;
|
||||||
|
props.form.fzrUserId = value.userId;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rules = computed(() => {
|
||||||
|
return {
|
||||||
|
mc: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (props.form.mc) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
callback(new Error("请输入服务站名称"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
qxmc: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (props.form.qxmc) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
callback(new Error("请选择所属区县"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fzr: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (props.form.fzrUserId && props.form.fzrXm && props.form.fzrSjhm) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
callback(new Error("请选择负责人"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
jd: [
|
||||||
|
{
|
||||||
|
required: sfjwd.value === "否",
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (props.form.jd) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
callback(new Error("请输入站点经度"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
wd: [
|
||||||
|
{
|
||||||
|
required: sfjwd.value === "否",
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (props.form.wd) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
callback(new Error("请输入站点纬度"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
205
packages/screen/src/views/ProjectManagement_Rebuild/index.js
Normal file
205
packages/screen/src/views/ProjectManagement_Rebuild/index.js
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import { h, ref, onMounted, reactive, watch, toRaw, nextTick } from "vue";
|
||||||
|
import { request } from "@/utils/request";
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import AddDialog from "./addDialog.vue";
|
||||||
|
|
||||||
|
const tableData = ref([]); // 表格数据
|
||||||
|
const modelVisible = ref(false); // 弹窗状态
|
||||||
|
const drawerVisible = ref(false); // 抽屉状态
|
||||||
|
// 弹窗内容
|
||||||
|
const model = reactive({
|
||||||
|
title: '',
|
||||||
|
content: null,
|
||||||
|
props: {},
|
||||||
|
onCancel: null,
|
||||||
|
onConfirm: null,
|
||||||
|
width: '',
|
||||||
|
});
|
||||||
|
const form = reactive({
|
||||||
|
});
|
||||||
|
const INIT_FORM = {
|
||||||
|
|
||||||
|
};
|
||||||
|
// 抽屉内容
|
||||||
|
const drawer = reactive({
|
||||||
|
title: '',
|
||||||
|
content: null,
|
||||||
|
props: {},
|
||||||
|
onCancel: null,
|
||||||
|
onConfirm: null,
|
||||||
|
direction: 'rtl',
|
||||||
|
size: '50%'
|
||||||
|
});
|
||||||
|
const dialogRef = ref(null); // 弹窗实例
|
||||||
|
const drawerRef = ref(null); // 抽屉实例
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "区县",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "路线编码",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "灾害类型",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "起点桩号",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "止点桩号",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "实施里程(公里)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "技术等级",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "总投资金额(万元)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "投资估算(万元)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "开工或预计开工时间",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "完工或预计完工时间",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "申报状态",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "审批状态",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "xxx",
|
||||||
|
label: "更新日期",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "操作",
|
||||||
|
fixed: "right",
|
||||||
|
width: 150,
|
||||||
|
render: (row) => () =>
|
||||||
|
h("div", { class: "action-btns" }, [
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{
|
||||||
|
type: "primary",
|
||||||
|
link: true,
|
||||||
|
onClick: async () => {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
() => "审批"
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{
|
||||||
|
type: "primary",
|
||||||
|
link: true,
|
||||||
|
style: "margin-left: 10px;",
|
||||||
|
onClick: async () => {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
() => "详情"
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 过滤条件
|
||||||
|
const filterData = reactive({
|
||||||
|
year: "",
|
||||||
|
code: "",
|
||||||
|
})
|
||||||
|
// 分页
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
pageSizes: [10, 20, 50],
|
||||||
|
layout: "prev, pager, next, jumper",
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
pagination.current = page;
|
||||||
|
pagination.pageSize = pageSize;
|
||||||
|
getTableData(filterData);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取预警列表
|
||||||
|
const getTableData = async (filterData) => {
|
||||||
|
try {
|
||||||
|
const res = await request({
|
||||||
|
url: '',
|
||||||
|
method: "GET",
|
||||||
|
params: {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 打开填报项目弹窗
|
||||||
|
const openAddDialog = () => {
|
||||||
|
model.title = '填报项目';
|
||||||
|
Object.assign(form, INIT_FORM);
|
||||||
|
model.props = {
|
||||||
|
form: form,
|
||||||
|
};
|
||||||
|
model.content = AddDialog;
|
||||||
|
model.onCancel = () => {
|
||||||
|
modelVisible.value = false;
|
||||||
|
};
|
||||||
|
model.onConfirm = async () => {
|
||||||
|
dialogType.value = '';
|
||||||
|
await dialogRef?.value?.dynamicComponentRef?.formRef.validate().then(() => {
|
||||||
|
console.log('@@@@@填报项目', form);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
ElMessage.error('请处理表单中的错误项');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
model.width = "70%"
|
||||||
|
modelVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
tableData,
|
||||||
|
filterData,
|
||||||
|
pagination,
|
||||||
|
columns,
|
||||||
|
|
||||||
|
modelVisible,
|
||||||
|
model,
|
||||||
|
drawerVisible,
|
||||||
|
drawer,
|
||||||
|
dialogRef,
|
||||||
|
drawerRef,
|
||||||
|
openAddDialog,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<div class="root">
|
||||||
|
<div class="search-box">
|
||||||
|
<el-date-picker v-model="script.filterData.year" type="year" placeholder="年度" format="YYYY"
|
||||||
|
style="width: 240px; margin-right: 10px" size="large" />
|
||||||
|
<el-input v-model="script.filterData.code" style="width: 240px; margin-right: 10px" size="large"
|
||||||
|
placeholder="路线编码" :suffix-icon="Search" />
|
||||||
|
</div>
|
||||||
|
<div class="event-box">
|
||||||
|
<el-button type="primary" @click="script.openAddDialog">项目填报</el-button>
|
||||||
|
<el-button type="primary" color="#952DE6" @click="">导出</el-button>
|
||||||
|
</div>
|
||||||
|
<DynamicTable :dataSource="script.tableData.value" :columns="script.columns" :autoHeight="true"
|
||||||
|
:pagination="script.pagination">
|
||||||
|
|
||||||
|
</DynamicTable>
|
||||||
|
<div class="model-box">
|
||||||
|
<MyDialog v-model="script.modelVisible.value" :title="script.model?.title"
|
||||||
|
:dynamicComponent="script.model?.content" :component-props="script.model?.props"
|
||||||
|
:onConfirm="script.model?.onConfirm" :onCancel="script.model?.onCancel" ref="dialogRef"
|
||||||
|
:width="script.model?.width">
|
||||||
|
</MyDialog>
|
||||||
|
<MyDrawer v-model="script.drawerVisible.value" :title="script.drawer?.title"
|
||||||
|
:dynamicComponent="script.drawer?.content" :component-props="script.drawer?.props"
|
||||||
|
:onConfirm="script.drawer?.onConfirm" :onCancel="script.drawer?.onCancel" ref="drawerRef"
|
||||||
|
:direction="script.drawer?.direction" :size="script.drawer?.size">
|
||||||
|
</MyDrawer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import DynamicTable from "../../component/DynamicTable";
|
||||||
|
import { Search } from "@element-plus/icons-vue";
|
||||||
|
import MyDialog from "../../component/MyDialog";
|
||||||
|
import MyDrawer from "../../component/MyDrawer";
|
||||||
|
import scriptFn from "./index.js";
|
||||||
|
const script = scriptFn();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.root {
|
||||||
|
height: 100%;
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-box {
|
||||||
|
margin: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user