567 lines
17 KiB
Vue
567 lines
17 KiB
Vue
<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>
|