添加议价池模块

This commit is contained in:
wkc
2026-01-22 09:58:21 +08:00
parent a0fd1d0e4e
commit b3b331c86c
6 changed files with 494 additions and 1 deletions

View File

@@ -0,0 +1,186 @@
# Design: 议价池显示组件
## Overview
本文档描述议价池显示组件的详细设计,包括组件结构、数据流、样式规范和实现细节。
## Component Architecture
### 组件层次结构
```
detail.vue (流程详情页面)
├── left-panel (左侧关键信息卡片)
└── right-panel (右侧面板)
├── detail-card (流程详情卡片)
├── ModelOutputDisplay (模型输出组件) [已存在]
└── BargainingPoolDisplay (议价池显示组件) [新增]
```
### 组件职责
| 组件 | 职责 |
|------|------|
| `detail.vue` | 流程详情页面容器,负责数据获取和子组件协调 |
| `BargainingPoolDisplay.vue` | 议价池数据展示,独立封装议价池相关的 UI 和逻辑 |
## Component Specification
### BargainingPoolDisplay.vue
#### Props
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `branchPool` | Number/String | 0 | 网点议价池 |
| `subBranchPool` | Number/String | 0 | 支行议价池 |
| `privateDomainPool` | Number/String | 0 | 私域池 |
#### Template Structure
```vue
<template>
<el-card class="bargaining-pool-card">
<div slot="header" class="card-header">
<span class="card-title">议价池</span>
</div>
<el-descriptions :column="3" border size="small">
<el-descriptions-item label="网点议价池">
{{ displayBranchPool }}
</el-descriptions-item>
<el-descriptions-item label="支行议价池">
{{ displaySubBranchPool }}
</el-descriptions-item>
<el-descriptions-item label="私域池">
{{ displayPrivateDomainPool }}
</el-descriptions-item>
</el-descriptions>
</el-card>
</template>
```
#### Computed Properties
| 属性名 | 说明 |
|--------|------|
| `displayBranchPool` | 返回网点议价池的显示值,处理 null/undefined/空字符串为 '0' |
| `displaySubBranchPool` | 返回支行议价池的显示值,处理 null/undefined/空字符串为 '0' |
| `displayPrivateDomainPool` | 返回私域池的显示值,处理 null/undefined/空字符串为 '0' |
#### Style Specification
议价池卡片样式将与 `ModelOutputDisplay` 保持一致:
```scss
.bargaining-pool-card {
// 与 model-output-card 相同的样式
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 20px;
}
}
```
## Data Flow
### API 响应结构(预期)
```javascript
{
"data": {
"loanPricingWorkflow": { ... },
"modelRetailOutputFields": { ... },
"modelCorpOutputFields": { ... },
"bargainingPool": { // 新增字段
"branchPool": 10, // 网点议价池
"subBranchPool": 5, // 支行议价池
"privateDomainPool": 3 // 私域池
}
}
}
```
### 组件集成
`detail.vue` 中:
```javascript
// data
bargainingPool: null,
// created() 中获取数据
getWorkflow(serialNum).then(response => {
this.workflowDetail = response.data.loanPricingWorkflow
this.retailOutput = response.data.modelRetailOutputFields
this.corpOutput = response.data.modelCorpOutputFields
this.bargainingPool = response.data.bargainingPool // 新增
this.loading = false
})
```
```vue
<!-- template 中使用组件 -->
<BargainingPoolDisplay
:branch-pool="bargainingPool?.branchPool"
:sub-branch-pool="bargainingPool?.subBranchPool"
:private-domain-pool="bargainingPool?.privateDomainPool"
/>
```
## Error Handling
### 数据缺失处理
当 API 响应中没有议价池数据时:
- 组件使用默认值 0 显示
- 不显示错误提示
- 保证页面正常展示
### 值格式化
```javascript
computed: {
displayBranchPool() {
const value = this.branchPool
if (value === null || value === undefined || value === '') {
return '0'
}
return value
},
// ... 其他字段类似
}
```
## Testing Considerations
### 单元测试场景
1. 组件渲染时显示默认值 0
2. 传入正确的议价池数据时正确显示
3. 处理 null/undefined 值时显示 0
### 集成测试场景
1. 流程详情页面加载时议价池卡片正确显示
2. 议价池卡片位置在模型输出卡片下方
3. 样式与模型输出卡片保持一致
## Future Enhancements
1. **后端数据对接**:当后端提供议价池 API 时,移除默认值逻辑
2. **单位显示**确认议价池数值单位BP 或金额)后添加单位标签
3. **交互功能**:可能需要添加议价池的编辑或调整功能

