调整上传数据页头部按钮与导入卡片布局

This commit is contained in:
wkc
2026-03-23 19:58:03 +08:00
parent 29be8a88a8
commit 27aab7f9bc
4 changed files with 204 additions and 199 deletions

View File

@@ -0,0 +1,34 @@
# 上传数据页卡片精简实施记录
## 变更时间
- 2026-03-23
## 变更内容
- 调整项目详情上传数据页右上角操作区,新增“导入导入”按钮,按钮直接复用现有流水批量上传弹窗入口。
- 删除上传区中的“征信导入”和“名单库选择”卡片,仅保留“流水导入”卡片。
- 调整上传卡片区布局,使单张“流水导入”卡片在上传区域内居中展示。
- 将“流水导入”卡片宽度由 `320px` 调整为 `420px`,同时保留 `max-width: 100%` 以兼容窄屏。
- 调整头部按钮视觉层级,将“查看报告”设为重要按钮,“征信导入”调整为默认按钮样式。
- 清理与已删除卡片对应的前端入口代码,避免保留无效页面分支。
## 涉及文件
- `ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue`
- `ruoyi-ui/tests/unit/upload-data-header-import-button.test.js`
- `ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js`
## 验证命令
- `node ruoyi-ui/tests/unit/upload-data-header-import-button.test.js`
- `node ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js`
- `node ruoyi-ui/tests/unit/upload-data-batch-upload.test.js`
- `node ruoyi-ui/tests/unit/upload-data-pull-bank-info-dialog-layout.test.js`
## 验证结果
- `node ruoyi-ui/tests/unit/upload-data-header-import-button.test.js`:通过
- `node ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js`:通过
- `node ruoyi-ui/tests/unit/upload-data-batch-upload.test.js`:通过
- `node ruoyi-ui/tests/unit/upload-data-pull-bank-info-dialog-layout.test.js`:通过

View File

