WholeProcessPlatform/frontend-sjgl/src/views/conventionalHydropower/terrEco/EditOtteModal.vue

562 lines
17 KiB
Vue
Raw Normal View History

2026-07-21 18:38:17 +08:00
<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">
2026-09-02 10:44:17 +08:00
<a-input-number v-model:value="formData.lttd" placeholder="请输入" :precision="6" :min="-90" :max="90" style="width: 100%" />
</a-form-item>
</a-col>
2026-07-21 18:38:17 +08:00
<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"
2026-07-21 18:38:17 +08:00
: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="usfl">
<a-select
v-model:value="formData.usfl"
placeholder="请选择是否启用"
allow-clear
>
<a-select-option :value="1">启用</a-select-option>
<a-select-option :value="0">禁用</a-select-option>
</a-select>
</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%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="投资(亿元)" name="inv">
<a-input-number
v-model:value="formData.inv"
placeholder="请输入投资"
style="width: 100%"
:min="0"
:precision="4"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="监测频次(min)" name="dtfrqcy">
<a-input-number
v-model:value="formData.dtfrqcy"
placeholder="请输入监测频次"
style="width: 100%"
:min="0"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="基本特性参数" name="jbtxcs">
<a-input
v-model:value="formData.jbtxcs"
placeholder="请输入基本特性参数"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="所属建设阶段" name="blprd">
<a-select
v-model:value="formData.blprd"
placeholder="请选择建设阶段"
allow-clear
>
<a-select-option :value="1001000">规划</a-select-option>
<a-select-option :value="1002000">预可研</a-select-option>
<a-select-option :value="1003000">环评</a-select-option>
<a-select-option :value="1004000">可研</a-select-option>
<a-select-option :value="1005000">在建</a-select-option>
<a-select-option :value="1009000">建设</a-select-option>
<a-select-option :value="1010000">蓄水阶段环保验收</a-select-option>
<a-select-option :value="1011000">竣工</a-select-option>
<a-select-option :value="1012000">竣工环保验收</a-select-option>
<a-select-option :value="1013000">环境影响后评价</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-textarea
v-model:value="formData.remark"
placeholder="请输入备注"
2026-08-04 09:01:23 +08:00
:maxlength="150"
show-count
2026-07-21 18:38:17 +08:00
:rows="2"
/>
</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">
2026-08-04 09:01:23 +08:00
import { ref, computed, watch, nextTick } from 'vue';
2026-07-21 18:38:17 +08:00
import { message } from 'ant-design-vue';
import { addOtteInfo, updateOtteInfo } 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';
2026-07-22 09:06:17 +08:00
import { useDraggable } from '@/utils/drag';
2026-07-21 18:38:17 +08:00
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)
});
2026-07-22 09:06:17 +08:00
useDraggable(visible, { boundary: true, resetOnOpen: true });
2026-07-21 18:38:17 +08:00
const formRef = ref();
const confirmLoading = ref(false);
const formData = ref<any>({});
const originalRecord = ref<any>({});
2026-08-04 09:01:23 +08:00
const originalLabels = ref<Record<string, string>>({});
2026-07-21 18:38:17 +08:00
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' }]
2026-07-21 18:38:17 +08:00
});
// 下拉选项
const engInfoOptions = ref<any[]>([]);
const sttpOptions = ref<any[]>([]);
2026-08-04 09:01:23 +08:00
const baseNameOptions = computed(() =>
2026-07-21 18:38:17 +08:00
jidiSelectEventStore.jidiData
.filter((item: any) => item.wbsCode !== 'all')
2026-08-04 09:01:23 +08:00
.map((item: any) => ({ label: item.wbsName, value: item.wbsCode }))
2026-07-21 18:38:17 +08:00
);
const engLoading = ref(false);
2026-07-21 18:38:17 +08:00
// 字段名 → 中文名映射
const fieldLabelMap: Record<string, string> = {
stcd: '工程编码',
stnm: '工程名称',
sttp: '测站类型',
stlc: '位置',
lgtd: '经度(°)',
lttd: '纬度(°)',
2026-07-21 18:38:17 +08:00
baseId: '水电基地',
rstcd: '所属电站',
usfl: '是否启用',
jcdt: '建成日期',
inv: '投资',
dtfrqcy: '监测频次',
jbtxcs: '基本特性参数',
blprd: '所属建设阶段',
remark: '备注'
};
// select 选项映射
const selectOptionsMap: Record<string, () => any[]> = {
sttp: () => sttpOptions.value,
usfl: () => [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 }
],
blprd: () => [
{ label: '规划', value: 1001000 },
{ label: '预可研', value: 1002000 },
{ label: '环评', value: 1003000 },
{ label: '可研', value: 1004000 },
{ label: '在建', value: 1005000 },
{ label: '建设', value: 1009000 },
{ label: '蓄水阶段环保验收', value: 1010000 },
{ label: '竣工', value: 1011000 },
{ label: '竣工环保验收', value: 1012000 },
{ label: '环境影响后评价', value: 1013000 }
],
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);
};
2026-08-04 09:01:23 +08:00
// 解析原始值的显示名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]);
};
2026-07-21 18:38:17 +08:00
const filterOption = (inputValue: string, option: any) => {
const label = option.label || option.value;
const keyword = inputValue || '';
return label.includes(keyword);
};
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
2026-08-04 09:01:23 +08:00
? (originalLabels.value[key] ?? resolveSelectLabel(key, oldNorm))
2026-07-21 18:38:17 +08:00
: 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 }
);
2026-08-04 09:01:23 +08:00
// 监听 record 变化
const isInitLoading = ref(false);
2026-07-21 18:38:17 +08:00
watch(
() => props.record,
newRecord => {
if (newRecord) {
2026-08-04 09:01:23 +08:00
isInitLoading.value = true;
2026-07-21 18:38:17 +08:00
const converted = { ...newRecord };
2026-08-04 09:01:23 +08:00
// 将 "-" 还原为 null避免 date-picker 显示 Invalid Date
for (const key in converted) {
if (converted[key] === '-') {
converted[key] = null;
}
}
2026-07-21 18:38:17 +08:00
originalRecord.value = { ...converted };
formData.value = { ...converted };
changeOrder.value = [];
2026-08-04 09:01:23 +08:00
// 编辑时必定加载电站选项,保证 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;
});
2026-07-21 18:38:17 +08:00
}
},
{ deep: true }
);
2026-08-04 09:01:23 +08:00
// 选项加载完成后重新解析原始显示名,避免 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;
}
};
2026-07-21 18:38:17 +08:00
const loadDropdownData = () => {
sttpGetKendoList({
filter: {
logic: 'and',
filters: [
{
field: 'sttpCode',
operator: 'in',
dataType: 'string',
value: ['OTTE']
}
]
}
}).then(res => {
sttpOptions.value = (res.data?.data || []).map((item: any) => ({
label: item.sttpName,
value: item.sttpCode
}));
});
2026-08-04 09:01:23 +08:00
// 从 store 获取水电基地数据computed 已自动响应,无需手动赋值)
2026-07-21 18:38:17 +08:00
};
watch(() => formData.value?.baseId, (newBaseId) => {
2026-08-04 09:01:23 +08:00
// record 加载时已在 record watch 中加载过电站选项,这里仅跳过清空
if (isInitLoading.value) {
return;
}
formData.value.rstcd = undefined;
loadEngOptions(newBaseId || undefined);
});
2026-07-21 18:38:17 +08:00
watch(
() => props.open,
val => {
if (val) {
if (props.isAdd) {
formData.value = {};
originalRecord.value = {};
changeOrder.value = [];
loadEngOptions();
2026-07-21 18:38:17 +08:00
}
loadDropdownData();
2026-07-21 18:38:17 +08:00
}
}
);
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 addOtteInfo({
engInfo,
source: source.trim() || '新增'
});
} else {
engInfo.stcd = formData.value.stcd;
changeOrder.value.forEach(item => {
2026-08-17 08:55:12 +08:00
// select 清空后值为 undefined/null统一转为空字符串避免序列化时字段被省略
let val = formData.value[item.field];
if (val === null || val === undefined) {
val = '';
}
engInfo[item.field] = val;
2026-07-21 18:38:17 +08:00
});
res = await updateOtteInfo({
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>