View File

@@ -0,0 +1,85 @@
# Proposal: 添加议价池显示组件
## Summary
在流程详情页面中,在"模型输出"卡片下方添加一个新的"议价池"卡片,用于展示网点议价池、支行议价池和私域池三个字段。默认值均为 0。
## Motivation
当前流程详情页面展示了模型输出的详细信息,但缺少议价池相关的数据展示。议价池是贷款定价业务中的重要参考指标,需要将其添加到详情页面以便用户查看。
## Proposed Change
### Scope
仅修改前端流程详情页面,在模型输出组件下方添加议价池显示组件。
### Components Affected
- `ruoyi-ui/src/views/loanPricing/workflow/detail.vue` - 添加议价池组件
### Components to Create
- `ruoyi-ui/src/views/loanPricing/workflow/components/BargainingPoolDisplay.vue` - 新建议价池显示组件
## Design Approach
### UI 结构
议价池卡片将使用与模型输出卡片相同的样式风格,包含:
- 卡片标题:议价池
- 三个字段展示:
- 网点议价池默认值0
- 支行议价池默认值0
- 私域池默认值0
### 组件设计
- 创建独立的 `BargainingPoolDisplay.vue` 组件
- 使用 `el-descriptions` 组件展示字段
- 支持通过 props 传入议价池数据
- 默认值处理:当数据为空或未定义时显示 0
### 数据来源
- 议价池数据将从后端 API 响应中获取
- 暂时使用默认值 0后续由后端提供实际数据
## Alternatives Considered
1. **将议价池字段添加到模型输出组件内部**
- 优点:减少组件数量
- 缺点:模型输出组件已比较复杂,议价池是独立的业务概念,应独立展示
- 结论:不采用
2. **将议价池字段添加到流程详情卡片中**
- 优点:集中展示流程相关信息
- 缺点:议价池与流程基本信息关联性较弱,与模型输出更相关
- 结论:不采用
3. **创建独立的议价池组件(已选方案)**
- 优点:职责清晰、易于维护、与模型输出组件并列展示
- 缺点:增加一个组件文件
- 结论:采用
## Dependencies
- 依赖现有的 `el-card``el-descriptions` 组件
- 依赖后端 API 返回议价池数据(当前可使用默认值)
## Rollout Plan
1. 创建 `BargainingPoolDisplay.vue` 组件
2.`detail.vue` 中引入并使用该组件
3. 传递议价池数据(当前使用默认值)
4. 测试页面展示效果
## Success Criteria
- 议价池卡片正确显示在模型输出卡片下方
- 三个字段(网点议价池、支行议价池、私域池)正确显示
- 默认值显示为 0
- 样式与现有卡片保持一致
## Open Questions
1. 议价池数据的具体字段名称是什么?
- 待确认:后端 API 中的议价池字段命名
2. 议价池数据的数值类型和单位是什么?
- 假设为数值类型BP 或金额)
- 待后端确认

View File

