Files
ccdi/ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue

1294 lines
34 KiB
Vue
Raw Normal View History

2026-03-02 19:18:45 +08:00
<template>
<div class="upload-data-container">
<!-- 主内容区 -->
<div class="main-content">
2026-03-24 21:45:55 +08:00
<div v-if="isProjectTagging || isProjectArchived" class="tagging-lock-tip">
{{ isProjectArchived ? "项目已归档,暂不可上传或拉取数据。" : "项目正在进行银行流水打标,暂不可上传或拉取数据。" }}
</div>
<!-- 文件上传记录列表 -->
<div class="file-list-section">
<div class="list-toolbar">
<el-button icon="el-icon-refresh" @click="handleManualRefresh">刷新</el-button>
<div class="toolbar-actions">
<el-button
size="small"
icon="el-icon-upload2"
:disabled="isProjectTagging || isProjectArchived"
@click="handleOpenBatchUploadDialog"
>
上传流水
</el-button>
<el-button
size="small"
icon="el-icon-download"
:disabled="isProjectTagging || isProjectArchived"
@click="handleFetchBankInfo"
>
拉取本行信息
</el-button>
<el-button
size="small"
icon="el-icon-upload2"
:disabled="isProjectTagging || isProjectArchived"
@click="handleGoCreditInfoPage"
>
征信导入
</el-button>
<el-button
size="small"
type="primary"
icon="el-icon-view"
:disabled="isReportDisabled"
@click="handleViewReport"
>
查看报告
</el-button>
</div>
</div>
<el-table :data="fileUploadList" v-loading="listLoading">
<el-table-column prop="fileName" label="文件名" min-width="200"></el-table-column>
<el-table-column prop="fileSize" label="文件大小" width="120">
<template slot-scope="scope">
{{ formatFileSize(scope.row.fileSize) }}
</template>
</el-table-column>
<el-table-column prop="fileStatus" label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="getStatusType(scope.row.fileStatus)" size="small">
{{ getStatusText(scope.row.fileStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="enterpriseNames" label="主体名称" min-width="150">
<template slot-scope="scope">
{{ scope.row.enterpriseNames || '-' }}
</template>
</el-table-column>
<el-table-column prop="accountNos" label="主体账号" min-width="180">
<template slot-scope="scope">
{{ scope.row.accountNos || '-' }}
</template>
</el-table-column>
<el-table-column prop="uploadTime" label="上传时间" width="180">
<template slot-scope="scope">
{{ formatUploadTime(scope.row.uploadTime) }}
</template>
</el-table-column>
<el-table-column prop="uploadUser" label="上传人" width="100"></el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<template slot-scope="scope">
<el-button
v-if="getRowAction(scope.row)"
type="text"
@click="handleRowAction(scope.row)"
>
{{ getRowAction(scope.row).text }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@current-change="handlePageChange"
:current-page="queryParams.pageNum"
:page-size="queryParams.pageSize"
:total="total"
layout="total, prev, pager, next, jumper"
style="margin-top: 16px; text-align: right"
></el-pagination>
</div>
2026-03-02 19:18:45 +08:00
</div>
2026-03-11 17:34:31 +08:00
<el-dialog
title="拉取本行信息"
:visible.sync="pullBankInfoDialogVisible"
:close-on-click-modal="false"
width="640px"
>
<el-form
class="pull-bank-info-form"
:model="pullBankInfoForm"
label-width="100px"
>
2026-03-11 17:34:31 +08:00
<el-form-item label="证件号码">
<el-input
v-model="pullBankInfoForm.idCardText"
type="textarea"
:rows="5"
2026-03-24 21:45:55 +08:00
:disabled="isProjectTagging || isProjectArchived"
2026-03-11 17:34:31 +08:00
placeholder="支持逗号、中文逗号、换行分隔"
/>
<div class="pull-bank-field-tip">
支持逗号中文逗号换行分隔文件解析结果会自动合并并去重
</div>
2026-03-11 17:34:31 +08:00
</el-form-item>
<el-form-item label="身份证文件">
<div class="pull-bank-file-panel">
<div class="pull-bank-file-actions">
<el-upload
class="pull-bank-file-upload"
action="#"
:auto-upload="false"
:limit="1"
:show-file-list="false"
:file-list="idCardFileList"
accept=".xls,.xlsx"
2026-03-24 21:45:55 +08:00
:disabled="isProjectTagging || isProjectArchived"
:on-change="handleIdCardFileChange"
:on-remove="handleIdCardFileRemove"
>
<el-button slot="trigger" size="small" type="primary" plain>
选择文件
</el-button>
</el-upload>
<div class="pull-bank-file-tip">
支持 .xls.xlsx 文件解析后自动补充证件号码
</div>
</div>
<div v-if="idCardFileList.length > 0" class="selected-id-card-file">
<div class="selected-id-card-file__info">
<i class="el-icon-document"></i>
<span class="selected-id-card-file__name">
{{ idCardFileList[0].name }}
</span>
</div>
<el-button type="text" @click="handleIdCardFileRemove">
移除
</el-button>
</div>
<div v-if="parsingIdCardFile" class="parse-tip">
正在解析身份证文件...
2026-03-11 17:34:31 +08:00
</div>
</div>
2026-03-11 17:34:31 +08:00
</el-form-item>
<el-form-item label="时间跨度">
<el-date-picker
class="pull-bank-range-picker"
2026-03-11 17:34:31 +08:00
v-model="pullBankInfoForm.dateRange"
2026-03-24 21:45:55 +08:00
:disabled="isProjectTagging || isProjectArchived"
2026-03-11 17:34:31 +08:00
type="daterange"
value-format="yyyy-MM-dd"
2026-03-12 13:41:40 +08:00
:picker-options="pullBankInfoDatePickerOptions"
2026-03-11 17:34:31 +08:00
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="pullBankInfoDialogVisible = false">取消</el-button>
<el-button
type="primary"
2026-03-24 21:45:55 +08:00
:disabled="isProjectTagging || isProjectArchived"
:loading="pullBankInfoLoading"
@click="handleConfirmPullBankInfo"
>
2026-03-11 17:34:31 +08:00
确认拉取
</el-button>
</span>
</el-dialog>
<!-- 批量上传弹窗 -->
<el-dialog
title="批量上传流水文件"
:visible.sync="batchUploadDialogVisible"
:close-on-click-modal="false"
width="700px"
>
<el-upload
class="batch-upload-area"
drag
action="#"
multiple
2026-03-24 21:45:55 +08:00
:disabled="isProjectTagging || isProjectArchived"
:auto-upload="false"
:on-change="handleBatchFileChange"
:show-file-list="false"
:file-list="selectedFiles"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
支持 PDFCSVExcel 格式文件最多100个文件单个文件不超过50MB
</div>
</el-upload>
<div v-if="selectedFiles.length > 0" class="selected-files">
<div class="files-header">
<span>已选择 {{ selectedFiles.length }} 个文件</span>
</div>
<div class="files-list">
<div
v-for="(file, index) in selectedFiles"
:key="index"
class="file-item"
>
<i class="el-icon-document"></i>
<span class="file-name">{{ file.name }}</span>
<span class="file-size">{{ formatFileSize(file.size) }}</span>
<el-button
type="text"
icon="el-icon-close"
@click="handleRemoveFile(index)"
></el-button>
</div>
</div>
</div>
<span slot="footer">
<el-button @click="batchUploadDialogVisible = false">取消</el-button>
<el-button
type="primary"
2026-03-24 21:45:55 +08:00
:disabled="isProjectTagging || isProjectArchived || selectedFiles.length === 0"
:loading="uploadLoading"
@click="handleBatchUpload"
>开始上传</el-button
>
</span>
</el-dialog>
2026-03-02 19:18:45 +08:00
</div>
</template>
<script>
import {
pullBankInfo,
parseIdCardFile,
batchUploadFiles,
getFileUploadList,
getFileUploadStatistics,
deleteFileUploadRecord,
2026-03-02 19:18:45 +08:00
} from "@/api/ccdiProjectUpload";
import { parseTime } from "@/utils/ruoyi";
import {
getUploadFileAction,
getUploadFileStatusText,
getUploadFileStatusType,
} from "./uploadFileActionRules";
2026-03-02 19:18:45 +08:00
export default {
name: "UploadData",
props: {
projectId: {
type: [String, Number],
default: null,
},
projectInfo: {
type: Object,
default: () => ({
projectName: "",
updateTime: "",
projectStatus: "0",
}),
},
},
data() {
return {
// 加载状态
loading: false,
// 当前选中的菜单
activeMenu: "upload",
// 当前菜单标题
currentMenuTitle: "上传数据",
2026-03-11 17:34:31 +08:00
pullBankInfoDialogVisible: false,
pullBankInfoLoading: false,
parsingIdCardFile: false,
idCardFileList: [],
pullBankInfoForm: {
idCardText: "",
dateRange: [],
},
2026-03-02 19:18:45 +08:00
// 侧边栏菜单项
menuItems: [
{ key: "upload", label: "上传数据", route: "upload" },
{ key: "config", label: "参数配置", route: "config" },
{ key: "overview", label: "结果总览", route: "overview" },
{ key: "special", label: "专项排查", route: "special" },
{ key: "detail", label: "流水明细查询", route: "detail" },
],
// === 批量上传相关 ===
batchUploadDialogVisible: false,
selectedFiles: [],
uploadLoading: false,
// === 统计数据 ===
statistics: {
uploading: 0,
parsing: 0,
parsed_success: 0,
parsed_failed: 0,
},
// === 文件列表相关 ===
fileUploadList: [],
listLoading: false,
queryParams: {
projectId: null,
pageNum: 1,
pageSize: 10,
},
total: 0,
// === 轮询相关 ===
pollingTimer: null,
pollingEnabled: false,
pollingInterval: 5000,
lastFileStatusSignature: "",
2026-03-02 19:18:45 +08:00
};
},
2026-03-12 13:41:40 +08:00
computed: {
pullBankInfoDatePickerOptions() {
return {
disabledDate: (time) => this.isPullBankInfoDateDisabled(time),
};
},
isProjectTagging() {
return String(this.projectInfo.projectStatus) === "3";
},
2026-03-24 21:45:55 +08:00
isProjectArchived() {
return String(this.projectInfo.projectStatus) === "2";
},
isReportDisabled() {
return ["0", "3"].includes(String(this.projectInfo.projectStatus));
},
},
2026-03-02 19:18:45 +08:00
created() {
this.updateActiveMenu();
},
mounted() {
// 加载统计数据和文件列表
this.loadStatistics();
this.loadFileList();
// 检查是否需要启动轮询
this.$nextTick(() => {
if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
this.startPolling();
}
});
},
beforeDestroy() {
this.stopPolling();
2026-03-02 19:18:45 +08:00
},
methods: {
/** 菜单点击 */
handleMenuClick(key, route) {
const menuItem = this.menuItems.find((m) => m.key === key);
if (menuItem) {
this.currentMenuTitle = menuItem.label;
}
if (key === "upload") {
this.activeMenu = key;
} else {
// 其他菜单项通知父组件跳转
this.$emit("menu-change", { key, route });
}
},
/** 更新当前选中菜单 */
updateActiveMenu() {
this.activeMenu = "upload";
this.currentMenuTitle = "上传数据";
},
handleViewReport() {
if (this.isReportDisabled) {
return;
}
this.$emit("menu-change", { key: "overview", route: "overview" });
},
handleGoCreditInfoPage() {
2026-03-24 21:45:55 +08:00
if (this.isProjectTagging || this.isProjectArchived) {
return;
}
this.$router.push("/maintain/creditInfo");
},
handleOpenBatchUploadDialog() {
if (this.isProjectTagging || this.isProjectArchived) {
return;
2026-03-02 19:18:45 +08:00
}
this.batchUploadDialogVisible = true;
this.selectedFiles = [];
2026-03-02 19:18:45 +08:00
},
2026-03-11 17:34:31 +08:00
openPullBankInfoDialog() {
this.pullBankInfoDialogVisible = true;
},
resetPullBankInfoForm() {
this.pullBankInfoForm = {
idCardText: "",
dateRange: [],
};
this.idCardFileList = [];
this.parsingIdCardFile = false;
this.pullBankInfoLoading = false;
},
parseIdCardText(text) {
return Array.from(
new Set(
(text || "")
.split(/[\n,]+/)
.map((item) => item.trim())
.filter(Boolean)
)
);
},
mergeIdCards(currentText, parsedIdCards) {
const merged = [
...this.parseIdCardText(currentText),
...((parsedIdCards || [])
.map((item) => String(item || "").trim())
.filter(Boolean)),
];
return Array.from(new Set(merged)).join(", ");
},
async handleIdCardFileChange(file, fileList) {
2026-03-24 21:45:55 +08:00
if (this.isProjectTagging || this.isProjectArchived) {
return;
}
const latestFile = (fileList || []).slice(-1);
const currentFile = latestFile[0] || file;
const fileName = (currentFile && currentFile.name) || "";
const isExcel = /\.(xls|xlsx)$/i.test(fileName);
if (!isExcel) {
this.idCardFileList = [];
this.$message.error("仅支持上传 .xls 或 .xlsx 文件");
return;
}
if (!currentFile || !currentFile.raw) {
this.idCardFileList = [];
this.$message.error("未获取到有效文件");
return;
}
this.idCardFileList = latestFile;
this.parsingIdCardFile = true;
try {
const res = await parseIdCardFile(currentFile.raw);
const parsedIdCards =
(res && res.data && Array.isArray(res.data.idCards) && res.data.idCards) ||
[];
this.pullBankInfoForm.idCardText = this.mergeIdCards(
this.pullBankInfoForm.idCardText,
parsedIdCards
);
this.$message.success(
`身份证文件解析成功,共 ${parsedIdCards.length} 条有效身份证`
);
} catch (error) {
this.idCardFileList = [];
this.$message.error(
"身份证文件解析失败:" +
((error && error.message) || "未知错误")
);
} finally {
this.parsingIdCardFile = false;
}
},
handleIdCardFileRemove() {
this.idCardFileList = [];
this.parsingIdCardFile = false;
},
2026-03-12 13:41:40 +08:00
getPullBankInfoTodayStart() {
const today = new Date();
today.setHours(0, 0, 0, 0);
return today;
},
getPullBankInfoMaxSelectableDate() {
const yesterday = this.getPullBankInfoTodayStart();
yesterday.setDate(yesterday.getDate() - 1);
return yesterday;
},
parsePullBankInfoDate(dateValue) {
if (!dateValue) return null;
const [year, month, day] = String(dateValue)
.split("-")
.map((item) => Number(item));
if (![year, month, day].every(Number.isFinite)) {
return null;
}
return new Date(year, month - 1, day);
},
isPullBankInfoDateDisabled(time) {
return time.getTime() >= this.getPullBankInfoTodayStart().getTime();
},
hasInvalidPullBankInfoDateRange(dateRange) {
const maxSelectableDate = this.getPullBankInfoMaxSelectableDate();
return (dateRange || []).some((dateValue) => {
const date = this.parsePullBankInfoDate(dateValue);
return !date || date.getTime() > maxSelectableDate.getTime();
});
},
buildFinalIdCardList() {
return this.parseIdCardText(this.pullBankInfoForm.idCardText);
},
async handleConfirmPullBankInfo() {
2026-03-24 21:45:55 +08:00
if (this.isProjectTagging || this.isProjectArchived) {
this.$message.warning(
this.isProjectArchived
? "项目已归档,暂不可上传或拉取数据"
: "项目正在进行银行流水打标,暂不可上传或拉取数据"
);
return;
}
const idCards = this.buildFinalIdCardList();
const [startDate, endDate] = this.pullBankInfoForm.dateRange || [];
if (idCards.length === 0) {
this.$message.warning("请至少输入一个身份证号");
return;
}
if (!startDate || !endDate) {
this.$message.warning("请选择完整的时间跨度");
return;
2026-03-12 13:41:40 +08:00
}
if (this.hasInvalidPullBankInfoDateRange([startDate, endDate])) {
this.$message.warning("时间跨度最晚只能选择到昨天");
return;
}
this.pullBankInfoLoading = true;
try {
const payload = {
projectId: this.projectId,
idCards,
startDate,
endDate,
};
const res = await pullBankInfo(payload);
this.pullBankInfoDialogVisible = false;
this.resetPullBankInfoForm();
this.$message.success((res && res.msg) || "拉取任务已提交");
await Promise.all([this.loadStatistics(), this.loadFileList()]);
this.lastFileStatusSignature = this.buildFileStatusSignature();
this.$emit("refresh-project");
const hasPollingRecords =
this.statistics.uploading > 0 ||
this.statistics.parsing > 0 ||
this.fileUploadList.some((item) =>
["uploading", "parsing"].includes(item.fileStatus)
);
if (hasPollingRecords) {
this.startPolling();
}
} catch (error) {
this.pullBankInfoLoading = false;
this.$message.error(
"拉取本行信息失败:" + ((error && error.message) || "未知错误")
);
}
},
2026-03-02 19:18:45 +08:00
/** 拉取本行信息 */
handleFetchBankInfo() {
2026-03-24 21:45:55 +08:00
if (this.isProjectTagging || this.isProjectArchived) {
this.$message.warning(
this.isProjectArchived
? "项目已归档,暂不可上传或拉取数据"
: "项目正在进行银行流水打标,暂不可上传或拉取数据"
);
return;
}
2026-03-11 17:34:31 +08:00
this.resetPullBankInfoForm();
this.openPullBankInfoDialog();
2026-03-02 19:18:45 +08:00
},
/** 格式化更新时间 */
formatUpdateTime(time) {
if (!time) return "-";
const date = new Date(time);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}`;
},
/** 获取状态样式类 */
getStatusClass() {
const status = String(this.projectInfo.projectStatus);
const statusMap = {
0: "processing",
1: "success",
2: "archived",
3: "tagging",
2026-03-02 19:18:45 +08:00
};
return statusMap[status] || "processing";
},
/** 获取状态标签 */
getStatusLabel() {
const status = String(this.projectInfo.projectStatus);
const statusMap = {
0: "进行中",
1: "已完成",
2: "已归档",
3: "打标中",
2026-03-02 19:18:45 +08:00
};
return statusMap[status] || "未知";
},
// === 批量上传相关方法 ===
/** 批量上传的文件选择变化 */
handleBatchFileChange(file, fileList) {
if (fileList.length > 100) {
this.$message.warning("最多上传100个文件");
fileList = fileList.slice(0, 100);
}
const validTypes = ['.pdf', '.csv', '.xlsx', '.xls'];
const invalidFiles = fileList.filter((f) => {
const ext = f.name.substring(f.name.lastIndexOf(".")).toLowerCase();
return !validTypes.includes(ext);
});
if (invalidFiles.length > 0) {
this.$message.error("仅支持 PDF、CSV、Excel 格式文件");
return;
}
const oversizedFiles = fileList.filter((f) => f.size > 50 * 1024 * 1024);
if (oversizedFiles.length > 0) {
this.$message.error("单个文件不能超过50MB");
return;
}
this.selectedFiles = fileList;
},
/** 删除已选文件 */
handleRemoveFile(index) {
this.selectedFiles.splice(index, 1);
},
/** 开始批量上传 */
async handleBatchUpload() {
2026-03-24 21:45:55 +08:00
if (this.isProjectTagging || this.isProjectArchived) {
this.$message.warning(
this.isProjectArchived
? "项目已归档,暂不可上传或拉取数据"
: "项目正在进行银行流水打标,暂不可上传或拉取数据"
);
return;
}
if (this.selectedFiles.length === 0) {
this.$message.warning("请选择要上传的文件");
return;
}
this.uploadLoading = true;
try {
const res = await batchUploadFiles(
this.projectId,
this.selectedFiles.map((f) => f.raw)
);
this.uploadLoading = false;
this.batchUploadDialogVisible = false;
this.$message.success("上传任务已提交,请查看处理进度");
// 刷新数据并启动轮询
await Promise.all([this.loadStatistics(), this.loadFileList()]);
this.lastFileStatusSignature = this.buildFileStatusSignature();
this.$emit("refresh-project");
this.startPolling();
} catch (error) {
this.uploadLoading = false;
this.$message.error("上传失败:" + (error.msg || "未知错误"));
}
},
// === 统计和列表相关方法 ===
/** 加载统计数据 */
async loadStatistics() {
try {
const res = await getFileUploadStatistics(this.projectId);
this.statistics = res.data || {
uploading: 0,
parsing: 0,
parsed_success: 0,
parsed_failed: 0,
};
} catch (error) {
console.error("加载统计数据失败:", error);
}
},
/** 加载文件列表 */
async loadFileList() {
this.listLoading = true;
try {
const params = {
projectId: this.projectId,
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize,
};
const res = await getFileUploadList(params);
this.fileUploadList = res.rows || [];
this.total = res.total || 0;
if (!this.lastFileStatusSignature) {
this.lastFileStatusSignature = this.buildFileStatusSignature();
}
} catch (error) {
this.$message.error("加载文件列表失败");
console.error(error);
} finally {
this.listLoading = false;
}
},
// === 轮询相关方法 ===
/** 启动轮询 */
startPolling() {
if (this.pollingEnabled) return;
this.pollingEnabled = true;
const poll = () => {
if (!this.pollingEnabled) return;
Promise.all([this.loadStatistics(), this.loadFileList()])
.then(() => {
const currentSignature = this.buildFileStatusSignature();
if (currentSignature !== this.lastFileStatusSignature) {
this.lastFileStatusSignature = currentSignature;
this.$emit("refresh-project");
}
if (
this.statistics.uploading === 0 &&
this.statistics.parsing === 0
) {
this.stopPolling();
return;
}
this.pollingTimer = setTimeout(poll, this.pollingInterval);
})
.catch((error) => {
console.error("轮询失败:", error);
this.pollingTimer = setTimeout(poll, this.pollingInterval);
});
};
poll();
},
/** 停止轮询 */
stopPolling() {
this.pollingEnabled = false;
if (this.pollingTimer) {
clearTimeout(this.pollingTimer);
this.pollingTimer = null;
}
},
/** 手动刷新 */
async handleManualRefresh() {
await Promise.all([this.loadStatistics(), this.loadFileList()]);
this.lastFileStatusSignature = this.buildFileStatusSignature();
this.$emit("refresh-project");
this.$message.success("刷新成功");
if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
this.startPolling();
}
},
// === 辅助方法 ===
/** 分页变化 */
handlePageChange(pageNum) {
this.queryParams.pageNum = pageNum;
this.loadFileList();
},
buildFileStatusSignature() {
return (this.fileUploadList || [])
.map((item) => `${item.id || ""}:${item.fileStatus || ""}`)
.join("|");
},
getRowAction(row) {
return getUploadFileAction(row.fileStatus);
},
handleRowAction(row) {
const action = this.getRowAction(row);
if (!action) {
return;
}
if (action.key === "viewError") {
this.handleViewError(row);
return;
}
if (action.key === "delete") {
this.handleDeleteFile(row);
}
},
handleViewError(row) {
this.$alert(row.errorMessage || "未知错误", "错误信息", {
confirmButtonText: "确定",
type: "error",
});
},
async handleDeleteFile(row) {
try {
await this.$confirm(
"删除该文件后将同步删除流水分析平台中的文件,并清除本系统中该文件对应的所有银行流水数据,项目内流水也将重新打标,是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
);
await deleteFileUploadRecord(row.id);
this.$message.success("删除成功,已开始项目重新打标");
await Promise.all([this.loadStatistics(), this.loadFileList()]);
if (this.hasActivePollingRecords()) {
this.startPolling();
} else {
this.stopPolling();
}
} catch (error) {
if (error === "cancel" || error === "close") {
return;
}
this.$message.error("删除失败:" + ((error && error.message) || "未知错误"));
}
},
hasActivePollingRecords() {
return (
this.statistics.uploading > 0 ||
this.statistics.parsing > 0 ||
this.fileUploadList.some((item) =>
["uploading", "parsing"].includes(item.fileStatus)
)
);
},
/** 状态文本映射 */
getStatusText(status) {
return getUploadFileStatusText(status);
},
/** 状态标签类型映射 */
getStatusType(status) {
return getUploadFileStatusType(status);
},
/** 格式化文件大小 */
formatFileSize(bytes) {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
},
/** 格式化上传时间 */
formatUploadTime(time) {
const formatted = parseTime(time, "{y}-{m}-{d} {h}:{i}:{s}");
return formatted || "-";
},
2026-03-02 19:18:45 +08:00
},
};
</script>
<style lang="scss" scoped>
.upload-data-container {
padding: 16px;
background: #fff;
min-height: 100%;
}
// 侧边栏
.sidebar {
background: #ffffff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
padding: 16px;
height: 100%;
display: flex;
flex-direction: column;
.sidebar-header {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid #ebeef5;
}
.sidebar-menu {
list-style: none;
padding: 0;
margin: 0 0 auto 0;
flex: 1;
.menu-item {
display: flex;
align-items: center;
padding: 10px 12px;
margin-bottom: 4px;
cursor: pointer;
border-radius: 4px;
font-size: 14px;
color: #606266;
transition: all 0.3s;
position: relative;
&:hover {
background: #f5f7fa;
color: #303133;
}
&.active {
background: #1890ff;
color: #ffffff;
.menu-dot {
display: block;
}
}
.menu-dot {
display: none;
width: 4px;
height: 4px;
background: #ffffff;
border-radius: 50%;
margin-right: 8px;
}
}
}
.sidebar-footer {
border-top: 1px solid #ebeef5;
padding-top: 16px;
margin-top: 16px;
.status {
font-size: 13px;
color: #606266;
margin-bottom: 8px;
.status-processing {
color: #1890ff;
font-weight: 500;
}
.status-success {
color: #52c41a;
font-weight: 500;
}
.status-archived {
color: #909399;
font-weight: 500;
}
}
.update-time {
font-size: 12px;
color: #909399;
}
}
}
// 主内容区
.main-content {
.tagging-lock-tip {
margin-bottom: 16px;
padding: 10px 14px;
color: #ad6800;
background: #fff8ec;
border: 1px solid #ffe0b2;
border-radius: 10px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65);
2026-03-02 19:18:45 +08:00
}
}
// 文件列表区域
.file-list-section {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 12px;
padding: 20px 24px 24px;
box-shadow: 0 12px 30px rgba(17, 24, 39, 0.06);
.list-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f2f5;
.toolbar-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 12px;
.el-button {
min-width: 104px;
}
}
}
::v-deep .el-table {
border: 1px solid #f0f2f5;
border-radius: 10px;
overflow: hidden;
}
::v-deep .el-table th {
font-weight: 600;
}
}
2026-03-02 19:18:45 +08:00
// 上传弹窗样式
::v-deep .el-dialog__wrapper {
.upload-area,
.batch-upload-area {
2026-03-02 19:18:45 +08:00
width: 100%;
}
.upload-area .el-upload,
.batch-upload-area .el-upload {
2026-03-02 19:18:45 +08:00
width: 100%;
}
2026-03-02 19:18:45 +08:00
.upload-area .el-upload-dragger,
.batch-upload-area .el-upload-dragger {
width: 100%;
height: 200px;
2026-03-02 19:18:45 +08:00
}
.el-upload__tip {
margin-top: 8px;
color: #909399;
font-size: 12px;
}
}
.pull-bank-info-form {
.pull-bank-field-tip {
margin-top: 8px;
font-size: 12px;
color: #909399;
line-height: 1.5;
}
}
.pull-bank-file-panel {
padding: 16px;
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #fafcff;
.pull-bank-file-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.pull-bank-file-tip {
font-size: 12px;
color: #606266;
line-height: 1.5;
}
}
.pull-bank-file-upload {
display: inline-flex;
flex: 0 0 auto;
}
.selected-id-card-file {
margin-top: 12px;
padding: 10px 12px;
border-radius: 6px;
background: #ffffff;
border: 1px solid #ebeef5;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
.selected-id-card-file__info {
min-width: 0;
display: flex;
align-items: center;
gap: 8px;
color: #303133;
}
.selected-id-card-file__name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.el-button {
flex-shrink: 0;
padding: 0;
}
}
.pull-bank-range-picker {
width: 100%;
}
// 批量上传弹窗样式
.batch-upload-area {
width: 100%;
::v-deep .el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 180px;
}
}
}
.selected-files {
margin-top: 16px;
border: 1px solid #ebeef5;
border-radius: 4px;
max-height: 300px;
overflow-y: auto;
.files-header {
padding: 12px 16px;
background: #f5f7fa;
border-bottom: 1px solid #ebeef5;
font-size: 14px;
font-weight: 500;
color: #606266;
}
.files-list {
padding: 8px;
.file-item {
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 4px;
transition: background 0.3s;
&:hover {
background: #f5f7fa;
}
i {
font-size: 18px;
color: #1890ff;
margin-right: 8px;
}
.file-name {
flex: 1;
font-size: 14px;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-size {
font-size: 12px;
color: #909399;
margin: 0 12px;
}
.el-button {
padding: 4px;
color: #909399;
&:hover {
color: #f56c6c;
}
}
}
}
}
.parse-tip {
margin-top: 8px;
font-size: 12px;
color: #909399;
}
2026-03-02 19:18:45 +08:00
// 响应式
@media (max-width: 1200px) {
.file-list-section .list-toolbar .toolbar-actions {
gap: 10px;
2026-03-02 19:18:45 +08:00
}
}
@media (max-width: 768px) {
.upload-data-container {
padding: 8px;
}
.sidebar {
margin-bottom: 16px;
}
.file-list-section .list-toolbar {
2026-03-02 19:18:45 +08:00
flex-direction: column;
align-items: flex-start;
gap: 12px;
.toolbar-actions {
width: 100%;
justify-content: flex-start;
}
2026-03-02 19:18:45 +08:00
}
.pull-bank-file-panel {
padding: 12px;
.pull-bank-file-actions {
align-items: stretch;
gap: 10px;
}
}
.pull-bank-file-upload {
width: 100%;
}
.selected-id-card-file {
align-items: flex-start;
flex-direction: column;
}
2026-03-02 19:18:45 +08:00
}
</style>