2026-04-27 09:28:16 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<a-table
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
:loading="fileLoading"
|
|
|
|
|
|
:data-source="fileTableData"
|
|
|
|
|
|
:columns="modalColumns"
|
2026-04-27 19:11:22 +08:00
|
|
|
|
:scroll="{ y: 500, x: '100%' }"
|
|
|
|
|
|
:pagination="false"
|
2026-04-29 14:40:16 +08:00
|
|
|
|
row-key="id"
|
2026-04-27 09:28:16 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template #bodyCell="{ column, record, index }">
|
2026-05-07 15:40:18 +08:00
|
|
|
|
<!--
|
|
|
|
|
|
<template v-if="column.key === 'index' || column.dataIndex === 'index'">
|
|
|
|
|
|
{{ index + 1 }}
|
|
|
|
|
|
</template> -->
|
2026-04-27 09:28:16 +08:00
|
|
|
|
<!-- 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) &&
|
2026-04-30 18:07:19 +08:00
|
|
|
|
record.warnings &&
|
|
|
|
|
|
record.warnings.includes(column.dataIndexKey)
|
2026-04-27 09:28:16 +08:00
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style="color: red; display: flex; align-items: center">
|
2026-04-28 19:27:42 +08:00
|
|
|
|
<span v-if="record[column.dataIndex]">{{ record[column.dataIndex] }}</span>
|
|
|
|
|
|
<span v-else> 请添加{{ column.title }}</span>
|
2026-04-27 09:28:16 +08:00
|
|
|
|
<exclamation-circle-outlined style="margin-left: 4px" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 3. 编辑状态下的单元格 (绑定到 editingData) -->
|
2026-04-28 19:27:42 +08:00
|
|
|
|
<template
|
|
|
|
|
|
v-else-if="
|
|
|
|
|
|
isEditing(index) && column.dataIndex != 'picpth' && column.dataIndex != 'vdpth'
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
2026-04-30 18:07:19 +08:00
|
|
|
|
<!-- 基地 -->
|
|
|
|
|
|
<!-- <template v-if="column.dataIndex === 'baseName'">
|
2026-04-27 09:28:16 +08:00
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="editingData.baseId"
|
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
|
show-search
|
2026-04-28 19:27:42 +08:00
|
|
|
|
allowClear
|
2026-04-27 09:28:16 +08:00
|
|
|
|
:filter-option="filterOption"
|
|
|
|
|
|
:loading="rowStates[index]?.baseLoading"
|
|
|
|
|
|
style="width: 100%"
|
2026-04-28 19:27:42 +08:00
|
|
|
|
@change="(val) => handleBaseChange(val, index, 'input')"
|
2026-04-27 09:28:16 +08:00
|
|
|
|
>
|
|
|
|
|
|
<a-select-option
|
|
|
|
|
|
v-for="opt in baseOptions"
|
|
|
|
|
|
:key="opt.baseid"
|
|
|
|
|
|
:value="opt.baseid"
|
|
|
|
|
|
:label="opt.basename"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ opt.basename }}
|
|
|
|
|
|
</a-select-option>
|
|
|
|
|
|
</a-select>
|
2026-04-30 18:07:19 +08:00
|
|
|
|
</template> -->
|
|
|
|
|
|
<!-- 流域名称 -->
|
|
|
|
|
|
<template v-if="column.dataIndex === 'hbrvnm'">
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="editingData.hbrvcd"
|
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
|
show-search
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
:filter-option="filterOption"
|
|
|
|
|
|
:loading="rowStates[index]?.hbrvcdLoading"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
@change="(val) => handleHbrvcdChange(val, index, 'input')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-select-option
|
|
|
|
|
|
v-for="opt in hbrvcdOptions"
|
|
|
|
|
|
:key="opt.hbrvcd"
|
|
|
|
|
|
:value="opt.hbrvcd"
|
|
|
|
|
|
:label="opt.hbrvnm"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ opt.hbrvnm }}
|
|
|
|
|
|
</a-select-option>
|
|
|
|
|
|
</a-select>
|
2026-04-27 09:28:16 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 电站名称 -->
|
|
|
|
|
|
<template v-else-if="column.dataIndex === 'ennm'">
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="editingData.rstcd"
|
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
|
show-search
|
2026-04-28 19:27:42 +08:00
|
|
|
|
allowClear
|
2026-04-27 09:28:16 +08:00
|
|
|
|
:filter-option="filterOption"
|
|
|
|
|
|
:loading="rowStates[index]?.engLoading"
|
|
|
|
|
|
style="width: 100%"
|
2026-04-30 18:07:19 +08:00
|
|
|
|
:disabled="!editingData.hbrvcd"
|
2026-04-28 19:27:42 +08:00
|
|
|
|
@change="(val) => handleEngChange(val, index, 'input')"
|
2026-04-27 09:28:16 +08:00
|
|
|
|
>
|
|
|
|
|
|
<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
|
2026-04-28 19:27:42 +08:00
|
|
|
|
allowClear
|
2026-04-27 09:28:16 +08:00
|
|
|
|
: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"
|
2026-04-30 18:07:19 +08:00
|
|
|
|
@change="(val) => delWarning(val, 'strdt')"
|
2026-04-27 09:28:16 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</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%"
|
2026-04-30 18:07:19 +08:00
|
|
|
|
@change="(val) => delWarning(val, 'direction')"
|
2026-04-27 09:28:16 +08:00
|
|
|
|
>
|
|
|
|
|
|
<a-select-option
|
|
|
|
|
|
v-for="item in direction"
|
|
|
|
|
|
:key="item.itemCode"
|
|
|
|
|
|
:value="item.itemCode"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ item.dictName }}
|
|
|
|
|
|
</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 数字输入框 -->
|
2026-04-28 19:27:42 +08:00
|
|
|
|
<template v-else-if="['fcnt'].includes(column.dataIndex)">
|
|
|
|
|
|
<a-input-number
|
|
|
|
|
|
v-model:value="editingData[column.dataIndex]"
|
|
|
|
|
|
style="width: 100%"
|
2026-05-05 22:38:24 +08:00
|
|
|
|
:step="1"
|
|
|
|
|
|
:precision="0"
|
2026-04-28 19:27:42 +08:00
|
|
|
|
:min="0"
|
2026-05-05 22:38:24 +08:00
|
|
|
|
@change="(val) => delWarning(val, column.dataIndex)"
|
2026-04-28 19:27:42 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else-if="['wt'].includes(column.dataIndex)">
|
2026-04-27 09:28:16 +08:00
|
|
|
|
<a-input-number
|
|
|
|
|
|
v-model:value="editingData[column.dataIndex]"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 是否鱼苗 -->
|
|
|
|
|
|
<template v-else-if="column.dataIndex === 'isfs'">
|
2026-04-30 18:07:19 +08:00
|
|
|
|
<a-radio-group
|
|
|
|
|
|
v-model:value="editingData.isfs"
|
|
|
|
|
|
@change="(val) => delWarning(val, 'isfs')"
|
|
|
|
|
|
>
|
2026-04-27 09:28:16 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
|
2026-04-28 19:27:42 +08:00
|
|
|
|
<template v-else-if="column.dataIndex === 'picpth'">
|
2026-04-27 19:11:22 +08:00
|
|
|
|
<div class="preview" v-for="(item, index) in record.picpthList">
|
2026-04-28 19:27:42 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="text"
|
|
|
|
|
|
:class="{ text_warning: record.picpthsWarnings.includes(item.name) }"
|
|
|
|
|
|
@click="emit('preview-click', record, 'image', index)"
|
|
|
|
|
|
>
|
2026-04-27 19:11:22 +08:00
|
|
|
|
{{ item.name }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-28 19:27:42 +08:00
|
|
|
|
<div v-if="record.picpthList.length == 0">暂无图片</div>
|
2026-04-27 19:11:22 +08:00
|
|
|
|
</template>
|
2026-04-28 19:27:42 +08:00
|
|
|
|
<template v-else-if="column.dataIndex === 'vdpth'">
|
|
|
|
|
|
<div class="preview" v-for="(item, index) in record.vdpthList">
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="text"
|
|
|
|
|
|
:class="{ text_warning: record.vdpthsWarnings.includes(item.name) }"
|
|
|
|
|
|
@click="emit('preview-click', record, 'video', index)"
|
|
|
|
|
|
>
|
2026-04-27 19:11:22 +08:00
|
|
|
|
{{ item.name }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-28 19:27:42 +08:00
|
|
|
|
<div v-if="record.vdpthList.length == 0">暂无视频</div>
|
2026-04-27 19:11:22 +08:00
|
|
|
|
</template>
|
2026-04-27 09:28:16 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</a-table>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-04-30 18:07:19 +08:00
|
|
|
|
import _ from "lodash";
|
2026-04-27 09:28:16 +08:00
|
|
|
|
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";
|
2026-04-30 18:07:19 +08:00
|
|
|
|
import {
|
|
|
|
|
|
getSelectForDropdown,
|
|
|
|
|
|
getEngInfoDropdown,
|
|
|
|
|
|
getFpssDropdown,
|
|
|
|
|
|
revalidateAndUpdateRow,
|
2026-05-07 15:40:18 +08:00
|
|
|
|
deleteRowById,
|
2026-04-30 18:07:19 +08:00
|
|
|
|
} from "@/api/select";
|
2026-04-27 09:28:16 +08:00
|
|
|
|
|
|
|
|
|
|
const props: any = defineProps({
|
2026-04-30 18:07:19 +08:00
|
|
|
|
taskId: { type: String, default: "" },
|
2026-05-07 15:40:18 +08:00
|
|
|
|
getFileList: { type: Function, default: () => {} },
|
2026-04-27 09:28:16 +08:00
|
|
|
|
fileTableData: { type: Array, default: () => [] },
|
|
|
|
|
|
fileLoading: { type: Boolean, default: false },
|
|
|
|
|
|
direction: { type: Array, default: () => [] },
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-07 15:40:18 +08:00
|
|
|
|
const emit = defineEmits(["preview-click", "update:fileLoading"]);
|
2026-04-27 09:28:16 +08:00
|
|
|
|
|
|
|
|
|
|
// --- 状态管理 ---
|
|
|
|
|
|
const editingRowIndex = ref<number | null>(null);
|
2026-04-30 18:07:19 +08:00
|
|
|
|
const hbrvcdOptions = ref<any[]>([]);
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const rowStates = reactive<Record<number, any>>({});
|
|
|
|
|
|
|
|
|
|
|
|
// 【核心】临时编辑数据,只在编辑模式下使用
|
|
|
|
|
|
const editingData = ref<any>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const modalColumns = ref([
|
2026-05-07 15:40:18 +08:00
|
|
|
|
{ dataIndex: "rowIndex", key: "rowIndex", title: "序号", width: 60, dataIndexKey: "rowIndex",align: "center",fixed: "left" },
|
2026-04-27 19:11:22 +08:00
|
|
|
|
{
|
2026-04-30 18:07:19 +08:00
|
|
|
|
dataIndex: "hbrvnm",
|
|
|
|
|
|
key: "hbrvnm",
|
|
|
|
|
|
dataIndexKey: "hbrvcd",
|
2026-04-27 19:11:22 +08:00
|
|
|
|
title: "流域",
|
|
|
|
|
|
width: 140,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "ennm",
|
|
|
|
|
|
key: "ennm",
|
|
|
|
|
|
dataIndexKey: "rstcd",
|
|
|
|
|
|
title: "电站名称",
|
|
|
|
|
|
width: 140,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "stnm",
|
|
|
|
|
|
key: "stnm",
|
|
|
|
|
|
dataIndexKey: "stcd",
|
|
|
|
|
|
title: "过鱼设施名称",
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
},
|
2026-04-28 19:27:42 +08:00
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "strdt",
|
|
|
|
|
|
key: "strdt",
|
|
|
|
|
|
dataIndexKey: "strdt",
|
|
|
|
|
|
title: "过鱼时间",
|
|
|
|
|
|
width: 190,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "ftpName",
|
|
|
|
|
|
key: "ftpName",
|
|
|
|
|
|
dataIndexKey: "ftp",
|
|
|
|
|
|
title: "鱼种类",
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
},
|
2026-04-27 09:28:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "isfs",
|
|
|
|
|
|
key: "isfs",
|
|
|
|
|
|
title: "是否鱼苗",
|
2026-04-28 19:27:42 +08:00
|
|
|
|
dataIndexKey: "isfs",
|
2026-04-27 09:28:16 +08:00
|
|
|
|
width: 130,
|
|
|
|
|
|
customRender: ({ text }: any) => {
|
|
|
|
|
|
const isYes = text === 1 || text === "1";
|
|
|
|
|
|
return h(Tag, { color: isYes ? "success" : "error", style: { margin: 0 } }, () =>
|
|
|
|
|
|
isYes ? "是" : "否"
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "direction",
|
2026-04-28 19:27:42 +08:00
|
|
|
|
dataIndexKey: "direction",
|
2026-04-27 09:28:16 +08:00
|
|
|
|
key: "direction",
|
|
|
|
|
|
title: "游向",
|
|
|
|
|
|
width: 120,
|
2026-04-28 19:27:42 +08:00
|
|
|
|
customRender: ({ text }: any) =>
|
|
|
|
|
|
props.direction.find((item: any) => item.itemCode === text)?.dictName || "-",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "fcnt",
|
|
|
|
|
|
key: "fcnt",
|
|
|
|
|
|
dataIndexKey: "fcnt",
|
|
|
|
|
|
title: "过鱼数量(尾)",
|
|
|
|
|
|
width: 120,
|
2026-04-27 09:28:16 +08:00
|
|
|
|
},
|
|
|
|
|
|
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 160 },
|
|
|
|
|
|
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 160 },
|
|
|
|
|
|
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
|
2026-04-27 19:11:22 +08:00
|
|
|
|
{ dataIndex: "picpth", key: "picpth", title: "图片", width: 160 },
|
|
|
|
|
|
{ dataIndex: "vdpth", key: "vdpth", title: "视频", width: 160 },
|
2026-04-27 09:28:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
title: "操作",
|
|
|
|
|
|
key: "action",
|
|
|
|
|
|
dataIndex: "action",
|
|
|
|
|
|
fixed: "right",
|
|
|
|
|
|
width: 100,
|
|
|
|
|
|
align: "center",
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// --- 初始化 ---
|
|
|
|
|
|
onMounted(() => {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
// loadBaseOptions();
|
|
|
|
|
|
loadHbrvcdOptions();
|
2026-04-27 09:28:16 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-30 18:07:19 +08:00
|
|
|
|
// const loadBaseOptions = async () => {
|
|
|
|
|
|
// try {
|
|
|
|
|
|
// const res = await getBaseDropdown({});
|
|
|
|
|
|
// let list = res.data || [];
|
|
|
|
|
|
// if (list && list.length > 0) {
|
|
|
|
|
|
// list = list.filter((item: any) => item.baseid !== "all");
|
|
|
|
|
|
// }
|
|
|
|
|
|
// baseOptions.value = list;
|
|
|
|
|
|
// } catch (e) {
|
|
|
|
|
|
// console.error("Load base options failed", e);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
const loadHbrvcdOptions = async () => {
|
2026-04-27 09:28:16 +08:00
|
|
|
|
try {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
const res = await getSelectForDropdown({});
|
2026-04-29 14:40:16 +08:00
|
|
|
|
let list = res.data || [];
|
2026-04-30 18:07:19 +08:00
|
|
|
|
if (list && list.length > 0) {
|
|
|
|
|
|
list = list.filter((item: any) => item.hbrvcd !== "all");
|
|
|
|
|
|
}
|
|
|
|
|
|
hbrvcdOptions.value = list;
|
2026-04-27 09:28:16 +08:00
|
|
|
|
} catch (e) {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
console.error("Load hbrvcd options failed", e);
|
2026-04-27 09:28:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const ensureRowState = (index: number) => {
|
|
|
|
|
|
if (!rowStates[index]) {
|
|
|
|
|
|
rowStates[index] = {
|
|
|
|
|
|
engOptions: [],
|
|
|
|
|
|
fpssOptions: [],
|
|
|
|
|
|
baseLoading: false,
|
2026-04-30 18:07:19 +08:00
|
|
|
|
hbrvcdLoading: false,
|
2026-04-27 09:28:16 +08:00
|
|
|
|
engLoading: false,
|
|
|
|
|
|
fpssLoading: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return rowStates[index];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// --- 级联逻辑 (操作 editingData) ---
|
2026-04-30 18:07:19 +08:00
|
|
|
|
// const handleBaseChange = async (
|
|
|
|
|
|
// hbrvcd: string,
|
|
|
|
|
|
// index: number,
|
|
|
|
|
|
// type: string = "start"
|
|
|
|
|
|
// ) => {
|
|
|
|
|
|
// const state = ensureRowState(index);
|
|
|
|
|
|
// editingData.value.baseName = hbrvcdOptions.value.find(
|
|
|
|
|
|
// (item: any) => item.hbrvcd == hbrvcd
|
|
|
|
|
|
// )?.basename;
|
|
|
|
|
|
// delWarning(hbrvcd, "hbrvcd");
|
|
|
|
|
|
// // 清空后续字段
|
|
|
|
|
|
// if (type != "start") {
|
|
|
|
|
|
// editingData.value.rstcd = undefined;
|
|
|
|
|
|
// editingData.value.stcd = undefined;
|
|
|
|
|
|
// editingData.value.ennm = undefined;
|
|
|
|
|
|
// editingData.value.stnm = undefined;
|
|
|
|
|
|
// state.engOptions = [];
|
|
|
|
|
|
// state.fpssOptions = [];
|
|
|
|
|
|
// }
|
|
|
|
|
|
// state.engLoading = true;
|
|
|
|
|
|
// try {
|
|
|
|
|
|
// const res = await getEngInfoDropdown({ hbrvcd });
|
|
|
|
|
|
// state.engOptions = res.data || [];
|
|
|
|
|
|
// } catch (e) {
|
|
|
|
|
|
// message.error("获取电站列表失败");
|
|
|
|
|
|
// } finally {
|
|
|
|
|
|
// state.engLoading = false;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// };
|
|
|
|
|
|
const handleHbrvcdChange = async (
|
|
|
|
|
|
hbrvcd: string,
|
2026-04-28 19:27:42 +08:00
|
|
|
|
index: number,
|
|
|
|
|
|
type: string = "start"
|
|
|
|
|
|
) => {
|
|
|
|
|
|
const state = ensureRowState(index);
|
2026-04-30 18:07:19 +08:00
|
|
|
|
editingData.value.hbrvnm = hbrvcdOptions.value.find(
|
|
|
|
|
|
(item: any) => item.hbrvcd == hbrvcd
|
|
|
|
|
|
)?.hbrvnm;
|
|
|
|
|
|
delWarning(hbrvcd, "hbrvcd");
|
|
|
|
|
|
if (hbrvcd == null) {
|
|
|
|
|
|
delWarning(null, "hbrvcd");
|
|
|
|
|
|
delWarning(null, "stcd");
|
|
|
|
|
|
}
|
2026-04-27 09:28:16 +08:00
|
|
|
|
// 清空后续字段
|
2026-04-28 19:27:42 +08:00
|
|
|
|
if (type != "start") {
|
|
|
|
|
|
editingData.value.rstcd = undefined;
|
|
|
|
|
|
editingData.value.stcd = undefined;
|
2026-04-30 18:07:19 +08:00
|
|
|
|
editingData.value.ennm = undefined;
|
|
|
|
|
|
editingData.value.stnm = undefined;
|
2026-04-28 19:27:42 +08:00
|
|
|
|
state.engOptions = [];
|
|
|
|
|
|
state.fpssOptions = [];
|
|
|
|
|
|
}
|
2026-04-27 09:28:16 +08:00
|
|
|
|
state.engLoading = true;
|
|
|
|
|
|
try {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
const res = await getEngInfoDropdown({ hbrvcd });
|
2026-04-27 09:28:16 +08:00
|
|
|
|
state.engOptions = res.data || [];
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
message.error("获取电站列表失败");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
state.engLoading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-28 19:27:42 +08:00
|
|
|
|
const handleEngChange = async (rstcd: string, index: number, type: string = "start") => {
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const state = ensureRowState(index);
|
2026-04-30 18:07:19 +08:00
|
|
|
|
editingData.value.ennm = state.engOptions.find(
|
|
|
|
|
|
(item: any) => item.stcd === rstcd
|
|
|
|
|
|
)?.ennm;
|
2026-04-28 19:27:42 +08:00
|
|
|
|
delWarning(rstcd, "rstcd");
|
2026-04-30 18:07:19 +08:00
|
|
|
|
if (rstcd == null) {
|
|
|
|
|
|
delWarning(null, "stcd");
|
|
|
|
|
|
}
|
|
|
|
|
|
// 清空后续字段
|
2026-04-28 19:27:42 +08:00
|
|
|
|
if (type != "start") {
|
|
|
|
|
|
editingData.value.stcd = undefined;
|
2026-04-30 18:07:19 +08:00
|
|
|
|
editingData.value.stnm = undefined;
|
2026-04-28 19:27:42 +08:00
|
|
|
|
state.fpssOptions = [];
|
2026-04-27 09:28:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
state.fpssLoading = true;
|
|
|
|
|
|
try {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
const res = await getFpssDropdown({ rstcd, hbrvcd: editingData.value.hbrvcd });
|
2026-04-27 09:28:16 +08:00
|
|
|
|
state.fpssOptions = res.data || [];
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
message.error("获取设施列表失败");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
state.fpssLoading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
const handleFpssChange = (stcd: string, index: number) => {
|
|
|
|
|
|
const state = ensureRowState(index);
|
2026-04-28 19:27:42 +08:00
|
|
|
|
delWarning(stcd, "stcd");
|
2026-04-27 09:28:16 +08:00
|
|
|
|
editingData.value.stnm = state.fpssOptions.find(
|
|
|
|
|
|
(item: any) => item.stcd === stcd
|
|
|
|
|
|
)?.stnm;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-28 19:27:42 +08:00
|
|
|
|
// 消除警告 / 添加警告
|
|
|
|
|
|
const delWarning = (val: any, key: string) => {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
// 确保 warnings 数组存在
|
|
|
|
|
|
if (!editingData.value.warnings) {
|
|
|
|
|
|
editingData.value.warnings = [];
|
2026-04-28 19:27:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-30 18:07:19 +08:00
|
|
|
|
const warnings = editingData.value.warnings;
|
2026-04-28 19:27:42 +08:00
|
|
|
|
const hasWarning = warnings.includes(key);
|
2026-04-27 09:28:16 +08:00
|
|
|
|
|
2026-04-30 18:07:19 +08:00
|
|
|
|
if (val !== null && val !== undefined && val !== "") {
|
2026-04-28 19:27:42 +08:00
|
|
|
|
// 1. 如果有值,且存在警告,则移除警告
|
|
|
|
|
|
if (hasWarning) {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
editingData.value.warnings = warnings.filter((w: string) => w !== key);
|
2026-04-28 19:27:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 2. 如果值为空 (null/undefined/''),且不存在警告,则添加警告
|
|
|
|
|
|
if (!hasWarning) {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
editingData.value.warnings.push(key);
|
2026-04-28 19:27:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// --- 编辑控制 ---
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const isEditing = (index: number) => editingRowIndex.value === index;
|
|
|
|
|
|
const startEdit = (index: number) => {
|
|
|
|
|
|
const originalRecord = props.fileTableData[index];
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 深拷贝原始数据到临时编辑区
|
2026-04-30 18:07:19 +08:00
|
|
|
|
let copiedData = JSON.parse(JSON.stringify(originalRecord));
|
2026-04-27 09:28:16 +08:00
|
|
|
|
|
2026-04-30 18:07:19 +08:00
|
|
|
|
editingData.value = copiedData;
|
2026-04-27 09:28:16 +08:00
|
|
|
|
// 2. 预处理:将 fsz/fwet 字符串拆分为 Min/Max 供输入框使用
|
|
|
|
|
|
processStringToMinMax(editingData.value);
|
|
|
|
|
|
|
|
|
|
|
|
editingRowIndex.value = index;
|
2026-04-30 18:07:19 +08:00
|
|
|
|
if (editingData.value.warnings.includes("hbrvcd")) {
|
|
|
|
|
|
editingData.value.hbrvcd = null;
|
|
|
|
|
|
editingData.value.hbrvnm = null;
|
|
|
|
|
|
editingData.value.rstcd = null;
|
|
|
|
|
|
editingData.value.stcd = null;
|
|
|
|
|
|
delWarning(null, "rstcd");
|
|
|
|
|
|
delWarning(null, "stcd");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (editingData.value.warnings.includes("rstcd")) {
|
|
|
|
|
|
editingData.value.stcd = null;
|
|
|
|
|
|
delWarning(null, "stcd");
|
|
|
|
|
|
}
|
2026-04-27 09:28:16 +08:00
|
|
|
|
|
|
|
|
|
|
// 3. 预加载下拉选项 (基于 editingData 的值)
|
2026-04-30 18:07:19 +08:00
|
|
|
|
if (editingData.value.hbrvcd == "" || editingData.value.hbrvcd == undefined) {
|
2026-04-28 19:27:42 +08:00
|
|
|
|
if (editingData.value.rstcd) {
|
2026-04-30 18:07:19 +08:00
|
|
|
|
handleHbrvcdChange("", index, "start").then(() => {
|
2026-04-28 19:27:42 +08:00
|
|
|
|
handleEngChange(editingData.value.rstcd, index, "start");
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
handleEngChange("", index, "start");
|
|
|
|
|
|
}
|
2026-04-30 18:07:19 +08:00
|
|
|
|
} else if (editingData.value.hbrvcd != "" && editingData.value.hbrvcd != undefined) {
|
|
|
|
|
|
handleHbrvcdChange(editingData.value.hbrvcd, index, "start").then(() => {
|
2026-04-28 19:27:42 +08:00
|
|
|
|
handleEngChange(editingData.value.rstcd, index, "start");
|
2026-04-27 09:28:16 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 辅助:字符串转 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;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-30 18:07:19 +08:00
|
|
|
|
const saveEdit = async (index: number) => {
|
2026-04-27 09:28:16 +08:00
|
|
|
|
// 1. 后处理:将 Min/Max 合并回 fsz/fwet
|
|
|
|
|
|
processMinMaxToString(editingData.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 创建新数组,替换对应索引的数据
|
|
|
|
|
|
const newData = [...props.fileTableData];
|
|
|
|
|
|
newData[index] = { ...editingData.value };
|
2026-04-30 18:07:19 +08:00
|
|
|
|
emit("update:fileLoading", true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res: any = await revalidateAndUpdateRow({
|
|
|
|
|
|
taskId: props.taskId,
|
|
|
|
|
|
data: newData[index],
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("保存成功");
|
2026-05-07 15:40:18 +08:00
|
|
|
|
props.getFileList();
|
2026-04-30 18:07:19 +08:00
|
|
|
|
editingRowIndex.value = null;
|
|
|
|
|
|
editingData.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
message.error("保存失败");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
emit("update:fileLoading", false);
|
|
|
|
|
|
}
|
2026-04-27 09:28:16 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const cancelEdit = () => {
|
|
|
|
|
|
editingRowIndex.value = null;
|
|
|
|
|
|
editingData.value = null;
|
|
|
|
|
|
// 由于我们从未修改过 props.fileTableData,所以不需要恢复操作,直接退出即可
|
|
|
|
|
|
};
|
2026-05-07 15:40:18 +08:00
|
|
|
|
// 删除按钮点击事件
|
|
|
|
|
|
const handlePreviewDelete = async (index: number) => {
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const newData = [...props.fileTableData];
|
2026-05-07 15:40:18 +08:00
|
|
|
|
emit("update:fileLoading", true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res: any = await deleteRowById({
|
|
|
|
|
|
taskId: props.taskId,
|
|
|
|
|
|
data: newData[index],
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("删除成功");
|
|
|
|
|
|
|
|
|
|
|
|
props.getFileList();
|
|
|
|
|
|
editingRowIndex.value = null;
|
|
|
|
|
|
editingData.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
message.error("删除失败");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
emit("update:fileLoading", false);
|
2026-04-27 09:28:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 鱼种类编辑 修改名称
|
|
|
|
|
|
const handleFtpChange = (val: any, opt: any) => {
|
|
|
|
|
|
editingData.value.ftpName = opt.name;
|
2026-04-28 19:27:42 +08:00
|
|
|
|
delWarning(val, "ftp");
|
2026-04-27 09:28:16 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-27 19:11:22 +08:00
|
|
|
|
// --- 辅助函数 ---
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const filterOption = (input: string, option: any) => {
|
|
|
|
|
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
defineExpose({
|
2026-04-27 19:11:22 +08:00
|
|
|
|
editingRowIndex,
|
2026-04-27 09:28:16 +08:00
|
|
|
|
editingData,
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
2026-04-27 19:11:22 +08:00
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.preview {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
color: #40a9ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: justify-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
.text {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.text_warning {
|
|
|
|
|
|
color: red;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|