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 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 21px 50px 17px 10px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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">
|
||||
<slot></slot>
|
||||
</div>
|
||||
@ -10,6 +10,13 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['back'])
|
||||
|
||||
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-block">
|
||||
<van-icon class="search-icon" name="search" />
|
||||
<input class="inner-input" v-model="modelValue" :placeholder="placeholder" />
|
||||
<van-icon class="close-icon" name="clear" v-if="modelValue !== ''" @click="modelValue = ''" />
|
||||
<input
|
||||
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 class="placeholder-block" v-if="modelValue === '111'">
|
||||
<van-icon class="search-icon" name="search" />
|
||||
<span class="placeholder-text">{{ placeholder }}</span>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
@ -26,7 +34,13 @@ const props = defineProps({
|
||||
default: '请输入关键词'
|
||||
}
|
||||
})
|
||||
|
||||
// 清空输入框
|
||||
const clearInput = () => {
|
||||
modelValue.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.search-input {
|
||||
position: relative;
|
||||
@ -37,19 +51,23 @@ const props = defineProps({
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
gap: 12px; // 输入框和按钮组间距
|
||||
padding-right: 12px; // 右侧内边距
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-block {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.search-icon, .close-icon {
|
||||
@ -59,10 +77,18 @@ const props = defineProps({
|
||||
transform: translateY(-50%);
|
||||
font-size: 20px;
|
||||
color: #9b9b9b;
|
||||
pointer-events: none; // 搜索图标不阻挡点击
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
left: unset;
|
||||
right: 20px;
|
||||
pointer-events: auto; // 清空图标可点击
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,28 +97,17 @@ const props = defineProps({
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.placeholder-block {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
.slot-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
|
||||
.search-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 3px;
|
||||
color: #9b9b9b;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
color: #9b9b9b;
|
||||
}
|
||||
gap: 8px; // 按钮间距
|
||||
flex-shrink: 0; // 防止压缩
|
||||
}
|
||||
</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',
|
||||
name: 'WarningMessageHandle',
|
||||
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)) },
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: group106Icon,
|
||||
text: "灾害管理",
|
||||
to: {
|
||||
name: "DisasterManagement",
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: group105Icon,
|
||||
text: "预警信息",
|
||||
@ -107,6 +114,13 @@ const gridItems = [
|
||||
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,
|
||||
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