hookehuyr

feat(plan): 新增多阶段提取方案功能

- 新增产品:宏挚传承保障计划(多阶段) (savings-gs-multistage)
- 年龄 < 12岁:固定显示 3 个阶段
- 年龄 ≥ 12岁:初始 1 个阶段,可添加至 4 个
- 提取期新增"一笔过"选项
- 递增百分比改为可选字段(不填传 null)
- 提交数据格式:withdrawal_stages 数组
- 删除冗余的 mock 文件 (src/api/mock/hotProducts.js)
- 文档归档到 docs/tasks/2026-02-25/archive/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
......@@ -2,6 +2,29 @@
记录项目开发过程中的重要变更和完成任务。
## 2026-02-25
### 21:00:00 - feat(plan): 新增多阶段提取方案功能
**影响文件**:
- `src/config/plan-templates.js` - 新增多阶段产品配置和 `savings-gs-multistage` 产品
- `src/components/plan/PlanTemplates/SavingsTemplate.vue` - 实现多阶段提取渲染和交互逻辑
- `src/api/mock/mock/hotProducts.js` - 添加多阶段产品 mock 数据
**变更摘要**:
- 新增产品:宏挚传承保障计划(多阶段) (`savings-gs-multistage`)
- 多阶段功能:
- 年龄 < 12岁:固定显示 3 个阶段
- 年龄 ≥ 12岁:初始 1 个阶段,可添加至 4 个
- 提取期新增"一笔过"选项
- 递增百分比改为可选字段(不填传 null)
- 提交数据格式:`withdrawal_stages` 数组
- 样式优化:阶段卡片白色背景
**相关文档**:
- `docs/tasks/2026-02-25/archive/多阶段提取方案设计.md`
- `docs/tasks/2026-02-25/archive/客户新需求-2026-02-25.md`
## 2026-02-24
### 23:59:18 - 完成任务
......@@ -15,3 +38,48 @@
**变更摘要**:
- 无详细描述
## 2026-02-25
### 20:09:02 - 完成任务
**影响文件**:
- `src/components/plan/PlanTemplates/CriticalIllnessTemplate.vue`
- `src/components/plan/PlanTemplates/LifeInsuranceTemplate.vue`
- `src/components/plan/PlanTemplates/SavingsTemplate.vue`
- `src/config/plan-templates.js`
**变更摘要**:
- 无详细描述
### 20:11:21 - 完成任务
**影响文件**:
- `src/components/plan/PlanTemplates/CriticalIllnessTemplate.vue`
- `src/components/plan/PlanTemplates/LifeInsuranceTemplate.vue`
- `src/components/plan/PlanTemplates/SavingsTemplate.vue`
- `src/config/plan-templates.js`
**变更摘要**:
- 无详细描述
### 20:12:04 - 完成任务
**影响文件**:
- `src/components/plan/PlanTemplates/CriticalIllnessTemplate.vue`
- `src/components/plan/PlanTemplates/LifeInsuranceTemplate.vue`
- `src/components/plan/PlanTemplates/SavingsTemplate.vue`
- `src/config/plan-templates.js`
**变更摘要**:
- 无详细描述
### 20:14:14 - 完成任务
**影响文件**:
- `src/components/plan/PlanTemplates/CriticalIllnessTemplate.vue`
- `src/components/plan/PlanTemplates/LifeInsuranceTemplate.vue`
- `src/components/plan/PlanTemplates/SavingsTemplate.vue`
- `src/config/plan-templates.js`
**变更摘要**:
- 无详细描述
......
# 多阶段提取方案设计文档
> **创建时间**: 2026-02-25
> **状态**: ⏳ 待客户确认需求
> **状态**: ✅ 需求已确认
> **设计师**: Claude Code
> **版本**: 1.1.0
> **更新内容**: 新增需求说明章节,辨析多阶段与多方案概念
> **版本**: 2.0.0
> **更新内容**: 客户确认只需多阶段提取,不需要多方案对比功能
---
## 📋 目录
1. [需求说明(待客户确认)](#需求说明待客户确认)
1. [需求说明(已确认)](#需求说明已确认)
2. [需求背景](#需求背景)
3. [现状分析](#现状分析)
4. [方案设计](#方案设计)
......@@ -18,25 +18,20 @@
6. [组件设计](#组件设计)
7. [Schema 配置](#schema-配置)
8. [字段映射](#字段映射)
9. [多方案需求分析](#多方案需求分析)
10. [实施清单](#实施清单)
11. [待确认事项](#待确认事项)
9. [实施清单](#实施清单)
10. [待确认事项](#待确认事项)
---
## 📖 需求说明(待客户确认)
## 📖 需求说明(确认)
> **重要说明**:以下是我们对客户资料的理解分析,**请客户确认实际需求**。
> **✅ 客户已确认**:2026-02-25
### 概念辨析:多阶段 vs 多方案
### 需求概述
客户提供的资料中出现了两个容易混淆的概念,需要明确区分:
**多阶段提取(Multi-Stage Withdrawal)****一个提取方案**中,包含**多个不同阶段**的提取计划。
#### 1️⃣ 多阶段提取(Multi-Stage Withdrawal)
**定义****一个提取方案**中,包含**多个不同阶段**的提取计划。
**示例**(3岁小公主 - 方案一):
**示例**(教育+创业+退休组合):
```
同一个方案内的三个阶段:
├─ 第一阶段:18-21岁,每年5万(教育基金)
......@@ -44,72 +39,11 @@
└─ 第三阶段:50-100岁,每年7万(退休养老年金)
```
**特点**
- ✅ 这是一个方案
- ✅ 包含多个提取阶段
- ✅ 每个阶段针对不同人生阶段的需求
#### 2️⃣ 多方案(Multiple Scenarios)
**定义****同一份计划书**中,展示**多个备选提取方案**供客户选择。
**示例**(3岁小公主 - 3个方案):
```
方案一:多阶段分时提取
└─ 教育(18-21岁) + 创业(30岁) + 退休(50-100岁)
方案二:长期定期提取
└─ 9-100岁,每年1.75万
方案三:财富传承
└─ 不提取,展示保单价值增长表
```
**特点**
- ✅ 这是三个独立的方案
- ✅ 每个方案代表不同的提取策略
- ✅ 客户可以在方案之间对比选择
### 对比总结
| 维度 | 多阶段 | 多方案 |
|------|--------|--------|
| **是什么** | 一个方案内的多个提取阶段 | 多个独立的备选方案 |
| **为什么** | 满足不同人生阶段的资金需求 | 提供不同风险/收益的提取策略 |
| **数据量** | 1个方案,3-5个阶段 | 3个方案,每个方案可能有多个阶段 |
| **客户选择** | 无需选择,按阶段执行 | 客户需要选择采用哪个方案 |
### 需要客户确认的问题
#### 问题 1:是否需要多阶段提取?
**说明**:是否需要在一个方案中设置多个提取阶段(如:教育→创业→退休)?
- [ ] 是,需要多阶段提取
- [ ] 否,每个方案只需要一种提取方式
#### 问题 2:是否需要多方案?
**说明**:是否需要在同一份计划书中展示多个备选方案供客户选择?
- [ ] 是,需要多方案对比(如:保守方案 vs 积极方案 vs 传承方案)
- [ ] 否,每个客户只需要一个方案
#### 问题 3:实施方案选择
### 客户确认要点
如果需要多方案,请选择实施方案:
**选项 A:单次提交,多方案对比**
- 优点:一次性输入多个方案,计划书可直接对比展示
- 缺点:需要后端改动支持,开发周期较长
**选项 B:多次提交,分别生成**(推荐)
- 优点:复用现有逻辑,无需后端改动,快速上线
- 缺点:每个方案单独生成,需要人工对比
**选项 C:混合方案**
- 第一阶段:先实现选项B(快速上线)
- 第二阶段:再升级为选项A(完整体验)
- ✅ 一份计划书里只有一个提取方案,不会有多个
- ✅ 需要支持在一个方案中设置多个不同阶段的提取计划
- ✅ 阶段数量建议 1-5 个
---
......@@ -596,189 +530,43 @@ const savingsSubmitMapping = {
---
## 多方案需求分析
> **说明**:如果客户确认需要多方案功能,以下是技术实施建议。
### 现有系统能力评估
| 功能 | 现有支持 | 客户需求 | 差距 |
|------|---------|---------|------|
| 单个提取方案 | ✅ 完全支持 | ✅ 需要 | 无 |
| 多阶段分时提取 | ❌ 不支持 | ✅ 需要 | 本文档已设计方案 |
| **多个提取方案** | ❌ 不支持 | ⏳ 待确认 | **需确认后设计** |
### 多方案架构设计建议
```javascript
formData = {
// ... 基础字段
withdrawal_enabled: '是',
// 多方案模式
withdrawal_scenarios: [
{
scenario_id: 1,
scenario_name: '方案一:教育+创业+退休',
withdrawal_mode: '多阶段分时提取',
withdrawal_stages: [
{ stage_name: '教育基金', start_age: 18, end_age: 21, withdrawal_type: '每年提取', annual_amount: 50000 },
{ stage_name: '创业金', start_age: 30, end_age: 30, withdrawal_type: '一笔过', lump_sum_amount: 400000 },
{ stage_name: '退休金', start_age: 50, end_age: 100, withdrawal_type: '每年提取', annual_amount: 70000 }
]
},
{
scenario_id: 2,
scenario_name: '方案二:长期年金',
withdrawal_mode: '单阶段定期提取',
withdrawal_start_age: 9,
withdrawal_period: 92, // 9-100岁
annual_withdrawal_amount: 17500
},
{
scenario_id: 3,
scenario_name: '方案三:财富传承',
withdrawal_mode: '不提取',
withdrawal_stages: []
}
]
}
```
#### 方案 B:多次提交,分别生成方案(推荐)
**优点**
- ✅ 表单简单,复用现有逻辑
- ✅ 无需后端改动
- ✅ 用户可灵活组合不同方案
**缺点**
- ❌ 每个方案单独生成,无法直接对比
- ❌ 用户需要多次操作
**用户流程**
```
用户填写方案一 → 提交 → 生成计划书1(方案一)
用户点击"添加方案" → 填写方案二 → 提交 → 生成计划书2(方案二)
用户点击"添加方案" → 填写方案三 → 提交 → 生成计划书3(方案三)
计划书列表展示 3 个方案,用户可选择查看/分享
```
#### 方案 C:混合方案(最佳用户体验)
**第一阶段**:快速实现
- 支持多次提交(方案B)
- 在计划书列表页添加"同一客户的多个方案"分组展示
**第二阶段**:完整方案
- 支持单次提交多方案(方案A)
- 计划书生成后可展示方案对比表格
### 实施建议
#### 短期(MVP)
1. **实现多阶段提取**(本文档原有方案)
2. **支持多次提交不同方案**
- 在计划书列表中按客户/产品分组
- 允许用户快速复制已有方案创建新方案
#### 中期(完整多方案)
1. **改造表单支持多方案**
- 添加"方案管理"组件
- 支持添加/删除/切换方案
2. **后端支持多方案数据结构**
- 新增 `withdrawal_scenarios_json` 字段
3. **计划书生成支持方案对比**
#### 技术架构调整
```javascript
// 数据结构扩展
formData = {
// ... 基础字段
// 新增:方案模式
scenario_mode: 'single' | 'multiple', // 单方案 | 多方案
// 单方案模式(现有逻辑复用)
withdrawal_enabled: '是',
withdrawal_mode: '单阶段定期提取' | '多阶段分时提取',
// ... 单方案字段
// 多方案模式(新增)
withdrawal_scenarios: [
{
scenario_id: Date.now(),
scenario_name: '方案一',
withdrawal_mode: '多阶段分时提取',
withdrawal_stages: [...]
},
{
scenario_id: Date.now() + 1,
scenario_name: '方案二',
withdrawal_mode: '单阶段定期提取',
// ... 单方案字段
}
]
}
```
---
## 待确认事项
### 与客户确认
#### 多阶段提取功能
- [ ] 确认是否需要"多阶段分时提取"功能
- [x] 确认需要"多阶段分时提取"功能 ✅
- [x] 确认一份计划书只有一个提取方案 ✅
- [ ] 确认最多支持多少个阶段(设计方案:1-5个)
- [ ] 确认预设方案是否满足需求(教育+创业+退休、退休年金、财富传承)
- [ ] 确认后端是否能够接收 `withdrawal_stages_json` 字段
- [ ] 确认多阶段提取的计划书生成方式
#### 多方案功能(重要)
- [ ] **确认是否需要在一个计划书中展示多个提取方案**
- [ ] 确认方案数量上限(客户资料中显示3个方案)
- [ ] 确认实施方案:
- **方案A**:单次提交多方案(需要后端改动)
- **方案B**:多次提交不同方案(快速实现,无需后端改动)
- **方案C**:混合方案(先B后A)
- [ ] 确认后端是否能够接收 `withdrawal_scenarios_json` 字段
### 技术细节
- [ ] 确认一笔过提取时的年龄表示方式(start_age = end_age)
- [ ] 确认金额单位(前端:分,后端:元)
- [ ] 确认阶段数量上限(设计方案:5个)
- [ ] 确认多方案UI交互设计(Tab切换?卡片列表?)
---
## 附录
### 客户文档提取方案参考
### 多阶段提取方案示例
**方案一(3岁小公主)**
```
18-21岁:每年 5万(教育基金)
30岁:一笔过 40万(创业金/婚嫁金)← "一笔过" = 一次性提取
50-100岁:每年 7万(退休养老年金)
**示例:教育+创业+退休组合**
```
阶段1 - 教育基金:
18-21岁,每年提取 5万
> **术语说明**:"一笔过"是客户使用的术语,等同于"一次性提取",即在特定年龄一次性全额提取保单价值,而非分期提取。
阶段2 - 创业金/婚嫁金:
30岁,一笔过提取 40万
**方案二(37岁)**
```
43-100岁:每年 3.75万(退休年金)
阶段3 - 退休养老年金:
50-100岁,每年提取 7万
```
**方案三(财富传承)**
```
不填写提取计划,展示保单价值增长表
```
> **术语说明**:"一笔过"是客户使用的术语,等同于"一次性提取",即在特定年龄一次性全额提取保单价值,而非分期提取。
### 现有系统架构参考
......
# 客户新需求记录(2026-02-25)
> **创建时间**: 2026-02-25
> **状态**: ⏳ 待确认方案
> **优先级**: 中
---
## 📋 需求清单
### 需求 1:重疾类产品缴费年期新增选项
**描述**:重疾类产品申请时,模板内"缴费年期"增加"直至65岁"选项
**影响产品**
- ✅ MPC 守护无间重疾(已有产品)
- ✅ MBC PRO 活跃人生重疾保 PRO(已有产品)
- ✅ MBC2 活跃人生重疾保 2(已有产品)
**当前状态**
- 现有缴费年期选项:10年、20年、25年
- 需要新增:直至65岁
**技术实现**
- 文件:`src/config/plan-templates.js`
- 修改三款重疾产品的 `payment_periods` 配置
---
### 需求 2:年龄与出生年月日二选一填写
**描述**:所有产品申请时,年龄与出生年月日改为二选一进行填写
**影响范围**
- ✅ 所有产品类型(人寿、重疾、储蓄)
**当前状态**
- 现有:必填出生年月日,年龄自动计算
- 需求:二选一填写(填年龄自动计算生日,或填生日自动计算年龄)
**UI 交互设计**
```
┌─────────────────────────────────┐
│ 出生日期(二选一) │
├─────────────────────────────────┤
│ ○ 填写年龄 │
│ 年龄: [ 30 ] 岁 │
│ │
│ ○ 填写出生年月日 │
│ 生日: [ 1990-01-01 ] │
└─────────────────────────────────┘
```
**技术实现**
- 新增表单字段:`age_input_mode`
- 修改 Schema:条件显示年龄或生日输入
- 添加自动计算逻辑
---
### 需求 3:储蓄类产品多阶段提取方案
**描述**:储蓄类产品的提取方案改为可设置多阶段
**客户确认**
- ✅ 一份计划书里只有一个提取方案,不会有多个
- ✅ 需要支持在一个方案中设置多个不同阶段的提取计划
**示例**(教育+创业+退休组合):
```
┌──────────────────────────────────────────┐
│ 方案一:多阶段分时提取 │
├──────────────────────────────────────────┤
│ 阶段1: 教育基金 │
│ 18-21岁,每年提取 5万 │
│ │
│ 阶段2: 创业金/婚嫁金 │
│ 30岁,一笔过提取 40万 │
│ │
│ 阶段3: 退休养老年金 │
│ 50-100岁,每年提取 7万 │
└──────────────────────────────────────────┘
```
**设计文档**:参见本目录下的 `多阶段提取方案设计.md`
---
## 📊 实施计划
### 优先级排序
| 优先级 | 需求 | 预估工时 | 依赖 |
|--------|------|----------|------|
| **P1** | 需求1:重疾缴费年期 | 30分钟 | 无 |
| **P2** | 需求2:年龄生日二选一 | 2-3小时 | 无 |
| **P3** | 需求3:多阶段提取 | 4-5小时 | 需求2 |
### 实施步骤
#### 第 1 步:重疾缴费年期新增选项(30分钟)
**文件**`src/config/plan-templates.js`
**修改内容**
```javascript
// 为三款重疾产品的 payment_periods 添加 "直至65岁" 选项
payment_periods: [
'10 年(15 日 - 65 岁)',
'20 年(15 日 - 65 岁)',
'25 年(15 日 - 60 岁)',
'直至65岁' // 新增
]
```
---
#### 第 2 步:年龄与出生年月日二选一(2-3小时)
**涉及文件**
1. `src/config/plan-templates.js` - Schema 配置
2. `src/components/plan/PlanFields/` - 新增组件
3. `src/components/plan/PlanTemplates/` - 模板组件逻辑
**Schema 扩展**
```javascript
base_fields: [
// 新增:年龄输入模式选择
{
id: 'age_input_mode',
key: 'age_input_mode',
type: 'radio',
label: '请选择输入方式',
options: ['填写年龄', '填写出生年月日'],
required: true,
default: '填写出生年月日'
},
// 年龄输入(条件显示)
{
id: 'age',
key: 'age',
type: 'age',
label: '年龄',
placeholder: '请输入年龄',
required: true,
show_when: { field: 'age_input_mode', op: 'eq', value: '填写年龄' },
clear_when_hidden: true
},
// 出生年月日输入(条件显示)
{
id: 'birthday',
key: 'birthday',
type: 'date',
label: '出生年月日',
placeholder: '请选择年月日',
required: true,
show_when: { field: 'age_input_mode', op: 'eq', value: '填写出生年月日' },
clear_when_hidden: true
}
]
```
**自动计算逻辑**
- 填写年龄 → 自动计算出生年月日(默认当年生日)
- 填写出生年月日 → 自动计算年龄(当前年份 - 出生年份)
---
#### 第 3 步:多阶段提取方案(4-5小时)
详见 `多阶段提取方案设计.md`
---
## 📝 待确认事项
### 客户确认
- [ ] **需求1确认**
- [ ] 确认"直至65岁"选项文字表述
- [ ] 确认是否需要年龄限制(如:最大投保年龄)
- [ ] **需求2确认**
- [ ] 确认默认选项(建议:填写出生年月日)
- [ ] 确认填写年龄时的默认生日(建议:当年生日)
- [ ] **需求3确认**
- [ ] 确认阶段数量上限(设计方案:1-5个)
- [ ] 确认预设方案模板(教育+创业+退休、退休年金、财富传承)
### 技术确认
- [ ] 后端能否接收新增字段:
- `age_input_mode`
- `withdrawal_stages_json`
- [ ] 后端年龄计算逻辑是否与前端一致
---
## 🔗 相关文档
- [多阶段提取方案设计](./多阶段提取方案设计.md)
- [计划书架构文档](../../plan/plan-entry-architecture.md)
- [Schema 配置规范](../../plan/plan-config-schema-reference.md)
---
**文档版本**: 1.0.0
**最后更新**: 2026-02-25
/**
* 热卖产品 Mock 数据
*
* @description 包含项目所有保险类型的产品 mock 数据,用于测试计划书模板显示
* 遵循项目 mock 数据规范,集成到 src/utils/mockData.js
* @module api/mock/hotProducts
* @author Claude Code
* @created 2026-02-09
*/
/**
* 热卖产品 Mock 数据列表
* @description 包含 9 种产品,覆盖所有计划书模板类型
*/
const HOT_PRODUCTS = [
// ====== 人寿保险 (LifeInsuranceTemplate) ======
{
id: 1,
product_name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
name: 'WIOP3E 盈传创富保障计划 3 - 优选版',
recommend: 'hot',
form_sn: 'life-insurance-wiop3e', // 对应 LifeInsuranceTemplate
categories: [
{ id: 'life', name: '人寿保险' }
],
tags: [
{ id: 't1', name: '终身寿险', bg_color: '#DBEAFE', text_color: '#1E40AF' },
{ id: 't2', name: '美元产品', bg_color: '#DCFCE7', text_color: '#166534' }
],
cover_image: 'https://picsum.photos/seed/wiop3e/400/300'
},
{
id: 2,
product_name: 'WIOP3 盈传创富保障计划 3',
name: 'WIOP3 盈传创富保障计划 3',
recommend: 'hot',
form_sn: 'life-insurance-wiop3', // 对应 LifeInsuranceTemplate
categories: [
{ id: 'life', name: '人寿保险' }
],
tags: [
{ id: 't1', name: '终身寿险', bg_color: '#DBEAFE', text_color: '#1E40AF' },
{ id: 't2', name: '美元产品', bg_color: '#DCFCE7', text_color: '#166534' }
],
cover_image: 'https://picsum.photos/seed/wiop3/400/300'
},
// ====== 重疾保险 (CriticalIllnessTemplate) ======
{
id: 3,
product_name: 'MPC 守护无间重疾',
name: 'MPC 守护无间重疾',
recommend: 'hot',
form_sn: 'critical-illness-mpc', // 对应 CriticalIllnessTemplate
categories: [
{ id: 'critical', name: '重疾保险' }
],
tags: [
{ id: 't1', name: '重疾保障', bg_color: '#FEF3C7', text_color: '#92400E' },
{ id: 't2', name: '终身保障', bg_color: '#E0E7FF', text_color: '#3730A3' }
],
cover_image: 'https://picsum.photos/seed/mpc/400/300'
},
{
id: 4,
product_name: 'MBC PRO 活跃人生重疾保 PRO',
name: 'MBC PRO 活跃人生重疾保 PRO',
recommend: 'hot',
form_sn: 'critical-illness-mbc-pro', // 对应 CriticalIllnessTemplate
categories: [
{ id: 'critical', name: '重疾保险' }
],
tags: [
{ id: 't1', name: '重疾保障', bg_color: '#FEF3C7', text_color: '#92400E' },
{ id: 't2', name: 'PRO 版本', bg_color: '#FEE2E2', text_color: '#991B1B' }
],
cover_image: 'https://picsum.photos/seed/mbc-pro/400/300'
},
{
id: 5,
product_name: 'MBC2 活跃人生重疾保 2',
name: 'MBC2 活跃人生重疾保 2',
recommend: 'hot',
form_sn: 'critical-illness-mbc2', // 对应 CriticalIllnessTemplate
categories: [
{ id: 'critical', name: '重疾保险' }
],
tags: [
{ id: 't1', name: '重疾保障', bg_color: '#FEF3C7', text_color: '#92400E' },
{ id: 't2', name: '升级版', bg_color: '#DCFCE7', text_color: '#166534' }
],
cover_image: 'https://picsum.photos/seed/mbc2/400/300'
},
// ====== 储蓄型产品 (SavingsTemplate) ======
{
id: 6,
product_name: 'GS 宏挚传承保障计划',
name: 'GS 宏挚传承保障计划',
recommend: 'hot',
form_sn: 'savings-gs', // 对应 SavingsTemplate
categories: [
{ id: 'savings', name: '储蓄保险' }
],
tags: [
{ id: 't1', name: '储蓄型', bg_color: '#E0E7FF', text_color: '#3730A3' },
{ id: 't2', name: '传承规划', bg_color: '#F3E8FF', text_color: '#6B21A8' }
],
cover_image: 'https://picsum.photos/seed/gs/400/300'
},
{
id: 7,
product_name: 'GC 宏挚家传保险计划',
name: 'GC 宏挚家传保险计划',
recommend: 'hot',
form_sn: 'savings-gc', // 对应 SavingsTemplate
categories: [
{ id: 'savings', name: '储蓄保险' }
],
tags: [
{ id: 't1', name: '储蓄型', bg_color: '#E0E7FF', text_color: '#3730A3' },
{ id: 't2', name: '家庭保障', bg_color: '#FEE2E2', text_color: '#991B1B' }
],
cover_image: 'https://picsum.photos/seed/gc/400/300'
},
{
id: 8,
product_name: 'FA 宏浚传承保障计划',
name: 'FA 宏浚传承保障计划',
recommend: 'hot',
form_sn: 'savings-fa', // 对应 SavingsTemplate
categories: [
{ id: 'savings', name: '储蓄保险' }
],
tags: [
{ id: 't1', name: '储蓄型', bg_color: '#E0E7FF', text_color: '#3730A3' },
{ id: 't2', name: '财富传承', bg_color: '#FEF3C7', text_color: '#92400E' }
],
cover_image: 'https://picsum.photos/seed/fa/400/300'
},
{
id: 9,
product_name: 'LV2 赤霞珠终身寿险计划2',
name: 'LV2 赤霞珠终身寿险计划2',
recommend: 'hot',
form_sn: 'savings-lv2', // 对应 SavingsTemplate
categories: [
{ id: 'savings', name: '储蓄保险' }
],
tags: [
{ id: 't1', name: '储蓄型', bg_color: '#E0E7FF', text_color: '#3730A3' },
{ id: 't2', name: '终身寿险', bg_color: '#DBEAFE', text_color: '#1E40AF' }
],
cover_image: 'https://picsum.photos/seed/lv2/400/300'
}
]
/**
* Mock: listAPI (热卖产品)
* @description 专门用于首页热卖产品的 Mock API,支持 form_sn 字段
* @param {Object} params - 请求参数
* @param {string} params.recommend - 推荐位(必须为 'hot')
* @returns {Promise<Object>} 模拟的 API 响应
*/
export async function mockHotProductsListAPI(params) {
const { recommend } = params
// 只返回热卖产品
if (recommend !== 'hot') {
return { code: 0, msg: '只支持热卖产品查询', data: { list: [], total: 0 } }
}
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 200))
console.log('[Mock] hotProductsListAPI - 返回热卖产品', HOT_PRODUCTS.length, '条')
return {
code: 1,
msg: 'success',
data: {
list: HOT_PRODUCTS,
total: HOT_PRODUCTS.length,
categories: [
{ id: 'life', name: '人寿保险' },
{ id: 'critical', name: '重疾保险' },
{ id: 'savings', name: '储蓄保险' }
]
}
}
}
/**
* 导出 Mock API 函数供其他模块使用
*/
export default {
mockHotProductsListAPI
}
......@@ -11,10 +11,10 @@
/**
* 热卖产品 Mock 数据列表
*
* @description 包含 9 种产品,覆盖所有计划书模板类型:
* @description 包含 10 种产品,覆盖所有计划书模板类型:
* - 人寿保险 (2 种): WIOP3E、WIOP3
* - 重疾保险 (3 种): MPC、MBC PRO、MBC2
* - 储蓄型产品 (4 种): GS、GC、FA、LV2
* - 储蓄型产品 (5 种): GS、GC、FA、LV2、GS(多阶段)
*/
export const hotProductsMockData = [
// ====== 人寿保险 (LifeInsuranceTemplate) ======
......@@ -165,6 +165,25 @@ export const hotProductsMockData = [
],
cover_image: 'https://picsum.photos/seed/lv2/400/300',
created_time: '2026-01-09 00:00:00'
},
// ====== 储蓄型产品 - 多阶段提取(新增 2026-02-25) ======
{
id: 10,
product_name: 'GS 宏挚传承保障计划(多阶段)',
recommend: 'hot',
form_sn: 'savings-gs-multistage', // 对应 SavingsTemplate 多阶段模式
categories: [
{ id: 'savings', name: '储蓄保险' }
],
tags: [
{ id: 't1', name: '多阶段提取', bg_color: '#F3E8FF', text_color: '#6B21A8' },
{ id: 't2', name: '新品', bg_color: '#DCFCE7', text_color: '#166534' },
{ id: 't3', name: '灵活规划', bg_color: '#FEF3C7', text_color: '#92400E' }
],
cover_image: 'https://picsum.photos/seed/gs-multistage/400/300',
created_time: '2026-02-25 00:00:00'
}
]
......@@ -288,6 +307,7 @@ export function getAllTemplateTypes() {
* - ✅ savings-gc - GC 宏挚家传保险计划
* - ✅ savings-fa - FA 宏浚传承保障计划
* - ✅ savings-lv2 - LV2 赤霞珠终身寿险计划2
* - ✅ savings-gs-multistage - GS 宏挚传承保障计划(多阶段) - 支持多阶段提取
*
* ## 测试步骤
*
......
......@@ -25,7 +25,92 @@
<div class="border-t border-gray-200 my-6"></div>
<div v-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section">
<!-- 多阶段提取计划(优先显示) -->
<div v-if="isMultiStageMode" class="multi-stage-withdrawal-section">
<h3 class="text-base font-semibold text-gray-900 mb-4">
款项提取(允许减少名义金额)
</h3>
<!-- 阶段卡片列表 -->
<div
v-for="(stage, index) in stages"
:key="index"
class="stage-card bg-white border border-gray-200 rounded-lg p-4 mb-4"
>
<!-- 阶段标题 -->
<div class="flex items-center justify-between mb-3">
<h4 class="text-sm font-medium text-gray-900">阶段{{ index + 1 }}</h4>
<!-- 删除按钮(≥12岁且至少有2个阶段时显示) -->
<nut-button
v-if="canRemoveStage && index > 0"
size="small"
type="danger"
@click="removeStage(index)"
>
删除
</nut-button>
</div>
<!-- 每年提取金额 -->
<PlanFieldAmount
v-model="stage.annual_withdrawal_amount"
label="每年提取金额"
placeholder="请输入每年提取金额"
inputLabel="请输入每年提取金额"
:required="true"
:currency="config.withdrawal_plan?.default_currency || config.currency"
class="mb-3"
/>
<!-- 由几岁开始 -->
<PlanFieldAgePicker
v-model="stage.withdrawal_start_age"
label="由几岁开始"
placeholder="请输入开始提取年龄"
:required="true"
class="mb-3"
/>
<!-- 提取期 -->
<PlanFieldSelect
v-model="stage.withdrawal_period"
label="提取期"
placeholder="请选择提取期"
:required="true"
:options="multiStagePeriodOptions"
class="mb-3"
/>
<!-- 每年递增提取之百分比(可选) -->
<div class="percentage-field">
<div class="text-sm text-gray-700 mb-2 flex items-center">
<span>每年递增提取之百分比(%</span>
<span class="text-gray-400 text-xs ml-2">(可选)</span>
</div>
<nut-input
v-model="stage.annual_increase_percentage"
type="digit"
placeholder="请输入递增百分比"
@input="(value) => onPercentageInput(value, `stages.${index}.annual_increase_percentage`)"
class="w-full"
/>
</div>
</div>
<!-- 添加阶段按钮(≥12岁且未达上限时显示) -->
<nut-button
v-if="canAddStage"
type="primary"
block
@click="addStage"
class="add-stage-btn"
>
+ 添加阶段
</nut-button>
</div>
<!-- 单阶段提取计划(原有逻辑) -->
<div v-else-if="config.withdrawal_plan?.enabled" class="withdrawal-plan-section">
<template v-for="field in withdrawalFields" :key="field.id || field.key">
<h3 v-if="field.section_title" class="text-base font-semibold text-gray-900 mb-4">
{{ field.section_title }}
......@@ -75,7 +160,7 @@
* :config="templateConfig"
* />
*/
import { reactive, watch, computed } from 'vue'
import { reactive, watch, computed, ref } from 'vue'
import Taro from '@tarojs/taro'
import PlanFieldName from '../PlanFields/NameInput.vue'
import PlanFieldAgePicker from '../PlanFields/AgePickerGlobal.vue'
......@@ -224,6 +309,158 @@ const getFieldProps = (field) => {
const { isFieldVisible } = useFieldDependencies(form, fieldDefinitions)
// ====== 多阶段提取计划逻辑 ======
/**
* 是否为多阶段模式
* @description 检查产品配置是否启用了 multi_stage_withdrawal
*/
const isMultiStageMode = computed(() => {
return props.config?.multi_stage_withdrawal?.enabled === true
})
/**
* 多阶段配置对象(便捷访问)
*/
const multiStageConfig = computed(() => {
return props.config?.multi_stage_withdrawal || {}
})
/**
* 多阶段提取期选项(包含"一笔过")
*/
const multiStagePeriodOptions = computed(() => {
return multiStageConfig.value.withdrawal_periods ||
props.config?.withdrawal_plan?.withdrawal_periods ||
[]
})
/**
* 阶段数据数组
* @description 每个阶段包含:annual_withdrawal_amount, withdrawal_start_age, withdrawal_period, annual_increase_percentage
*/
const stages = ref([])
/**
* 是否可添加阶段
* @description 年龄 ≥ 阈值且未达上限
*/
const canAddStage = computed(() => {
const age = parseInt(form.age) || 0
const threshold = multiStageConfig.value.age_threshold || 12
const limit = multiStageConfig.value.stage_limit || 4
return age >= threshold && stages.value.length < limit
})
/**
* 是否可删除阶段
* @description 年龄 ≥ 阈值且至少有2个阶段
*/
const canRemoveStage = computed(() => {
const age = parseInt(form.age) || 0
const threshold = multiStageConfig.value.age_threshold || 12
return age >= threshold && stages.value.length > 1
})
/**
* 创建空的阶段数据
* @returns {Object} 空阶段对象
*/
const createStage = () => ({
annual_withdrawal_amount: null,
withdrawal_start_age: null,
withdrawal_period: null,
annual_increase_percentage: null
})
/**
* 初始化阶段数据
* @description 根据年龄初始化阶段数量:
* - 年龄 < 12岁:固定 3 组
* - 年龄 ≥ 12岁:初始 1 组
*/
const initializeStages = () => {
const age = parseInt(form.age) || 0
const threshold = multiStageConfig.value.age_threshold || 12
if (age < threshold) {
// 固定 3 组
stages.value = [createStage(), createStage(), createStage()]
} else {
// 初始 1 组
stages.value = [createStage()]
}
}
/**
* 添加新阶段
* @description 在当前阶段列表末尾添加一个空阶段
*/
const addStage = () => {
if (!canAddStage.value) return
stages.value.push(createStage())
}
/**
* 删除指定阶段
* @param {number} index - 要删除的阶段索引
*/
const removeStage = (index) => {
if (!canRemoveStage.value) return
stages.value.splice(index, 1)
}
/**
* 同步阶段数据到表单
* @description 将 stages 数组同步到 form.withdrawal_stages,以便父组件获取
* 同时清理 undefined 值为 null,确保提交数据格式正确
*/
watch(
stages,
(newStages) => {
// 清理每个阶段的 undefined 值为 null
const cleanedStages = newStages.map(stage => ({
annual_withdrawal_amount: stage.annual_withdrawal_amount ?? null,
withdrawal_start_age: stage.withdrawal_start_age ?? null,
withdrawal_period: stage.withdrawal_period ?? null,
annual_increase_percentage: stage.annual_increase_percentage ?? null
}))
form.withdrawal_stages = cleanedStages
},
{ deep: true }
)
// 监听年龄变化,重新初始化阶段(仅在多阶段模式下)
watch(
() => form.age,
(newAge, oldAge) => {
if (isMultiStageMode.value && newAge !== oldAge && newAge !== undefined) {
const threshold = multiStageConfig.value.age_threshold || 12
const newAgeInt = parseInt(newAge) || 0
const oldAgeInt = parseInt(oldAge) || 0
// 跨越阈值时重新初始化
if ((newAgeInt < threshold && oldAgeInt >= threshold) ||
(newAgeInt >= threshold && oldAgeInt < threshold)) {
initializeStages()
}
}
}
)
// 组件挂载时初始化阶段(多阶段模式)
watch(
isMultiStageMode,
(enabled) => {
if (enabled && stages.value.length === 0) {
initializeStages()
}
},
{ immediate: true }
)
// ====== 原有表单逻辑 ======
/**
* 获取 Schema 默认值
* @param {Object} value - 当前表单数据
......@@ -371,7 +608,7 @@ watch(
/**
* 百分比输入清洗,避免非法字符
* @param {string|number} value - 输入值
* @param {string} key - 目标字段 key
* @param {string} key - 目标字段 key(支持多阶段路径:stages.${index}.annual_increase_percentage)
*/
const onPercentageInput = (value, key) => {
// 转换为字符串(处理 value 为 null 或其他类型的情况)
......@@ -401,7 +638,17 @@ const onPercentageInput = (value, key) => {
}
}
form[key] = cleaned
// 处理多阶段路径(如 stages.0.annual_increase_percentage)
if (key.startsWith('stages.')) {
const pathParts = key.split('.')
const stageIndex = parseInt(pathParts[1])
const fieldKey = pathParts[2]
if (!Number.isNaN(stageIndex) && stages.value[stageIndex]) {
stages.value[stageIndex][fieldKey] = cleaned
}
} else {
form[key] = cleaned
}
}
const isEmptyValue = (value) => {
......@@ -434,6 +681,12 @@ const isFieldRequired = (field) => {
* @returns {boolean} 校验是否通过
*/
const validate = () => {
// 多阶段模式校验
if (isMultiStageMode.value) {
return validateMultiStage()
}
// 单阶段模式校验(原有逻辑)
const fields = [...baseFields.value, ...(props.config.withdrawal_plan?.enabled ? withdrawalFields.value : [])]
// 年龄与出生年月日二选一校验
......@@ -484,6 +737,77 @@ const validate = () => {
}
/**
* 多阶段表单校验
* @description 校验每个阶段的必填字段
* @returns {boolean} 校验是否通过
*/
const validateMultiStage = () => {
// 基础字段校验(年龄与出生年月日)
const hasAge = !isEmptyValue(form.age)
const hasBirthday = !isEmptyValue(form.birthday)
if (!hasAge && !hasBirthday) {
Taro.showToast({ title: '年龄与出生年月日至少填写一项', icon: 'none' })
return false
}
// 如果都填写了,以生日为准,重新计算年龄
if (hasAge && hasBirthday) {
const birthYear = new Date(form.birthday).getFullYear()
const currentYear = new Date().getFullYear()
form.age = currentYear - birthYear
}
// 基础字段校验(其他字段)
for (const field of baseFields.value) {
if (field.key === 'age') continue // 已处理
if (isFieldRequired(field)) {
const value = form[field.key]
if (isEmptyValue(value)) {
Taro.showToast({ title: getRequiredMessage(field), icon: 'none' })
return false
}
}
}
// 多阶段字段校验
for (let i = 0; i < stages.value.length; i++) {
const stage = stages.value[i]
const stageLabel = `阶段${i + 1}`
// 每年提取金额(必填)
if (isEmptyValue(stage.annual_withdrawal_amount)) {
Taro.showToast({ title: `${stageLabel}:请输入每年提取金额`, icon: 'none' })
return false
}
// 由几岁开始(必填)
if (isEmptyValue(stage.withdrawal_start_age)) {
Taro.showToast({ title: `${stageLabel}:请输入由几岁开始`, icon: 'none' })
return false
}
// 提取期(必填)
if (isEmptyValue(stage.withdrawal_period)) {
Taro.showToast({ title: `${stageLabel}:请选择提取期`, icon: 'none' })
return false
}
// 每年递增提取之百分比(可选,校验范围)
if (!isEmptyValue(stage.annual_increase_percentage)) {
const percentage = parseFloat(stage.annual_increase_percentage)
if (Number.isNaN(percentage) || percentage < 0 || percentage > 100) {
Taro.showToast({ title: `${stageLabel}:递增百分比请输入0-100之间的数值`, icon: 'none' })
return false
}
}
}
return true
}
/**
* 清除验证错误
* @description 由于使用 Toast 显示错误,无需清除状态
* 保留此方法以保持接口一致性
......@@ -506,4 +830,30 @@ defineExpose({
padding-left: 20rpx !important;
}
}
/* 多阶段提取计划样式 */
.multi-stage-withdrawal-section {
.stage-card {
background: #ffffff;
border-radius: 12rpx;
padding: 32rpx;
margin-bottom: 32rpx;
border: 1rpx solid #e5e7eb;
}
.stage-title {
font-size: 28rpx;
font-weight: 600;
color: #111827;
}
.percentage-field {
margin-top: 24rpx;
}
.add-stage-btn {
margin-top: 24rpx;
border-radius: 12rpx;
}
}
</style>
......
......@@ -111,6 +111,23 @@ const savingsFormSchema = {
// 当 withdrawal_mode 切换时,不可见的字段会自动清空
}
/**
* 多阶段提取计划配置
* @description 用于"宏挚传承保障计划(多阶段)"等支持多阶段提取的产品
* @updated 2026-02-25 - 新增多阶段提取功能
*/
const multiStageWithdrawalConfig = {
enabled: true,
stage_limit: 4, // 阶段上限(预留修改空间)
age_threshold: 12, // 年龄阈值(<12岁固定3组,≥12岁可添加)
withdrawal_periods: [
'1年', '2年', '3年', '5年',
'10年', '15年', '20年', '终身',
'一笔过' // 新增:一次性提取选项
],
percentage_optional: true // 递增百分比可选
}
export const PLAN_TEMPLATES = {
// 人寿保险产品 - WIOP3E
'life-insurance-wiop3e': {
......@@ -208,7 +225,7 @@ export const PLAN_TEMPLATES = {
// ====== 储蓄型产品(统一逻辑) ======
// GS - 宏挚传承保障计划
// GS - 宏挚传承保障计划(普通模式)
'savings-gs': {
name: '宏挚传承保障计划',
component: 'SavingsTemplate',
......@@ -351,6 +368,38 @@ export const PLAN_TEMPLATES = {
submit_mapping: savingsSubmitMapping
}
},
// GS - 宏挚传承保障计划(多阶段模式)⭐ 新增
// @description 支持多阶段提取计划,根据年龄控制阶段数量
// @updated 2026-02-25 - 新增多阶段提取功能
'savings-gs-multistage': {
name: '宏挚传承保障计划(多阶段)',
component: 'SavingsTemplate',
category: 'savings',
config: {
currency: 'USD',
payment_periods: [
'整付',
'3 年',
'5 年',
'10 年',
'15 年',
],
age_range: { min: 0, max: 100 },
insurance_period: '终身',
// 多阶段提取计划配置
multi_stage_withdrawal: multiStageWithdrawalConfig,
withdrawal_plan: {
enabled: true,
currencies: ['HKD', 'USD', 'CNY'],
default_currency: 'USD',
withdrawal_modes: ['指定提取金额'], // 多阶段模式只支持指定提取金额
withdrawal_periods: multiStageWithdrawalConfig.withdrawal_periods
},
form_schema: savingsFormSchema,
submit_mapping: savingsSubmitMapping
}
},
}
/**
......