WholeProcessPlatform/frontend/src/views/shuJuTianBao/guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoTable.vue

503 lines
15 KiB
Vue
Raw Normal View History

<template>
<a-table
size="small"
:loading="fileLoading"
:data-source="fileTableData"
:columns="modalColumns"
height="500"
:scroll="{ y: 500, x: '100%' }"
:row-key="(record, index) => index"
>
<template #bodyCell="{ column, record, index }">
<!-- 1. 操作列 -->
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
<div class="flex">
<template v-if="editingRowIndex === index">
<a-button type="link" size="small" @click="saveEdit(index)">保存</a-button>
<a-button type="link" size="small" @click="cancelEdit">取消</a-button>
</template>
<template v-else>
<a-button type="link" size="small" @click="startEdit(index)">编辑</a-button>
<a-button type="link" danger size="small" @click="handlePreviewDelete(index)"
>删除</a-button
>
</template>
</div>
</template>
<!-- 2. 警告提示列 (非编辑状态) -->
<template
v-else-if="
!isEditing(index) &&
column.dataIndex &&
record._warnings &&
record._warnings.includes(column.dataIndex)
"
>
<div style="color: red; display: flex; align-items: center">
<span>{{ getDisplayValue(column.dataIndex, record) }}</span>
<exclamation-circle-outlined style="margin-left: 4px" />
</div>
</template>
<!-- 3. 编辑状态下的单元格 (绑定到 editingData) -->
<template v-else-if="isEditing(index)">
<template v-if="column.dataIndex === 'baseName'">
<a-select
v-model:value="editingData.baseId"
placeholder="请选择"
show-search
:filter-option="filterOption"
:loading="rowStates[index]?.baseLoading"
style="width: 100%"
@change="(val) => handleBaseChange(val, index)"
>
<a-select-option
v-for="opt in baseOptions"
:key="opt.baseid"
:value="opt.baseid"
:label="opt.basename"
>
{{ opt.basename }}
</a-select-option>
</a-select>
</template>
<!-- 电站名称 -->
<template v-else-if="column.dataIndex === 'ennm'">
<a-select
v-model:value="editingData.rstcd"
placeholder="请选择"
show-search
:filter-option="filterOption"
:loading="rowStates[index]?.engLoading"
style="width: 100%"
:disabled="!editingData.baseId"
@change="(val) => handleEngChange(val, index)"
>
<a-select-option
v-for="opt in rowStates[index]?.engOptions || []"
:key="opt.stcd"
:value="opt.stcd"
:label="opt.ennm"
>
{{ opt.ennm }}
</a-select-option>
</a-select>
</template>
<!-- 过鱼设施 -->
<template v-else-if="column.dataIndex === 'stnm'">
<a-select
v-model:value="editingData.stcd"
placeholder="请选择"
show-search
:filter-option="filterOption"
:loading="rowStates[index]?.fpssLoading"
style="width: 100%"
:disabled="!editingData.rstcd"
@change="(val) => handleFpssChange(val, index)"
>
<a-select-option
v-for="opt in rowStates[index]?.fpssOptions || []"
:key="opt.stcd"
:value="opt.stcd"
:label="opt.stnm"
>
{{ opt.stnm }}
</a-select-option>
</a-select>
</template>
<!-- 过鱼时间 -->
<template v-else-if="column.dataIndex === 'strdt'">
<a-date-picker
v-model:value="editingData.strdt"
show-time
style="width: 100%"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</template>
<!-- 鱼种类 -->
<template v-else-if="column.dataIndex === 'ftpName'">
<fishSearch
v-model="editingData.ftp"
style="width: 100%"
@update:modelValue="handleFtpChange"
/>
</template>
<!-- 游向 -->
<template v-else-if="column.dataIndex === 'direction'">
<a-select
v-model:value="editingData.direction"
placeholder="请选择"
style="width: 100%"
>
<a-select-option
v-for="item in direction"
:key="item.itemCode"
:value="item.itemCode"
>
{{ item.dictName }}
</a-select-option>
</a-select>
</template>
<!-- 数字输入框 -->
<template v-else-if="['fcnt', 'wt'].includes(column.dataIndex)">
<a-input-number
v-model:value="editingData[column.dataIndex]"
style="width: 100%"
:min="0"
/>
</template>
<!-- 是否鱼苗 -->
<template v-else-if="column.dataIndex === 'isfs'">
<a-radio-group v-model:value="editingData.isfs">
<a-radio :value="1"></a-radio>
<a-radio :value="0"></a-radio>
</a-radio-group>
</template>
<!-- 体长 (编辑态使用 Min/Max) -->
<template v-else-if="column.dataIndex === 'fsz'">
<div class="flex">
<a-input-number
v-model:value="editingData.bodyLengthMin"
style="width: 50%"
:min="0"
/>
<span class="px-[2px]">~</span>
<a-input-number
v-model:value="editingData.bodyLengthMax"
style="width: 50%"
:min="0"
/>
</div>
</template>
<!-- 体重 (编辑态使用 Min/Max) -->
<template v-else-if="column.dataIndex === 'fwet'">
<div class="flex align-center">
<a-input-number
v-model:value="editingData.weightMin"
style="width: 50%"
:min="0"
/>
<span class="px-[2px]">~</span>
<a-input-number
v-model:value="editingData.weightMax"
style="width: 50%"
:min="0"
/>
</div>
</template>
<!-- 其他默认文本输入 -->
<template v-else>
<a-input v-model:value="editingData[column.dataIndex]" size="small" />
</template>
</template>
<!-- 4. 普通展示状态 (直接显示 record) -->
<!-- <template v-else>
{{ getDisplayValue(column.dataIndex, record) }}
</template> -->
</template>
</a-table>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, h } from "vue";
import { message, Tag } from "ant-design-vue";
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
import fishSearch from "@/components/fishSearch/index.vue";
import { getBaseDropdown, getEngInfoDropdown, getFpssDropdown } from "@/api/select";
import dayjs from "dayjs";
const props: any = defineProps({
fileTableData: { type: Array, default: () => [] },
fileLoading: { type: Boolean, default: false },
direction: { type: Array, default: () => [] },
});
const emit = defineEmits(["update:fileTableData"]);
// --- 状态管理 ---
const editingRowIndex = ref<number | null>(null);
const baseOptions = ref<any[]>([]);
const rowStates = reactive<Record<number, any>>({});
// 【核心】临时编辑数据,只在编辑模式下使用
const editingData = ref<any>(null);
const modalColumns = ref([
{ dataIndex: "baseName", key: "baseName", title: "流域", width: 140 },
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 140 },
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150 },
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 190 },
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
{
dataIndex: "isfs",
key: "isfs",
title: "是否鱼苗",
width: 130,
customRender: ({ text }: any) => {
const isYes = text === 1 || text === "1";
return h(Tag, { color: isYes ? "success" : "error", style: { margin: 0 } }, () =>
isYes ? "是" : "否"
);
},
},
{
dataIndex: "direction",
key: "direction",
title: "游向",
width: 120,
customRender: ({ text }: any) =>
props.direction.find((item: any) => item.itemCode === text)?.dictName || "-",
},
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 },
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 160 },
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 160 },
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
{ dataIndex: "picpth", key: "level5", title: "图片", width: 100 },
{ dataIndex: "vdpth", key: "level6", title: "视频", width: 100 },
{
title: "操作",
key: "action",
dataIndex: "action",
fixed: "right",
width: 100,
align: "center",
},
]);
// --- 初始化 ---
onMounted(() => {
loadBaseOptions();
});
const loadBaseOptions = async () => {
try {
const res = await getBaseDropdown({});
baseOptions.value = res.data || [];
} catch (e) {
console.error("Load base options failed", e);
}
};
const ensureRowState = (index: number) => {
if (!rowStates[index]) {
rowStates[index] = {
engOptions: [],
fpssOptions: [],
baseLoading: false,
engLoading: false,
fpssLoading: false,
};
}
return rowStates[index];
};
// --- 级联逻辑 (操作 editingData) ---
const handleBaseChange = async (baseId: string, index: number) => {
console.log(baseId)
editingData.value.baseName = baseOptions.value.find(
(item: any) => item.baseid == baseId
)?.basename;
if (baseId && editingData.value._warnings) {
editingData.value._warnings = editingData.value._warnings.filter((w: string) => w !== 'baseName');
}
const state = ensureRowState(index);
// 清空后续字段
editingData.value.rstcd = undefined;
editingData.value.stcd = undefined;
state.engOptions = [];
state.fpssOptions = [];
if (!baseId) return;
state.engLoading = true;
try {
const res = await getEngInfoDropdown({ baseId });
state.engOptions = res.data || [];
} catch (e) {
message.error("获取电站列表失败");
} finally {
state.engLoading = false;
}
};
const handleEngChange = async (rstcd: string, index: number) => {
const state = ensureRowState(index);
if (rstcd && editingData.value._warnings) {
editingData.value._warnings = editingData.value._warnings.filter((w: string) => w !== 'ennm');
}
editingData.value.ennm = state.engOptions.find(
(item: any) => item.stcd === rstcd
)?.ennm;
editingData.value.stcd = undefined;
state.fpssOptions = [];
if (!rstcd || !editingData.value.baseId) return;
state.fpssLoading = true;
try {
const res = await getFpssDropdown({ rstcd, baseId: editingData.value.baseId });
state.fpssOptions = res.data || [];
} catch (e) {
message.error("获取设施列表失败");
} finally {
state.fpssLoading = false;
}
};
const handleFpssChange = (stcd: string, index: number) => {
const state = ensureRowState(index);
if (stcd && editingData.value._warnings) {
editingData.value._warnings = editingData.value._warnings.filter((w: string) => w !== 'stnm');
}
editingData.value.stnm = state.fpssOptions.find(
(item: any) => item.stcd === stcd
)?.stnm;
};
// --- 编辑控制 ---
const isEditing = (index: number) => editingRowIndex.value === index;
const startEdit = (index: number) => {
const originalRecord = props.fileTableData[index];
// 1. 深拷贝原始数据到临时编辑区
editingData.value = JSON.parse(JSON.stringify(originalRecord));
// 2. 预处理:将 fsz/fwet 字符串拆分为 Min/Max 供输入框使用
processStringToMinMax(editingData.value);
editingRowIndex.value = index;
// 3. 预加载下拉选项 (基于 editingData 的值)
if (editingData.value.baseId && !editingData.value.rstcd) {
handleBaseChange(editingData.value.baseId, index);
} else if (editingData.value.baseId && editingData.value.rstcd) {
handleBaseChange(editingData.value.baseId, index).then(() => {
handleEngChange(editingData.value.rstcd, index);
});
}
};
// 辅助:字符串转 Min/Max
const processStringToMinMax = (data: any) => {
if (data.fsz) {
const sizes = String(data.fsz).split("~");
data.bodyLengthMin = sizes[0] || "";
data.bodyLengthMax = sizes[1] || sizes[0] || "";
} else {
data.bodyLengthMin = "";
data.bodyLengthMax = "";
}
if (data.fwet) {
const weights = String(data.fwet).split("~");
data.weightMin = weights[0] || "";
data.weightMax = weights[1] || weights[0] || "";
} else {
data.weightMin = "";
data.weightMax = "";
}
};
// 辅助Min/Max 转字符串
const processMinMaxToString = (data: any) => {
// 处理体长
if (data.bodyLengthMin !== "" || data.bodyLengthMax !== "") {
if (data.bodyLengthMin == data.bodyLengthMax) {
data.fsz = data.bodyLengthMin;
} else {
data.fsz = `${data.bodyLengthMin}~${data.bodyLengthMax}`;
}
} else {
data.fsz = "";
}
// 处理体重
if (data.weightMin !== "" || data.weightMax !== "") {
if (data.weightMin == data.weightMax) {
data.fwet = data.weightMin;
} else {
data.fwet = `${data.weightMin}~${data.weightMax}`;
}
} else {
data.fwet = "";
}
// 清理临时字段,避免污染原数据
delete data.bodyLengthMin;
delete data.bodyLengthMax;
delete data.weightMin;
delete data.weightMax;
};
const saveEdit = (index: number) => {
// 1. 后处理:将 Min/Max 合并回 fsz/fwet
processMinMaxToString(editingData.value);
// 2. 创建新数组,替换对应索引的数据
const newData = [...props.fileTableData];
newData[index] = { ...editingData.value };
// 3. 通知父组件更新
emit("update:fileTableData", newData);
// 4. 重置状态
editingRowIndex.value = null;
editingData.value = null;
message.success("保存成功");
};
const cancelEdit = () => {
editingRowIndex.value = null;
editingData.value = null;
// 由于我们从未修改过 props.fileTableData所以不需要恢复操作直接退出即可
};
const handlePreviewDelete = (index: number) => {
const newData = [...props.fileTableData];
newData.splice(index, 1);
emit("update:fileTableData", newData);
if (editingRowIndex.value === index) {
editingRowIndex.value = null;
editingData.value = null;
} else if (editingRowIndex.value !== null && editingRowIndex.value > index) {
editingRowIndex.value--;
}
message.success("删除成功");
};
// 鱼种类编辑 修改名称
const handleFtpChange = (val: any, opt: any) => {
editingData.value.ftpName = opt.name;
};
// --- 辅助函数 ---
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
// 统一获取显示值
const getDisplayValue = (dataIndex: string, record: any) => {
const val = record[dataIndex];
if (val === undefined || val === null) return "-";
if (dataIndex === "isfs") return val === 1 ? "是" : "否";
if (dataIndex === "strdt") return val ? dayjs(val).format("YYYY-MM-DD HH:mm:ss") : "-";
return val;
};
defineExpose({
editingRowIndex,
editingData,
});
</script>