@@ -0,0 +1,42 @@
# loan-pricing-workflow-ui Spec Delta
## ADDED Requirements
### Requirement: 议价池信息展示
系统 SHALL 在流程详情页面的模型输出信息下方展示议价池信息。
#### Scenario: 显示议价池信息
- **WHEN** 用户访问流程详情页面
- **THEN** 系统在模型输出卡片下方显示"议价池"卡片,包含以下三个字段:
- 网点议价池:数值类型,默认值为 0
- 支行议价池:数值类型,默认值为 0
- 私域池:数值类型,默认值为 0
#### Scenario: 议价池数据格式化
- **WHEN** 议价池数据为 null、undefined 或空字符串
- **THEN** 系统将显示值格式化为 "0"
#### Scenario: 议价池卡片样式
- **WHEN** 用户查看流程详情页面
- **THEN** 议价池卡片的样式(标题栏、边框、内边距)与模型输出卡片保持一致
### Requirement: 议价池组件封装
系统 SHALL 将议价池展示功能封装为独立的 Vue 组件。
#### Scenario: 组件独立性
- **WHEN** 议价池显示组件被创建
- **THEN** 组件文件位于 `ruoyi-ui/src/views/loanPricing/workflow/components/BargainingPoolDisplay.vue`
#### Scenario: 组件 Props 接口
- **WHEN** 父组件使用议价池显示组件
- **THEN** 组件接收以下 props
- `branch-pool`网点议价池值Number/String默认值为 0
- `sub-branch-pool`支行议价池值Number/String默认值为 0
- `private-domain-pool`私域池值Number/String默认值为 0

View File

@@ -0,0 +1,68 @@
# Tasks: 添加议价池显示组件
## Task List
### 1. 创建议价池显示组件 ✅
**文件**: `ruoyi-ui/src/views/loanPricing/workflow/components/BargainingPoolDisplay.vue`
**描述**: 创建新的 Vue 组件用于展示议价池信息
**验收标准**:
- [x] 组件使用 `el-card` 包装,标题为"议价池"
- [x] 使用 `el-descriptions` 展示三个字段:网点议价池、支行议价池、私域池
- [x] 定义 props`branchPool``subBranchPool``privateDomainPool`,默认值为 0
- [x] 实现计算属性处理 null/undefined/空字符串,返回 '0'
- [x] 样式与 `ModelOutputDisplay` 保持一致
**依赖**: 无
---
### 2. 在详情页面中引入并使用议价池组件 ✅
**文件**: `ruoyi-ui/src/views/loanPricing/workflow/detail.vue`
**描述**: 在流程详情页面中引入并配置议价池组件
**验收标准**:
- [x]`components` 中注册 `BargainingPoolDisplay` 组件
- [x]`data` 中添加 `bargainingPool: null`
- [x]`getDetail()` 方法中从 API 响应获取议价池数据:`response.data.bargainingPool`
- [x] 在 template 中,`ModelOutputDisplay` 组件下方添加 `BargainingPoolDisplay` 组件
- [x] 传递 props`:branch-pool``:sub-branch-pool``:private-domain-pool`
**依赖**: Task 1
---
### 3. 验证页面展示效果 ✅
**描述**: 启动前端开发服务器,验证议价池组件正确显示
**验收标准**:
- [x] 访问任意流程详情页面
- [x] 确认议价池卡片显示在模型输出卡片下方
- [x] 确认三个字段显示为 "0"(默认值)
- [x] 确认卡片样式与模型输出卡片一致
**依赖**: Task 1, Task 2
---
## Dependencies Graph
```
Task 1 (创建组件) ✅
Task 2 (集成到详情页) ✅
Task 3 (验证效果) ✅
```
## Implementation Notes
- 使用 `&&` 操作符替代可选链 `?.` 以兼容 Vue 2.6
- 构建验证通过 (`npm run build:prod` 完成)
## Notes
- 当前使用默认值 0后续后端提供议价池 API 后需要更新数据获取逻辑
- 议价池数值的单位BP 或金额)尚未确认,暂不添加单位标签

View File

