调整上传数据页轻改版前端实现

This commit is contained in:
wkc
2026-03-25 14:11:54 +08:00
parent 85f4e7bc61
commit ed54b01d26
4 changed files with 87 additions and 492 deletions

View File

@@ -0,0 +1,18 @@
# 上传数据页轻改版前端实施记录
## 本次改动
- 移除上传数据页中的上传流水卡片
- 将上传流水提升为右上角唯一主按钮
- 统一头部、锁定提示和文件记录区视觉
## 影响文件
- `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-file-list-table.test.js`
- `node ruoyi-ui/tests/unit/project-upload-credit-entry-jump.test.js`

View File

@@ -9,6 +9,14 @@
<el-button <el-button
size="small" size="small"
type="primary" type="primary"
icon="el-icon-upload2"
:disabled="isProjectTagging || isProjectArchived"
@click="handleOpenBatchUploadDialog"
>
上传流水
</el-button>
<el-button
size="small"
icon="el-icon-view" icon="el-icon-view"
:disabled="isReportDisabled" :disabled="isReportDisabled"
@click="handleViewReport" @click="handleViewReport"
@@ -38,34 +46,6 @@
{{ isProjectArchived ? "项目已归档暂不可上传或拉取数据" : "项目正在进行银行流水打标暂不可上传或拉取数据" }} {{ isProjectArchived ? "项目已归档暂不可上传或拉取数据" : "项目正在进行银行流水打标暂不可上传或拉取数据" }}
</div> </div>
<!-- 上传模块 -->
<div class="upload-section">
<div class="upload-cards">
<div
v-for="card in uploadCards"
:key="card.key"
class="upload-card"
:class="{ 'is-disabled': card.disabled }"
>
<div class="card-icon">
<i :class="card.icon"></i>
</div>
<div class="card-title">{{ card.title }}</div>
<div class="card-desc">{{ card.desc }}</div>
<el-button
size="small"
:type="card.uploaded ? 'primary' : ''"
:icon="card.uploaded ? 'el-icon-view' : 'el-icon-upload2'"
:plain="!card.uploaded"
:disabled="card.disabled"
@click="handleUploadClick(card.key)"
>
{{ card.btnText }}
</el-button>
</div>
</div>
</div>
<!-- 文件上传记录列表 --> <!-- 文件上传记录列表 -->
<div class="file-list-section"> <div class="file-list-section">
<div class="list-toolbar"> <div class="list-toolbar">
@@ -124,47 +104,6 @@
style="margin-top: 16px; text-align: right" style="margin-top: 16px; text-align: right"
></el-pagination> ></el-pagination>
</div> </div>
<!-- 数据质量检查
<div class="quality-check-section">
<div class="section-header">
<i class="el-icon-warning-outline warning-icon"></i>
<span>检查结果</span>
</div>
<div class="metrics">
<div v-for="metric in metrics" :key="metric.key" class="metric-card">
<div class="metric-title">{{ metric.title }}</div>
<div class="metric-value" :class="`value-${metric.level}`">
{{ metric.value }}
</div>
<div class="progress-ring-container">
<svg class="progress-ring" viewBox="0 0 32 32">
<circle
class="progress-ring-bg"
cx="16"
cy="16"
r="14"
fill="none"
stroke="#f0f0f0"
stroke-width="4"
/>
<circle
class="progress-ring-progress"
:stroke="getProgressColor(metric.level)"
cx="16"
cy="16"
r="14"
fill="none"
stroke-width="4"
:stroke-dasharray="circumference"
:stroke-dashoffset="getProgressOffset(metric.value)"
stroke-linecap="round"
/>
</svg>
</div>
</div>
</div>
</div> -->
</div> </div>
<el-dialog <el-dialog
@@ -319,7 +258,6 @@
<script> <script>
import { import {
getUploadStatus,
pullBankInfo, pullBankInfo,
parseIdCardFile, parseIdCardFile,
batchUploadFiles, batchUploadFiles,
@@ -358,8 +296,6 @@ export default {
activeMenu: "upload", activeMenu: "upload",
// 当前菜单标题 // 当前菜单标题
currentMenuTitle: "上传数据", currentMenuTitle: "上传数据",
// 圆环周长
circumference: 2 * Math.PI * 14,
pullBankInfoDialogVisible: false, pullBankInfoDialogVisible: false,
pullBankInfoLoading: false, pullBankInfoLoading: false,
parsingIdCardFile: false, parsingIdCardFile: false,
@@ -368,8 +304,6 @@ export default {
idCardText: "", idCardText: "",
dateRange: [], dateRange: [],
}, },
// 上传状态列表
uploadStatusList: [],
// 侧边栏菜单项 // 侧边栏菜单项
menuItems: [ menuItems: [
{ key: "upload", label: "上传数据", route: "upload" }, { key: "upload", label: "上传数据", route: "upload" },
@@ -378,39 +312,6 @@ export default {
{ key: "special", label: "专项排查", route: "special" }, { key: "special", label: "专项排查", route: "special" },
{ key: "detail", label: "流水明细查询", route: "detail" }, { key: "detail", label: "流水明细查询", route: "detail" },
], ],
// 上传卡片
uploadCards: [
{
key: "transaction",
title: "流水导入",
desc: "支持 PDF、CSV、Excel 格式文件上传",
icon: "el-icon-document",
btnText: "上传流水",
uploaded: false,
disabled: false,
},
],
// 质量指标
metrics: [
{
key: "completeness",
title: "数据完整性",
value: "98.5%",
level: "success",
},
{
key: "consistency",
title: "格式一致性",
value: "95.2%",
level: "info",
},
{
key: "continuity",
title: "余额连续性",
value: "92.8%",
level: "info",
},
],
// === 批量上传相关 === // === 批量上传相关 ===
batchUploadDialogVisible: false, batchUploadDialogVisible: false,
selectedFiles: [], selectedFiles: [],
@@ -457,22 +358,10 @@ export default {
return ["0", "3"].includes(String(this.projectInfo.projectStatus)); return ["0", "3"].includes(String(this.projectInfo.projectStatus));
}, },
}, },
watch: {
"projectInfo.projectStatus"() {
this.syncUploadCardDisabledState();
},
},
created() { created() {
// 加载初始数据
// this.loadInitialData();
// 监听路由变化更新选中菜单
this.updateActiveMenu(); this.updateActiveMenu();
this.syncUploadCardDisabledState();
}, },
mounted() { mounted() {
// 组件挂载后监听项目ID变化
this.$watch("projectId", this.loadInitialData);
// 加载统计数据和文件列表 // 加载统计数据和文件列表
this.loadStatistics(); this.loadStatistics();
this.loadFileList(); this.loadFileList();
@@ -488,76 +377,6 @@ export default {
this.stopPolling(); this.stopPolling();
}, },
methods: { methods: {
/** 加载初始数据 */
async loadInitialData() {
if (!this.projectId) return;
try {
this.loading = true;
const uploadStatusRes = await getUploadStatus(this.projectId);
this.uploadStatusList = uploadStatusRes.data || [];
// 更新上传卡片状态
this.updateUploadCards();
// 模拟更新质量指标实际应从API获取
this.updateQualityMetrics();
} catch (error) {
console.error("加载初始数据失败:", error);
this.$message.error("加载数据失败");
} finally {
this.loading = false;
}
},
/** 更新上传卡片状态 */
updateUploadCards() {
const statusMap = {};
this.uploadStatusList.forEach((item) => {
statusMap[item.uploadType] = item;
});
this.uploadCards.forEach((card) => {
const status = statusMap[card.key.toUpperCase()];
if (status) {
card.uploaded = status.uploaded;
card.btnText = status.uploaded
? "已上传" + card.title.replace("导入", "").replace("上传", "")
: card.btnText;
}
});
this.syncUploadCardDisabledState();
},
syncUploadCardDisabledState() {
this.uploadCards = this.uploadCards.map((card) => {
if (card.key === "transaction") {
return {
...card,
disabled: this.isProjectTagging || this.isProjectArchived,
};
}
return card;
});
},
/** 更新质量指标 */
updateQualityMetrics() {
// 模拟更新质量指标
this.metrics.forEach((metric) => {
if (metric.key === "completeness") {
metric.value = "98.5%";
metric.level = "success";
} else if (metric.key === "consistency") {
metric.value = "95.2%";
metric.level = "info";
} else if (metric.key === "continuity") {
metric.value = "92.8%";
metric.level = "info";
}
});
},
/** 菜单点击 */ /** 菜单点击 */
handleMenuClick(key, route) { handleMenuClick(key, route) {
const menuItem = this.menuItems.find((m) => m.key === key); const menuItem = this.menuItems.find((m) => m.key === key);
@@ -588,24 +407,12 @@ export default {
} }
this.$router.push("/maintain/creditInfo"); this.$router.push("/maintain/creditInfo");
}, },
/** 上传卡片点击 */ handleOpenBatchUploadDialog() {
handleUploadClick(key) { if (this.isProjectTagging || this.isProjectArchived) {
const card = this.uploadCards.find((c) => c.key === key); return;
if (!card || card.disabled) return;
if (key === "transaction") {
this.batchUploadDialogVisible = true;
this.selectedFiles = [];
}
},
async loadUploadStatus() {
try {
const res = await getUploadStatus(this.projectId);
this.uploadStatusList = res.data || [];
this.updateUploadCards();
} catch (error) {
console.error("加载上传状态失败:", error);
} }
this.batchUploadDialogVisible = true;
this.selectedFiles = [];
}, },
openPullBankInfoDialog() { openPullBankInfoDialog() {
this.pullBankInfoDialogVisible = true; this.pullBankInfoDialogVisible = true;
@@ -797,21 +604,6 @@ export default {
this.resetPullBankInfoForm(); this.resetPullBankInfoForm();
this.openPullBankInfoDialog(); this.openPullBankInfoDialog();
}, },
/** 获取进度条偏移 */
getProgressOffset(value) {
const percentage = parseFloat(value);
return this.circumference * (1 - percentage / 100);
},
/** 获取进度条颜色 */
getProgressColor(level) {
const colorMap = {
success: "#52c41a",
info: "#1890ff",
warning: "#fa8c16",
danger: "#f5222d",
};
return colorMap[level] || "#1890ff";
},
/** 格式化更新时间 */ /** 格式化更新时间 */
formatUpdateTime(time) { formatUpdateTime(time) {
if (!time) return "-"; if (!time) return "-";
@@ -1233,209 +1025,73 @@ export default {
.content-header { .content-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: flex-start;
margin-bottom: 20px; gap: 16px;
margin-bottom: 16px;
padding: 20px 24px;
border: 1px solid #ebeef5;
border-radius: 12px;
background: linear-gradient(135deg, #fffaf5 0%, #ffffff 58%, #fff4ec 100%);
box-shadow: 0 10px 24px rgba(140, 76, 38, 0.08);
.content-title { .content-title {
margin: 0; margin: 0;
font-size: 18px; font-size: 20px;
font-weight: 600; font-weight: 600;
color: #303133; color: #303133;
line-height: 32px;
} }
.header-actions { .header-actions {
display: flex; display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 12px; gap: 12px;
.el-button {
min-width: 104px;
}
} }
} }
.tagging-lock-tip { .tagging-lock-tip {
margin-bottom: 16px; margin-bottom: 16px;
padding: 12px 16px; padding: 10px 14px;
color: #ad6800; color: #ad6800;
background: #fff7e6; background: #fff8ec;
border: 1px solid #ffd591; border: 1px solid #ffe0b2;
border-radius: 6px; border-radius: 10px;
} box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65);
// 上传模块
.upload-section {
background: #ffffff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
padding: 20px;
margin-bottom: 16px;
.upload-cards {
display: flex;
justify-content: center;
.upload-card {
width: 420px;
max-width: 100%;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 20px 16px;
text-align: center;
transition: all 0.3s;
background-color: #fff;
&:hover {
border-color: #1890ff;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15);
}
&.is-disabled {
background-color: #fafafa;
border-color: #ebeef5;
opacity: 0.7;
&:hover {
border-color: #ebeef5;
box-shadow: none;
}
.card-icon,
.card-title,
.card-desc {
color: #c0c4cc;
}
}
.card-icon {
font-size: 32px;
color: #1890ff;
margin-bottom: 12px;
i {
font-size: 32px;
}
}
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.card-desc {
font-size: 13px;
color: #909399;
margin-bottom: 16px;
min-height: 36px;
line-height: 1.4;
}
.el-upload {
width: 100%;
}
}
}
}
// 数据质量检查
.quality-check-section {
background: #ffffff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
padding: 20px;
.section-header {
display: flex;
align-items: center;
margin-bottom: 20px;
.warning-icon {
font-size: 18px;
color: #fa8c16;
margin-right: 8px;
}
span {
font-size: 15px;
font-weight: 500;
color: #303133;
}
}
.metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 32px;
.metric-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fafafa;
.metric-title {
font-size: 14px;
color: #606266;
margin-bottom: 12px;
}
.metric-value {
font-size: 24px;
font-weight: 600;
margin-bottom: 12px;
&.value-success {
color: #52c41a;
}
&.value-info {
color: #1890ff;
}
&.value-warning {
color: #fa8c16;
}
&.value-danger {
color: #f5222d;
}
}
.progress-ring-container {
width: 48px;
height: 48px;
.progress-ring {
width: 48px;
height: 48px;
transform: rotate(-90deg);
.progress-ring-bg {
stroke: #ebeef5;
}
.progress-ring-progress {
transition: stroke-dashoffset 0.5s ease;
}
}
}
}
}
} }
} }
// 文件列表区域 // 文件列表区域
.file-list-section { .file-list-section {
background: #fff; background: #fff;
border-radius: 4px; border: 1px solid #ebeef5;
padding: 20px; border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); padding: 20px 24px 24px;
box-shadow: 0 12px 30px rgba(17, 24, 39, 0.06);
.list-toolbar { .list-toolbar {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f2f5;
}
::v-deep .el-table {
border: 1px solid #f0f2f5;
border-radius: 10px;
overflow: hidden;
}
::v-deep .el-table th {
background: #faf6f2;
color: #5f4b3a;
font-weight: 600;
} }
} }
@@ -1619,15 +1275,9 @@ export default {
// 响应式 // 响应式
@media (max-width: 1200px) { @media (max-width: 1200px) {
.upload-section .upload-cards { .main-content .content-header {
grid-template-columns: repeat(2, 1fr); padding: 18px 20px;
} }
.quality-check-section .metrics {
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -1643,14 +1293,11 @@ export default {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 12px; gap: 12px;
}
.upload-section .upload-cards { .header-actions {
grid-template-columns: 1fr; width: 100%;
} justify-content: flex-start;
}
.quality-check-section .metrics {
grid-template-columns: 1fr;
} }
.file-list-section .list-toolbar { .file-list-section .list-toolbar {

View File

@@ -9,36 +9,14 @@ const componentPath = path.resolve(
const source = fs.readFileSync(componentPath, "utf8"); const source = fs.readFileSync(componentPath, "utf8");
assert( assert(
/<el-button[\s\S]*?:disabled="card\.disabled"[\s\S]*?@click="handleUploadClick\(card\.key\)"/.test( /<el-button[\s\S]*?:disabled="isProjectTagging \|\| isProjectArchived"[\s\S]*?@click="handleOpenBatchUploadDialog"[\s\S]*?>\s*上传流水\s*<\/el-button>/.test(
source source
), ),
"上传卡片按钮应绑定禁用状态" "头部上传流水按钮应在项目打标中或已归档时禁用"
); );
assert( ["uploadCards:", "syncUploadCardDisabledState()", "handleUploadClick(key)", 'class="upload-section"'].forEach((token) => {
/uploadCards:\s*\[[\s\S]*?key:\s*"transaction"[\s\S]*?btnText:\s*"上传流水"[\s\S]*?disabled:\s*false[\s\S]*?\],/.test( assert(!source.includes(token), `旧上传卡片逻辑未清理: ${token}`);
source });
),
"上传卡片区应只保留一张默认可用的流水导入卡片"
);
assert(
/syncUploadCardDisabledState\(\)\s*\{[\s\S]*?card\.key === "transaction"[\s\S]*?disabled:\s*this\.isProjectTagging\s*\|\|\s*this\.isProjectArchived/.test(
source
),
"流水导入卡片应在项目打标中或已归档时同步置灰"
);
assert(
!/key:\s*"credit"/.test(source) && !/key:\s*"namelist"/.test(source),
"上传卡片区不应再保留征信导入或名单库选择卡片配置"
);
assert(
/handleUploadClick\(key\)\s*\{[\s\S]*?if\s*\(!card\s*\|\|\s*card\.disabled\)\s*return;/.test(
source
),
"禁用卡片点击后不应继续执行上传逻辑"
);
console.log("upload-data-disabled-cards test passed"); console.log("upload-data-disabled-cards test passed");

View File

@@ -9,70 +9,22 @@ const componentPath = path.resolve(
const source = fs.readFileSync(componentPath, "utf8"); const source = fs.readFileSync(componentPath, "utf8");
assert( 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( /<div class="header-actions">[\s\S]*?@click="handleOpenBatchUploadDialog"[\s\S]*?>\s*上传流水\s*<\/el-button>[\s\S]*?@click="handleViewReport"[\s\S]*?>\s*查看报告\s*<\/el-button>[\s\S]*?@click="handleFetchBankInfo"[\s\S]*?>\s*拉取本行信息\s*<\/el-button>[\s\S]*?@click="handleGoCreditInfoPage"[\s\S]*?>\s*征信导入\s*<\/el-button>/.test(
source source
), ),
"页面右上角按钮顺序应为查看报告、拉取本行信息、征信导入" "页面右上角按钮顺序应为上传流水、查看报告、拉取本行信息、征信导入"
); );
assert( assert(
/<el-button[\s\S]*?:disabled="isReportDisabled"[\s\S]*?@click="handleViewReport"[\s\S]*?>\s*查看报告\s*<\/el-button>/.test( /<el-button[\s\S]*?type="primary"[\s\S]*?@click="handleOpenBatchUploadDialog"[\s\S]*?>\s*上传流水\s*<\/el-button>/.test(
source source
), ),
"查看报告按钮应绑定禁用状态" "上传流水按钮应为头部唯一主按钮"
); );
assert( assert(
/<el-button[\s\S]*?type="primary"[\s\S]*?@click="handleViewReport"[\s\S]*?>\s*查看报告\s*<\/el-button>/.test( !/uploadCards:/.test(source) && !/class="upload-cards"/.test(source),
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"); console.log("upload-data-header-import-button test passed");