@@ -9,10 +9,11 @@
<el-button
size="small"
type="primary"
icon="el-icon-document"
@click="handleGenerateReport"
icon="el-icon-view"
:disabled="isReportDisabled"
@click="handleViewReport"
>
生成报告
查看报告
</el-button>
<el-button
size="small"
@@ -22,6 +23,14 @@
>
拉取本行信息
</el-button>
<el-button
size="small"
icon="el-icon-upload2"
:disabled="isProjectTagging"
@click="handleOpenCreditUpload"
>
征信导入
</el-button>
</div>
</div>
@@ -158,11 +167,10 @@
</div> -->
</div>
<!-- 上传弹窗 -->
<el-dialog
v-if="showUploadDialog"
:title="uploadDialogTitle"
:visible.sync="showUploadDialog"
v-if="showCreditUploadDialog"
title="征信导入"
:visible.sync="showCreditUploadDialog"
:close-on-click-modal="false"
width="500px"
>
@@ -172,53 +180,25 @@
action="#"
:disabled="isProjectTagging"
:auto-upload="false"
:on-change="handleFileChange"
:file-list="fileList"
:on-change="handleCreditFileChange"
:file-list="creditFileList"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
支持 {{ uploadFileTypes }} 格式文件
支持 HTML 格式文件
</div>
</el-upload>
<span slot="footer">
<el-button @click="showUploadDialog = false">取消</el-button>
<el-button @click="showCreditUploadDialog = false">取消</el-button>
<el-button
type="primary"
:disabled="isProjectTagging"
@click="handleConfirmUpload"
:loading="uploading"
>确定</el-button
>
</span>
</el-dialog>
<!-- 名单选择弹窗 -->
<el-dialog
:title="'名单库选择'"
:visible.sync="showNameListDialog"
width="600px"
>
<el-form :model="nameListForm" label-width="100px">
<el-form-item label="名单类型">
<el-select v-model="nameListForm.type" placeholder="请选择名单类型">
<el-option label="黑名单" value="blacklist"></el-option>
<el-option label="灰名单" value="graylist"></el-option>
<el-option label="白名单" value="whitelist"></el-option>
</el-select>
</el-form-item>
<el-form-item label="名单来源">
<el-select v-model="nameListForm.source" placeholder="请选择名单来源">
<el-option label="中台管理系统" value="platform"></el-option>
<el-option label="本地上传" value="local"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="showNameListDialog = false">取消</el-button>
<el-button type="primary" @click="handleConfirmNameList"
>确定</el-button
:loading="creditUploading"
@click="handleConfirmCreditUpload"
>
确定
</el-button>
</span>
</el-dialog>
@@ -375,11 +355,9 @@
<script>
import {
getImportStatus,
getNameListOptions,
getUploadStatus,
pullBankInfo,
parseIdCardFile,
updateNameListSelection,
uploadFile,
batchUploadFiles,
getFileUploadList,
@@ -419,13 +397,9 @@ export default {
currentMenuTitle: "上传数据",
// 圆环周长
circumference: 2 * Math.PI * 14,
// 上传弹窗
showUploadDialog: false,
uploadDialogTitle: "",
uploadFileType: "",
uploadFileTypes: "",
fileList: [],
uploading: false,
showCreditUploadDialog: false,
creditFileList: [],
creditUploading: false,
pullBankInfoDialogVisible: false,
pullBankInfoLoading: false,
parsingIdCardFile: false,
@@ -434,16 +408,8 @@ export default {
idCardText: "",
dateRange: [],
},
// 名单选择弹窗
showNameListDialog: false,
nameListForm: {
type: "",
source: "platform",
},
// 上传状态列表
uploadStatusList: [],
// 名单库选项列表
nameListOptions: [],
// 侧边栏菜单项
menuItems: [
{ key: "upload", label: "上传数据", route: "upload" },
@@ -463,24 +429,6 @@ export default {
uploaded: false,
disabled: false,
},
{
key: "credit",
title: "征信导入",
desc: "支持 HTML 格式征信数据解析",
icon: "el-icon-s-data",
btnText: "上传征信",
uploaded: false,
disabled: true,
},
{
key: "namelist",
title: "名单库选择",
desc: "选择中台管理系统的名单",
icon: "el-icon-s-order",
btnText: "选择名单",
uploaded: false,
disabled: true,
},
],
// 质量指标
metrics: [
@@ -542,6 +490,9 @@ export default {
isProjectTagging() {
return String(this.projectInfo.projectStatus) === "3";
},
isReportDisabled() {
return ["0", "3"].includes(String(this.projectInfo.projectStatus));
},
},
watch: {
"projectInfo.projectStatus"() {
@@ -580,15 +531,8 @@ export default {
try {
this.loading = true;
// 并行加载上传状态和名单库选项
const [uploadStatusRes, nameListRes] = await Promise.all([
getUploadStatus(this.projectId),
getNameListOptions(),
]);
const uploadStatusRes = await getUploadStatus(this.projectId);
this.uploadStatusList = uploadStatusRes.data || [];
this.nameListOptions = nameListRes.data || [];
// 更新上传卡片状态
this.updateUploadCards();
@@ -669,76 +613,75 @@ export default {
this.activeMenu = "upload";
this.currentMenuTitle = "上传数据";
},
handleViewReport() {
if (this.isReportDisabled) {
return;
}
this.$emit("menu-change", { key: "overview", route: "overview" });
},
handleOpenCreditUpload() {
if (this.isProjectTagging) {
return;
}
this.creditFileList = [];
this.showCreditUploadDialog = true;
},
/** 上传卡片点击 */
handleUploadClick(key) {
const card = this.uploadCards.find((c) => c.key === key);
if (!card || card.disabled) return;
if (key === "transaction") {
// 流水导入 - 打开批量上传弹窗
this.batchUploadDialogVisible = true;
this.selectedFiles = [];
} else if (key === "credit") {
// 征信导入 - 保持现有逻辑
this.uploadFileType = key;
this.uploadDialogTitle = `上传${card.title}`;
this.uploadFileTypes = card.desc.replace(/.*支持|上传/g, "").trim();
this.showUploadDialog = true;
} else if (key === "namelist") {
// 名单库选择 - 保持现有逻辑
this.showNameListDialog = true;
}
},
/** 文件选择变化 */
handleFileChange(file, fileList) {
this.fileList = fileList.slice(-1); // 只保留最后一个文件
handleCreditFileChange(file, fileList) {
this.creditFileList = fileList.slice(-1);
},
/** 确认上传 */
async handleConfirmUpload() {
async handleConfirmCreditUpload() {
if (this.isProjectTagging) {
this.$message.warning("项目正在进行银行流水打标,暂不可上传或拉取数据");
return;
}
if (this.fileList.length === 0) {
if (this.creditFileList.length === 0) {
this.$message.warning("请选择要上传的文件");
return;
}
this.uploading = true;
this.creditUploading = true;
try {
// 调用上传API
const res = await uploadFile(
this.projectId,
this.uploadFileType.toUpperCase(),
this.fileList[0].raw
"CREDIT",
this.creditFileList[0].raw
);
this.uploading = false;
this.showUploadDialog = false;
this.showCreditUploadDialog = false;
this.$message.success("征信文件上传成功,正在处理中...");
this.$emit("data-uploaded", { type: "credit" });
this.$message.success("文件上传成功,正在处理中...");
this.$emit("data-uploaded", { type: this.uploadFileType });
// 刷新上传状态
await this.loadUploadStatus();
// 开始轮询任务状态
this.startPolling(res.data);
this.pollCreditImportStatus(res && res.data);
} catch (error) {
this.uploading = false;
this.$message.error("上传失败:" + (error.msg || "未知错误"));
this.$message.error("征信上传失败:" + (error.msg || "未知错误"));
} finally {
this.fileList = [];
this.creditUploading = false;
this.creditFileList = [];
}
},
async pollCreditImportStatus(taskId) {
if (!taskId) {
return;
}
/** 轮询任务状态 */
startPolling(taskId) {
const maxAttempts = 20; // 最多轮询20次约10分钟
const maxAttempts = 20;
let attempts = 0;
const poll = async () => {
if (attempts >= maxAttempts) {
this.$message.warning("文件处理超时,请稍后查看");
this.$message.warning("征信导入处理超时,请稍后查看");
return;
}
@@ -747,39 +690,36 @@ export default {
const status = res.data;
if (!status) {
attempts++;
setTimeout(poll, 30000); // 30秒后再次查询
attempts += 1;
setTimeout(poll, 30000);
return;
}
if (status.uploadStatus === "SUCCESS") {
// 处理完成,刷新状态
await this.loadUploadStatus();
this.$message.success("文件处理完成");
this.$message.success("征信导入处理完成");
return;
} else if (status.uploadStatus === "FAILED") {
// 处理失败
}
if (status.uploadStatus === "FAILED") {
await this.loadUploadStatus();
this.$message.error(
"文件处理失败:" + (status.errorMessage || "未知错误")
"征信导入处理失败:" + (status.errorMessage || "未知错误")
);
return;
}
// 继续处理中
attempts++;
attempts += 1;
setTimeout(poll, 30000);
} catch (error) {
console.error("轮询任务状态失败:", error);
attempts++;
console.error("轮询征信导入状态失败:", error);
attempts += 1;
setTimeout(poll, 30000);
}
};
poll();
},
/** 加载上传状态 */
async loadUploadStatus() {
try {
const res = await getUploadStatus(this.projectId);
@@ -789,63 +729,6 @@ export default {
console.error("加载上传状态失败:", error);
}
},
/** 确认选择名单 */
async handleConfirmNameList() {
if (!this.nameListForm.type || !this.nameListForm.source) {
this.$message.warning("请完善名单选择信息");
return;
}
try {
// 调用更新名单API
await updateNameListSelection(this.projectId, {
nameLists: [
{
type: this.nameListForm.type,
source: this.nameListForm.source,
},
],
});
const card = this.uploadCards.find((c) => c.key === "namelist");
if (card) {
card.uploaded = true;
card.btnText = "已选择名单";
}
this.showNameListDialog = false;
this.$message.success("名单选择成功");
this.$emit("name-selected", this.nameListForm);
} catch (error) {
this.$message.error("名单选择失败:" + (error.msg || "未知错误"));
}
},
/** 生成报告 */
async handleGenerateReport() {
this.$confirm("确认生成报告吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
const loading = this.$loading({
lock: true,
text: "正在生成报告...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
// await generateReport(this.projectId);
loading.close();
this.$message.success("报告生成成功");
this.$emit("generate-report");
} catch (error) {
this.$message.error("生成报告失败:" + (error.msg || "未知错误"));
}
})
.catch(() => {});
},
openPullBankInfoDialog() {
this.pullBankInfoDialogVisible = true;
},
@@ -1491,11 +1374,12 @@ export default {
margin-bottom: 16px;
.upload-cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
display: flex;
justify-content: center;
.upload-card {
width: 420px;
max-width: 100%;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 20px 16px;

View File

@@ -16,13 +16,22 @@ assert(
);
assert(
/key:\s*"credit"[\s\S]*?disabled:\s*true/.test(source),
"征信导入卡片应配置为禁用"
/uploadCards:\s*\[[\s\S]*?key:\s*"transaction"[\s\S]*?btnText:\s*"上传流水"[\s\S]*?disabled:\s*false[\s\S]*?\],/.test(
source
),
"上传卡片区应只保留一张默认可用的流水导入卡片"
);
assert(
/key:\s*"namelist"[\s\S]*?disabled:\s*true/.test(source),
"名单库选择卡片应配置为禁用"
/syncUploadCardDisabledState\(\)\s*\{[\s\S]*?card\.key === "transaction"[\s\S]*?disabled:\s*this\.isProjectTagging/.test(
source
),
"流水导入卡片应在项目打标中时同步置灰"
);
assert(
!/key:\s*"credit"/.test(source) && !/key:\s*"namelist"/.test(source),
"上传卡片区不应再保留征信导入或名单库选择卡片配置"
);
assert(

View File

@@ -0,0 +1,78 @@
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const componentPath = path.resolve(
__dirname,
"../../src/views/ccdiProject/components/detail/UploadData.vue"
);
const source = fs.readFileSync(componentPath, "utf8");
assert(
/<div class="header-actions">[\s\S]*?@click="handleViewReport"[\s\S]*?>\s*查看报告\s*<\/el-button>[\s\S]*?@click="handleFetchBankInfo"[\s\S]*?>\s*拉取本行信息\s*<\/el-button>[\s\S]*?@click="handleOpenCreditUpload"[\s\S]*?>\s*征信导入\s*<\/el-button>/.test(
source
),
"页面右上角按钮顺序应为查看报告、拉取本行信息、征信导入"
);
assert(
/<el-button[\s\S]*?:disabled="isReportDisabled"[\s\S]*?@click="handleViewReport"[\s\S]*?>\s*查看报告\s*<\/el-button>/.test(
source
),
"查看报告按钮应绑定禁用状态"
);
assert(
/<el-button[\s\S]*?type="primary"[\s\S]*?@click="handleViewReport"[\s\S]*?>\s*查看报告\s*<\/el-button>/.test(
source
),
"查看报告按钮应为重要按钮"
);
assert(
/<el-button[\s\S]*?@click="handleOpenCreditUpload"[\s\S]*?>\s*征信导入\s*<\/el-button>/.test(
source
) &&
!/<el-button[\s\S]*?@click="handleOpenCreditUpload"[\s\S]*?type="primary"[\s\S]*?>\s*征信导入\s*<\/el-button>/.test(
source
),
"征信导入按钮应为默认样式"
);
assert(
/isReportDisabled\(\)\s*\{[\s\S]*?\["0",\s*"3"\]\.includes\(String\(this\.projectInfo\.projectStatus\)\)/.test(
source
),
"项目状态为进行中或打标中时应禁用查看报告"
);
assert(
/uploadCards:\s*\[[\s\S]*?key:\s*"transaction"[\s\S]*?btnText:\s*"上传流水"[\s\S]*?disabled:\s*false[\s\S]*?\],/.test(
source
),
"上传卡片区应只保留流水导入卡片"
);
assert(
!/key:\s*"credit"/.test(source),
"上传卡片区不应再保留征信导入卡片配置"
);
assert(
!/key:\s*"namelist"/.test(source),
"上传卡片区不应再保留名单库选择卡片配置"
);
assert(
/\.upload-cards\s*\{[\s\S]*?justify-content:\s*center;/.test(source),
"单张流水导入卡片应在上传区居中显示"
);
assert(
/\.upload-card\s*\{[\s\S]*?width:\s*420px;[\s\S]*?max-width:\s*100%;/.test(
source
),
"流水导入卡片应加宽到 420px并保持窄屏自适应"
);
console.log("upload-data-header-import-button test passed");