WholeProcessPlatform/frontend-sjgl/src/views/conventionalHydropower/qiXiDi/EditFhModal.vue
2026-08-31 14:04:06 +08:00

567 lines
17 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="80vw"
: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="8">
<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="8">
<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="8">
<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="8">
<a-form-item label="图层编码" name="layerCode">
<a-input
v-model:value="formData.layerCode"
placeholder="请输入图层编码"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<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="8">
<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="8">
<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="8">
<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="8">
<a-form-item label="保护对象" name="bhdx">
<a-select
v-model:value="formData.bhdx"
mode="multiple"
placeholder="请选择保护对象"
allow-clear
show-search
:max-tag-count="2"
:filter-option="filterOption"
>
<a-select-option
v-for="item in fishOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>{{ item.label }}</a-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="保护范围" name="bhfw">
<a-input
v-model:value="formData.bhfw"
placeholder="请输入保护范围"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="保护核心长度(km)" name="bhhxcd">
<a-input-number
v-model:value="formData.bhhxcd"
placeholder="请输入"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="保护河流" name="bhhl">
<a-input
v-model:value="formData.bhhl"
placeholder="请输入保护河流"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="保护河段" name="bhhd">
<a-input
v-model:value="formData.bhhd"
placeholder="请输入保护河段"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="保护措施" name="bhcs">
<a-input
v-model:value="formData.bhcs"
placeholder="请输入保护措施"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<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="8">
<a-form-item label="投资(亿元)" name="inv">
<a-input-number
v-model:value="formData.inv"
placeholder="请输入"
:precision="3"
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 { message } from 'ant-design-vue';
import { addFhInfo, updateFhInfo } from '@/api/DataQueryMenuModule';
import { getEngInfoDropdown, selectForDropdown } from '@/api/select';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
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' }]
});
const engInfoOptions = ref<any[]>([]);
const baseNameOptions = computed(() =>
jidiSelectEventStore.jidiData
.filter((item: any) => item.wbsCode !== 'all')
.map((item: any) => ({ label: item.wbsName, value: item.wbsCode }))
);
const engLoading = ref(false);
const fieldLabelMap: Record<string, string> = {
stcd: '栖息地站码',
stnm: '栖息地名称',
stlc: '站址',
layerCode: '图层编码',
baseId: '水电基地',
rstcd: '所属电站',
bhdx: '保护对象',
bhfw: '保护范围',
bhhxcd: '保护核心长度(km)',
bhhl: '保护河流',
bhhd: '保护河段',
bhcs: '保护措施',
bhfs: '保护方式',
inv: '投资(亿元)',
lgtd: '经度(°)',
lttd: '纬度(°)'
};
const selectOptionsMap: Record<string, () => any[]> = {
baseId: () => baseNameOptions.value,
rstcd: () => engInfoOptions.value,
bhdx: () => fishOptions.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 '空';
// bhdx 多选:按逗号分隔,分别解析 label 再合并
if (field === 'bhdx') {
const arr = Array.isArray(value) ? value : String(value).split(',');
return arr
.map((v: any) => {
const trimmed = String(v).trim();
if (!trimmed) return '';
const opt = fishOptions.value.find(
(o: any) => String(o.value) === trimmed
);
return opt ? opt.label : trimmed;
})
.filter(Boolean)
.join('、');
}
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 fishOptions = ref<any[]>([]);
const getFishzy = async () => {
try {
const res = await selectForDropdown({
"select": ["name","id"],
"filter": {"logic": "and","filters": []}});
fishOptions.value = (res.data || []).map((item: any) => ({
label: item.name ,
value: item.id
}));
} catch (error) {
console.error('获取保护对象列表失败:', error);
}
};
watch(
() => formData.value,
newData => {
const original = originalRecord.value;
for (const key in newData) {
const oldVal = original[key];
const newVal = newData[key];
if (oldVal !== newVal) {
// bhdx 多选:数组 vs 字符串比较,需标准化
if (key === 'bhdx') {
const normalize = (v: any) => {
if (!v) return '';
const arr = Array.isArray(v) ? v : String(v).split(',');
return arr
.map((s: string) => s.trim())
.filter(Boolean)
.sort()
.join(',');
};
if (normalize(oldVal) === normalize(newVal)) continue;
}
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 };
// bhdx 多选回显:逗号分隔字符串 → 数组
const bhdxArr = converted.bhdx
? String(converted.bhdx)
.split(',')
.map((s: string) => s.trim())
.filter(Boolean)
: [];
formData.value = {
...converted,
bhdx: bhdxArr.length > 0 ? bhdxArr : undefined
};
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, fishOptions], () => {
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;
}
};
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();
}
getFishzy();
}
}
);
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);
// bhdx 多选:数组 → 逗号分隔字符串
if (Array.isArray(engInfo.bhdx)) engInfo.bhdx = engInfo.bhdx.join(',');
res = await addFhInfo({ 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;
});
// bhdx 多选:数组 → 逗号分隔字符串
if (Array.isArray(engInfo.bhdx)) engInfo.bhdx = engInfo.bhdx.join(',');
res = await updateFhInfo({ 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>