WholeProcessPlatform/frontend-sjgl/src/views/conventionalHydropower/zhenXIZhiWuYuan/EditVpModal.vue
2026-08-20 16:58:14 +08:00

331 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<a-modal v-model:open="visible" :title="isAdd ? '新增珍稀植物园' : '编辑珍稀植物园'" width="60vw" :confirm-loading="confirmLoading" @ok="handleOk" @cancel="handleCancel">
<a-form ref="formRef" :model="formData" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" :rules="formRules" class="max-h-[70vh] overflow-y-auto pr-4">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="植物园编码" name="stcd">
<a-input v-model:value="formData.stcd" placeholder="请输入植物园编码" :disabled="!isAdd" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="植物园名称" name="stnm">
<a-input v-model:value="formData.stnm" placeholder="请输入植物园名称" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="测站类型" name="sttp">
<a-select v-model:value="formData.sttp" placeholder="请选择测站类型" allow-clear show-search :filter-option="filterOption">
<a-select-option v-for="item in sttpOptions" :key="item.value" :label="item.label" :value="item.value">{{ item.label }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="站址" name="stlc">
<a-input v-model:value="formData.stlc" placeholder="请输入站址" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="经度(°)" name="lgtd">
<a-input-number v-model:value="formData.lgtd" placeholder="请输入" :precision="6" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="纬度(°)" name="lttd">
<a-input-number v-model:value="formData.lttd" placeholder="请输入" :precision="6" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="水电基地" name="baseId">
<a-select v-model:value="formData.baseId" placeholder="请选择水电基地" allow-clear show-search :filter-option="filterOption">
<a-select-option v-for="item in baseNameOptions" :key="item.value" :label="item.label" :value="item.value">{{ item.label }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="所属电站" name="rstcd">
<a-select v-model:value="formData.rstcd" placeholder="请选择所属电站" allow-clear show-search :loading="engLoading" :filter-option="filterOption">
<a-select-option v-for="item in engInfoOptions" :key="item.value" :label="item.label" :value="item.value">{{ item.label }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="开工日期" name="swdt">
<a-date-picker v-model:value="formData.swdt" format="YYYY-MM-DD" value-format="YYYY-MM-DD HH:mm:ss" :disabled-date="disabledSwdtDate" placeholder="请选择开工日期" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="建成日期" name="jcdt">
<a-date-picker v-model:value="formData.jcdt" format="YYYY-MM-DD" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择建成日期" style="width: 100%" :disabled-date="disabledJcdtDate" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="保护方式" name="bhfs">
<a-input v-model:value="formData.bhfs" placeholder="请输入保护方式" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="面积(km²)" name="area">
<a-input-number v-model:value="formData.area" placeholder="请输入" :precision="2" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="投资(亿元)" name="inv">
<a-input-number v-model:value="formData.inv" placeholder="请输入" :precision="4" style="width: 100%" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
<ConfirmModal v-model:open="confirmModalVisible" :diff-list="diffList" :confirm-loading="confirmLoading" @confirm="handleConfirmSubmit" @cancel="handleConfirmCancel" />
</template>
<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue';
import dayjs from 'dayjs';
import { message } from 'ant-design-vue';
import { addVpInfo, updateVpInfo } from '@/api/DataQueryMenuModule';
import { getEngInfoDropdown } from '@/api/select';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { sttpGetKendoList } from '@/api/system/map/ConfigManagement';
import ConfirmModal from '@/components/ConfirmModal/index.vue';
import { useDraggable } from '@/utils/drag';
const props = defineProps<{ open: boolean; record?: any; isAdd?: boolean }>();
const emit = defineEmits(['update:open', 'success']);
const jidiSelectEventStore = useJidiSelectEventStore();
const visible = computed({ get: () => props.open, set: val => emit('update:open', val) });
useDraggable(visible, { boundary: true, resetOnOpen: true });
const formRef = ref();
const confirmLoading = ref(false);
const formData = ref<any>({});
const originalRecord = ref<any>({});
const originalLabels = ref<Record<string, string>>({});
const confirmModalVisible = ref(false);
const formRules = ref<any>({
stcd: [{ required: true, message: '请输入植物园编码', trigger: 'blur' }],
stnm: [{ required: true, message: '请输入植物园名称', trigger: 'blur' }],
sttp: [{ required: true, message: '请选择测站类型', trigger: 'change' }]
});
const engInfoOptions = ref<any[]>([]);
const sttpOptions = ref<any[]>([]);
const engLoading = ref(false);
const baseNameOptions = computed(() =>
jidiSelectEventStore.jidiData.filter((item: any) => item.wbsCode !== 'all').map((item: any) => ({ label: item.wbsName, value: item.wbsCode }))
);
const fieldLabelMap: Record<string, string> = {
stcd: '植物园编码', stnm: '植物园名称', stlc: '站址', sttp: '测站类型',
baseId: '水电基地', rstcd: '所属电站', swdt: '开工日期', jcdt: '建成日期',
bhfs: '保护方式', area: '面积(km²)', inv: '投资(亿元)',
lgtd: '经度(°)', lttd: '纬度(°)'
};
const selectOptionsMap: Record<string, () => any[]> = {
sttp: () => sttpOptions.value,
baseId: () => baseNameOptions.value,
rstcd: () => engInfoOptions.value
};
const changeOrder = ref<{ field: string; label: string; oldValue: string; newValue: string }[]>([]);
const diffList = changeOrder;
const resolveSelectLabel = (field: string, value: any): string => {
if (value === null || value === undefined || value === '') return '空';
const getOptions = selectOptionsMap[field];
if (getOptions) {
const opt = getOptions().find((o: any) => String(o.value) === String(value));
if (opt) return opt.label;
}
return String(value);
};
// 解析原始值的显示名rstcd 直接用 record 自带的电站名称 ennm不依赖异步选项
const resolveOriginalLabel = (field: string): string => {
const original = originalRecord.value;
if (field === 'rstcd') {
const ennm = original.ennm || original.rstcdName;
if (ennm) return String(ennm);
}
return resolveSelectLabel(field, original[field]);
};
const filterOption = (inputValue: string, option: any) => {
const label = option.label || option.value;
return label.includes(inputValue || '');
};
// 开工日期不能晚于建成日期
const disabledSwdtDate = (current: any) => {
if (!formData.value.jcdt) return false;
return current && current > dayjs(formData.value.jcdt).endOf('day');
};
// 建成日期不能早于开工日期
const disabledJcdtDate = (current: any) => {
if (!formData.value.swdt) return false;
return current && current < dayjs(formData.value.swdt).startOf('day');
};
watch(() => formData.value, newData => {
const original = originalRecord.value;
for (const key in newData) {
const oldVal = original[key];
const newVal = newData[key];
if (oldVal !== newVal) {
const existingIdx = changeOrder.value.findIndex(c => c.field === key);
const oldNorm = oldVal === null || oldVal === undefined || oldVal === '-' ? null : oldVal;
const newNorm = newVal === null || newVal === undefined ? null : newVal;
if (oldNorm === newNorm) continue;
if (oldNorm !== null && newNorm !== null && !isNaN(Number(oldNorm)) && !isNaN(Number(newNorm)) && Number(oldNorm) === Number(newNorm)) continue;
const isSelect = !!selectOptionsMap[key];
const oldDisplay = isSelect ? (originalLabels.value[key] ?? resolveSelectLabel(key, oldNorm)) : oldNorm === null ? '空' : String(oldNorm);
const newDisplay = isSelect ? resolveSelectLabel(key, newNorm) : newNorm === null ? '空' : String(newNorm);
const entry = { field: key, label: fieldLabelMap[key] || key, oldValue: oldDisplay, newValue: newDisplay };
if (existingIdx >= 0) changeOrder.value[existingIdx] = entry;
else changeOrder.value.push(entry);
} else {
changeOrder.value = changeOrder.value.filter(c => c.field !== key);
}
}
}, { deep: true });
// 监听 record 变化
const isInitLoading = ref(false);
watch(() => props.record, newRecord => {
if (newRecord) {
isInitLoading.value = true;
const converted = { ...newRecord };
// 将 "-" 还原为 null避免 date-picker 显示 Invalid Date
for (const key in converted) {
if (converted[key] === '-') {
converted[key] = null;
}
}
originalRecord.value = { ...converted };
formData.value = { ...converted };
changeOrder.value = [];
// 编辑时必定加载电站选项,保证 rstcd 回显与 diff 解析
loadEngOptions(converted.baseId || undefined);
// 提前解析 select 字段的原始显示名,防止联动刷新选项后丢失 label
const labels: Record<string, string> = {};
for (const key in converted) {
if (selectOptionsMap[key]) {
labels[key] = resolveOriginalLabel(key);
}
}
originalLabels.value = labels;
// 表单填充完成后自动复位标志,避免残留 true 导致后续切换流域不清空电站
nextTick(() => {
isInitLoading.value = false;
});
}
}, { deep: true });
// 选项加载完成后重新解析原始显示名,避免 record 加载时选项未就绪导致存成 code
watch(
[baseNameOptions, engInfoOptions, sttpOptions],
() => {
const original = originalRecord.value;
if (!original || Object.keys(original).length === 0) return;
const labels: Record<string, string> = {};
for (const key in original) {
if (selectOptionsMap[key]) {
labels[key] = resolveOriginalLabel(key);
}
}
originalLabels.value = labels;
}
);
const loadEngOptions = async (baseId?: string) => {
engLoading.value = true;
try {
const params = baseId ? { baseId } : {};
const res = await getEngInfoDropdown(params);
engInfoOptions.value = (res.data || []).map((item: any) => ({ label: item.ennm, value: item.stcd }));
} catch (error) {
console.error('获取电站列表失败:', error);
} finally {
engLoading.value = false;
}
};
const loadDropdownData = () => {
sttpGetKendoList({
filter: { logic: 'and', filters: [{ field: 'sttpCode', operator: 'in', dataType: 'string', value: ['VP', 'AI_VP_GRAZE', 'AI_VP_WATER'] }] }
}).then(res => {
sttpOptions.value = (res.data?.data || []).map((item: any) => ({ label: item.sttpName, value: item.sttpCode }));
});
// 从 store 获取水电基地数据computed 已自动响应,无需手动赋值)
};
// 水电基地变化时联动电站
watch(() => formData.value?.baseId, (newBaseId) => {
// record 加载时已在 record watch 中加载过电站选项,这里仅跳过清空
if (isInitLoading.value) {
return;
}
formData.value.rstcd = undefined;
loadEngOptions(newBaseId || undefined);
});
watch(() => props.open, val => {
if (val) {
if (props.isAdd) {
formData.value = {};
originalRecord.value = {};
changeOrder.value = [];
loadEngOptions();
}
loadDropdownData();
}
});
const handleOk = async () => {
try {
await formRef.value?.validateFields();
if (!props.isAdd && changeOrder.value.length === 0) { message.info('未检测到任何修改'); return; }
confirmModalVisible.value = true;
} catch (error) { console.error('验证失败:', error); }
};
const handleConfirmSubmit = async (source: string) => {
confirmLoading.value = true;
try {
const engInfo: Record<string, any> = {};
let res: any;
if (props.isAdd) {
Object.assign(engInfo, formData.value);
res = await addVpInfo({ engInfo, source: source.trim() || '新增' });
} else {
engInfo.stcd = formData.value.stcd;
changeOrder.value.forEach(item => {
// select 清空后值为 undefined/null统一转为空字符串避免序列化时字段被省略
let val = formData.value[item.field];
if (val === null || val === undefined) {
val = '';
}
engInfo[item.field] = val;
});
res = await updateVpInfo({ engInfo, source: source.trim() || '编辑' });
}
if (res?.code == 0 || res?.success) {
message.success(props.isAdd ? '新增成功' : '编辑成功');
confirmModalVisible.value = false;
visible.value = false;
emit('success');
} else { message.error(res?.msg || (props.isAdd ? '新增失败' : '编辑失败')); }
} catch (error) { message.error('提交失败,请重试'); }
finally { confirmLoading.value = false; }
};
const handleConfirmCancel = () => { confirmModalVisible.value = false; };
const handleCancel = () => { visible.value = false; confirmModalVisible.value = false; formRef.value?.resetFields(); };
</script>