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

1337 lines
36 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="upload-data-container">
<!-- 主内容区 -->
<div class="main-content">
<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="sourceProjectName" label="来源" min-width="180">
<template slot-scope="scope">
<span v-if="scope.row.sourceType === 'HISTORY_IMPORT'">
历史导入 · {{ scope.row.sourceProjectName || '-' }}
</span>
<span v-else>-</span>
</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>
</div>
<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"
>
<el-form-item label="证件号码">
<el-input
v-model="pullBankInfoForm.idCardText"
type="textarea"
:rows="5"
:disabled="isProjectTagging || isProjectArchived"
placeholder="仅支持英文逗号分隔"
/>
<div class="pull-bank-field-tip">
仅支持英文逗号分隔文件解析结果会自动合并并去重
</div>
</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"
: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">
正在解析身份证文件...
</div>
</div>
</el-form-item>
<el-form-item label="时间跨度">
<el-date-picker
class="pull-bank-range-picker"
v-model="pullBankInfoForm.dateRange"
:disabled="isProjectTagging || isProjectArchived"
type="daterange"
value-format="yyyy-MM-dd"
:picker-options="pullBankInfoDatePickerOptions"
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"
:disabled="isProjectTagging || isProjectArchived"
:loading="pullBankInfoLoading"
@click="handleConfirmPullBankInfo"
>
确认拉取
</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
: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"
:disabled="isProjectTagging || isProjectArchived || selectedFiles.length === 0"
:loading="uploadLoading"
@click="handleBatchUpload"
>开始上传</el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
import {
pullBankInfo,
parseIdCardFile,
batchUploadFiles,
getFileUploadList,
getFileUploadStatistics,
deleteFileUploadRecord,
} from "@/api/ccdiProjectUpload";
import { parseTime } from "@/utils/ruoyi";
import {
getUploadFileAction,
getUploadFileStatusText,
getUploadFileStatusType,
} from "./uploadFileActionRules";
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: "上传数据",
pullBankInfoDialogVisible: false,
pullBankInfoLoading: false,
parsingIdCardFile: false,
idCardFileList: [],
pullBankInfoForm: {
idCardText: "",
dateRange: [],
},
// 侧边栏菜单项
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: "",
};
},
computed: {
pullBankInfoDatePickerOptions() {
return {
disabledDate: (time) => this.isPullBankInfoDateDisabled(time),
};
},
isProjectTagging() {
return String(this.projectInfo.projectStatus) === "3";
},
isProjectArchived() {
return String(this.projectInfo.projectStatus) === "2";
},
isReportDisabled() {
return ["0", "3"].includes(String(this.projectInfo.projectStatus));
},
},
created() {
this.updateActiveMenu();
},
mounted() {
// 加载统计数据和文件列表
this.loadStatistics();
this.loadFileList();
// 检查是否需要启动轮询
this.$nextTick(() => {
if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
this.startPolling();
}
});
},
beforeDestroy() {
this.stopPolling();
},
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() {
if (this.isProjectTagging || this.isProjectArchived) {
return;
}
this.$router.push("/maintain/creditInfo");
},
handleOpenBatchUploadDialog() {
if (this.isProjectTagging || this.isProjectArchived) {
return;
}
this.batchUploadDialogVisible = true;
this.selectedFiles = [];
},
openPullBankInfoDialog() {
this.pullBankInfoDialogVisible = true;
},
resetPullBankInfoForm() {
this.pullBankInfoForm = {
idCardText: "",
dateRange: this.buildDefaultPullBankInfoDateRange(),
};
this.idCardFileList = [];
this.parsingIdCardFile = false;
this.pullBankInfoLoading = false;
},
parseIdCardText(text) {
return Array.from(
new Set(
(text || "")
.split(/,+/)
.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) {
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;
},
getPullBankInfoTodayStart() {
const today = new Date();
today.setHours(0, 0, 0, 0);
return today;
},
getPullBankInfoMinSelectableDate() {
return new Date(2025, 0, 1);
},
getPullBankInfoMaxSelectableDate() {
const yesterday = this.getPullBankInfoTodayStart();
yesterday.setDate(yesterday.getDate() - 1);
return yesterday;
},
formatPullBankInfoDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
},
buildDefaultPullBankInfoDateRange() {
const minSelectableDate = this.getPullBankInfoMinSelectableDate();
const maxSelectableDate = this.getPullBankInfoMaxSelectableDate();
const defaultStartDate = new Date(maxSelectableDate.getTime());
defaultStartDate.setFullYear(defaultStartDate.getFullYear() - 1);
if (defaultStartDate.getTime() < minSelectableDate.getTime()) {
return [
this.formatPullBankInfoDate(minSelectableDate),
this.formatPullBankInfoDate(maxSelectableDate),
];
}
return [
this.formatPullBankInfoDate(defaultStartDate),
this.formatPullBankInfoDate(maxSelectableDate),
];
},
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) {
const minSelectableDate = this.getPullBankInfoMinSelectableDate();
const todayStart = this.getPullBankInfoTodayStart();
return (
time.getTime() < minSelectableDate.getTime() ||
time.getTime() >= todayStart.getTime()
);
},
hasInvalidPullBankInfoDateRange(dateRange) {
const minSelectableDate = this.getPullBankInfoMinSelectableDate();
const maxSelectableDate = this.getPullBankInfoMaxSelectableDate();
return (dateRange || []).some((dateValue) => {
const date = this.parsePullBankInfoDate(dateValue);
return (
!date ||
date.getTime() < minSelectableDate.getTime() ||
date.getTime() > maxSelectableDate.getTime()
);
});
},
buildFinalIdCardList() {
return this.parseIdCardText(this.pullBankInfoForm.idCardText);
},
async handleConfirmPullBankInfo() {
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;
}
if (this.hasInvalidPullBankInfoDateRange([startDate, endDate])) {
this.$message.warning("时间跨度仅支持 2025-01-01 至昨天");
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) || "未知错误")
);
}
},
/** 拉取本行信息 */
handleFetchBankInfo() {
if (this.isProjectTagging || this.isProjectArchived) {
this.$message.warning(
this.isProjectArchived
? "项目已归档,暂不可上传或拉取数据"
: "项目正在进行银行流水打标,暂不可上传或拉取数据"
);
return;
}
this.resetPullBankInfoForm();
this.openPullBankInfoDialog();
},
/** 格式化更新时间 */
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",
};
return statusMap[status] || "processing";
},
/** 获取状态标签 */
getStatusLabel() {
const status = String(this.projectInfo.projectStatus);
const statusMap = {
0: "进行中",
1: "已完成",
2: "已归档",
3: "打标中",
};
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() {
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);
},
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 || "-";
},
},
};
</script>
<style lang="scss" scoped>
.upload-data-container {
padding: 16px;
background: transparent;
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: #a56a2a;
background: #fbf3ea;
border: 1px solid #eddcc8;
border-radius: 10px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65);
}
}
// 文件列表区域
.file-list-section {
background: #fff;
border: 1px solid var(--ccdi-border);
border-radius: 12px;
padding: 20px 24px 24px;
box-shadow: var(--ccdi-shadow);
.list-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--ccdi-line);
.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 var(--ccdi-border);
border-radius: 10px;
overflow: hidden;
}
::v-deep .el-table th {
font-weight: 600;
}
}
// 上传弹窗样式
::v-deep .el-dialog__wrapper {
.upload-area,
.batch-upload-area {
width: 100%;
}
.upload-area .el-upload,
.batch-upload-area .el-upload {
width: 100%;
}
.upload-area .el-upload-dragger,
.batch-upload-area .el-upload-dragger {
width: 100%;
height: 200px;
}
.el-upload__tip {
margin-top: 8px;
color: var(--ccdi-text-muted);
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 var(--ccdi-border);
border-radius: 8px;
background: #f8fbfe;
.pull-bank-file-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.pull-bank-file-tip {
font-size: 12px;
color: var(--ccdi-text-secondary);
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 var(--ccdi-border);
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: var(--ccdi-text-primary);
}
.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 var(--ccdi-border);
border-radius: 4px;
max-height: 300px;
overflow-y: auto;
.files-header {
padding: 12px 16px;
background: #f7fafd;
border-bottom: 1px solid var(--ccdi-line);
font-size: 14px;
font-weight: 500;
color: var(--ccdi-text-secondary);
}
.files-list {
padding: 8px;
.file-item {
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 4px;
transition: background 0.3s;
&:hover {
background: #f7fafd;
}
i {
font-size: 18px;
color: var(--ccdi-primary);
margin-right: 8px;
}
.file-name {
flex: 1;
font-size: 14px;
color: var(--ccdi-text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-size {
font-size: 12px;
color: var(--ccdi-text-muted);
margin: 0 12px;
}
.el-button {
padding: 4px;
color: var(--ccdi-text-muted);
&:hover {
color: #b55252;
}
}
}
}
}
.parse-tip {
margin-top: 8px;
font-size: 12px;
color: var(--ccdi-text-muted);
}
// 响应式
@media (max-width: 1200px) {
.file-list-section .list-toolbar .toolbar-actions {
gap: 10px;
}
}
@media (max-width: 768px) {
.upload-data-container {
padding: 8px;
}
.sidebar {
margin-bottom: 16px;
}
.file-list-section .list-toolbar {
flex-direction: column;
align-items: flex-start;
gap: 12px;
.toolbar-actions {
width: 100%;
justify-content: flex-start;
}
}
.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;
}
}
</style>