2026-04-20 09:07:03 +08:00
|
|
|
|
<template>
|
2026-04-20 16:57:54 +08:00
|
|
|
|
<div class="guoYuSheShiShuJuTianBao-page">
|
|
|
|
|
|
<GuoYuSheShiShuJuTianBaoSearch
|
2026-04-24 15:31:32 +08:00
|
|
|
|
ref="searchRef"
|
|
|
|
|
|
:guoyuStatus="guoyuStatus"
|
|
|
|
|
|
:direction="direction"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
:handle-add="handleAdd"
|
2026-04-22 17:53:20 +08:00
|
|
|
|
:batchData="batchData"
|
2026-04-24 15:31:32 +08:00
|
|
|
|
:importBtn="importBtn"
|
|
|
|
|
|
:batchDelBtn="batchDelBtn"
|
|
|
|
|
|
:submitBtn="submitBtn"
|
|
|
|
|
|
:successBtn="successBtn"
|
|
|
|
|
|
@reset="handleReset"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
@search-finish="handleSearchFinish"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<!-- 主表格 -->
|
2026-04-22 17:53:20 +08:00
|
|
|
|
<BasicTable
|
|
|
|
|
|
ref="tableRef"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
:columns="columns"
|
2026-04-22 17:53:20 +08:00
|
|
|
|
:list-url="getFishDraftPage"
|
|
|
|
|
|
:search-params="{}"
|
|
|
|
|
|
:enable-row-selection="true"
|
2026-04-27 19:11:22 +08:00
|
|
|
|
:get-checkbox-props="getCheckboxProps"
|
|
|
|
|
|
:transform-data="customTransform"
|
2026-04-22 17:53:20 +08:00
|
|
|
|
@selection-change="handleSelectionChange"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
>
|
2026-04-22 17:53:20 +08:00
|
|
|
|
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
|
|
|
|
|
<template #bodyCell="{ column, record }">
|
|
|
|
|
|
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
|
|
|
|
|
<div class="flex">
|
2026-04-24 15:31:32 +08:00
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleSubmit([record.id])"
|
|
|
|
|
|
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
|
|
|
|
|
>提交</a-button
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleEdit(record, 'edit')"
|
2026-04-27 09:28:16 +08:00
|
|
|
|
v-if="
|
|
|
|
|
|
record.status === 'DRAFT' ||
|
|
|
|
|
|
record.status === 'REJECTED' ||
|
|
|
|
|
|
record.status === 'SUBMITTED'
|
|
|
|
|
|
"
|
2026-04-24 15:31:32 +08:00
|
|
|
|
>编辑</a-button
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
danger
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleDelete([record.id])"
|
|
|
|
|
|
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
2026-04-22 17:53:20 +08:00
|
|
|
|
>删除</a-button
|
|
|
|
|
|
>
|
2026-04-24 15:31:32 +08:00
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleEdit(record, 'view')"
|
2026-04-27 11:58:47 +08:00
|
|
|
|
v-if="record.status === 'SUBMITTED' || record.status === 'APPROVED'"
|
2026-04-24 15:31:32 +08:00
|
|
|
|
>查看</a-button
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleSuccess([record.id])"
|
|
|
|
|
|
v-if="record.status === 'SUBMITTED'"
|
|
|
|
|
|
>审批</a-button
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
danger
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleReject(record.id)"
|
|
|
|
|
|
v-if="record.status === 'SUBMITTED'"
|
|
|
|
|
|
>驳回</a-button
|
|
|
|
|
|
>
|
2026-04-22 17:53:20 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</BasicTable>
|
2026-04-24 15:31:32 +08:00
|
|
|
|
<!-- 隐藏的文件输入框 -->
|
|
|
|
|
|
<input
|
|
|
|
|
|
ref="fileInputRef"
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
accept=".zip,application/zip,application/x-zip-compressed"
|
|
|
|
|
|
style="display: none"
|
|
|
|
|
|
@change="handleFileSelect"
|
|
|
|
|
|
/>
|
2026-04-20 16:57:54 +08:00
|
|
|
|
<!-- 导入预览 Modal -->
|
|
|
|
|
|
<a-modal
|
|
|
|
|
|
title="导入数据预览"
|
|
|
|
|
|
ok-text="提交导入"
|
2026-04-24 15:31:32 +08:00
|
|
|
|
cancel-text="取消导入"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
:width="1500"
|
2026-04-22 17:53:20 +08:00
|
|
|
|
v-model:open="visible"
|
2026-04-27 19:11:22 +08:00
|
|
|
|
:maskClosable="false"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
:confirm-loading="fileLoading"
|
|
|
|
|
|
>
|
2026-04-27 09:28:16 +08:00
|
|
|
|
<GuoYuSheShiShuJuTianBaoTable
|
|
|
|
|
|
ref="modalTableRef"
|
|
|
|
|
|
:fileLoading="fileLoading"
|
|
|
|
|
|
:fileTableData="fileTableData"
|
|
|
|
|
|
:direction="direction"
|
2026-04-27 19:11:22 +08:00
|
|
|
|
@preview-click="handlePreviewClick"
|
|
|
|
|
|
@update:file-table-data="(val) => (fileTableData = val)"
|
2026-04-27 09:28:16 +08:00
|
|
|
|
/>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<a-button key="back" @click="handleCustomCancel">取消导入</a-button>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
key="submit"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
:loading="fileLoading"
|
|
|
|
|
|
@click="handleModalOk"
|
|
|
|
|
|
>
|
|
|
|
|
|
提交导入
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</template>
|
2026-04-20 16:57:54 +08:00
|
|
|
|
</a-modal>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 新增/编辑 Modal (对应 React 的 EditModal) -->
|
|
|
|
|
|
<EditModal
|
2026-04-22 17:53:20 +08:00
|
|
|
|
v-model:visible="editModalVisible"
|
2026-04-24 15:31:32 +08:00
|
|
|
|
:is-view="isView"
|
|
|
|
|
|
:direction="direction"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
:initial-values="currentRecord"
|
|
|
|
|
|
:loading="submitLoading"
|
|
|
|
|
|
@cancel="editModalCancel"
|
|
|
|
|
|
@ok="handleEditSubmit"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2026-04-27 19:11:22 +08:00
|
|
|
|
<!-- 媒体预览 Modal -->
|
2026-04-20 16:57:54 +08:00
|
|
|
|
<a-modal
|
2026-04-27 19:11:22 +08:00
|
|
|
|
v-model:open="mediaPreviewVisible"
|
|
|
|
|
|
:title="videoPreviewTitle"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
:footer="null"
|
2026-04-27 19:11:22 +08:00
|
|
|
|
width="900px"
|
|
|
|
|
|
@cancel="closeMediaPreview"
|
2026-04-20 16:57:54 +08:00
|
|
|
|
>
|
2026-04-27 19:11:22 +08:00
|
|
|
|
<div class="flex h-[60vh] gap-4">
|
|
|
|
|
|
<!-- 左侧:混合列表 (图片+视频) -->
|
|
|
|
|
|
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="(item, index) in previewList"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
class="mb-2 cursor-pointer list-item"
|
|
|
|
|
|
:class="{ select: index === currentMediaIndex }"
|
|
|
|
|
|
@click="currentMediaIndex = index"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="file-name">{{ item.name }}</span>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 删除按钮 -->
|
|
|
|
|
|
<div class="list-item-delete" @click.stop="handleDeleteMedia(item, index)">
|
|
|
|
|
|
<CloseCircleOutlined />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧:动态预览区域 -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 图片预览 -->
|
|
|
|
|
|
<a-image
|
|
|
|
|
|
v-if="
|
|
|
|
|
|
currentMediaItem &&
|
|
|
|
|
|
currentMediaItem.url != '' &&
|
|
|
|
|
|
currentMediaItem.type === 'image'
|
|
|
|
|
|
"
|
|
|
|
|
|
:src="currentMediaItem.url"
|
|
|
|
|
|
class="max-w-full max-h-full object-contain"
|
|
|
|
|
|
:preview="{ visible: false }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-else-if="
|
|
|
|
|
|
currentMediaItem &&
|
|
|
|
|
|
currentMediaItem.url == '' &&
|
|
|
|
|
|
currentMediaItem.type === 'image'
|
|
|
|
|
|
"
|
|
|
|
|
|
class="text-gray-400"
|
|
|
|
|
|
>
|
|
|
|
|
|
暂无图片预览
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 视频预览 -->
|
|
|
|
|
|
<video
|
|
|
|
|
|
v-else-if="
|
|
|
|
|
|
currentMediaItem &&
|
|
|
|
|
|
currentMediaItem.url != '' &&
|
|
|
|
|
|
currentMediaItem.type === 'video'
|
|
|
|
|
|
"
|
|
|
|
|
|
:src="currentMediaItem.url"
|
|
|
|
|
|
controls
|
|
|
|
|
|
autoplay
|
|
|
|
|
|
class="max-w-full max-h-[50vh]"
|
|
|
|
|
|
>
|
|
|
|
|
|
您的浏览器不支持视频播放
|
|
|
|
|
|
</video>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-else-if="
|
|
|
|
|
|
currentMediaItem &&
|
|
|
|
|
|
currentMediaItem.url == '' &&
|
|
|
|
|
|
currentMediaItem.type === 'video'
|
|
|
|
|
|
"
|
|
|
|
|
|
class="text-gray-400"
|
|
|
|
|
|
>
|
|
|
|
|
|
暂无视频预览
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 底部切换控制 -->
|
|
|
|
|
|
<div class="absolute bottom-4 flex gap-4 z-10">
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
shape="circle"
|
|
|
|
|
|
:icon="h(LeftOutlined)"
|
|
|
|
|
|
@click="prevMedia"
|
|
|
|
|
|
:disabled="currentMediaIndex === 0"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
|
|
|
|
|
|
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
shape="circle"
|
|
|
|
|
|
:icon="h(RightOutlined)"
|
|
|
|
|
|
@click="nextMedia"
|
|
|
|
|
|
:disabled="currentMediaIndex === previewList.length - 1"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-20 16:57:54 +08:00
|
|
|
|
</a-modal>
|
|
|
|
|
|
</div>
|
2026-04-20 09:07:03 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2026-04-27 09:28:16 +08:00
|
|
|
|
import { ref, computed, onMounted, h, nextTick } from "vue";
|
2026-04-22 17:53:20 +08:00
|
|
|
|
import { message, Modal } from "ant-design-vue"; // 假设使用 ant-design-vue
|
2026-04-27 19:11:22 +08:00
|
|
|
|
import { LeftOutlined, RightOutlined, CloseCircleOutlined } from "@ant-design/icons-vue"; // 引入图标组件
|
2026-04-22 17:53:20 +08:00
|
|
|
|
import BasicTable from "@/components/BasicTable/index.vue";
|
|
|
|
|
|
import GuoYuSheShiShuJuTianBaoSearch from "./guoYuSheShiShuJuTianBaoSearch.vue";
|
2026-04-27 09:28:16 +08:00
|
|
|
|
import GuoYuSheShiShuJuTianBaoTable from "./guoYuSheShiShuJuTianBaoTable.vue";
|
2026-04-22 17:53:20 +08:00
|
|
|
|
import EditModal from "./guoYuSheShiShuJuTianBaoForm.vue";
|
2026-04-27 19:11:22 +08:00
|
|
|
|
|
2026-04-22 17:53:20 +08:00
|
|
|
|
import {
|
|
|
|
|
|
getFishDraftPage,
|
|
|
|
|
|
addFishDraft,
|
|
|
|
|
|
editFishDraft,
|
|
|
|
|
|
delFishDraft,
|
2026-04-24 15:31:32 +08:00
|
|
|
|
submitFishDraft,
|
|
|
|
|
|
successFishDraft,
|
|
|
|
|
|
rejectFishDraft,
|
|
|
|
|
|
importFishZip,
|
|
|
|
|
|
cancelImportTask,
|
2026-04-27 09:28:16 +08:00
|
|
|
|
checkImportStatus,
|
|
|
|
|
|
batchSaveDraft,
|
|
|
|
|
|
getLastImportResult,
|
2026-04-27 19:11:22 +08:00
|
|
|
|
markImportTaskSuccess,
|
2026-04-22 17:53:20 +08:00
|
|
|
|
} from "@/api/guoYuSheShiShuJuTianBao";
|
2026-04-24 15:31:32 +08:00
|
|
|
|
import { Tag } from "ant-design-vue"; // 确保导入 Tag
|
|
|
|
|
|
import { getDictItemsByCode } from "@/api/dict";
|
2026-04-27 19:11:22 +08:00
|
|
|
|
import dayjs from "dayjs";
|
2026-04-20 16:57:54 +08:00
|
|
|
|
// import { FileImageOutlined, VideoCameraOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
|
|
|
|
|
|
2026-04-27 19:11:22 +08:00
|
|
|
|
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
|
2026-04-20 16:57:54 +08:00
|
|
|
|
// --- 类型定义 ---
|
|
|
|
|
|
interface FormData {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
[key: string]: any;
|
2026-04-20 16:57:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ColumnConfig {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
dataIndex: string;
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
width?: number;
|
2026-04-24 15:31:32 +08:00
|
|
|
|
fixed?: string;
|
2026-04-22 17:53:20 +08:00
|
|
|
|
customRender?: (text: any, record: any) => any;
|
2026-04-20 16:57:54 +08:00
|
|
|
|
}
|
2026-04-24 15:31:32 +08:00
|
|
|
|
const fileInputRef = ref<any>(null);
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const tableRef = ref<any>(null);
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// 字典项
|
|
|
|
|
|
const direction = ref<any>([]);
|
|
|
|
|
|
const guoyuStatus = ref<any>([]);
|
2026-04-20 16:57:54 +08:00
|
|
|
|
// --- 基础配置 ---
|
|
|
|
|
|
const baseColumnsConfig: ColumnConfig[] = [
|
2026-04-24 15:31:32 +08:00
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "baseName",
|
|
|
|
|
|
key: "baseName",
|
2026-04-27 09:28:16 +08:00
|
|
|
|
title: "流域",
|
2026-04-24 15:31:32 +08:00
|
|
|
|
width: 120,
|
|
|
|
|
|
fixed: "left",
|
|
|
|
|
|
},
|
|
|
|
|
|
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
|
|
|
|
|
|
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150, fixed: "left" },
|
2026-04-22 17:53:20 +08:00
|
|
|
|
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
|
2026-04-24 15:31:32 +08:00
|
|
|
|
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
|
2026-04-22 17:53:20 +08:00
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "isfs",
|
|
|
|
|
|
key: "isfs",
|
|
|
|
|
|
title: "是否鱼苗",
|
|
|
|
|
|
width: 74,
|
|
|
|
|
|
customRender: ({ text }: any) => {
|
2026-04-24 15:31:32 +08:00
|
|
|
|
const isYes = text === 1 || text === "1";
|
|
|
|
|
|
return h(
|
|
|
|
|
|
Tag,
|
|
|
|
|
|
{
|
|
|
|
|
|
color: isYes ? "success" : "error", // Antdv Tag 的颜色预设
|
|
|
|
|
|
style: { margin: 0 }, // 去除默认 margin,使其在表格中对齐更好
|
|
|
|
|
|
},
|
|
|
|
|
|
() => (isYes ? "是" : "否")
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
2026-04-22 17:53:20 +08:00
|
|
|
|
},
|
2026-04-24 15:31:32 +08:00
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "direction",
|
|
|
|
|
|
key: "direction",
|
|
|
|
|
|
title: "游向",
|
|
|
|
|
|
width: 80,
|
|
|
|
|
|
customRender: ({ text }: any) =>
|
|
|
|
|
|
direction.value.find((item: any) => item.itemCode === text)?.dictName || "-",
|
2026-04-22 17:53:20 +08:00
|
|
|
|
},
|
|
|
|
|
|
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 },
|
|
|
|
|
|
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 110 },
|
|
|
|
|
|
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 110 },
|
|
|
|
|
|
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
|
|
|
|
|
|
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 },
|
2026-04-24 15:31:32 +08:00
|
|
|
|
{
|
|
|
|
|
|
dataIndex: "status",
|
|
|
|
|
|
key: "status",
|
|
|
|
|
|
title: "状态",
|
2026-04-27 19:11:22 +08:00
|
|
|
|
width: 74,
|
2026-04-24 15:31:32 +08:00
|
|
|
|
customRender: ({ text }: any) => {
|
|
|
|
|
|
let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
|
|
|
|
|
|
return h(
|
|
|
|
|
|
Tag,
|
|
|
|
|
|
{
|
|
|
|
|
|
color: data?.custom1 || "error", // Antdv Tag 的颜色预设
|
|
|
|
|
|
},
|
|
|
|
|
|
() => data?.dictName || "-"
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
2026-04-22 17:53:20 +08:00
|
|
|
|
];
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
|
|
|
|
|
// --- 状态定义 ---
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const visible = ref(false); // 导入预览 Modal
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 编辑相关状态
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const editModalVisible = ref(false);
|
2026-04-24 15:31:32 +08:00
|
|
|
|
const isView = ref(false);
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const currentRecord = ref<FormData | null>(null);
|
|
|
|
|
|
const submitLoading = ref(false);
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-27 19:11:22 +08:00
|
|
|
|
const mediaPreviewVisible = ref(false);
|
|
|
|
|
|
const videoPreviewTitle = ref("视频预览");
|
|
|
|
|
|
interface MediaItem {
|
|
|
|
|
|
type: "image" | "video";
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
url: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
const previewList = ref<MediaItem[]>([]);
|
|
|
|
|
|
const currentMediaIndex = ref(0);
|
|
|
|
|
|
const tablePreviewRecord = ref<any>({});
|
|
|
|
|
|
|
|
|
|
|
|
// 计算当前选中的项
|
|
|
|
|
|
const currentMediaItem = computed(() => previewList.value[currentMediaIndex.value]);
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 表格数据
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const fileTableData = ref<any[]>([]);
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const orgFileTableData = ref<any[]>([]);
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const batchData = ref<any[]>([]);
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const modalTableRef = ref<any>(null);
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const fileLoading = ref(false);
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const taskId = ref<string>("");
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 主表格 Columns
|
|
|
|
|
|
const columns = computed(() => {
|
|
|
|
|
|
return [
|
|
|
|
|
|
...baseColumnsConfig.map((col) => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
if (col.dataIndex === "level5") {
|
2026-04-20 16:57:54 +08:00
|
|
|
|
return {
|
|
|
|
|
|
...col,
|
|
|
|
|
|
customRender: ({ text }: any) => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
if (!text) return "-";
|
|
|
|
|
|
// 实际应渲染 Icon 和点击事件,此处简化
|
|
|
|
|
|
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
2026-04-20 16:57:54 +08:00
|
|
|
|
}
|
2026-04-22 17:53:20 +08:00
|
|
|
|
if (col.dataIndex === "level6") {
|
2026-04-20 16:57:54 +08:00
|
|
|
|
return {
|
|
|
|
|
|
...col,
|
|
|
|
|
|
customRender: ({ text }: any) => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
if (!text) return "-";
|
|
|
|
|
|
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
2026-04-20 16:57:54 +08:00
|
|
|
|
}
|
2026-04-22 17:53:20 +08:00
|
|
|
|
return { ...col, visible: true };
|
2026-04-20 16:57:54 +08:00
|
|
|
|
}),
|
|
|
|
|
|
{
|
2026-04-22 17:53:20 +08:00
|
|
|
|
title: "操作",
|
|
|
|
|
|
key: "action",
|
|
|
|
|
|
dataIndex: "action",
|
|
|
|
|
|
fixed: "right",
|
2026-04-24 15:31:32 +08:00
|
|
|
|
width: 200,
|
2026-04-22 17:53:20 +08:00
|
|
|
|
align: "center",
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
});
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
|
|
|
|
|
// --- 业务逻辑方法 ---
|
|
|
|
|
|
|
|
|
|
|
|
const handleAdd = () => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
currentRecord.value = null;
|
|
|
|
|
|
editModalVisible.value = true;
|
|
|
|
|
|
};
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// 修改
|
|
|
|
|
|
const handleEdit = (record: any, type: string) => {
|
|
|
|
|
|
if (type == "view") {
|
|
|
|
|
|
isView.value = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
isView.value = false;
|
|
|
|
|
|
}
|
2026-04-22 17:53:20 +08:00
|
|
|
|
currentRecord.value = { ...record };
|
|
|
|
|
|
editModalVisible.value = true;
|
|
|
|
|
|
};
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// 单个/批量 删除过鱼数据
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const handleDelete = (ids: any[]) => {
|
2026-04-20 16:57:54 +08:00
|
|
|
|
Modal.confirm({
|
2026-04-24 15:31:32 +08:00
|
|
|
|
title: "是否确认 删除 选中数据吗?",
|
2026-04-22 17:53:20 +08:00
|
|
|
|
onOk: async () => {
|
|
|
|
|
|
let res: any = await delFishDraft(ids);
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("删除成功");
|
|
|
|
|
|
tableRef.value?.getList();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// 批量删除 - 按钮
|
|
|
|
|
|
const batchDelBtn = () => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
handleDelete(batchData.value);
|
|
|
|
|
|
};
|
2026-04-24 15:31:32 +08:00
|
|
|
|
//单个/ 批量提交过鱼数据
|
|
|
|
|
|
const handleSubmit = (ids: any[]) => {
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: "是否 提交 选中数据吗?",
|
|
|
|
|
|
onOk: async () => {
|
|
|
|
|
|
let res: any = await submitFishDraft(ids);
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("提交成功");
|
|
|
|
|
|
tableRef.value?.getList();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
// 批量提交-按钮
|
|
|
|
|
|
const submitBtn = async () => {
|
|
|
|
|
|
handleSubmit(batchData.value);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 单个/ 批量审批过鱼数据
|
|
|
|
|
|
const handleSuccess = (ids: any[]) => {
|
|
|
|
|
|
let rejectReason = "";
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: "是否确认 审批通过 选中数据?",
|
|
|
|
|
|
// 使用 h 函数创建内容
|
|
|
|
|
|
content: h("div", { style: "margin-top: 10px;" }, [
|
|
|
|
|
|
h("div", { style: "margin-bottom: 8px;" }, [
|
|
|
|
|
|
h("span", { style: "color: red;" }, "* "),
|
|
|
|
|
|
h("span", "审批原因:"),
|
|
|
|
|
|
]),
|
|
|
|
|
|
h("textarea", {
|
|
|
|
|
|
class: "ant-input", // 使用 antdv 的样式类,使其看起来像 Antdv 输入框
|
|
|
|
|
|
placeholder: "请输入审批原因",
|
|
|
|
|
|
rows: 4,
|
|
|
|
|
|
style: {
|
|
|
|
|
|
width: "100%",
|
|
|
|
|
|
resize: "none",
|
|
|
|
|
|
border: "1px solid #d9d9d9",
|
|
|
|
|
|
padding: "8px",
|
|
|
|
|
|
borderRadius: "4px",
|
|
|
|
|
|
},
|
|
|
|
|
|
// 监听输入事件,更新局部变量
|
|
|
|
|
|
onInput: (e: Event) => {
|
|
|
|
|
|
rejectReason = (e.target as HTMLTextAreaElement).value;
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
]),
|
|
|
|
|
|
okText: "确认审批",
|
|
|
|
|
|
cancelText: "取消",
|
|
|
|
|
|
onOk: async () => {
|
|
|
|
|
|
let res: any = await successFishDraft({ ids: ids, approveComment: rejectReason });
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("审批成功");
|
|
|
|
|
|
tableRef.value?.getList();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
// 批量审批-按钮
|
|
|
|
|
|
const successBtn = async () => {
|
|
|
|
|
|
handleSuccess(batchData.value);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 批量驳回过鱼数据
|
|
|
|
|
|
const handleReject = (id: any) => {
|
|
|
|
|
|
let rejectReason = "";
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: "是否确认 驳回 选中数据?",
|
|
|
|
|
|
// 使用 h 函数创建内容
|
|
|
|
|
|
content: h("div", { style: "margin-top: 10px;" }, [
|
|
|
|
|
|
h("div", { style: "margin-bottom: 8px;" }, [
|
|
|
|
|
|
h("span", { style: "color: red;" }, "* "),
|
|
|
|
|
|
h("span", "驳回原因:"),
|
|
|
|
|
|
]),
|
|
|
|
|
|
h("textarea", {
|
|
|
|
|
|
class: "ant-input", // 使用 antdv 的样式类,使其看起来像 Antdv 输入框
|
|
|
|
|
|
placeholder: "请输入驳回原因",
|
|
|
|
|
|
rows: 4,
|
|
|
|
|
|
style: {
|
|
|
|
|
|
width: "100%",
|
|
|
|
|
|
resize: "none",
|
|
|
|
|
|
border: "1px solid #d9d9d9",
|
|
|
|
|
|
padding: "8px",
|
|
|
|
|
|
borderRadius: "4px",
|
|
|
|
|
|
},
|
|
|
|
|
|
// 监听输入事件,更新局部变量
|
|
|
|
|
|
onInput: (e: Event) => {
|
|
|
|
|
|
rejectReason = (e.target as HTMLTextAreaElement).value;
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
]),
|
|
|
|
|
|
okText: "确认驳回",
|
|
|
|
|
|
cancelText: "取消",
|
|
|
|
|
|
onOk: async () => {
|
|
|
|
|
|
// 校验驳回原因不能为空
|
|
|
|
|
|
if (!rejectReason || rejectReason.trim() === "") {
|
|
|
|
|
|
message.warning("请输入驳回原因");
|
|
|
|
|
|
return Promise.reject(); // 阻止 Modal 关闭
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let res: any = await rejectFishDraft({ id: id, rejectReason: rejectReason });
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("驳回成功");
|
|
|
|
|
|
tableRef.value?.getList();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-04-27 10:35:06 +08:00
|
|
|
|
// 多选禁用
|
|
|
|
|
|
const getCheckboxProps = (record: any) => {
|
|
|
|
|
|
return {
|
2026-04-27 19:11:22 +08:00
|
|
|
|
disabled: ['SUBMITTED', 'APPROVED'].includes(record.status),
|
2026-04-27 10:35:06 +08:00
|
|
|
|
};
|
|
|
|
|
|
};
|
2026-04-27 19:11:22 +08:00
|
|
|
|
const handleDataLoaded = (params: any, data: any) => {
|
|
|
|
|
|
console.log(params, data);
|
|
|
|
|
|
return
|
|
|
|
|
|
};
|
2026-04-22 17:53:20 +08:00
|
|
|
|
// 多选
|
2026-04-24 15:31:32 +08:00
|
|
|
|
const handleSelectionChange = (keys: any) => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
batchData.value = keys;
|
|
|
|
|
|
};
|
2026-04-20 16:57:54 +08:00
|
|
|
|
const editModalCancel = () => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
editModalVisible.value = false;
|
|
|
|
|
|
};
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// 编辑/新增-按钮
|
2026-04-20 16:57:54 +08:00
|
|
|
|
const handleEditSubmit = async (values: FormData) => {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
submitLoading.value = true;
|
|
|
|
|
|
if (currentRecord.value) {
|
|
|
|
|
|
// 编辑逻辑
|
|
|
|
|
|
let res: any = await editFishDraft({
|
2026-04-24 15:31:32 +08:00
|
|
|
|
...values,
|
2026-04-22 17:53:20 +08:00
|
|
|
|
});
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("编辑成功");
|
|
|
|
|
|
editModalVisible.value = false;
|
|
|
|
|
|
tableRef.value?.getList();
|
2026-04-20 16:57:54 +08:00
|
|
|
|
}
|
2026-04-22 17:53:20 +08:00
|
|
|
|
submitLoading.value = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 新增逻辑
|
|
|
|
|
|
let res: any = await addFishDraft({
|
|
|
|
|
|
...values,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("新增成功");
|
|
|
|
|
|
editModalVisible.value = false;
|
|
|
|
|
|
tableRef.value?.getList();
|
|
|
|
|
|
}
|
|
|
|
|
|
submitLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const isRowDataEqual = (oldRow: any, newRow: any, fields: string[]): boolean => {
|
|
|
|
|
|
for (const field of fields) {
|
|
|
|
|
|
// 处理 undefined/null 的情况,确保比较的一致性
|
|
|
|
|
|
let oldVal = oldRow?.[field];
|
|
|
|
|
|
let newVal = newRow?.[field];
|
|
|
|
|
|
|
|
|
|
|
|
// 特殊处理:如果都是空值,视为相等
|
2026-04-27 19:11:22 +08:00
|
|
|
|
if (
|
|
|
|
|
|
(oldVal === undefined || oldVal === null || oldVal === "") &&
|
|
|
|
|
|
(newVal === undefined || newVal === null || newVal === "")
|
|
|
|
|
|
) {
|
2026-04-27 09:28:16 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 严格不相等
|
|
|
|
|
|
if (oldVal !== newVal) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const checkTableDataChanges = () => {
|
|
|
|
|
|
// 1. 定义需要比对的核心字段 (对应 Form 中的字段)
|
|
|
|
|
|
const oldData = orgFileTableData.value;
|
|
|
|
|
|
const newData = fileTableData.value;
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 如果行数不同,肯定有变化
|
|
|
|
|
|
if (oldData.length !== newData.length) {
|
|
|
|
|
|
return { hasChanged: true, changedCount: newData.length };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let changedCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 逐行比对
|
|
|
|
|
|
for (let i = 0; i < newData.length; i++) {
|
|
|
|
|
|
const oldRow = oldData[i];
|
|
|
|
|
|
const newRow = newData[i];
|
2026-04-27 19:11:22 +08:00
|
|
|
|
|
2026-04-27 09:28:16 +08:00
|
|
|
|
// 如果某一行数据不一致,计数+1
|
2026-04-27 19:11:22 +08:00
|
|
|
|
if (!isRowDataEqual(oldRow, newRow, ["baseId"])) {
|
2026-04-27 09:28:16 +08:00
|
|
|
|
changedCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { hasChanged: changedCount > 0, changedCount };
|
|
|
|
|
|
};
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-27 09:28:16 +08:00
|
|
|
|
// 提交导入
|
2026-04-20 16:57:54 +08:00
|
|
|
|
const handleModalOk = () => {
|
2026-04-27 19:11:22 +08:00
|
|
|
|
console.log(orgFileTableData.value);
|
|
|
|
|
|
console.log(fileTableData.value);
|
|
|
|
|
|
console.log(modalTableRef.value.editingData);
|
2026-04-27 09:28:16 +08:00
|
|
|
|
if (modalTableRef.value.editingData != undefined) {
|
|
|
|
|
|
message.warning("请点击保存后提交数据!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-27 19:11:22 +08:00
|
|
|
|
for (let i = 0; i < fileTableData.value.length; i++) {
|
|
|
|
|
|
// if (fileTableData.value[i]._warnings?.length > 0) {
|
|
|
|
|
|
// message.warning("请检查数据,标红部分数据有误!");
|
|
|
|
|
|
// return;
|
|
|
|
|
|
// }
|
|
|
|
|
|
if (fileTableData.value[i].picpthsWarnings?.length > 0) {
|
|
|
|
|
|
message.warning("请检查图片,图片路径有误!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (fileTableData.value[i].vdpthsWarnings?.length > 0) {
|
|
|
|
|
|
message.warning("请检查视频,视频路径有误!");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
fileTableData.value.forEach((item) => {
|
|
|
|
|
|
item.tm = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
|
|
|
|
|
});
|
|
|
|
|
|
// const { hasChanged, changedCount } = checkTableDataChanges();
|
|
|
|
|
|
|
|
|
|
|
|
// // 3. 如果没有变化,直接返回,不执行后续操作
|
|
|
|
|
|
// if (!hasChanged) {
|
|
|
|
|
|
// message.info("数据未发生任何变化,无需提交");
|
|
|
|
|
|
// return;
|
|
|
|
|
|
// }
|
2026-04-27 11:58:47 +08:00
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: "是否提交导入数据?",
|
|
|
|
|
|
onOk: async () => {
|
|
|
|
|
|
let res: any = await batchSaveDraft(fileTableData.value);
|
|
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("导入成功");
|
|
|
|
|
|
tableRef.value?.getList();
|
2026-04-27 19:11:22 +08:00
|
|
|
|
visible.value = false;
|
|
|
|
|
|
await markImportTaskSuccess({ id: taskId.value });
|
2026-04-27 11:58:47 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
message.error("导入失败,请检查数据是否正确");
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
2026-04-22 17:53:20 +08:00
|
|
|
|
};
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-27 09:28:16 +08:00
|
|
|
|
// 关闭导入弹窗
|
|
|
|
|
|
const handleCustomCancel = () => {
|
2026-04-24 15:31:32 +08:00
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: "是否取消导入数据?",
|
2026-04-27 09:28:16 +08:00
|
|
|
|
content: "未提交的数据将丢失",
|
|
|
|
|
|
okText: "确定",
|
2026-04-24 15:31:32 +08:00
|
|
|
|
onOk: async () => {
|
2026-04-27 09:28:16 +08:00
|
|
|
|
// 可选:调用取消接口
|
2026-04-27 19:11:22 +08:00
|
|
|
|
let res: any = await cancelImportTask({ taskId: taskId.value });
|
2026-04-27 09:28:16 +08:00
|
|
|
|
if (res && res?.code == 0) {
|
|
|
|
|
|
message.success("取消成功");
|
|
|
|
|
|
tableRef.value?.getList();
|
2026-04-27 11:58:47 +08:00
|
|
|
|
visible.value = false;
|
2026-04-27 09:28:16 +08:00
|
|
|
|
}
|
2026-04-24 15:31:32 +08:00
|
|
|
|
},
|
|
|
|
|
|
});
|
2026-04-22 17:53:20 +08:00
|
|
|
|
};
|
2026-04-27 11:58:47 +08:00
|
|
|
|
// 导入-按钮
|
|
|
|
|
|
const importBtn = async () => {
|
|
|
|
|
|
let res: any = await checkImportStatus();
|
|
|
|
|
|
taskId.value = "";
|
|
|
|
|
|
if (res?.code == 0) {
|
2026-04-27 19:11:22 +08:00
|
|
|
|
const { hasImportingTask, currentTask } = res?.data || {};
|
2026-04-27 11:58:47 +08:00
|
|
|
|
if (currentTask) {
|
|
|
|
|
|
taskId.value = currentTask.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hasImportingTask) {
|
|
|
|
|
|
visible.value = true;
|
|
|
|
|
|
nextTick(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
fileLoading.value = true;
|
|
|
|
|
|
modalTableRef.value.editingRowIndex = null;
|
|
|
|
|
|
let res1: any = await getLastImportResult();
|
|
|
|
|
|
fileTableaAnalysis(res1, "get");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error("导入失败");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
fileLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fileInputRef.value?.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
message.error("导入查询失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 上传文件
|
2026-04-27 19:11:22 +08:00
|
|
|
|
const fileChange = (file: File) => {
|
2026-04-27 11:58:47 +08:00
|
|
|
|
try {
|
2026-04-27 19:11:22 +08:00
|
|
|
|
nextTick(async () => {
|
|
|
|
|
|
visible.value = true;
|
|
|
|
|
|
fileLoading.value = true;
|
|
|
|
|
|
fileTableData.value = [];
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
|
formData.append("file", file);
|
|
|
|
|
|
let res: any = await importFishZip(formData);
|
|
|
|
|
|
const { code } = res.data || {};
|
|
|
|
|
|
if (code == 1) {
|
|
|
|
|
|
message.error("导入失败");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
taskId.value = res.data.taskId;
|
|
|
|
|
|
message.success("导入成功");
|
|
|
|
|
|
fileTableaAnalysis(res, "file");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-04-27 11:58:47 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
fileLoading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 获取文件列表
|
2026-04-24 15:31:32 +08:00
|
|
|
|
const handleFileSelect = (e: Event) => {
|
|
|
|
|
|
const target = e.target as HTMLInputElement;
|
|
|
|
|
|
const file = target.files?.[0];
|
|
|
|
|
|
if (!file) return;
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// 校验文件大小 (50MB)
|
|
|
|
|
|
const maxSize = 50 * 1024 * 1024;
|
|
|
|
|
|
if (file.size > maxSize) {
|
|
|
|
|
|
message.error("文件大小不能超过50MB");
|
|
|
|
|
|
resetFileInput();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// 校验文件类型
|
|
|
|
|
|
const isZip =
|
|
|
|
|
|
file.name.toLowerCase().endsWith(".zip") ||
|
|
|
|
|
|
file.type === "application/zip" ||
|
|
|
|
|
|
file.type === "application/x-zip-compressed";
|
|
|
|
|
|
|
|
|
|
|
|
if (!isZip) {
|
|
|
|
|
|
message.error("请选择.zip格式的压缩包");
|
|
|
|
|
|
resetFileInput();
|
|
|
|
|
|
return;
|
2026-04-20 16:57:54 +08:00
|
|
|
|
}
|
2026-04-27 09:28:16 +08:00
|
|
|
|
fileChange(file);
|
2026-04-24 15:31:32 +08:00
|
|
|
|
resetFileInput();
|
2026-04-22 17:53:20 +08:00
|
|
|
|
};
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-24 15:31:32 +08:00
|
|
|
|
const resetFileInput = () => {
|
|
|
|
|
|
if (fileInputRef.value) {
|
|
|
|
|
|
fileInputRef.value.value = "";
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-04-27 09:28:16 +08:00
|
|
|
|
const fileTableaAnalysis = (res: any, type: string) => {
|
|
|
|
|
|
let data = [];
|
|
|
|
|
|
let list = [];
|
|
|
|
|
|
if (type == "file") {
|
|
|
|
|
|
list = res.data.failedRows;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
list = res.data.result.failedRowDetails;
|
|
|
|
|
|
}
|
|
|
|
|
|
list.forEach((item) => {
|
|
|
|
|
|
data.push({
|
|
|
|
|
|
...item.data,
|
2026-04-27 19:11:22 +08:00
|
|
|
|
vdpthList: item.vdpthList,
|
|
|
|
|
|
vdpthsWarnings: item.vdpthsWarnings,
|
|
|
|
|
|
picpthList: item.picpthList,
|
|
|
|
|
|
picpthsWarnings: item.picpthsWarnings,
|
2026-04-27 09:28:16 +08:00
|
|
|
|
_warnings: item.warnings,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
fileTableData.value = data || [];
|
|
|
|
|
|
orgFileTableData.value = JSON.parse(JSON.stringify(fileTableData.value));
|
|
|
|
|
|
fileLoading.value = false;
|
2026-04-24 15:31:32 +08:00
|
|
|
|
};
|
|
|
|
|
|
const handleReset = (values) => {
|
|
|
|
|
|
handleSearchFinish(values);
|
|
|
|
|
|
};
|
2026-04-27 19:11:22 +08:00
|
|
|
|
// 自定义数据转换函数
|
|
|
|
|
|
const customTransform = (res: any) => {
|
|
|
|
|
|
const rawRecords = res?.data?.records || [];
|
|
|
|
|
|
const modifiedRecords = rawRecords.map((item: any) => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
picpthList: item.picpth ? item.picpth.split(",").map((item: string) => baseUrl + "/?" + item) || [] : [],
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
return {
|
|
|
|
|
|
records: modifiedRecords,
|
|
|
|
|
|
total: res?.data?.total || 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
2026-04-24 15:31:32 +08:00
|
|
|
|
// 搜索-按钮
|
2026-04-22 17:53:20 +08:00
|
|
|
|
const handleSearchFinish = (values: any) => {
|
2026-04-24 15:31:32 +08:00
|
|
|
|
const filters = [
|
|
|
|
|
|
values.ftp && {
|
|
|
|
|
|
field: "ftp",
|
|
|
|
|
|
operator: "eq",
|
|
|
|
|
|
dataType: "string",
|
|
|
|
|
|
value: values.ftp,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: "TM",
|
|
|
|
|
|
operator: "gte",
|
|
|
|
|
|
dataType: "date",
|
|
|
|
|
|
value: values.strdt[0],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
field: "TM",
|
|
|
|
|
|
operator: "lte",
|
|
|
|
|
|
dataType: "date",
|
|
|
|
|
|
value: values.strdt[1],
|
|
|
|
|
|
},
|
|
|
|
|
|
values.direction && {
|
|
|
|
|
|
field: "direction",
|
|
|
|
|
|
operator: "eq",
|
|
|
|
|
|
dataType: "string",
|
|
|
|
|
|
value: values.direction,
|
|
|
|
|
|
},
|
|
|
|
|
|
values.status && {
|
|
|
|
|
|
field: "status",
|
|
|
|
|
|
operator: "eq",
|
|
|
|
|
|
dataType: "string",
|
|
|
|
|
|
value: values.status,
|
|
|
|
|
|
},
|
|
|
|
|
|
values.stcd && {
|
|
|
|
|
|
field: "stcd",
|
|
|
|
|
|
operator: "eq",
|
|
|
|
|
|
dataType: "string",
|
|
|
|
|
|
value: values.stcd,
|
|
|
|
|
|
},
|
|
|
|
|
|
values.rstcd && {
|
|
|
|
|
|
field: "rstcd",
|
|
|
|
|
|
operator: "eq",
|
|
|
|
|
|
dataType: "string",
|
|
|
|
|
|
value: values.rstcd,
|
|
|
|
|
|
},
|
|
|
|
|
|
values.baseId !== "all" && {
|
|
|
|
|
|
field: "baseId",
|
|
|
|
|
|
operator: "eq",
|
|
|
|
|
|
dataType: "string",
|
|
|
|
|
|
value: values.baseId,
|
|
|
|
|
|
},
|
|
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
|
|
|
|
|
|
const filter = {
|
|
|
|
|
|
logic: "and",
|
|
|
|
|
|
filters: filters,
|
|
|
|
|
|
};
|
|
|
|
|
|
tableRef.value?.getList(filter);
|
2026-04-22 17:53:20 +08:00
|
|
|
|
};
|
2026-04-27 19:11:22 +08:00
|
|
|
|
// 处理预览点击 (由子组件触发)
|
|
|
|
|
|
const handlePreviewClick = (record: any, type: string, index: number) => {
|
|
|
|
|
|
const mixedList: MediaItem[] = [];
|
|
|
|
|
|
tablePreviewRecord.value = record;
|
|
|
|
|
|
if (type === "image") {
|
|
|
|
|
|
videoPreviewTitle.value = "图片预览";
|
|
|
|
|
|
const nameList = record.picpthList;
|
|
|
|
|
|
nameList.forEach((item: any) => {
|
|
|
|
|
|
mixedList.push({
|
|
|
|
|
|
type: "image",
|
|
|
|
|
|
name: item.name,
|
|
|
|
|
|
url: item.value ? `${baseUrl}/?${item.value}` : "",
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
videoPreviewTitle.value = "视频预览";
|
|
|
|
|
|
const nameList = record.vdpthList;
|
|
|
|
|
|
nameList.forEach((item: any) => {
|
|
|
|
|
|
mixedList.push({
|
|
|
|
|
|
type: "video",
|
|
|
|
|
|
name: item.name, // 视频通常没有单独的名称字段,可根据需要调整
|
|
|
|
|
|
url: item.value ? `${baseUrl}/?${item.value}` : "",
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
2026-04-27 19:11:22 +08:00
|
|
|
|
mediaPreviewVisible.value = true;
|
|
|
|
|
|
currentMediaIndex.value = index;
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
previewList.value = mixedList;
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 【新增】统一切换逻辑
|
|
|
|
|
|
const prevMedia = () => {
|
|
|
|
|
|
if (currentMediaIndex.value > 0) {
|
|
|
|
|
|
currentMediaIndex.value--;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const nextMedia = () => {
|
|
|
|
|
|
if (currentMediaIndex.value < previewList.value.length - 1) {
|
|
|
|
|
|
currentMediaIndex.value++;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 【新增】统一删除逻辑
|
|
|
|
|
|
const handleDeleteMedia = (item: any, index: number) => {
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: "确认删除",
|
|
|
|
|
|
content: "确定要从预览列表中移除该项吗?",
|
|
|
|
|
|
onOk: () => {
|
|
|
|
|
|
previewList.value.splice(index, 1);
|
|
|
|
|
|
console.log(previewList.value);
|
|
|
|
|
|
if (videoPreviewTitle.value == "图片预览") {
|
|
|
|
|
|
if (previewList.value.length == 0) {
|
|
|
|
|
|
tablePreviewRecord.value.picpthList = [];
|
|
|
|
|
|
tablePreviewRecord.value.picpthsWarnings = [];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
tablePreviewRecord.value.picpthList.splice(index, 1);
|
|
|
|
|
|
tablePreviewRecord.value.picpthsWarnings = tablePreviewRecord.value.picpthsWarnings.filter(
|
|
|
|
|
|
(warningName: any) => warningName !== item.name
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (previewList.value.length == 0) {
|
|
|
|
|
|
tablePreviewRecord.value.vdpthList = [];
|
|
|
|
|
|
tablePreviewRecord.value.vdpthsWarnings = [];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
tablePreviewRecord.value.vdpthList.splice(index, 1);
|
|
|
|
|
|
tablePreviewRecord.value.vdpthsWarnings = tablePreviewRecord.value.vdpthsWarnings.filter(
|
|
|
|
|
|
(warningName: any) => warningName !== item.name
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (previewList.value.length === 0) {
|
|
|
|
|
|
mediaPreviewVisible.value = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 调整索引
|
|
|
|
|
|
if (index <= currentMediaIndex.value) {
|
|
|
|
|
|
currentMediaIndex.value = Math.max(0, currentMediaIndex.value - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const closeMediaPreview = () => {
|
|
|
|
|
|
mediaPreviewVisible.value = false;
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
previewList.value = [];
|
|
|
|
|
|
currentMediaIndex.value = 0;
|
|
|
|
|
|
});
|
2026-04-22 17:53:20 +08:00
|
|
|
|
};
|
2026-04-20 16:57:54 +08:00
|
|
|
|
|
|
|
|
|
|
// --- 生命周期 ---
|
2026-04-24 15:31:32 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
getDictItemsByCode({ dictCode: "direction" }).then((res) => {
|
|
|
|
|
|
direction.value = res.data;
|
|
|
|
|
|
});
|
|
|
|
|
|
getDictItemsByCode({ dictCode: "guoyuStatus" }).then((res) => {
|
|
|
|
|
|
guoyuStatus.value = res.data;
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2026-04-20 09:07:03 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
2026-04-20 16:57:54 +08:00
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.guoYuSheShiShuJuTianBao-page {
|
2026-04-22 17:53:20 +08:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
padding: 20px;
|
2026-04-20 16:57:54 +08:00
|
|
|
|
}
|
2026-04-27 19:11:22 +08:00
|
|
|
|
.media-list-container {
|
|
|
|
|
|
background-color: rgb(234, 241, 251);
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
.list-item {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
line-height: 36px;
|
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
.file-name {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.select {
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background-color: rgb(37, 93, 138);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-item-delete {
|
|
|
|
|
|
color: red;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transition: opacity 0.3s;
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover .list-item-delete {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-20 16:57:54 +08:00
|
|
|
|
</style>
|