diff --git a/docs/reports/implementation/2026-03-25-project-upload-page-light-redesign-frontend-record.md b/docs/reports/implementation/2026-03-25-project-upload-page-light-redesign-frontend-record.md new file mode 100644 index 00000000..978652d2 --- /dev/null +++ b/docs/reports/implementation/2026-03-25-project-upload-page-light-redesign-frontend-record.md @@ -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` diff --git a/ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue b/ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue index 73ff4bdf..e5c8dfac 100644 --- a/ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue +++ b/ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue @@ -9,6 +9,14 @@ + 上传流水 + + - -
-
-
-
- -
-
{{ card.title }}
-
{{ card.desc }}
- - {{ card.btnText }} - -
-
-
-
@@ -124,47 +104,6 @@ style="margin-top: 16px; text-align: right" >
- -
import { - getUploadStatus, pullBankInfo, parseIdCardFile, batchUploadFiles, @@ -358,8 +296,6 @@ export default { activeMenu: "upload", // 当前菜单标题 currentMenuTitle: "上传数据", - // 圆环周长 - circumference: 2 * Math.PI * 14, pullBankInfoDialogVisible: false, pullBankInfoLoading: false, parsingIdCardFile: false, @@ -368,8 +304,6 @@ export default { idCardText: "", dateRange: [], }, - // 上传状态列表 - uploadStatusList: [], // 侧边栏菜单项 menuItems: [ { key: "upload", label: "上传数据", route: "upload" }, @@ -378,39 +312,6 @@ export default { { key: "special", label: "专项排查", route: "special" }, { 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, selectedFiles: [], @@ -457,22 +358,10 @@ export default { return ["0", "3"].includes(String(this.projectInfo.projectStatus)); }, }, - watch: { - "projectInfo.projectStatus"() { - this.syncUploadCardDisabledState(); - }, - }, created() { - // 加载初始数据 - // this.loadInitialData(); - // 监听路由变化更新选中菜单 this.updateActiveMenu(); - this.syncUploadCardDisabledState(); }, mounted() { - // 组件挂载后监听项目ID变化 - this.$watch("projectId", this.loadInitialData); - // 加载统计数据和文件列表 this.loadStatistics(); this.loadFileList(); @@ -488,76 +377,6 @@ export default { this.stopPolling(); }, 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) { const menuItem = this.menuItems.find((m) => m.key === key); @@ -588,24 +407,12 @@ export default { } this.$router.push("/maintain/creditInfo"); }, - /** 上传卡片点击 */ - handleUploadClick(key) { - const card = this.uploadCards.find((c) => c.key === key); - 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); + handleOpenBatchUploadDialog() { + if (this.isProjectTagging || this.isProjectArchived) { + return; } + this.batchUploadDialogVisible = true; + this.selectedFiles = []; }, openPullBankInfoDialog() { this.pullBankInfoDialogVisible = true; @@ -797,21 +604,6 @@ export default { this.resetPullBankInfoForm(); 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) { if (!time) return "-"; @@ -1233,209 +1025,73 @@ export default { .content-header { display: flex; justify-content: space-between; - align-items: center; - margin-bottom: 20px; + align-items: flex-start; + 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 { margin: 0; - font-size: 18px; + font-size: 20px; font-weight: 600; color: #303133; + line-height: 32px; } .header-actions { display: flex; + flex-wrap: wrap; + justify-content: flex-end; gap: 12px; + + .el-button { + min-width: 104px; + } } } .tagging-lock-tip { margin-bottom: 16px; - padding: 12px 16px; + padding: 10px 14px; color: #ad6800; - background: #fff7e6; - border: 1px solid #ffd591; - border-radius: 6px; - } - - // 上传模块 - .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; - } - } - } - } - } + background: #fff8ec; + border: 1px solid #ffe0b2; + border-radius: 10px; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65); } } // 文件列表区域 .file-list-section { background: #fff; - border-radius: 4px; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + 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: flex-end; align-items: center; 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) { - .upload-section .upload-cards { - grid-template-columns: repeat(2, 1fr); + .main-content .content-header { + padding: 18px 20px; } - - .quality-check-section .metrics { - grid-template-columns: repeat(3, 1fr); - gap: 16px; - } - } @media (max-width: 768px) { @@ -1643,14 +1293,11 @@ export default { flex-direction: column; align-items: flex-start; gap: 12px; - } - .upload-section .upload-cards { - grid-template-columns: 1fr; - } - - .quality-check-section .metrics { - grid-template-columns: 1fr; + .header-actions { + width: 100%; + justify-content: flex-start; + } } .file-list-section .list-toolbar { diff --git a/ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js b/ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js index 32e5e92e..06868894 100644 --- a/ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js +++ b/ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js @@ -9,36 +9,14 @@ const componentPath = path.resolve( const source = fs.readFileSync(componentPath, "utf8"); assert( - /\s*上传流水\s*<\/el-button>/.test( source ), - "上传卡片按钮应绑定禁用状态" + "头部上传流水按钮应在项目打标中或已归档时禁用" ); -assert( - /uploadCards:\s*\[[\s\S]*?key:\s*"transaction"[\s\S]*?btnText:\s*"上传流水"[\s\S]*?disabled:\s*false[\s\S]*?\],/.test( - 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 - ), - "禁用卡片点击后不应继续执行上传逻辑" -); +["uploadCards:", "syncUploadCardDisabledState()", "handleUploadClick(key)", 'class="upload-section"'].forEach((token) => { + assert(!source.includes(token), `旧上传卡片逻辑未清理: ${token}`); +}); console.log("upload-data-disabled-cards test passed"); diff --git a/ruoyi-ui/tests/unit/upload-data-header-import-button.test.js b/ruoyi-ui/tests/unit/upload-data-header-import-button.test.js index 8cca079f..b947e929 100644 --- a/ruoyi-ui/tests/unit/upload-data-header-import-button.test.js +++ b/ruoyi-ui/tests/unit/upload-data-header-import-button.test.js @@ -9,70 +9,22 @@ const componentPath = path.resolve( const source = fs.readFileSync(componentPath, "utf8"); assert( - /
[\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( + /
[\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 ), - "页面右上角按钮顺序应为查看报告、拉取本行信息、征信导入" + "页面右上角按钮顺序应为上传流水、查看报告、拉取本行信息、征信导入" ); assert( - /\s*查看报告\s*<\/el-button>/.test( + /\s*上传流水\s*<\/el-button>/.test( source ), - "查看报告按钮应绑定禁用状态" + "上传流水按钮应为头部唯一主按钮" ); assert( - /\s*查看报告\s*<\/el-button>/.test( - source - ), - "查看报告按钮应为重要按钮" -); - -assert( - /\s*征信导入\s*<\/el-button>/.test( - source - ) && - !/\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,并保持窄屏自适应" + !/uploadCards:/.test(source) && !/class="upload-cards"/.test(source), + "上传卡片数据和模板应已清理" ); console.log("upload-data-header-import-button test passed");