@@ -0,0 +1,100 @@
<template>
<el-card class="bargaining-pool-card">
<div slot="header" class="card-header">
<span class="card-title">议价池</span>
</div>
<el-descriptions :column="4" border size="small">
<el-descriptions-item label="网点议价池">
{{ displayBranchPool }}
</el-descriptions-item>
<el-descriptions-item label="支行议价池">
{{ displaySubBranchPool }}
</el-descriptions-item>
<el-descriptions-item label="私域池">
{{ displayPrivateDomainPool }}
</el-descriptions-item>
<el-descriptions-item label="超利分成">
{{ displayExcessProfitShare }}
</el-descriptions-item>
</el-descriptions>
</el-card>
</template>
<script>
export default {
name: "BargainingPoolDisplay",
props: {
branchPool: {
type: [Number, String],
default: 0
},
subBranchPool: {
type: [Number, String],
default: 0
},
privateDomainPool: {
type: [Number, String],
default: 0
},
excessProfitShare: {
type: [Number, String],
default: 0
}
},
computed: {
displayBranchPool() {
const value = this.branchPool
if (value === null || value === undefined || value === '') {
return '0'
}
return value
},
displaySubBranchPool() {
const value = this.subBranchPool
if (value === null || value === undefined || value === '') {
return '0'
}
return value
},
displayPrivateDomainPool() {
const value = this.privateDomainPool
if (value === null || value === undefined || value === '') {
return '0'
}
return value
},
displayExcessProfitShare() {
const value = this.excessProfitShare
if (value === null || value === undefined || value === '') {
return '0'
}
return value
}
}
}
</script>
<style lang="scss" scoped>
.bargaining-pool-card {
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 20px;
}
}
</style>

View File

@@ -101,6 +101,14 @@
:retail-output="retailOutput" :retail-output="retailOutput"
:corp-output="corpOutput" :corp-output="corpOutput"
/> />
<!-- 议价池卡片 -->
<BargainingPoolDisplay
:branch-pool="bargainingPool && bargainingPool.branchPool"
:sub-branch-pool="bargainingPool && bargainingPool.subBranchPool"
:private-domain-pool="bargainingPool && bargainingPool.privateDomainPool"
:excess-profit-share="bargainingPool && bargainingPool.excessProfitShare"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -109,11 +117,13 @@
<script> <script>
import { getWorkflow } from "@/api/loanPricing/workflow" import { getWorkflow } from "@/api/loanPricing/workflow"
import ModelOutputDisplay from "./components/ModelOutputDisplay.vue" import ModelOutputDisplay from "./components/ModelOutputDisplay.vue"
import BargainingPoolDisplay from "./components/BargainingPoolDisplay.vue"
export default { export default {
name: "LoanPricingWorkflowDetail", name: "LoanPricingWorkflowDetail",
components: { components: {
ModelOutputDisplay ModelOutputDisplay,
BargainingPoolDisplay
}, },
data() { data() {
return { return {
@@ -121,6 +131,7 @@ export default {
workflowDetail: null, workflowDetail: null,
retailOutput: null, retailOutput: null,
corpOutput: null, corpOutput: null,
bargainingPool: null,
activeTab: 'basic' activeTab: 'basic'
} }
}, },
@@ -142,6 +153,7 @@ export default {
this.workflowDetail = response.data.loanPricingWorkflow this.workflowDetail = response.data.loanPricingWorkflow
this.retailOutput = response.data.modelRetailOutputFields this.retailOutput = response.data.modelRetailOutputFields
this.corpOutput = response.data.modelCorpOutputFields this.corpOutput = response.data.modelCorpOutputFields
this.bargainingPool = response.data.bargainingPool
this.loading = false this.loading = false
}).catch(error => { }).catch(error => {
this.$modal.msgError("获取流程详情失败:" + (error.message || "未知错误")) this.$modal.msgError("获取流程详情失败:" + (error.message || "未知错误"))