添加议价池模块
This commit is contained in:
186
openspec/changes/add-bargaining-pool-display/design.md
Normal file
186
openspec/changes/add-bargaining-pool-display/design.md
Normal 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. **交互功能**:可能需要添加议价池的编辑或调整功能
|
||||
85
openspec/changes/add-bargaining-pool-display/proposal.md
Normal file
85
openspec/changes/add-bargaining-pool-display/proposal.md
Normal 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 或金额)
|
||||
- 待后端确认
|
||||
@@ -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
|
||||
68
openspec/changes/add-bargaining-pool-display/tasks.md
Normal file
68
openspec/changes/add-bargaining-pool-display/tasks.md
Normal 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 或金额)尚未确认,暂不添加单位标签
|
||||
@@ -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>
|
||||
@@ -101,6 +101,14 @@
|
||||
:retail-output="retailOutput"
|
||||
: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>
|
||||
@@ -109,11 +117,13 @@
|
||||
<script>
|
||||
import { getWorkflow } from "@/api/loanPricing/workflow"
|
||||
import ModelOutputDisplay from "./components/ModelOutputDisplay.vue"
|
||||
import BargainingPoolDisplay from "./components/BargainingPoolDisplay.vue"
|
||||
|
||||
export default {
|
||||
name: "LoanPricingWorkflowDetail",
|
||||
components: {
|
||||
ModelOutputDisplay
|
||||
ModelOutputDisplay,
|
||||
BargainingPoolDisplay
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -121,6 +131,7 @@ export default {
|
||||
workflowDetail: null,
|
||||
retailOutput: null,
|
||||
corpOutput: null,
|
||||
bargainingPool: null,
|
||||
activeTab: 'basic'
|
||||
}
|
||||
},
|
||||
@@ -142,6 +153,7 @@ export default {
|
||||
this.workflowDetail = response.data.loanPricingWorkflow
|
||||
this.retailOutput = response.data.modelRetailOutputFields
|
||||
this.corpOutput = response.data.modelCorpOutputFields
|
||||
this.bargainingPool = response.data.bargainingPool
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
this.$modal.msgError("获取流程详情失败:" + (error.message || "未知错误"))
|
||||
|
||||
Reference in New Issue
Block a user