Parcourir la source

新增活动管理原型与配套文档

csk il y a 12 heures
Parent
commit
278a9320e3
65 fichiers modifiés avec 6938 ajouts et 40 suppressions
  1. 156 0
      .claude/skills/pc-admin-style-governance/SKILL.md
  2. 67 0
      .claude/skills/pc-admin-style-governance/references/module-template.md
  3. 29 0
      .claude/skills/pc-admin-style-governance/references/review-checklist.md
  4. 47 0
      .claude/skills/pc-admin-style-governance/references/style-audit.md
  5. 1 11
      .claude/skills/pc-demo/references/code-style.md
  6. 26 26
      .claude/skills/pc-demo/references/page-types.md
  7. 798 0
      工作空间/产研/权益/汇丰全行/产品原型/h5-demo/权益首页活动.html
  8. 366 0
      工作空间/产研/权益/汇丰全行/产品原型/h5-demo/活动详情.html
  9. 209 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/audit/活动后台配置审查结论.md
  10. 90 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/design-qa.md
  11. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/compare-layer.png
  12. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/compare-mgm.png
  13. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/compare-ntb.png
  14. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-layer-html-style.png
  15. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-layer.png
  16. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-mgm.png
  17. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-ntb-html-style.png
  18. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-ntb.png
  19. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-asset-detail-after.png
  20. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-after-rule.png
  21. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-after-top.png
  22. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-comparison.png
  23. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-reference-rule.png
  24. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-reference-tab.png
  25. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-overseas-after.png
  26. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-overseas-comparison.png
  27. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-overseas-description-after.png
  28. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-overseas-reference.png
  29. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/ntb-rule-style-after.png
  30. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/ntb-rule-style-comparison.png
  31. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/ntb-rule-style-reference.png
  32. 15 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/Layout.vue
  33. 369 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/activity/ActivityAchievementConfig.vue
  34. 86 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/activity/ActivityRewardEditor.vue
  35. 393 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/activity/ActivityRuleEditor.vue
  36. 84 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/router/index.js
  37. 540 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/store/activityStore.js
  38. 254 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityConfigList.vue
  39. 26 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityConfigOverview.vue
  40. 80 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityCrowdManagement.vue
  41. 767 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityEdit.vue
  42. 38 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityGrantPlan.vue
  43. 153 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityManagement.vue
  44. 245 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityMutexGroup.vue
  45. 21 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityNotificationRecord.vue
  46. 126 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityProgressTemplate.vue
  47. 76 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityReport.vue
  48. 70 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityView.vue
  49. 98 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/CreditCardActivityEntry.vue
  50. 2 2
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/PageAssembly.vue
  51. 1 1
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/PageZoneConfig.vue
  52. 760 0
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo/活动管理-达标与进度.html
  53. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo截图/MGM.png
  54. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo截图/NTB.png
  55. BIN
      工作空间/产研/权益/汇丰全行/产品原型/pc-demo截图/层层礼.png
  56. BIN
      工作空间/产研/权益/汇丰全行/测试用例/渠道管理_测试用例.xlsx
  57. 27 0
      工作空间/产研/权益/汇丰全行/测试用例/渠道管理_测试用例.xlsx.inspect.ndjson
  58. BIN
      工作空间/产研/权益/汇丰全行/测试用例/组装页2_测试用例_更新版.xlsx
  59. 33 0
      工作空间/产研/权益/汇丰全行/测试用例/组装页2_测试用例_更新版.xlsx.inspect.ndjson
  60. BIN
      工作空间/产研/权益/汇丰全行/测试用例/组装页_测试用例_基于原表更新版.xlsx
  61. 2 0
      工作空间/产研/权益/汇丰全行/测试用例/组装页_测试用例_基于原表更新版.xlsx.inspect.ndjson
  62. BIN
      工作空间/产研/权益/汇丰全行/需求文档/Copy of CUPD管理台0108-问题提出版_260119 (002).xlsx
  63. BIN
      工作空间/产研/权益/汇丰全行/需求文档/Reward Platform活动模块方案细化(1).pptx
  64. 0 0
      工作空间/产研/权益/汇丰全行/需求文档/新建 文本文档.txt
  65. 883 0
      工作空间/产研/权益/汇丰全行/需求文档/权益平台活动模块_产品文档_后台菜单进度模板版.md

+ 156 - 0
.claude/skills/pc-admin-style-governance/SKILL.md

@@ -0,0 +1,156 @@
+---
+name: pc-admin-style-governance
+description: PC 管理后台风格统一与模块治理专家。用于把已有后台模块改造成指定基准模块的设计风格和操作逻辑,并约束后续新增模块持续保持一致。当用户提到"风格统一"、"操作逻辑统一"、"对齐权益管理"、"活动管理改造"、"新增模块规范"、"后台模块治理"、"页面规范沉淀"、"统一后台设计"时,必须使用此 skill。
+---
+# PC 管理后台风格统一与模块治理专家
+
+## 适用场景
+
+本 skill 解决两类问题:
+
+1.  **现有模块改造**:例如活动管理和权益管理风格不同,需要把活动管理改成权益管理的设计风格和操作逻辑。
+2.  **新增模块约束**:之后新增后台模块时,必须沿用已确认的页面结构、组件模式、交互流程和文案规范。
+
+本 skill 不替代具体技术栈构建 skill。实际编码时,应结合项目现有技术栈执行;如果项目是 Vue 3 + Element Plus,就不能混入 React/antd 规范。
+
+## 核心原则
+
+- 以已被确认的**基准模块**为准,不凭空创造另一套设计。
+- 只保留业务差异,统一页面结构、操作位置、状态展示、弹窗/抽屉和反馈逻辑。
+- 同一结构出现两次以上,优先抽公共组件、配置或页面模板。
+- 新增模块先套模板,再补业务字段和业务流程。
+- 规范必须能被代码执行,不停留在抽象描述。
+
+## 工作流程
+
+### 1. 确定基准模块
+
+优先使用用户明确指定的基准模块,例如:
+
+- 权益管理
+- 已验收的列表页
+- 已验收的详情页
+- 已验收的新增/编辑页
+
+如果用户没有指定基准模块,先在项目中查找最完整、最新、风格最统一的后台模块作为候选,并说明选择依据。
+
+### 2. 读取基准模块
+
+必须阅读基准模块的相关代码,而不是只看截图或凭记忆判断。重点读取:
+
+- 列表页
+- 详情页
+- 新增/编辑页
+- 搜索筛选区
+- 表格列和操作列
+- 状态标签
+- 弹窗、抽屉、确认框
+- 菜单和路由配置
+- 公共组件和公共样式
+
+### 3. 读取目标模块
+
+对要改造的模块做同样梳理,例如活动管理:
+
+- 页面结构是否一致
+- 筛选区字段、按钮和展开逻辑是否一致
+- 操作按钮位置和层级是否一致
+- 表格操作列是否一致
+- 状态展示是否一致
+- 新增/编辑/详情入口是否一致
+- 删除、停用、审核等危险操作是否有统一确认
+
+### 4. 输出差异清单
+
+改造前先形成差异清单,按两类归因:
+
+| 类型 | 处理方式 |
+| --- | --- |
+| 业务差异 | 保留,例如字段、状态流转、业务规则 |
+| 风格/交互差异 | 必须对齐基准模块 |
+
+差异清单应具体到页面区域和组件,不写泛泛的"样式不同"。
+
+### 5. 实施改造
+
+改造顺序:
+
+1.  统一页面外层容器和布局。
+2.  统一筛选区结构、按钮位置、查询/重置逻辑。
+3.  统一操作按钮区和按钮层级。
+4.  统一表格密度、列操作、分页和空状态。
+5.  统一状态标签、金额/日期/枚举格式化。
+6.  统一弹窗、抽屉、确认框和消息提示。
+7.  抽取或复用公共组件。
+
+### 6. 固化新增模块规范
+
+完成现有模块改造后,应把复用规则沉淀到以下位置之一:
+
+- 公共组件
+- 页面模板
+- 配置文件
+- 样式变量
+- 项目文档
+- 本 skill 的 references
+
+后续新增模块必须先匹配页面类型,再套对应模板。
+
+## 页面一致性检查表
+
+### 列表页
+
+- 页面标题、面包屑、菜单高亮一致。
+- 筛选区默认展示项数量和布局一致。
+- 查询、重置、更多筛选按钮位置一致。
+- 操作按钮区位置一致,主按钮样式一致。
+- 表格表头、行高、操作列位置一致。
+- 分页位置、默认页大小和页大小选项一致。
+- 空状态、加载态、错误提示一致。
+
+### 详情页
+
+- 返回入口位置一致。
+- 顶部操作按钮位置一致。
+- 基础信息展示方式一致。
+- Tab 或分组结构一致。
+- 明细表格样式一致。
+
+### 表单页
+
+- 新增和编辑尽量复用同一表单组件。
+- 表单分组、字段间距、必填标识一致。
+- 提交、取消按钮位置一致。
+- 校验文案风格一致。
+- 保存成功、失败提示一致。
+
+### 弹窗和抽屉
+
+- 简单确认使用确认框。
+- 复杂筛选或侧滑详情使用抽屉。
+- 新增/编辑弹窗宽度、标题、按钮顺序一致。
+- 危险操作必须二次确认。
+
+## 输出要求
+
+执行本 skill 时,最终说明应包含:
+
+- 使用哪个模块作为基准。
+- 改造了哪些页面或组件。
+- 哪些差异被保留为业务差异。
+- 新增了哪些公共组件、模板或规范。
+- 验证方式和结果。
+
+## 禁止事项
+
+- 不允许在没有读取基准模块代码的情况下直接改造目标模块。
+- 不允许为了统一风格删除必要业务字段。
+- 不允许同一后台系统中混用两套组件库规范。
+- 不允许新增模块绕开公共模板独立实现完整页面结构。
+- 不允许只改颜色和间距,却不统一操作逻辑。
+
+## 参考文件
+
+- [references/style-audit.md](./references/style-audit.md)
+- [references/module-template.md](./references/module-template.md)
+- [references/review-checklist.md](./references/review-checklist.md)

+ 67 - 0
.claude/skills/pc-admin-style-governance/references/module-template.md

@@ -0,0 +1,67 @@
+# 后台模块模板
+
+## 列表页模板
+
+```text
+页面容器
+├── 页面标题/面包屑
+├── 筛选区
+│   ├── 常用筛选项
+│   ├── 查询
+│   ├── 重置
+│   └── 更多筛选
+├── 操作区
+│   ├── 新增
+│   ├── 导出/导入
+│   └── 批量操作
+├── 表格区
+│   ├── 选择列
+│   ├── 业务字段列
+│   ├── 状态列
+│   └── 操作列
+└── 分页区
+```
+
+## 列表页基础行为
+
+- 查询:使用当前筛选条件刷新列表,并回到第 1 页。
+- 重置:清空筛选条件,刷新列表,并回到第 1 页。
+- 页码变化:刷新当前页数据。
+- 页大小变化:更新页大小,并回到第 1 页。
+- 批量操作:无选中项时禁用或提示。
+- 危险操作:删除、停用、批量删除、批量停用必须二次确认。
+
+## 表单页模板
+
+```text
+页面容器/弹窗/抽屉
+├── 基础信息
+├── 规则配置
+├── 适用范围
+├── 其他设置
+└── 底部操作
+    ├── 取消
+    └── 保存
+```
+
+## 详情页模板
+
+```text
+页面容器
+├── 返回/标题/顶部操作
+├── 基础信息
+├── 状态或流程信息
+├── Tab/分组内容
+└── 明细表格/操作记录
+```
+
+## 公共组件建议
+
+- `PageContainer`:统一页面边距、背景和标题区。
+- `SearchPanel`:统一筛选项布局、查询、重置和更多筛选。
+- `DataTable`:统一表格、分页、加载态和空状态。
+- `StatusTag`:统一状态文案和颜色。
+- `ConfirmAction`:统一危险操作确认。
+- `FormSection`:统一表单分组标题和间距。
+
+公共组件应优先表达稳定结构,不承载过多业务判断。

+ 29 - 0
.claude/skills/pc-admin-style-governance/references/review-checklist.md

@@ -0,0 +1,29 @@
+# 模块新增和改造评审清单
+
+## 提交前自查
+
+- 是否明确了基准模块。
+- 是否读取了基准模块和目标模块代码。
+- 是否区分了业务差异和风格/交互差异。
+- 是否复用了现有公共组件。
+- 是否新增了必要的公共组件或模板。
+- 是否同步更新菜单和路由。
+- 是否处理了空状态、加载态、失败态。
+- 是否验证了查询、重置、分页、批量操作。
+- 是否验证了新增、编辑、详情、删除、停用等核心流程。
+
+## 代码评审重点
+
+- 新模块是否绕开了统一页面结构。
+- 是否手写了本应复用的状态标签、确认弹窗或表格操作。
+- 是否引入了第二套组件库或风格规范。
+- 是否存在只改样式但操作逻辑仍不一致的问题。
+- 是否为了套模板损失了业务字段或业务流程。
+
+## 验证建议
+
+- 启动本地开发服务检查页面。
+- 对比基准模块和目标模块的列表页、详情页、表单页。
+- 检查不同屏幕宽度下筛选区和表格是否稳定。
+- 检查所有危险操作是否有二次确认。
+- 检查新增模块菜单、路由、标题、面包屑是否一致。

+ 47 - 0
.claude/skills/pc-admin-style-governance/references/style-audit.md

@@ -0,0 +1,47 @@
+# 风格差异审计方法
+
+## 审计输入
+
+- 基准模块路径
+- 目标模块路径
+- 菜单和路由配置
+- 公共组件目录
+- 公共样式目录
+
+## 审计维度
+
+| 区域 | 检查点 |
+| --- | --- |
+| 页面外层 | 容器、背景、边距、标题、面包屑 |
+| 筛选区 | 字段数量、排列、查询、重置、更多筛选 |
+| 操作区 | 新增、导出、批量操作、按钮层级 |
+| 表格区 | 行高、表头、选择列、操作列、空状态 |
+| 分页区 | 位置、默认页大小、页大小选项 |
+| 状态展示 | 文案、颜色、标签类型、枚举映射 |
+| 表单 | 分组、校验、按钮、回显 |
+| 详情 | 返回、操作按钮、信息分组、Tab |
+| 弹窗/抽屉 | 宽度、标题、按钮顺序、关闭行为 |
+| 反馈 | 成功提示、失败提示、确认文案 |
+
+## 差异记录格式
+
+```text
+模块:活动管理
+基准:权益管理
+
+1. 筛选区
+   - 基准:常用筛选 4 项,查询/重置在右侧,更多筛选用抽屉。
+   - 目标:筛选项分两行展示,没有更多筛选。
+   - 处理:改为基准结构,保留活动名称、活动状态等业务字段。
+
+2. 操作列
+   - 基准:查看、编辑、停用通过文字按钮展示,危险操作二次确认。
+   - 目标:按钮样式不一致,删除无确认。
+   - 处理:统一操作列,补确认框。
+```
+
+## 判断标准
+
+- 字段不同通常是业务差异。
+- 按钮位置、交互流程、状态颜色、布局密度不同通常是风格/交互差异。
+- 如果不确定,优先保留业务含义,对齐呈现方式。

+ 1 - 11
.claude/skills/pc-demo/references/code-style.md

@@ -3,9 +3,7 @@
 ## 组件命名
 
 -   PascalCase,如 `OrderList.jsx`
-    
 -   一个页面一个目录,目录下可包含子组件
-    
 
 ## 文件组织
 
@@ -19,16 +17,12 @@ pages/
 ## 样式方案
 
 -   优先使用 antd 组件自带样式 + CSS Modules
-    
 -   避免全局样式污染
-    
 
 ## 注释规范
 
 -   只在复杂业务逻辑处添加注释
-    
 -   不做冗余注释
-    
 
 ## Hooks 使用
 
@@ -42,8 +36,4 @@ pages/
 ## antd 组件使用
 
 -   直接使用,不做二次封装(除非有明确的复用需求)
-    
--   使用主题色 `#FF6B00` 作为主色调
-    
-
-<br>
+-   使用主题色 `#FF6B00` 作为主色调

+ 26 - 26
.claude/skills/pc-demo/references/page-types.md

@@ -22,54 +22,54 @@
 
 ### 筛选区规范
 
-- 默认展示一行常用筛选项(3-5个),使用 antd 的 `Form` + `inline` 布局
-- 行末有"查询"和"重置"按钮
-- 提供"更多筛选"按钮,点击后从右侧弹出 `Drawer` 抽屉,展示完整筛选条件
-- 抽屉底部有"查询"和"重置"按钮
+-   默认展示一行常用筛选项(3-5个),使用 antd 的 `Form` + `inline` 布局
+-   行末有"查询"和"重置"按钮
+-   提供"更多筛选"按钮,点击后从右侧弹出 `Drawer` 抽屉,展示完整筛选条件
+-   抽屉底部有"查询"和"重置"按钮
 
 ### 操作按钮区规范
 
-- 位于筛选区下方、列表区上方
-- 按钮靠左排列
-- 常见按钮:新增、批量导出、批量删除等
-- 使用 antd 的 `Button` 组件,主操作用 `type="primary"`(主题色背景)
+-   位于筛选区下方、列表区上方
+-   按钮靠左排列
+-   常见按钮:新增、批量导出、批量删除等
+-   使用 antd 的 `Button` 组件,主操作用 `type="primary"`(主题色背景)
 
 ### 列表区规范
 
-- 使用 antd 的 `Table` 组件
-- 支持行选择(checkbox)
-- 操作列放在最右侧,使用 `Space` 组件排列操作按钮(查看、编辑、删除等)
-- 操作按钮使用文字链接风格(`type="link"`)
-- 表头使用浅灰色背景
+-   使用 antd 的 `Table` 组件
+-   支持行选择(checkbox)
+-   操作列放在最右侧,使用 `Space` 组件排列操作按钮(查看、编辑、删除等)
+-   操作按钮使用文字链接风格(`type="link"`)
+-   表头使用浅灰色背景
 
 ### 分页组件规范
 
-- 使用 antd 的 `Pagination` 组件
-- 默认每页 **20** 条,可选 10/20/50
-- 模拟数据默认 100 条,支持真实的前端分页逻辑
-- 右对齐
+-   使用 antd 的 `Pagination` 组件
+-   默认每页 **20** 条,可选 10/20/50
+-   模拟数据默认 100 条,支持真实的前端分页逻辑
+-   右对齐
 
 ### Mock 数据
 
 每个列表页在 `mock/` 目录下创建对应的模拟数据文件,详见 [mock-data.md](./mock-data.md)。
 
----
+* * *
 
 ## 详情页
 
 根据实际业务需求灵活开发,常见模式:
 
-- **顶部信息卡**:使用 antd 的 `Descriptions` 组件展示基本信息
-- **Tab 切换区**:使用 antd 的 `Tabs` 组件组织不同维度信息
-- **操作按钮**:页面右上角放置操作按钮(编辑、审批等)
-- 从列表页点击"查看"跳转到详情页时,在导航栏新增一个标签
+-   **顶部信息卡**:使用 antd 的 `Descriptions` 组件展示基本信息
+-   **Tab 切换区**:使用 antd 的 `Tabs` 组件组织不同维度信息
+-   **操作按钮**:页面右上角放置操作按钮(编辑、审批等)
+-   从列表页点击"查看"跳转到详情页时,在导航栏新增一个标签
 
----
+* * *
 
 ## 看板页
 
 根据实际业务需求灵活开发,常见模式:
 
-- **统计卡片**:使用 antd 的 `Card` + `Statistic` 展示关键指标
-- **图表区**:预留图表位置,使用 antd 的 `Card` 作为容器
-- **列表/表格**:展示 TOP 数据或待办事项
+-   **统计卡片**:使用 antd 的 `Card` + `Statistic` 展示关键指标
+-   **图表区**:预留图表位置,使用 antd 的 `Card` 作为容器
+-   **列表/表格**:展示 TOP 数据或待办事项

+ 798 - 0
工作空间/产研/权益/汇丰全行/产品原型/h5-demo/权益首页活动.html

@@ -0,0 +1,798 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <title>权益专区</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <style>
+        .no-scrollbar::-webkit-scrollbar { display: none; }
+        .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
+        .tab-indicator { transition: all 0.3s ease; }
+        .view-transition { transition: transform 0.35s cubic-bezier(0.4, 0.0, 0.2, 1); }
+        .pb-safe { padding-bottom: env(safe-area-inset-bottom); }
+    </style>
+</head>
+<body class="bg-gray-100 flex justify-center h-screen overflow-hidden font-sans">
+
+    <div class="fixed top-[15%] right-0 z-[100] flex flex-col items-end gap-3 max-w-sm pointer-events-none">
+        
+        <!-- 等级控制 (全局显示) -->
+        <div class="relative group pointer-events-auto">
+            <button onclick="toggleSimPanel()" class="bg-black/70 text-white text-[11px] py-1.5 px-2 rounded-l-md shadow-md flex items-center gap-1 backdrop-blur-sm">
+                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
+                等级
+            </button>
+            <div id="sim-panel" class="absolute top-0 right-[110%] bg-white/95 backdrop-blur-md border border-gray-200 p-2 rounded-lg shadow-xl w-[140px] text-[11px] transition-all origin-right scale-0 opacity-0 pointer-events-none">
+                <div class="font-bold mb-1.5 text-gray-800 border-b pb-1 text-center">切换等级</div>
+                <div class="flex flex-col gap-1.5">
+                    <button onclick="setUserLevel('prospect')" id="btn-state-prospect" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">潜客</button>
+                    <button onclick="setUserLevel('credit_only')" id="btn-state-credit_only" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">仅持有信用卡</button>
+                    <button onclick="setUserLevel('advance')" id="btn-state-advance" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">运筹理财</button>
+                    <button onclick="setUserLevel('premier')" id="btn-state-premier" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">卓越理财</button>
+                    <button onclick="setUserLevel('elite')" id="btn-state-elite" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">卓越理财·尊尚</button>
+                    <button onclick="setUserLevel('global')" id="btn-state-global" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">环球私人银行</button>
+                </div>
+            </div>
+        </div>
+
+        <!-- 数据控制 (默认隐藏,仅在全部活动页显示) -->
+        <div id="data-control-wrapper" class="relative group hidden pointer-events-auto">
+            <button onclick="toggleDataPanel()" class="bg-blue-600/80 text-white text-[11px] py-1.5 px-2 rounded-l-md shadow-md flex items-center gap-1 backdrop-blur-sm">
+                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>
+                数据
+            </button>
+            <div id="data-panel" class="absolute top-0 right-[110%] bg-white/95 backdrop-blur-md border border-gray-200 p-2 rounded-lg shadow-xl w-[120px] text-[11px] transition-all origin-right scale-0 opacity-0 pointer-events-none">
+                <div class="font-bold mb-1.5 text-gray-800 border-b pb-1 text-center">活动数据</div>
+                <div class="flex flex-col gap-1.5">
+                    <button onclick="setAllActivitiesData(true)" id="btn-data-true" class="px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium">有数据</button>
+                    <button onclick="setAllActivitiesData(false)" id="btn-data-false" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">无数据</button>
+                </div>
+            </div>
+        </div>
+
+        <!-- 详情页控制 (默认隐藏,仅在活动详情页显示) -->
+        <div id="detail-control-wrapper" class="hidden flex-col gap-3 pointer-events-auto">
+            <!-- 按钮状态控制 -->
+            <div class="relative group">
+                <button onclick="toggleDetailBtnPanel()" class="bg-indigo-600/80 text-white text-[11px] py-1.5 px-2 rounded-l-md shadow-md flex items-center gap-1 backdrop-blur-sm">
+                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="9" x2="15" y2="15"></line><line x1="15" y1="9" x2="9" y2="15"></line></svg>
+                    按钮
+                </button>
+                <div id="detail-btn-panel" class="absolute top-0 right-[110%] bg-white/95 backdrop-blur-md border border-gray-200 p-2 rounded-lg shadow-xl w-[120px] text-[11px] transition-all origin-right scale-0 opacity-0 pointer-events-none">
+                    <div class="font-bold mb-1.5 text-gray-800 border-b pb-1 text-center">按钮状态</div>
+                    <div class="flex flex-col gap-1.5">
+                        <button onclick="setDetailBtnState('enroll')" id="btn-detail-enroll" class="px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium">报名</button>
+                        <button onclick="setDetailBtnState('progress')" id="btn-detail-progress" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">查看进度</button>
+                        <button onclick="setDetailBtnState('benefits')" id="btn-detail-benefits" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">查看权益</button>
+                        <button onclick="setDetailBtnState('more')" id="btn-detail-more" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">查看更多</button>
+                    </div>
+                </div>
+            </div>
+            
+            <!-- 报名结果控制 (仅报名状态下显示) -->
+            <div class="relative group" id="enroll-result-control">
+                <button onclick="toggleEnrollResultPanel()" class="bg-purple-600/80 text-white text-[11px] py-1.5 px-2 rounded-l-md shadow-md flex items-center gap-1 backdrop-blur-sm">
+                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
+                    结果
+                </button>
+                <div id="enroll-result-panel" class="absolute top-0 right-[110%] bg-white/95 backdrop-blur-md border border-gray-200 p-2 rounded-lg shadow-xl w-[120px] text-[11px] transition-all origin-right scale-0 opacity-0 pointer-events-none">
+                    <div class="font-bold mb-1.5 text-gray-800 border-b pb-1 text-center">报名结果</div>
+                    <div class="flex flex-col gap-1.5">
+                        <button onclick="setEnrollCondition('success')" id="cond-success" class="px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium">通过校验</button>
+                        <button onclick="setEnrollCondition('not_eligible')" id="cond-not_eligible" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">不符合</button>
+                        <button onclick="setEnrollCondition('full')" id="cond-full" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">名额不足</button>
+                        <button onclick="setEnrollCondition('exclusive')" id="cond-exclusive" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">命中互斥</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="w-full max-w-[400px] h-full bg-white flex flex-col relative shadow-2xl overflow-hidden">
+        
+        <!-- 顶部状态栏 (固定) -->
+        <div class="h-11 w-full flex justify-between items-center px-5 text-black bg-white z-[90] relative shrink-0">
+            <span class="text-[15px] font-medium">9:41</span>
+            <div class="flex gap-1.5 items-center">
+                <svg width="18" height="12" viewBox="0 0 18 12" fill="none"><path d="M1.5 12V9H3.5V12H1.5ZM6 12V6.5H8V12H6ZM10.5 12V3.5H12.5V12H10.5ZM15 12V0H17V12H15Z" fill="black"/></svg>
+                <svg width="16" height="12" viewBox="0 0 16 12" fill="none"><path d="M8 12C9.10457 12 10 11.1046 10 10C10 8.89543 9.10457 8 8 8C6.89543 8 6 8.89543 6 10C6 11.1046 6.89543 12 8 12Z" fill="black"/><path d="M2.34315 5.65685C5.46734 2.53266 10.5327 2.53266 13.6569 5.65685" stroke="black" stroke-width="1.5" stroke-linecap="round"/><path d="M5.17157 8.48528C6.73367 6.92318 9.26633 6.92318 10.8284 8.48528" stroke="black" stroke-width="1.5" stroke-linecap="round"/></svg>
+                <svg width="25" height="12" viewBox="0 0 25 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="black"/><rect x="2" y="2" width="18" height="8" rx="1" fill="black"/><path d="M23 4V8" stroke="black" stroke-width="1.5" stroke-linecap="round"/></svg>
+            </div>
+        </div>
+
+        <!-- 视图层容器 (包含所有子页面) -->
+        <div class="flex-1 relative w-full overflow-hidden bg-gray-100">
+
+            <div id="home-view" class="absolute inset-0 bg-white flex flex-col view-transition translate-x-0 z-10">
+                <header class="h-11 flex items-center justify-between px-4 bg-white z-20 shrink-0">
+                    <button class="text-gray-800 p-1"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg></button>
+                    <h1 class="text-[17px] font-medium text-gray-900 absolute left-1/2 -translate-x-1/2">权益介绍页</h1>
+                    <div class="w-8"></div>
+                </header>
+                <div class="flex-1 overflow-y-auto no-scrollbar pb-8">
+                    <!-- 潜客横幅 -->
+                    <div id="no-tier-banner" onclick="showZone()" class="px-4 py-3 bg-[#fdfaf8] border-b border-gray-100 flex items-center justify-between cursor-pointer active:bg-gray-50 transition">
+                        <div class="flex items-center gap-3">
+                            <div class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center border border-gray-200">
+                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="14" x="3" y="8" rx="2"/><path d="M12 5a3 3 0 1 0-3 3"/><path d="M15 8a3 3 0 1 0-3-3"/><path d="M12 8v10"/><path d="M3 12h18"/></svg>
+                            </div>
+                            <div>
+                                <div class="text-[15px] text-gray-900">权益首页</div>
+                                <div class="text-[12px] text-gray-500">一键查看您的所有权益</div>
+                            </div>
+                        </div>
+                        <button class="text-[13px] text-gray-500 flex items-center pointer-events-none">
+                            立即查看 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
+                        </button>
+                    </div>
+                    <!-- Tabs -->
+                    <div class="flex justify-between px-2 pt-2 border-b border-gray-200 relative">
+                        <button onclick="switchTab('premier')" class="flex-1 py-3 text-[14px] font-medium relative text-gray-900" id="tab-btn-premier">卓越理财</button>
+                        <button onclick="switchTab('elite')" class="flex-1 py-3 text-[14px] font-medium relative text-gray-500" id="tab-btn-elite">卓越理财·尊尚</button>
+                        <button onclick="switchTab('global')" class="flex-1 py-3 text-[14px] font-medium relative text-gray-500" id="tab-btn-global">环球私人银行</button>
+                        <div id="tab-indicator" class="absolute bottom-0 h-[2px] bg-[#d70014] tab-indicator" style="width: 33.33%; left: 0%;"></div>
+                    </div>
+                    <!-- 动态内容区 -->
+                    <div id="content-area" class="px-5 pt-5 pb-10"></div>
+                </div>
+            </div>
+
+            <div id="zone-view" class="absolute inset-0 bg-[#f4f5f8] flex flex-col view-transition translate-x-full z-20">
+                <header class="h-11 flex items-center justify-between px-4 bg-white z-20 shrink-0 border-b border-gray-100">
+                    <button onclick="hideZone()" class="text-gray-800 p-1"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg></button>
+                    <h1 class="text-[17px] font-medium text-gray-900 absolute left-1/2 -translate-x-1/2">权益首页</h1>
+                    <div class="w-8"></div>
+                </header>
+                <div class="flex-1 overflow-y-auto no-scrollbar pb-8" id="zone-content-area"></div>
+            </div>
+
+            <div id="my-activities-view" class="absolute inset-0 bg-white flex flex-col view-transition translate-x-full z-30">
+                <header class="h-11 flex items-center justify-between px-4 bg-white z-20 shrink-0">
+                    <button onclick="closeMyActivities()" class="text-gray-800 p-1"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg></button>
+                    <h1 class="text-[17px] font-medium text-gray-900 absolute left-1/2 -translate-x-1/2">我的活动</h1>
+                    <div class="w-8"></div>
+                </header>
+                
+                <!-- My Activities Tabs -->
+                <div class="flex border-b border-gray-200 shrink-0 relative">
+                    <button class="flex-1 py-3 text-[15px] font-medium text-gray-900 text-center" onclick="switchActivitiesTab('ongoing')" id="tab-my-ongoing">进行中</button>
+                    <button class="flex-1 py-3 text-[15px] font-medium text-gray-500 text-center" onclick="switchActivitiesTab('ended')" id="tab-my-ended">已结束</button>
+                    <div id="my-activities-indicator" class="absolute bottom-0 h-[2px] bg-[#d70014] transition-all duration-300" style="width: 50%; left: 0%;"></div>
+                </div>
+
+                <div class="flex-1 overflow-y-auto no-scrollbar bg-white">
+                    <!-- 进行中列表 -->
+                    <div id="activities-ongoing-content" class="bg-white">
+                        <div class="flex items-center justify-between py-4 px-5 border-b border-gray-100 cursor-pointer active:bg-gray-50 transition" onclick="openActivityDetail('my_activity')">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=100&h=100&fit=crop" class="w-14 h-14 object-cover shrink-0">
+                                <div class="flex-1 overflow-hidden">
+                                    <h4 class="text-[14px] text-gray-900 font-medium truncate mb-1">2026 汇丰母亲节特别活动</h4>
+                                    <div class="text-[12px] text-gray-400">2026年/9月20日 周六14:00</div>
+                                </div>
+                            </div>
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><path d="m9 18 6-6-6-6"/></svg>
+                        </div>
+                        <div class="flex items-center justify-between py-4 px-5 border-b border-gray-100 cursor-pointer active:bg-gray-50 transition" onclick="openActivityDetail('my_activity')">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <img src="https://images.unsplash.com/photo-1563986768494-4dee2763ff3f?w=100&h=100&fit=crop" class="w-14 h-14 object-cover shrink-0">
+                                <div class="flex-1 overflow-hidden">
+                                    <h4 class="text-[14px] text-gray-900 font-medium truncate mb-1">2026 汇丰中国理财讲座</h4>
+                                    <div class="text-[12px] text-gray-400">2026年/9月20日 周六14:00</div>
+                                </div>
+                            </div>
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><path d="m9 18 6-6-6-6"/></svg>
+                        </div>
+                        <!-- 信用卡活动专区入口 -->
+                        <div class="flex items-center justify-between py-4 px-5 border-b border-gray-100 cursor-pointer active:bg-gray-50 transition mt-2">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <div class="w-14 h-14 bg-gray-200 shrink-0"></div>
+                                <h4 class="text-[15px] text-gray-900 font-medium truncate flex-1">信用卡活动</h4>
+                            </div>
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><path d="m9 18 6-6-6-6"/></svg>
+                        </div>
+                        <div class="px-5 py-4 text-[12px] text-gray-500 leading-relaxed">
+                            温馨提示:如需查看信用卡活动进度,请点击进行中-信用卡活动专区。
+                        </div>
+                    </div>
+
+                    <!-- 已结束列表 (Grayscale) -->
+                    <div id="activities-ended-content" class="bg-white hidden">
+                        <div class="flex items-center justify-between py-4 px-5 border-b border-gray-100 cursor-pointer opacity-40 grayscale transition" onclick="openActivityDetail('my_activity')">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=100&h=100&fit=crop" class="w-14 h-14 object-cover shrink-0">
+                                <div class="flex-1 overflow-hidden">
+                                    <h4 class="text-[14px] text-gray-900 font-medium truncate mb-1">2026 汇丰母亲节特别活动</h4>
+                                    <div class="text-[12px] text-gray-400">2026年/9月20日 周六14:00</div>
+                                </div>
+                            </div>
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><path d="m9 18 6-6-6-6"/></svg>
+                        </div>
+                        <div class="flex items-center justify-between py-4 px-5 border-b border-gray-100 cursor-pointer opacity-40 grayscale transition" onclick="openActivityDetail('my_activity')">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <img src="https://images.unsplash.com/photo-1563986768494-4dee2763ff3f?w=100&h=100&fit=crop" class="w-14 h-14 object-cover shrink-0">
+                                <div class="flex-1 overflow-hidden">
+                                    <h4 class="text-[14px] text-gray-900 font-medium truncate mb-1">2026 汇丰中国理财讲座</h4>
+                                    <div class="text-[12px] text-gray-400">2026年/9月20日 周六14:00</div>
+                                </div>
+                            </div>
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><path d="m9 18 6-6-6-6"/></svg>
+                        </div>
+                        <div class="flex items-center justify-between py-4 px-5 border-b border-gray-100 cursor-pointer opacity-40 grayscale transition" onclick="openActivityDetail('my_activity')">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <img src="https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=100&h=100&fit=crop" class="w-14 h-14 object-cover shrink-0">
+                                <div class="flex-1 overflow-hidden">
+                                    <h4 class="text-[14px] text-gray-900 font-medium truncate mb-1">汇丰中国 3Q 层层升级礼遇</h4>
+                                    <div class="text-[12px] text-gray-400">2026年/12月12日-2026年/12月24日</div>
+                                </div>
+                            </div>
+                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><path d="m9 18 6-6-6-6"/></svg>
+                        </div>
+                        <div class="px-5 py-4 text-[12px] text-gray-500 leading-relaxed">
+                            温馨提示:如需查看信用卡活动进度,请点击进行中-信用卡活动专区。
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div id="all-activities-view" class="absolute inset-0 bg-white flex flex-col view-transition translate-x-full z-40">
+                <header class="h-11 flex items-center justify-between px-4 bg-white z-20 shrink-0">
+                    <button onclick="closeAllActivities()" class="text-gray-800 p-1"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg></button>
+                    <h1 class="text-[17px] font-medium text-gray-900 absolute left-1/2 -translate-x-1/2">全部活动</h1>
+                    <div class="w-8"></div>
+                </header>
+                
+                <!-- All Activities Tabs -->
+                <div class="flex border-b border-gray-200 shrink-0 relative">
+                    <button class="flex-1 py-3 text-[14px] font-medium text-gray-900 text-center" onclick="switchAllActivitiesTab('online')" id="tab-all-online">线上活动</button>
+                    <button class="flex-1 py-3 text-[14px] font-medium text-gray-500 text-center" onclick="switchAllActivitiesTab('offline')" id="tab-all-offline">线下活动</button>
+                    <div id="all-activities-indicator" class="absolute bottom-0 h-[2px] bg-[#d70014] transition-all duration-300" style="width: 50%; left: 0%;"></div>
+                </div>
+
+                <div class="flex-1 overflow-y-auto no-scrollbar bg-white relative">
+                    <!-- 数据容器 -->
+                    <div id="all-activities-content" class="pb-24 flex flex-col min-h-full">
+                        <!-- JS Render Here -->
+                    </div>
+                    
+                    <!-- 信用卡入口 (固定在内容区底部) -->
+                    <div class="absolute bottom-6 left-5 right-5 h-12 border border-[#d70014] rounded text-[#d70014] flex items-center justify-center font-medium text-[15px] cursor-pointer active:bg-gray-50 transition bg-white" onclick="showToast('正在跳转信用卡平台...')">
+                        信用卡活动专区入口
+                    </div>
+                </div>
+            </div>
+
+            <div id="activity-detail-view" class="absolute inset-0 bg-white flex flex-col view-transition translate-x-full z-45">
+                <header class="h-11 flex items-center justify-between px-4 bg-white/90 backdrop-blur-sm z-20 shrink-0 absolute top-0 left-0 w-full">
+                    <button onclick="closeActivityDetail()" class="text-gray-800 p-1"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg></button>
+                    <h1 class="text-[17px] font-medium text-gray-900 absolute left-1/2 -translate-x-1/2">活动详情</h1>
+                    <div class="w-8"></div>
+                </header>
+
+                <div class="flex-1 overflow-y-auto no-scrollbar pb-24 relative bg-white">
+                    <img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800&auto=format&fit=crop" class="w-full h-[220px] object-cover shrink-0 block">
+                    
+                    <div class="px-5 py-5">
+                        <h2 class="text-[20px] font-medium text-gray-900 mb-4 tracking-wide">2025 汇丰母亲节特别活动</h2>
+                        
+                        <div class="text-[13px] text-gray-600 space-y-2 mb-8 tracking-wide">
+                            <p>活动对象:文字文字文字</p>
+                            <p>活动报名时间:年/月/日/时-年/月/日/时</p>
+                            <p>活动进行时间:年/月/日/时-年/月/日/时</p>
+                            <p>活动地点:北京华贸丽思卡尔顿酒店大宴会厅</p>
+                            <p>报名人数:数字(配置则展示)</p>
+                        </div>
+
+                        <h3 class="text-[17px] font-medium text-gray-900 mb-4">活动详情</h3>
+                        <div class="text-[14px] text-gray-600 leading-[1.8] mb-8 text-justify tracking-wide">
+                            <p class="mb-4">带领嘉宾初步进入风水的世界,探索古人智慧的深度。用传统智慧理念和专业的财富规划帮助客户更好把握机遇。实现财富积累,代代传承。</p>
+                            <p class="mb-4">特邀嘉宾:赵丹青<br>现系国际青年易经学会会员;<br>香港中美易经学会会员及“中华周易学会”理事;<br>担任香港《楼盘与风水》杂志编委,《财务圈》杂志上开设风水专栏,为LV, Tiffany, Ports等著名国际时尚品牌公司堪舆;<br>任澳大利亚周易研究会副会长。</p>
+                            <p>活动流程:<br>14:00 - 14:30 嘉宾签到<br>14:30 - 14:45 汇丰私人财富规划简介<br>14:45 - 16:15 嘉宾主题分享-习住家风水 创财富人生<br>16:15 - 17:00 茶歇&自由交流</p>
+                        </div>
+
+                        <!-- 层级 Tabs -->
+                        <div class="flex border-b border-gray-200 mb-5 relative">
+                            <button class="flex-1 py-3 text-[14px] font-medium text-gray-900 text-center" onclick="switchDetailTier(1)" id="tier-tab-1">层级一</button>
+                            <button class="flex-1 py-3 text-[14px] font-medium text-gray-500 text-center" onclick="switchDetailTier(2)" id="tier-tab-2">层级二</button>
+                            <button class="flex-1 py-3 text-[14px] font-medium text-gray-500 text-center" onclick="switchDetailTier(3)" id="tier-tab-3">层级三</button>
+                            <button class="flex-1 py-3 text-[14px] font-medium text-gray-500 text-center" onclick="switchDetailTier(4)" id="tier-tab-4">层级四</button>
+                            <div id="detail-tier-indicator" class="absolute bottom-0 h-[2px] bg-[#d70014] transition-all duration-300" style="width: 25%; left: 0%;"></div>
+                        </div>
+
+                        <!-- 层级内容 -->
+                        <div>
+                            <h4 class="text-[15px] font-medium text-gray-900 mb-2">达标要求</h4>
+                            <p class="text-[14px] text-gray-600 leading-[1.6] mb-5 tracking-wide">活动期间,首次升级为“合格卓越理财”客户,且保持3个月,3个自然月任一月末日终,较2025年3月末新增持有1类以上资产大类。</p>
+                            <h4 class="text-[15px] font-medium text-gray-900 mb-2">达标奖励</h4>
+                            <p class="text-[14px] text-gray-600 leading-[1.6] tracking-wide mb-10">32万汇友荟积分(可兑换约等值¥500好礼)</p>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 底部操作栏 -->
+                <div class="absolute bottom-0 left-0 w-full h-[68px] bg-white border-t border-gray-100 flex items-center justify-center px-4 z-30 shrink-0 shadow-[0_-2px_10px_rgba(0,0,0,0.03)] pb-safe">
+                    <button id="detail-main-btn" class="w-full h-11 bg-[#d70014] text-white text-[16px] font-medium rounded-[2px]" onclick="handleDetailAction()">报名</button>
+                </div>
+            </div>
+
+        </div> <!-- 结束视图层容器 -->
+
+        <!-- 积分规则弹窗 -->
+        <div id="rule-modal-backdrop" class="absolute inset-0 bg-black/50 z-[60] opacity-0 pointer-events-none transition-opacity duration-300" onclick="closeRuleModal()"></div>
+        <div id="rule-modal" class="absolute bottom-0 left-0 w-full bg-white z-[70] rounded-t-2xl transform translate-y-full transition-transform duration-300 flex flex-col" style="height: 85%;">
+            <div class="flex justify-between items-center px-5 py-4 border-b border-gray-100 shrink-0">
+                <h2 class="text-[17px] font-medium text-gray-900">积分规则</h2>
+                <button onclick="closeRuleModal()" class="text-gray-500 p-1 hover:bg-gray-100 rounded-full transition">
+                    <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
+                </button>
+            </div>
+            <div class="flex-1 overflow-y-auto px-5 py-5 no-scrollbar">
+                <h3 class="text-[14px] font-medium text-gray-800 mb-4">汇丰银行(中国)有限公司积分使用规则</h3>
+                <div class="text-[13px] text-gray-600 leading-[1.8] space-y-4 text-justify">
+                    <p>本积分使用规则(“本规则”)为汇丰银行(中国)有限公司(“本行”)的客户(“客户”)使用本行信用卡及借记卡积分(即“汇友荟”汇丰客户关系积分)(简称“汇友荟积分”)兑换本行提供的礼品、里程或刷卡金等项目(具体以本行积分换礼平台正式公布的有效兑换项目为准,以下称“兑换项目”)等需要遵守的使用规则。</p>
+                    <p class="font-medium text-gray-800 pt-2">一、 兑换及使用渠道</p>
+                    <p>1. 除本规则以下相关兑换项目项下有特别说明外,客户可使用信用卡积分及“汇友荟”汇丰客户关系积分在积分换礼平台兑换相关项目,具体兑换渠道如下:<br>
+                    (1) 登录“汇丰中国客户服务”微信服务号,点击“我的汇丰”—“我的积分”—“积分商城”页面兑换;或<br>
+                    (2) 登录汇丰银行App,在“我的”点击“我的积分”—“积分商城”页面兑换礼品;<br>
+                    (3) 人工服务申请兑换:拨打本行24小时客服热线95366或4008695366(该渠道仅限本行信用卡主卡持卡人兑换部分项目申请时使用)兑换。</p>
+                    <p>2. 积分一旦兑换成功,不可撤销、更改或退还。若因系统或网络原因导致兑换失败,积分将自动退回账户。</p>
+                    <p class="font-medium text-gray-800 pt-2">二、 积分有效期</p>
+                    <p>信用卡积分有效期与信用卡有效期一致。如客户在信用卡有效期届满前未消耗完毕,积分将于信用卡到期次月清零。“汇友荟”积分有效期详见对应活动的具体规则。请客户关注积分到期日并及时兑换。</p>
+                    <p class="pt-10 pb-10 text-center text-gray-300">-- 到底了 --</p>
+                </div>
+            </div>
+        </div>
+
+        <!-- 余额计算规则弹窗 -->
+        <div id="balance-rule-modal-backdrop" class="absolute inset-0 bg-black/50 z-[60] opacity-0 pointer-events-none transition-opacity duration-300" onclick="closeBalanceRuleModal()"></div>
+        <div id="balance-rule-modal" class="absolute bottom-0 left-0 w-full bg-white z-[70] rounded-t-2xl transform translate-y-full transition-transform duration-300 flex flex-col min-h-[220px]">
+            <div class="flex justify-between items-center px-5 py-4 shrink-0 border-b border-gray-50">
+                <h2 class="text-[18px] font-medium text-gray-900">提示</h2>
+                <button onclick="closeBalanceRuleModal()" class="text-gray-500 p-1 hover:bg-gray-100 rounded-full transition">
+                    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
+                </button>
+            </div>
+            <div class="flex-1 px-5 pb-8 pt-4">
+                <div class="text-[15px] text-gray-700 leading-[1.6]">
+                    月内日均余额 = 当月内每日账户余额之和 / 当月天数。
+                </div>
+            </div>
+        </div>
+
+        <!-- 报名通用提示弹窗 (不符合/已满/互斥) -->
+        <div id="alert-modal-backdrop" class="absolute inset-0 bg-black/50 z-[60] opacity-0 pointer-events-none transition-opacity duration-300" onclick="closeAlertModal()"></div>
+        <div id="alert-modal" class="absolute bottom-0 left-0 w-full bg-white z-[70] rounded-t-xl transform translate-y-full transition-transform duration-300 flex flex-col min-h-[200px]">
+            <div class="flex justify-between items-center px-5 py-4 shrink-0">
+                <h2 class="text-[17px] font-medium text-gray-900">提示</h2>
+                <button onclick="closeAlertModal()" class="text-gray-500 p-1 hover:bg-gray-100 rounded-full transition">
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
+                </button>
+            </div>
+            <div class="px-5 pb-6 pt-2 text-[15px] text-gray-700" id="alert-modal-msg">
+                您暂不符合报名条件
+            </div>
+            <div class="px-5 pb-8">
+                <button onclick="closeAlertModal()" class="w-full py-2.5 border border-[#cccccc] rounded-[2px] text-[16px] text-gray-800 bg-white font-medium active:bg-gray-50">返回首页</button>
+            </div>
+        </div>
+
+        <!-- 报名确认弹窗 -->
+        <div id="confirm-modal-backdrop" class="absolute inset-0 bg-black/50 z-[60] opacity-0 pointer-events-none transition-opacity duration-300" onclick="closeConfirmModal()"></div>
+        <div id="confirm-modal" class="absolute bottom-0 left-0 w-full bg-white z-[70] rounded-t-xl transform translate-y-full transition-transform duration-300 flex flex-col min-h-[220px]">
+            <div class="flex justify-between items-center px-5 py-4 shrink-0">
+                <h2 class="text-[17px] font-medium text-gray-900">报名确认</h2>
+                <button onclick="closeConfirmModal()" class="text-gray-500 p-1 hover:bg-gray-100 rounded-full transition">
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
+                </button>
+            </div>
+            <div class="px-5 pb-8 pt-2 text-[15px] text-gray-700">
+                你确定报名参加此「2025 汇丰母亲节特别活动」?
+            </div>
+            <div class="flex px-5 pb-8 gap-3">
+                <button onclick="closeConfirmModal()" class="flex-1 py-2.5 border border-[#cccccc] rounded-[2px] text-[16px] text-gray-800 bg-white font-medium active:bg-gray-50">取消</button>
+                <button onclick="closeConfirmModal(); showToast('报名成功')" class="flex-1 py-2.5 rounded-[2px] text-[16px] text-white bg-[#d70014] font-medium active:opacity-90">确认</button>
+            </div>
+        </div>
+
+        <!-- Toast -->
+        <div id="toast" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-black/80 text-white px-4 py-2 rounded-md text-[14px] z-[100] opacity-0 pointer-events-none transition-opacity duration-300 text-center w-max max-w-[80%]"></div>
+
+        <!-- 底部 Home Indicator (iOS风格) -->
+        <div class="h-8 w-full flex justify-center items-center z-[80] absolute bottom-0 pointer-events-none">
+            <div class="w-32 h-1 bg-gray-900 rounded-full"></div>
+        </div>
+    </div>
+
+    <script>
+        // --- 图标库 ---
+        const icons = {
+            cap: `<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M22 10v6M2 10l10-5 10 5-10 5z"/><path d="M6 12v5c3 3 9 3 12 0v-5"/></svg>`,
+            heart: `<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>`,
+            bulb: `<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.9 1.2 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/></svg>`,
+            gift: `<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><rect x="3" y="8" width="18" height="14" rx="2"/><path d="M12 5a3 3 0 1 0-3 3"/><path d="M15 8a3 3 0 1 0-3-3"/><path d="M12 8v14"/><path d="M3 12h18"/></svg>`,
+            diamond: `<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M6 3h12l4 6-10 13L2 9Z"/></svg>`,
+            medbox: `<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><rect x="4" y="8" width="16" height="12" rx="2"/><path d="M8 8V6a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M12 11v4M10 13h4"/></svg>`,
+            car: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 12 10s-6.7.6-8.5 1.1C2.7 11.3 2 12.1 2 13v3c0 .6.4 1 1 1h2"/><rect width="16" height="8" x="4" y="10" rx="2"/><path d="M7 14h.01"/><path d="M17 14h.01"/><path d="m10 10-1.5-4h7l-1.5 4"/></svg>`,
+            plane: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.2-1.1.7l-1.2 3.6 7.7 4.4-4.2 4.2-3.1-.8c-.4-.1-.8.1-1 .5l-.8 2.3 5.4 1.8 1.8 5.4 2.3-.8c.4-.2.6-.6.5-1l-.8-3.1 4.2-4.2 4.4 7.7 3.6-1.2c.5-.2.8-.6.7-1.1Z"/></svg>`,
+            globe: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><circle cx="12" cy="12" r="10"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/><path d="M2 12h20"/></svg>`,
+            coffee: `<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#ba6534" stroke-width="1.5"><path d="M17 8h1a4 4 0 1 1 0 8h-1"/><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/><line x1="6" x2="6" y1="2" y2="4"/><line x1="10" x2="10" y1="2" y2="4"/><line x1="14" x2="14" y1="2" y2="4"/></svg>`,
+            planeRed: `<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#d70014" stroke-width="1.5"><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.2-1.1.7l-1.2 3.6 7.7 4.4-4.2 4.2-3.1-.8c-.4-.1-.8.1-1 .5l-.8 2.3 5.4 1.8 1.8 5.4 2.3-.8c.4-.2.6-.6.5-1l-.8-3.1 4.2-4.2 4.4 7.7 3.6-1.2c.5-.2.8-.6.7-1.1Z"/></svg>`,
+            help: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5" class="ml-1 inline-block"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>`,
+            copy: `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5" class="ml-1 inline-block cursor-pointer"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`,
+            doc: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="mr-1 inline-block"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`,
+            clipboard: `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>`,
+            clock: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="inline-block mr-1"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`,
+            chevronRight: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="1.5"><path d="m9 18 6-6-6-6"/></svg>`
+        };
+
+        // --- 全局状态 ---
+        let currentUserLevel = 'advance'; 
+        let currentHomeTab = 'premier';
+        
+        let currentAllActivitiesTab = 'online';
+        let hasAllActivitiesData = true;
+
+        let currentDetailBtnState = 'enroll'; 
+        let currentEnrollCondition = 'success';
+
+        let isSimPanelOpen = false;
+        let isDataPanelOpen = false;
+        let isDetailBtnPanelOpen = false;
+        let isEnrollResultPanelOpen = false;
+
+        // --- 初始化渲染 ---
+        document.addEventListener('DOMContentLoaded', () => {
+            // 修复:直接使用 setUserLevel 完成页面数据渲染和侧边栏状态的高亮初始化
+            setUserLevel('advance'); 
+        });
+
+        // --- ====== View A: 首页逻辑 ====== ---
+        const homeTabData = {
+            premier: { title: '卓越理财', subtitle: '为您和您的家人提供尊崇专属礼遇和权益', imageSrc: 'https://images.unsplash.com/photo-1518893494013-481c1d8ed3fd?w=800&fit=crop', features: [{ title: '国际教育', desc: '享专属国际化学校优惠及背景提升项目' }, { title: '酒店礼遇', desc: '高端酒店及商场会籍匹配' }, { title: '环球礼遇', desc: '汇丰中国信用卡持卡人,尊享机场贵宾厅及礼宾车等礼遇' }] },
+            elite: { title: '卓越理财·尊尚', subtitle: '开启尊尚体验,享受环球出行、高端运动等VIP礼遇。', imageSrc: 'https://images.unsplash.com/photo-1610701596007-11502861dcfa?w=800&fit=crop', features: [{ title: '国际教育', desc: '享多项升学规划指导' }, { title: '专属礼宾服务', desc: '高端酒店及商场会籍匹配' }, { title: '尊尚生日礼', desc: '尊享体验任选其一' }] },
+            global: { title: '环球私人银行', subtitle: '开启环球「创臻享」及「汇佑健康」之旅。', imageSrc: 'https://images.unsplash.com/photo-1550684848-fac1c5b4e853?w=800&fit=crop', features: [{ title: '高尔夫权益', desc: '练习场畅达及VIP预约' }, { title: '臻享体验', desc: '米其林级别私宴预定服务等' }, { title: '环球旅行', desc: '境外接送机及升房礼遇' }] }
+        };
+
+        function renderHomePage() {
+            const isBasic = ['prospect', 'credit_only', 'advance'].includes(currentUserLevel);
+            document.getElementById('no-tier-banner').style.display = isBasic ? 'flex' : 'none';
+
+            ['premier', 'elite', 'global'].forEach((tab, index) => {
+                const btn = document.getElementById(`tab-btn-${tab}`);
+                btn.className = (tab === currentHomeTab) ? "flex-1 py-3 text-[14px] font-medium relative text-gray-900" : "flex-1 py-3 text-[14px] font-medium relative text-gray-500";
+                if(tab === currentHomeTab) document.getElementById('tab-indicator').style.left = `${index * 33.33}%`;
+            });
+
+            const data = homeTabData[currentHomeTab];
+            const levelWeights = { 'prospect': 0, 'credit_only': 0, 'advance': 0, 'premier': 1, 'elite': 2, 'global': 3 };
+            const isUnlocked = levelWeights[currentUserLevel] >= levelWeights[currentHomeTab];
+
+            let buttonHTML = isUnlocked ? `<button onclick="showZone()" class="w-full py-3 mt-4 mb-6 border border-gray-300 rounded text-[15px] font-medium text-gray-900 bg-white active:bg-gray-50 transition">进入权益专区</button>` : `<button class="w-full py-3 mt-4 mb-6 rounded text-[15px] font-medium text-[#b3b3b3] bg-[#f7f7f7] cursor-not-allowed">待解锁</button>`;
+
+            let featuresHTML = currentHomeTab === 'global' 
+                ? '<div class="flex flex-col border-t border-gray-100 pt-2">' + data.features.map(f => `<div class="flex items-center gap-4 py-4 border-b border-gray-100 last:border-0"><div class="w-12 h-12 rounded-full border border-gray-200 bg-[#fbfcfc] flex items-center justify-center text-gray-500 shrink-0">${icons.clipboard}</div><div class="flex-1"><div class="text-[15px] font-medium text-gray-900">${f.title}</div><div class="text-[13px] text-gray-500 mt-1">${f.desc}</div></div></div>`).join('') + '</div>'
+                : '<ul class="text-sm">' + data.features.map(f => `<li class="flex items-start mb-3 relative pl-3"><span class="absolute left-0 top-[8px] w-1 h-1 bg-gray-600 rounded-full"></span><div><span class="text-gray-900 font-medium text-[13px] mr-1">${f.title}</span><p class="text-gray-500 text-[12px] leading-relaxed mt-0.5">${f.desc}</p></div></li>`).join('') + '</ul>';
+
+            document.getElementById('content-area').innerHTML = `
+                <div class="w-full h-[150px] overflow-hidden mb-4 rounded-sm"><img src="${data.imageSrc}" class="w-full h-full object-cover"></div>
+                <h2 class="text-xl font-medium text-gray-900 mb-1.5">${data.title}</h2>
+                <p class="text-[13px] text-gray-500 leading-snug">${data.subtitle}</p>
+                ${buttonHTML}
+                ${featuresHTML}
+            `;
+        }
+        function switchTab(tabId) { currentHomeTab = tabId; renderHomePage(); }
+
+        // --- ====== View B: 专区页逻辑 (包含精彩活动) ====== ---
+        function renderZonePage() {
+            // (简化的层级数据,确保框架完整)
+            const isAdvanced = ['premier', 'elite', 'global'].includes(currentUserLevel);
+            const isPointsExpanded = isAdvanced;
+
+            let html = `<div class="w-full h-2 bg-transparent shrink-0"></div>`; // Placeholder top
+            
+            // 积分与常用兑换 (省略具体判断,展示全貌)
+            html += `
+                <div class="bg-white px-5 py-5 pb-8 mb-2 shadow-sm border-b border-gray-100">
+                    <div class="flex justify-between items-center mb-5">
+                        <h3 class="text-[16px] font-medium text-gray-900">积分商城</h3>
+                        <div class="text-[13px] text-gray-900 font-medium flex items-center">进入积分商城 <svg class="w-4 h-4 ml-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 18l6-6-6-6"/></svg></div>
+                    </div>
+                    <div class="flex justify-between items-center py-2 border-b border-transparent">
+                        <div><div class="text-[20px] font-medium text-gray-800">3,000,000</div><div class="text-[12px] text-gray-400 mt-1">我的总积分</div></div>
+                    </div>
+                    
+                    <div class="mt-6">
+                        <div class="flex justify-between items-center mb-4">
+                            <h3 class="text-[16px] font-medium text-gray-900">常用兑换</h3>
+                            <div class="text-[13px] text-gray-800 flex items-center font-medium">${icons.doc}积分明细</div>
+                        </div>
+                        <div class="flex overflow-x-auto no-scrollbar gap-3 pb-2 snap-x">
+                            <div class="min-w-[100px] bg-white border border-gray-200 rounded-sm flex flex-col items-center snap-start shadow-sm"><div class="w-full h-[65px] bg-[#f7fbff] flex items-center justify-center">${icons.planeRed}</div><div class="text-[13px] text-gray-700 py-2.5">航空里程</div></div>
+                            <div class="min-w-[100px] bg-white border border-gray-200 rounded-sm flex flex-col items-center snap-start shadow-sm"><div class="w-full h-[65px] bg-[#fffaf5] flex items-center justify-center">${icons.coffee}</div><div class="text-[13px] text-gray-700 py-2.5">贵宾厅</div></div>
+                            <div class="min-w-[100px] bg-white border border-gray-200 rounded-sm flex flex-col items-center snap-start shadow-sm"><div class="w-full h-[65px] bg-[#f9fafb] flex items-center justify-center"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="1.5"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg></div><div class="text-[13px] text-gray-700 py-2.5">年费</div></div>
+                        </div>
+                        <div class="flex justify-end mt-6">
+                            <div class="text-[12px] text-gray-900 font-medium flex items-center cursor-pointer" onclick="openRuleModal()">${icons.doc} 积分规则</div>
+                        </div>
+                    </div>
+                </div>
+            `;
+
+            // 【新增】精彩活动模块 (6个等级都有)
+            html += `
+                <div class="bg-white pb-8 shadow-sm">
+                    <div class="px-5 py-5 flex justify-between items-center">
+                        <h3 class="text-[18px] font-medium text-gray-900">精彩活动</h3>
+                        <div class="text-[13px] text-gray-900 font-medium flex items-center cursor-pointer" onclick="showMyActivities()">我的活动 ${icons.chevronRight}</div>
+                    </div>
+                    
+                    <!-- 轮播大图卡片 -->
+                    <div class="px-5 mb-8">
+                        <div class="w-full bg-white flex flex-col cursor-pointer active:opacity-90 transition shadow-sm border border-gray-100 rounded-b-sm" onclick="openActivityDetail('normal')">
+                            <img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800&auto=format&fit=crop" class="w-full h-[160px] object-cover">
+                            <div class="p-4">
+                                <h4 class="text-[17px] text-gray-900 font-bold mb-2 truncate">汇丰层层升级礼</h4>
+                                <div class="text-[13px] text-gray-500 mb-1.5">线上活动</div>
+                                <div class="text-[12px] text-gray-400 flex items-center">${icons.clock} 2026/1/1 8:00 - 2026/3/31 23:29</div>
+                            </div>
+                        </div>
+                        <div class="flex justify-center gap-2 mt-4">
+                            <div class="w-[4px] h-[4px] bg-black rounded-full border border-black"></div>
+                            <div class="w-[4px] h-[4px] border border-black rounded-full"></div>
+                        </div>
+                    </div>
+
+                    <!-- 活动列表 -->
+                    <div class="px-5">
+                        <div class="flex items-center justify-between py-4 border-b border-gray-100 cursor-pointer active:bg-gray-50" onclick="openActivityDetail('normal')">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <img src="https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=100&h=100&fit=crop" class="w-[60px] h-[60px] object-cover shrink-0 rounded-[2px]">
+                                <div class="flex-1 overflow-hidden">
+                                    <h4 class="text-[15px] text-gray-900 font-medium truncate mb-1">汇丰卓越理财新户达标礼</h4>
+                                    <div class="text-[13px] text-gray-500 mb-0.5">线上活动</div>
+                                    <div class="text-[12px] text-gray-400">2026/1/1 8:00 - 2026/3/31 23:29</div>
+                                </div>
+                            </div>
+                            ${icons.chevronRight}
+                        </div>
+                        <div class="flex items-center justify-between py-4 border-b border-gray-100 cursor-pointer active:bg-gray-50" onclick="openActivityDetail('normal')">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <img src="https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=100&h=100&fit=crop" class="w-[60px] h-[60px] object-cover shrink-0 rounded-[2px]">
+                                <div class="flex-1 overflow-hidden">
+                                    <h4 class="text-[15px] text-gray-900 font-medium truncate mb-1">汇丰 1+1 新客户礼遇</h4>
+                                    <div class="text-[13px] text-gray-500 mb-0.5">线上活动</div>
+                                    <div class="text-[12px] text-gray-400">2026/1/1 8:00 - 2026/3/31 23:29</div>
+                                </div>
+                            </div>
+                            ${icons.chevronRight}
+                        </div>
+                        <div class="flex items-center justify-between py-4 border-b border-gray-100 cursor-pointer active:bg-gray-50" onclick="openActivityDetail('normal')">
+                            <div class="flex items-center gap-4 w-full pr-2">
+                                <img src="https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?w=100&h=100&fit=crop" class="w-[60px] h-[60px] object-cover shrink-0 rounded-[2px]">
+                                <div class="flex-1 overflow-hidden">
+                                    <h4 class="text-[15px] text-gray-900 font-medium truncate mb-1">汇丰财富峰会</h4>
+                                    <div class="text-[13px] text-gray-500 mb-0.5">线下活动</div>
+                                    <div class="text-[12px] text-gray-400">2026/1/1 8:00 - 2026/3/31 23:29</div>
+                                </div>
+                            </div>
+                            ${icons.chevronRight}
+                        </div>
+                        
+                        <button onclick="showAllActivities()" class="w-full py-3 mt-6 bg-[#f7f7f7] text-[#333] font-medium border border-[#ddd] rounded-sm text-[14px] active:bg-gray-200 transition">
+                            更多活动
+                        </button>
+                    </div>
+                </div>
+            `;
+            document.getElementById('zone-content-area').innerHTML = html;
+        }
+
+        // --- ====== View Navigation ====== ---
+        function showZone() {
+            renderZonePage();
+            document.getElementById('home-view').classList.add('-translate-x-full');
+            document.getElementById('zone-view').classList.remove('translate-x-full');
+        }
+        function hideZone() {
+            document.getElementById('home-view').classList.remove('-translate-x-full');
+            document.getElementById('zone-view').classList.add('translate-x-full');
+        }
+
+        function showMyActivities() { document.getElementById('my-activities-view').classList.remove('translate-x-full'); }
+        function closeMyActivities() { document.getElementById('my-activities-view').classList.add('translate-x-full'); }
+
+        function showAllActivities() {
+            renderAllActivitiesList();
+            document.getElementById('all-activities-view').classList.remove('translate-x-full');
+            document.getElementById('data-control-wrapper').classList.remove('hidden');
+        }
+        function closeAllActivities() {
+            document.getElementById('all-activities-view').classList.add('translate-x-full');
+            document.getElementById('data-control-wrapper').classList.add('hidden');
+            if(isDataPanelOpen) toggleDataPanel();
+        }
+
+        function openActivityDetail(source) {
+            document.getElementById('activity-detail-view').classList.remove('translate-x-full');
+            // 根据入口设置按钮
+            if (source === 'my_activity') setDetailBtnState('progress');
+            else setDetailBtnState('enroll');
+            
+            // 面板切换
+            document.getElementById('data-control-wrapper').classList.add('hidden');
+            document.getElementById('detail-control-wrapper').classList.remove('hidden');
+            document.getElementById('detail-control-wrapper').classList.add('flex');
+        }
+        function closeActivityDetail() {
+            document.getElementById('activity-detail-view').classList.add('translate-x-full');
+            document.getElementById('detail-control-wrapper').classList.add('hidden');
+            document.getElementById('detail-control-wrapper').classList.remove('flex');
+            if(isDetailBtnPanelOpen) toggleDetailBtnPanel();
+            if(isEnrollResultPanelOpen) toggleEnrollResultPanel();
+            if(!document.getElementById('all-activities-view').classList.contains('translate-x-full')) {
+                document.getElementById('data-control-wrapper').classList.remove('hidden');
+            }
+        }
+
+        // --- ====== Tabs Logic ====== ---
+        function switchActivitiesTab(tabId) {
+            const tabs = ['ongoing', 'ended'];
+            tabs.forEach(t => {
+                document.getElementById(`tab-my-${t}`).className = t === tabId ? 'flex-1 py-3 text-[15px] font-medium text-gray-900 text-center' : 'flex-1 py-3 text-[15px] font-medium text-gray-500 text-center';
+                const content = document.getElementById(`activities-${t}-content`);
+                if(t === tabId) content.classList.remove('hidden'); else content.classList.add('hidden');
+            });
+            document.getElementById('my-activities-indicator').style.left = tabId === 'ongoing' ? '0%' : '50%';
+        }
+
+        function switchAllActivitiesTab(tab) {
+            currentAllActivitiesTab = tab;
+            document.getElementById('tab-all-online').className = tab === 'online' ? 'flex-1 py-3 text-[14px] font-medium text-gray-900 text-center' : 'flex-1 py-3 text-[14px] font-medium text-gray-500 text-center';
+            document.getElementById('tab-all-offline').className = tab === 'offline' ? 'flex-1 py-3 text-[14px] font-medium text-gray-900 text-center' : 'flex-1 py-3 text-[14px] font-medium text-gray-500 text-center';
+            document.getElementById('all-activities-indicator').style.left = tab === 'online' ? '0%' : '50%';
+            renderAllActivitiesList();
+        }
+
+        function switchDetailTier(tier) {
+            document.getElementById('detail-tier-indicator').style.left = `${(tier - 1) * 25}%`;
+            for (let i = 1; i <= 4; i++) {
+                document.getElementById(`tier-tab-${i}`).className = i === tier ? 'flex-1 py-3 text-[14px] font-medium text-gray-900 text-center' : 'flex-1 py-3 text-[14px] font-medium text-gray-500 text-center';
+            }
+        }
+
+        // --- ====== Data Logic ====== ---
+        function setAllActivitiesData(hasData) {
+            hasAllActivitiesData = hasData;
+            document.getElementById('btn-data-true').className = hasData ? 'px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium' : 'px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition';
+            document.getElementById('btn-data-false').className = !hasData ? 'px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium' : 'px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition';
+            renderAllActivitiesList();
+            toggleDataPanel();
+        }
+
+        function renderAllActivitiesList() {
+            const container = document.getElementById('all-activities-content');
+            if (!hasAllActivitiesData) {
+                container.innerHTML = `<div class="flex flex-col items-center justify-center pt-24 text-gray-400"><svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" class="mb-4"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="9" x2="15" y2="15"></line><line x1="15" y1="9" x2="9" y2="15"></line></svg><p class="text-[14px]">暂无可参与活动</p></div>`;
+                return;
+            }
+            
+            const list = currentAllActivitiesTab === 'online' ? [1,2,3,4] : [1,2];
+            const suffix = currentAllActivitiesTab === 'online' ? '线上' : '线下';
+            
+            let html = list.map(i => `
+                <div class="flex items-center justify-between py-4 px-5 border-b border-gray-100 cursor-pointer active:bg-gray-50 transition" onclick="openActivityDetail('normal')">
+                    <div class="flex items-center gap-4 w-full pr-2">
+                        <img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=100&h=100&fit=crop" class="w-[60px] h-[60px] object-cover shrink-0 rounded-[2px]">
+                        <div class="flex-1 overflow-hidden">
+                            <h4 class="text-[15px] text-gray-900 font-medium truncate mb-1">汇丰${suffix}活动示例 ${i}</h4>
+                            <div class="text-[12px] text-gray-400">2026/09/20 周六 14:00</div>
+                        </div>
+                    </div>
+                    ${icons.chevronRight}
+                </div>
+            `).join('');
+            container.innerHTML = html;
+        }
+
+        // --- ====== Panels & Modals ====== ---
+        function toggleSimPanel() {
+            isSimPanelOpen = !isSimPanelOpen;
+            const panel = document.getElementById('sim-panel');
+            isSimPanelOpen ? panel.classList.remove('scale-0', 'opacity-0', 'pointer-events-none') : panel.classList.add('scale-0', 'opacity-0', 'pointer-events-none');
+        }
+        function toggleDataPanel() {
+            isDataPanelOpen = !isDataPanelOpen;
+            const panel = document.getElementById('data-panel');
+            isDataPanelOpen ? panel.classList.remove('scale-0', 'opacity-0', 'pointer-events-none') : panel.classList.add('scale-0', 'opacity-0', 'pointer-events-none');
+        }
+        function toggleDetailBtnPanel() {
+            isDetailBtnPanelOpen = !isDetailBtnPanelOpen;
+            const panel = document.getElementById('detail-btn-panel');
+            if (isDetailBtnPanelOpen) { panel.classList.remove('scale-0', 'opacity-0', 'pointer-events-none'); if(isEnrollResultPanelOpen) toggleEnrollResultPanel(); }
+            else panel.classList.add('scale-0', 'opacity-0', 'pointer-events-none');
+        }
+        function toggleEnrollResultPanel() {
+            isEnrollResultPanelOpen = !isEnrollResultPanelOpen;
+            const panel = document.getElementById('enroll-result-panel');
+            if (isEnrollResultPanelOpen) { panel.classList.remove('scale-0', 'opacity-0', 'pointer-events-none'); if(isDetailBtnPanelOpen) toggleDetailBtnPanel(); }
+            else panel.classList.add('scale-0', 'opacity-0', 'pointer-events-none');
+        }
+
+        function setUserLevel(level) {
+            currentUserLevel = level;
+            currentHomeTab = ['prospect', 'credit_only', 'advance'].includes(level) ? 'premier' : level;
+            renderHomePage();
+            if(!document.getElementById('zone-view').classList.contains('translate-x-full')) renderZonePage();
+            
+            ['prospect', 'credit_only', 'advance', 'premier', 'elite', 'global'].forEach(lvl => {
+                document.getElementById(`btn-state-${lvl}`).className = lvl === currentUserLevel ? 'px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium' : 'px-2 py-1.5 rounded bg-gray-100 text-gray-700 hover:bg-gray-200 transition';
+            });
+            if(isSimPanelOpen) toggleSimPanel();
+        }
+
+        function setDetailBtnState(state) {
+            currentDetailBtnState = state;
+            ['enroll', 'progress', 'benefits', 'more'].forEach(s => {
+                document.getElementById(`btn-detail-${s}`).className = s === state ? 'px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium' : 'px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition';
+            });
+
+            const mainBtn = document.getElementById('detail-main-btn');
+            if(state === 'enroll') mainBtn.innerText = '报名';
+            if(state === 'progress') mainBtn.innerText = '查看进度';
+            if(state === 'benefits') mainBtn.innerText = '查看权益';
+            if(state === 'more') mainBtn.innerText = '查看更多';
+            
+            document.getElementById('enroll-result-control').classList.toggle('hidden', state !== 'enroll');
+            if(state !== 'enroll' && isEnrollResultPanelOpen) toggleEnrollResultPanel();
+            if(isDetailBtnPanelOpen) toggleDetailBtnPanel();
+        }
+
+        function setEnrollCondition(cond) {
+            currentEnrollCondition = cond;
+            ['success', 'not_eligible', 'full', 'exclusive'].forEach(c => {
+                document.getElementById(`cond-${c}`).className = c === cond ? 'px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium' : 'px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition';
+            });
+            if(isEnrollResultPanelOpen) toggleEnrollResultPanel();
+        }
+
+        function handleDetailAction() {
+            if (currentDetailBtnState === 'more') {
+                closeActivityDetail();
+                showAllActivities();
+            } else if (currentDetailBtnState === 'progress') {
+                showToast('正在为您拉取达标进度...');
+            } else if (currentDetailBtnState === 'benefits') {
+                showToast('正在为您拉取权益信息...');
+            } else if (currentDetailBtnState === 'enroll') {
+                if (currentEnrollCondition === 'success') {
+                    document.getElementById('confirm-modal-backdrop').classList.remove('opacity-0', 'pointer-events-none');
+                    document.getElementById('confirm-modal').classList.remove('translate-y-full');
+                } else {
+                    let msg = currentEnrollCondition === 'not_eligible' ? '您暂不符合报名条件' : currentEnrollCondition === 'full' ? '很遗憾,该活动报名已满。' : '您已参与同类型活动,不可重复报名 (互斥)';
+                    document.getElementById('alert-modal-msg').innerText = msg;
+                    document.getElementById('alert-modal-backdrop').classList.remove('opacity-0', 'pointer-events-none');
+                    document.getElementById('alert-modal').classList.remove('translate-y-full');
+                }
+            }
+        }
+
+        function openRuleModal() { document.getElementById('rule-modal-backdrop').classList.remove('opacity-0', 'pointer-events-none'); document.getElementById('rule-modal').classList.remove('translate-y-full'); }
+        function closeRuleModal() { document.getElementById('rule-modal-backdrop').classList.add('opacity-0', 'pointer-events-none'); document.getElementById('rule-modal').classList.add('translate-y-full'); }
+        function openBalanceRuleModal() { document.getElementById('balance-rule-modal-backdrop').classList.remove('opacity-0', 'pointer-events-none'); document.getElementById('balance-rule-modal').classList.remove('translate-y-full'); }
+        function closeBalanceRuleModal() { document.getElementById('balance-rule-modal-backdrop').classList.add('opacity-0', 'pointer-events-none'); document.getElementById('balance-rule-modal').classList.add('translate-y-full'); }
+        function closeAlertModal() { document.getElementById('alert-modal-backdrop').classList.add('opacity-0', 'pointer-events-none'); document.getElementById('alert-modal').classList.add('translate-y-full'); }
+        function closeConfirmModal() { document.getElementById('confirm-modal-backdrop').classList.add('opacity-0', 'pointer-events-none'); document.getElementById('confirm-modal').classList.add('translate-y-full'); }
+        
+        let toastTimeout;
+        function showToast(msg) {
+            const toast = document.getElementById('toast');
+            toast.innerText = msg;
+            toast.classList.remove('opacity-0');
+            clearTimeout(toastTimeout);
+            toastTimeout = setTimeout(() => { toast.classList.add('opacity-0'); }, 2000);
+        }
+    </script>
+</body>
+</html>

+ 366 - 0
工作空间/产研/权益/汇丰全行/产品原型/h5-demo/活动详情.html

@@ -0,0 +1,366 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <title>活动详情</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <style>
+        .no-scrollbar::-webkit-scrollbar { display: none; }
+        .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
+        .pb-safe { padding-bottom: env(safe-area-inset-bottom); }
+    </style>
+</head>
+<body class="bg-gray-100 flex justify-center h-screen overflow-hidden font-sans">
+
+    <!-- === 右侧全局控制面板 (仅用于演示和测试交互) === -->
+    <div class="fixed top-[15%] right-0 z-[100] flex flex-col items-end gap-3 max-w-sm">
+        
+        <!-- 按钮状态控制 -->
+        <div class="relative group">
+            <button onclick="toggleBtnPanel()" class="bg-indigo-600/90 text-white text-[12px] py-2 px-3 rounded-l-md shadow-md flex items-center gap-1.5 backdrop-blur-sm">
+                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="9" x2="15" y2="15"></line><line x1="15" y1="9" x2="9" y2="15"></line></svg>
+                按钮状态
+            </button>
+            <div id="btn-panel" class="absolute top-0 right-[110%] bg-white/95 backdrop-blur-md border border-gray-200 p-2.5 rounded-lg shadow-xl w-[140px] text-[12px] transition-all origin-right scale-0 opacity-0 pointer-events-none">
+                <div class="font-bold mb-2 text-gray-800 border-b pb-1 text-center">底部按钮切换</div>
+                <div class="flex flex-col gap-1.5">
+                    <button onclick="setBtnState('enroll')" id="ctrl-btn-enroll" class="px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium">报名 (默认)</button>
+                    <button onclick="setBtnState('progress')" id="ctrl-btn-progress" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">查看达标进度</button>
+                    <button onclick="setBtnState('benefits')" id="ctrl-btn-benefits" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">查看权益</button>
+                    <button onclick="setBtnState('more')" id="ctrl-btn-more" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">查看更多活动</button>
+                </div>
+            </div>
+        </div>
+        
+        <!-- 报名结果控制 (仅在按钮为“报名”时显示) -->
+        <div class="relative group transition-all duration-300" id="result-control-wrapper">
+            <button onclick="toggleResultPanel()" class="bg-purple-600/90 text-white text-[12px] py-2 px-3 rounded-l-md shadow-md flex items-center gap-1.5 backdrop-blur-sm">
+                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
+                报名结果
+            </button>
+            <div id="result-panel" class="absolute top-0 right-[110%] bg-white/95 backdrop-blur-md border border-gray-200 p-2.5 rounded-lg shadow-xl w-[140px] text-[12px] transition-all origin-right scale-0 opacity-0 pointer-events-none">
+                <div class="font-bold mb-2 text-gray-800 border-b pb-1 text-center">点击报名后校验</div>
+                <div class="flex flex-col gap-1.5">
+                    <button onclick="setEnrollCondition('success')" id="ctrl-cond-success" class="px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium">通过校验(确认)</button>
+                    <button onclick="setEnrollCondition('not_eligible')" id="ctrl-cond-not_eligible" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">不符合条件</button>
+                    <button onclick="setEnrollCondition('full')" id="ctrl-cond-full" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">名额不足</button>
+                    <button onclick="setEnrollCondition('exclusive')" id="ctrl-cond-exclusive" class="px-2 py-1.5 rounded bg-gray-100 text-gray-700 transition">命中互斥</button>
+                </div>
+            </div>
+        </div>
+
+    </div>
+
+    <!-- === 移动端视图容器 === -->
+    <div class="w-full max-w-[400px] h-full bg-white flex flex-col relative shadow-2xl overflow-hidden">
+        
+        <!-- 顶部状态栏 -->
+        <div class="h-11 w-full flex justify-between items-center px-5 text-black bg-white/90 backdrop-blur-md z-30 relative shrink-0">
+            <span class="text-[15px] font-medium">9:41</span>
+            <div class="flex gap-1.5 items-center">
+                <svg width="18" height="12" viewBox="0 0 18 12" fill="none"><path d="M1.5 12V9H3.5V12H1.5ZM6 12V6.5H8V12H6ZM10.5 12V3.5H12.5V12H10.5ZM15 12V0H17V12H15Z" fill="black"/></svg>
+                <svg width="16" height="12" viewBox="0 0 16 12" fill="none"><path d="M8 12C9.10457 12 10 11.1046 10 10C10 8.89543 9.10457 8 8 8C6.89543 8 6 8.89543 6 10C6 11.1046 6.89543 12 8 12Z" fill="black"/><path d="M2.34315 5.65685C5.46734 2.53266 10.5327 2.53266 13.6569 5.65685" stroke="black" stroke-width="1.5" stroke-linecap="round"/><path d="M5.17157 8.48528C6.73367 6.92318 9.26633 6.92318 10.8284 8.48528" stroke="black" stroke-width="1.5" stroke-linecap="round"/></svg>
+                <svg width="25" height="12" viewBox="0 0 25 12" fill="none"><rect x="0.5" y="0.5" width="21" height="11" rx="2.5" stroke="black"/><rect x="2" y="2" width="18" height="8" rx="1" fill="black"/><path d="M23 4V8" stroke="black" stroke-width="1.5" stroke-linecap="round"/></svg>
+            </div>
+        </div>
+
+        <!-- 导航栏 -->
+        <header class="h-11 flex items-center justify-between px-4 bg-white/90 backdrop-blur-md z-20 shrink-0 border-b border-gray-100">
+            <button class="text-gray-800 p-1"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg></button>
+            <h1 class="text-[17px] font-medium text-gray-900 absolute left-1/2 -translate-x-1/2">活动详情</h1>
+            <div class="w-8"></div>
+        </header>
+
+        <!-- 内容滚动区 (去除预留的底部高度,添加底部安全区距离) -->
+        <div class="flex-1 overflow-y-auto no-scrollbar pb-safe relative bg-white">
+            
+            <!-- 头图 -->
+            <img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800&auto=format&fit=crop" class="w-full h-[200px] object-cover shrink-0 block">
+            
+            <div class="px-5 py-6">
+                <!-- 基础信息 -->
+                <h2 class="text-[20px] font-medium text-gray-900 mb-4 tracking-wide leading-tight">2025 汇丰母亲节特别活动</h2>
+                
+                <div class="text-[13px] text-gray-600 space-y-2.5 mb-8 tracking-wide">
+                    <p>活动对象:文字文字文字</p>
+                    <p>活动报名时间:年/月/日/时-年/月/日/时</p>
+                    <p>活动进行时间:年/月/日/时-年/月/日/时</p>
+                    <p>活动地点:北京华贸丽思卡尔顿酒店大宴会厅</p>
+                    <p>报名人数:80人</p>
+                </div>
+
+                <!-- 富文本活动内容 -->
+                <h3 class="text-[17px] font-medium text-gray-900 mb-4">活动内容</h3>
+                <div class="text-[14px] text-gray-600 leading-[1.8] mb-8 text-justify tracking-wide">
+                    <p class="mb-4">带领嘉宾初步进入风水的世界,探索古人智慧的深度。用传统智慧理念和专业的财富规划帮助客户更好把握机遇。实现财富积累,代代传承。</p>
+                    <p class="mb-4">特邀嘉宾:赵丹青<br>现系国际青年易经学会会员;<br>香港中美易经学会会员及“中华周易学会”理事;<br>担任香港《楼盘与风水》杂志编委,《财务圈》杂志上开设风水专栏,为LV, Tiffany, Ports等著名国际时尚品牌公司堪舆;<br>任澳大利亚周易研究会副会长。</p>
+                    <p>活动流程:<br>14:00 - 14:30 嘉宾签到<br>14:30 - 14:45 汇丰私人财富规划简介<br>14:45 - 16:15 嘉宾主题分享-习住家风水 创财富人生<br>16:15 - 17:00 茶歇&自由交流</p>
+                </div>
+
+                <!-- 达标层级 Tabs (支持最多5层) -->
+                <div class="flex overflow-x-auto no-scrollbar border-b border-gray-200 mb-5 relative scroll-smooth" id="tier-tabs-container">
+                    <button class="shrink-0 min-w-[72px] py-3 text-[14px] font-medium text-gray-900 text-center" onclick="switchTier(1)" id="tab-tier-1">层级一</button>
+                    <button class="shrink-0 min-w-[72px] py-3 text-[14px] font-medium text-gray-500 text-center" onclick="switchTier(2)" id="tab-tier-2">层级二</button>
+                    <button class="shrink-0 min-w-[72px] py-3 text-[14px] font-medium text-gray-500 text-center" onclick="switchTier(3)" id="tab-tier-3">层级三</button>
+                    <button class="shrink-0 min-w-[72px] py-3 text-[14px] font-medium text-gray-500 text-center" onclick="switchTier(4)" id="tab-tier-4">层级四</button>
+                    <button class="shrink-0 min-w-[72px] py-3 text-[14px] font-medium text-gray-500 text-center" onclick="switchTier(5)" id="tab-tier-5">层级五</button>
+                    <!-- 滑动指示器 -->
+                    <div id="tier-indicator" class="absolute bottom-0 h-[2px] bg-[#d70014] transition-all duration-300" style="width: 72px; left: 0px;"></div>
+                </div>
+
+                <!-- 达标层级内容展示区 -->
+                <div id="tier-content-area" class="min-h-[150px]">
+                    <h4 class="text-[15px] font-medium text-gray-900 mb-2">达标要求</h4>
+                    <p class="text-[14px] text-gray-600 leading-[1.6] mb-6 tracking-wide">活动期间,首次升级为“合格卓越理财”客户,且保持3个月,3个自然月任一月末日终,较2025年3月末新增持有1类以上资产大类。</p>
+                    
+                    <h4 class="text-[15px] font-medium text-gray-900 mb-2">达标奖励</h4>
+                    <p class="text-[14px] text-gray-600 leading-[1.6] tracking-wide mb-4">32万汇友荟积分(可兑换约等值¥500好礼)</p>
+                </div>
+
+                <!-- === 操作栏 (跟随滚动,放在内容最下方) === -->
+                <div class="mt-8 mb-4">
+                    <button id="main-action-btn" class="w-full h-[44px] bg-[#d70014] text-white text-[16px] font-medium rounded-[2px] transition active:opacity-90" onclick="handleMainAction()">
+                        报名
+                    </button>
+                </div>
+            </div>
+        </div>
+
+        <!-- ================= 弹窗区域 ================= -->
+
+        <!-- 1. 报名通用提示弹窗 (不符合/已满/互斥) -->
+        <div id="alert-modal-backdrop" class="absolute inset-0 bg-black/50 z-[60] opacity-0 pointer-events-none transition-opacity duration-300" onclick="closeAlertModal()"></div>
+        <div id="alert-modal" class="absolute bottom-0 left-0 w-full bg-white z-[70] rounded-t-xl transform translate-y-full transition-transform duration-300 flex flex-col pb-safe">
+            <div class="flex justify-between items-center px-5 py-4 shrink-0">
+                <h2 class="text-[17px] font-medium text-gray-900">提示</h2>
+                <button onclick="closeAlertModal()" class="text-gray-500 p-1 hover:bg-gray-100 rounded-full transition">
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
+                </button>
+            </div>
+            <div class="px-5 pb-6 pt-2 text-[15px] text-gray-700" id="alert-modal-msg">
+                您暂不符合报名条件
+            </div>
+            <div class="px-5 pb-5">
+                <button onclick="closeAlertModal()" class="w-full py-2.5 border border-[#cccccc] rounded-[2px] text-[16px] text-gray-800 bg-white font-medium active:bg-gray-50">返回首页</button>
+            </div>
+        </div>
+
+        <!-- 2. 报名确认弹窗 -->
+        <div id="confirm-modal-backdrop" class="absolute inset-0 bg-black/50 z-[60] opacity-0 pointer-events-none transition-opacity duration-300" onclick="closeConfirmModal()"></div>
+        <div id="confirm-modal" class="absolute bottom-0 left-0 w-full bg-white z-[70] rounded-t-xl transform translate-y-full transition-transform duration-300 flex flex-col pb-safe">
+            <div class="flex justify-between items-center px-5 py-4 shrink-0 border-b border-gray-50">
+                <h2 class="text-[17px] font-medium text-gray-900">报名确认</h2>
+                <button onclick="closeConfirmModal()" class="text-gray-500 p-1 hover:bg-gray-100 rounded-full transition">
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
+                </button>
+            </div>
+            <div class="px-5 pb-8 pt-5 text-[15px] text-gray-700 leading-relaxed">
+                你确定报名参加此「2025 汇丰母亲节特别活动」?
+            </div>
+            <div class="flex px-5 pb-5 gap-3">
+                <button onclick="closeConfirmModal()" class="flex-1 py-2.5 border border-[#cccccc] rounded-[2px] text-[16px] text-gray-800 bg-white font-medium active:bg-gray-50">取消</button>
+                <button onclick="confirmEnrollment()" class="flex-1 py-2.5 rounded-[2px] text-[16px] text-white bg-[#d70014] font-medium active:opacity-90">确认</button>
+            </div>
+        </div>
+
+        <!-- Toast 提示 (用于操作反馈) -->
+        <div id="toast" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-black/80 text-white px-5 py-2.5 rounded text-[14px] z-[100] opacity-0 pointer-events-none transition-opacity duration-300 text-center w-max max-w-[80%]"></div>
+
+    </div>
+
+    <script>
+        // --- 状态变量 ---
+        let currentBtnState = 'enroll'; // enroll | progress | benefits | more
+        let currentEnrollCondition = 'success'; // success | not_eligible | full | exclusive
+        let currentTier = 1;
+
+        let isBtnPanelOpen = false;
+        let isResultPanelOpen = false;
+
+        // --- 初始化面板 UI ---
+        document.addEventListener('DOMContentLoaded', () => {
+            updateBtnPanelUI();
+            updateResultPanelUI();
+        });
+
+        // --- 层级切换逻辑 (最多5层) ---
+        function switchTier(tier) {
+            currentTier = tier;
+            const tabWidth = 72; // min-w-[72px]
+            
+            // 移动指示器
+            document.getElementById('tier-indicator').style.left = `${(tier - 1) * tabWidth}px`;
+            
+            // 更新文字样式
+            for (let i = 1; i <= 5; i++) {
+                const tab = document.getElementById(`tab-tier-${i}`);
+                if(i === tier) {
+                    tab.className = 'shrink-0 min-w-[72px] py-3 text-[14px] font-medium text-gray-900 text-center';
+                } else {
+                    tab.className = 'shrink-0 min-w-[72px] py-3 text-[14px] font-medium text-gray-500 text-center';
+                }
+            }
+
+            // 模拟更换数据 (这里简单通过 JS 替换奖励文案以示区别)
+            const rewards = [
+                "32万汇友荟积分(可兑换约等值¥500好礼)",
+                "64万汇友荟积分(可兑换约等值¥1000好礼)",
+                "128万汇友荟积分(可兑换约等值¥2000好礼)",
+                "免费双人豪华酒店住宿一晚",
+                "汇丰私行专属定制礼盒一份"
+            ];
+
+            document.getElementById('tier-content-area').innerHTML = `
+                <h4 class="text-[15px] font-medium text-gray-900 mb-2">达标要求</h4>
+                <p class="text-[14px] text-gray-600 leading-[1.6] mb-6 tracking-wide">
+                    活动期间,首次升级为“合格卓越理财”客户,且保持3个月,3个自然月任一月末日终,较2025年3月末新增持有${tier}类以上资产大类。(此为层级${tier}的模拟数据)
+                </p>
+                <h4 class="text-[15px] font-medium text-gray-900 mb-2">达标奖励</h4>
+                <p class="text-[14px] text-gray-600 leading-[1.6] tracking-wide mb-4">${rewards[tier-1]}</p>
+            `;
+        }
+
+        // --- 底部主按钮逻辑 ---
+        function handleMainAction() {
+            if (currentBtnState === 'enroll') {
+                if (currentEnrollCondition === 'success') {
+                    openConfirmModal();
+                } else {
+                    let msg = '';
+                    if (currentEnrollCondition === 'not_eligible') msg = '您暂不符合报名条件';
+                    else if (currentEnrollCondition === 'full') msg = '很遗憾,该活动报名已满。';
+                    else if (currentEnrollCondition === 'exclusive') msg = '您已参与同类型活动,不可重复报名';
+                    openAlertModal(msg);
+                }
+            } else if (currentBtnState === 'progress') {
+                showToast('正在为您拉取达标进度...');
+            } else if (currentBtnState === 'benefits') {
+                showToast('正在为您拉取权益信息...');
+            } else if (currentBtnState === 'more') {
+                showToast('即将跳转全部活动列表...');
+            }
+        }
+
+        // --- 弹窗控制逻辑 ---
+        function openAlertModal(msg) {
+            document.getElementById('alert-modal-msg').innerText = msg;
+            document.getElementById('alert-modal-backdrop').classList.remove('opacity-0', 'pointer-events-none');
+            document.getElementById('alert-modal').classList.remove('translate-y-full');
+        }
+        function closeAlertModal() {
+            document.getElementById('alert-modal-backdrop').classList.add('opacity-0', 'pointer-events-none');
+            document.getElementById('alert-modal').classList.add('translate-y-full');
+        }
+
+        function openConfirmModal() {
+            document.getElementById('confirm-modal-backdrop').classList.remove('opacity-0', 'pointer-events-none');
+            document.getElementById('confirm-modal').classList.remove('translate-y-full');
+        }
+        function closeConfirmModal() {
+            document.getElementById('confirm-modal-backdrop').classList.add('opacity-0', 'pointer-events-none');
+            document.getElementById('confirm-modal').classList.add('translate-y-full');
+        }
+        
+        function confirmEnrollment() {
+            closeConfirmModal();
+            // 报名成功后,可以自动切换为查看进度状态
+            setTimeout(() => {
+                showToast('报名成功!');
+                setBtnState('progress');
+            }, 300);
+        }
+
+        // --- Toast 提示 ---
+        let toastTimeout;
+        function showToast(msg) {
+            const toast = document.getElementById('toast');
+            toast.innerText = msg;
+            toast.classList.remove('opacity-0');
+            clearTimeout(toastTimeout);
+            toastTimeout = setTimeout(() => { toast.classList.add('opacity-0'); }, 2000);
+        }
+
+        // --- 控制面板交互 (用于调试/演示) ---
+        function toggleBtnPanel() {
+            isBtnPanelOpen = !isBtnPanelOpen;
+            const panel = document.getElementById('btn-panel');
+            if (isBtnPanelOpen) {
+                panel.classList.remove('scale-0', 'opacity-0', 'pointer-events-none');
+                if (isResultPanelOpen) toggleResultPanel();
+            } else {
+                panel.classList.add('scale-0', 'opacity-0', 'pointer-events-none');
+            }
+        }
+
+        function toggleResultPanel() {
+            isResultPanelOpen = !isResultPanelOpen;
+            const panel = document.getElementById('result-panel');
+            if (isResultPanelOpen) {
+                panel.classList.remove('scale-0', 'opacity-0', 'pointer-events-none');
+                if (isBtnPanelOpen) toggleBtnPanel();
+            } else {
+                panel.classList.add('scale-0', 'opacity-0', 'pointer-events-none');
+            }
+        }
+
+        function setBtnState(state) {
+            currentBtnState = state;
+            updateBtnPanelUI();
+            
+            const mainBtn = document.getElementById('main-action-btn');
+            const resultWrapper = document.getElementById('result-control-wrapper');
+            
+            // 切换按钮文案和控制结果面板的显示/隐藏
+            if (state === 'enroll') {
+                mainBtn.innerText = '报名';
+                mainBtn.className = 'w-full h-[44px] bg-[#d70014] text-white text-[16px] font-medium rounded-[2px] transition active:opacity-90';
+                resultWrapper.style.display = 'block'; // 只有报名状态才需要校验结果控制
+            } else {
+                resultWrapper.style.display = 'none';
+                if (isResultPanelOpen) toggleResultPanel();
+                
+                // 非报名状态:统一使用白底、灰框、深灰字的样式
+                const secondaryBtnClass = 'w-full h-[44px] border border-[#999999] text-[#333333] bg-white text-[15px] font-medium rounded-[2px] transition active:bg-gray-50';
+                
+                if (state === 'progress') {
+                    mainBtn.innerText = '查看达标进度';
+                    mainBtn.className = secondaryBtnClass;
+                } else if (state === 'benefits') {
+                    mainBtn.innerText = '查看权益';
+                    mainBtn.className = secondaryBtnClass;
+                } else if (state === 'more') {
+                    mainBtn.innerText = '查看更多活动';
+                    mainBtn.className = secondaryBtnClass;
+                }
+            }
+
+            if(isBtnPanelOpen) toggleBtnPanel();
+        }
+
+        function setEnrollCondition(cond) {
+            currentEnrollCondition = cond;
+            updateResultPanelUI();
+            if(isResultPanelOpen) toggleResultPanel();
+        }
+
+        function updateBtnPanelUI() {
+            const states = ['enroll', 'progress', 'benefits', 'more'];
+            states.forEach(s => {
+                document.getElementById(`ctrl-btn-${s}`).className = s === currentBtnState 
+                    ? 'px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium' 
+                    : 'px-2 py-1.5 rounded bg-gray-100 text-gray-700 hover:bg-gray-200 transition';
+            });
+        }
+
+        function updateResultPanelUI() {
+            const conditions = ['success', 'not_eligible', 'full', 'exclusive'];
+            conditions.forEach(c => {
+                document.getElementById(`ctrl-cond-${c}`).className = c === currentEnrollCondition 
+                    ? 'px-2 py-1.5 rounded bg-[#d70014] text-white transition font-medium' 
+                    : 'px-2 py-1.5 rounded bg-gray-100 text-gray-700 hover:bg-gray-200 transition';
+            });
+        }
+    </script>
+</body>
+</html>

+ 209 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/audit/活动后台配置审查结论.md

@@ -0,0 +1,209 @@
+# 活动后台配置审查结论
+
+## 审查范围
+
+- 移动端依据:`Reward Platform活动模块方案细化(1).pptx`
+- 银行后台参考:`Copy of CUPD管理台0108-问题提出版_260119 (002).xlsx`
+- 二次整理稿:`权益平台活动模块_产品文档_后台菜单进度模板版.md`
+- 当前实现:`pc-demo` 活动模块源码
+
+本次只审查信息架构、字段归属和业务闭环,不修改代码。浏览器连接失败,因此没有使用旧截图作为本次视觉证据,视觉与交互细节仍需后续逐页复核。
+
+## 总结
+
+当前 Demo **不适合继续按 MD 逐字段补齐**。主要问题不是字段少,而是 MD 把移动端能力、后台资源、业务规则、运营查询都拆成了独立菜单,当前实现又把这些概念直接平铺为 11 个菜单,导致系统越来越像“字段目录”,不像银行运营人员实际配置活动的工作台。
+
+PPT 说明的是移动端目标体验和 Phase 2 场景;Excel 才是银行后台的初步配置参考。MD 可以做需求索引,但不应该作为页面和菜单的逐字实现规范。
+
+## P0:必须调整
+
+### 1. 菜单过度拆分
+
+当前 11 个菜单中,以下内容不适合全部作为一级菜单:
+
+- 报名表单、通知模板应归入“内容管理”。
+- 权益发放计划应归入现有“权益管理”。
+- Campaign、Channel、链接、二维码和版位应归入“渠道/投放管理”。
+- 信用卡活动入口属于页面入口或版位配置,不属于活动核心配置。
+- 进度模板是否独立,取决于是否真的会跨活动复用;Demo 阶段可作为活动配置中的模板选择器或二级菜单。
+
+建议活动管理下保留 5 个主要入口:
+
+1. 活动管理
+2. 客群管理
+3. 活动内容配置
+4. 活动规则与进度配置
+5. 活动进度查询
+
+权益发放沿用权益管理,渠道投放沿用渠道管理,不重复建立活动专属体系。
+
+### 2. “达标规则”和“进度模板”仍然混在一起
+
+虽然已经新增进度模板页,但达标规则页仍包含:
+
+- 进度样式
+- 层级数、任务数
+- 对客文案、问号说明
+- 发奖次数 Z
+
+这些不是同一层的数据:
+
+- 达标规则只负责数据来源、标签/字段、操作符、规则值、AND/OR、观察周期和输出状态。
+- 进度模板负责层级、任务布局、状态文案、按钮文案和 MGM 双视角展示。
+- 发奖次数和奖励跳转属于权益发放计划。
+
+当前拆分在菜单上完成了,但数据模型没有真正拆开。
+
+### 3. 活动主配置缺少 PPT 的关键报名分支
+
+当前“客群与报名”只有报名、确认、补充信息和名额,缺少:
+
+- 是否留资
+- 关联留资表
+- 第三方跳转链接
+- 补充信息关闭后是否已报名成功
+- 无需报名时按钮跳转逻辑
+- 无进度直接发奖
+
+PPT 的关键不是多几个输入框,而是必须能表达以下互斥流程:
+
+1. 需要报名 / 无需报名
+2. 需要确认 / 不需要确认
+3. 需要补充信息 / 不需要补充信息
+4. 平台内完成 / 跳第三方继续
+5. 有进度 / 无进度直接发奖 / 无进度无奖励
+
+这些应做成条件化流程配置,不应一直显示所有字段。
+
+### 4. 报名表单的数据结构不正确
+
+当前表单只支持一组 `字段名称 + 字段类型 + 最大长度`,但 PPT 明确是最多两个可独立配置的收集字段。每个字段至少需要:
+
+- 字段名称
+- 对客标签
+- 类型
+- 是否必填
+- 占位文案
+- 长度/格式校验
+- 错误文案
+
+第三方跳转链接属于报名流程,不应放在表单字段资源里。
+
+### 5. 报表页不是“活动进度查询”
+
+当前页面只是活动汇总表,展示报名百分比、达标配置、奖品和互斥状态。Excel 的“活动进度查询”实际包含客户级记录,MD 也列出了六类查询:
+
+- 客户活动报名
+- 客户达标情况
+- 客户权益发放
+- 积分明细
+- 立减金发放
+- 信用卡权益领取及使用
+
+至少第一版 Demo 应提供“报名记录、达标进度、权益发放”三个 Tab,并支持按活动 ID、Customer ID、状态和时间查询。
+
+## P1:重要缺口
+
+### 6. 客群管理只是文字说明,不是可配置规则
+
+当前用“规则说明”文本框代替标签组合,不能表达 Excel 中的:
+
+- 标签
+- 操作符
+- 规则值
+- AND/OR 组合
+- Sizing
+- 黑名单/白名单
+- 客户号预览
+
+Demo 不需要真实圈客接口,但必须把规则构造器的结构表现出来。
+
+### 7. 权益发放计划字段不足
+
+当前缺少:
+
+- 权益/礼品 ID
+- 单人发放数量
+- 每日发放上限
+- 是否需要客户领取
+- 按钮文案映射
+- 跳转目标
+- 发奖通知模板
+- 剩余库存和库存预警
+
+另外“引用活动”不应手工输入,应由系统根据引用关系计算。
+
+### 8. 投放配置没有银行 Excel 中的归因模型
+
+当前只有活动名称、渠道、渠道码、物料、版位和链接,缺少:
+
+- Campaign ID
+- Channel ID
+- 投放开始/结束时间
+- MobileX 版位
+- 权益首页 1-8 版位
+- 草稿/已发布/已下线
+- 生成链接、二维码/太阳码的明确操作
+
+“活动名称”和“版位”也应选择已有资源,不能自由输入。
+
+### 9. 互斥配置字段较全,但业务模型仍偏自由输入
+
+互斥活动目前是文本输入,无法建立真实引用关系。应改为活动或活动类型选择,并补充:
+
+- 生效时间
+- 优先级
+- Case A/B/C 的处理结果
+- 推荐人侧文案
+- T+1 修正后是否保留报名记录
+- 已发奖客户不可回滚的保护说明
+
+### 10. 缺少发布后的稳定性控制
+
+当前资源可以直接编辑、停用,但没有表现:
+
+- 被活动引用后是否允许修改
+- 已发布活动使用中的规则是否锁定
+- 修改是否生成新版本
+- 变更影响多少活动和已报名客户
+- 审核记录与驳回原因
+
+银行后台最需要避免的是运营修改规则后影响存量客户,这比继续补字段优先级更高。
+
+## P2:Demo 表现问题
+
+- 活动图片使用 URL 输入框,不像真实运营后台,应表现上传、尺寸要求和预览。
+- 活动详情用普通多行文本,不足以表达图文详情和白名单链接。
+- 当前通用弹窗只检查第一个必填字段,不能证明配置闭环。
+- 多个页面把“引用活动”做成手工文本,容易造成虚假的关联关系。
+- 当前活动管理的六个 Tab 方向正确,但需要根据开关动态显示字段并给出流程摘要。
+
+## 推荐的第一版范围
+
+### 第一版必须完整演示
+
+1. 新建普通报名活动
+2. 新建无需报名活动
+3. 配置最多两个补充信息字段
+4. 配置展示客群和报名客群
+5. 选择一种进度样式和一条达标规则
+6. 选择权益发放计划
+7. 配置 NTB 活动实时互斥
+8. 提交审核、审核通过、发布
+9. 查询报名、达标和发放记录
+
+### 第一版只保留入口或示意
+
+- MGM T+1 极限 Case
+- 信用卡活动完整体系
+- 多渠道效果分析
+- 高级短信变量
+- 真实发奖、真实标签和真实二维码生成
+
+## 推荐实施顺序
+
+1. 先收敛菜单和字段归属。
+2. 修正报名流程、达标规则、进度模板、发放计划的数据边界。
+3. 补齐三个客户级查询 Tab。
+4. 再完善互斥 Case 和投放配置。
+5. 最后做视觉和交互逐页验证。

+ 90 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/design-qa.md

@@ -0,0 +1,90 @@
+# Design QA
+
+## Comparison targets
+
+- Source visual truth:
+  - `../pc-demo截图/NTB.png`
+  - `../pc-demo截图/MGM.png`
+  - `../pc-demo截图/层层礼.png`
+  - `活动管理-达标与进度.html`(视觉样式)
+  - `qa/ntb-rule-style-reference.png`(NTB 规则标签样式)
+  - `qa/layer-compact-reference-tab.png`(层级 Tab 标注)
+  - `qa/layer-compact-reference-rule.png`(层层礼规则布局标注)
+  - `qa/layer-overseas-reference.png`(层层礼境外消费规则标注)
+- Implementation screenshots:
+  - `qa/implementation-ntb.png`
+  - `qa/implementation-mgm.png`
+  - `qa/implementation-layer-html-style.png`
+  - `qa/ntb-rule-style-after.png`
+  - `qa/layer-compact-after-top.png`
+  - `qa/layer-compact-after-rule.png`
+  - `qa/layer-overseas-after.png`
+  - `qa/layer-asset-detail-after.png`
+  - `qa/layer-overseas-description-after.png`
+- Combined comparisons:
+  - `qa/compare-ntb.png`
+  - `qa/compare-mgm.png`
+  - `qa/compare-layer.png`
+  - `qa/ntb-rule-style-comparison.png`
+  - `qa/layer-compact-comparison.png`
+  - `qa/layer-overseas-comparison.png`
+- Viewport: 1280 x 720
+- Route: `/customization/hsbc/activities/add`
+- State: “进度与奖励”Tab,分别选择 NTB、MGM、层层礼
+
+## Full-view comparison evidence
+
+- 三种模板的配置区顺序与截图一致。
+- 页面保留现有管理后台外壳和 Element Plus 控件。
+- 配置区采用 HTML 参考中的白色卡片、浅灰标题栏、灰色字段面板、轻边框、8px 圆角和紧凑按钮层级。
+
+## Focused region comparison evidence
+
+- NTB:提示话术、时间标签、TRB 进度标签、连续达标参数和权益奖励字段已逐项核对;规则标签卡片的纵向字段顺序、是/否下拉框、连续月数、允许断开和右上角删除图标与最新参考图一致。
+- MGM:积分奖励置于规则前;开户时间、TRB、达标话术、不达标原因及被推荐人进阶奖励已逐项核对。
+- 层层礼:层级1/2 Tab、层级内活动1/2、资产大类明细、境外消费和活动独立奖励已逐项核对;资产大类明细按“是否需要对比 / 基准月+新增类别 / 限制标签1达标月”三行排列;境外消费规则描述显示指定占位提示;规则内布尔项均为“是/否”下拉框。
+- 浏览器 DOM 检查确认层级2可以正常切换,三个模板切换无控制台错误。
+
+## Findings
+
+- 未发现 P0、P1 或 P2 问题。
+- P3:截图层级 Tab 使用红色强调,HTML 使用蓝色按钮和中性灰面板;按用户最新说明,视觉以 HTML 为准,保留蓝色交互强调。
+- P3:参考图没有完整后台外壳,当前实现包含侧栏和顶部导航。这是现有系统框架,不属于配置区偏差。
+- P3:最新 NTB 参考图内容区宽于现有后台可用区域;实现保留固定控件宽度并在窄屏自动换行,属于现有布局约束下的预期差异。
+
+## Patches made
+
+- 将三个模板从共用表单拆分为独立条件渲染。
+- 补齐 NTB 时间标签和奖励配置。
+- 按最新截图将 NTB 默认奖励改为积分,并补齐标签描述。
+- 调整 MGM 奖励、规则、被推荐进度的字段顺序和默认内容。
+- 补齐层层礼层级 Tab、每层多个活动、活动依赖提示。
+- 层层礼默认规则调整为活动1使用 TRB、活动2使用境外消费,两项活动分别配置权益奖励。
+- 保留资产大类明细作为可选规则,不再默认生成。
+- 增加旧活动数据归一化,避免编辑时出现空规则和缺失活动2。
+- 将 NTB 规则标签改为参考图中的纵向表单:首行集中配置标签、条件和值,后续依次配置开户当月、连续达标、标签描述及三类提示。
+- NTB 布尔字段由开关改为“是/否”下拉框,连续达标时联动显示月数和是否允许断开。
+- NTB 规则标签使用右上角删除图标,并在删除全部标签后阻止保存。
+- 层层礼层级 Tab 改为每项 120px 的紧凑布局,不再平分整行。
+- 层层礼规则首行移除“判断条件”和“规则值”标签,只保留相邻控件。
+- 层层礼连续达标、连续月份、允许断开收紧为同一行;规则标题、规则描述、标签进度提示改为三行中等宽度输入框。
+- 层层礼境外消费规则新增“消费时间:小于等于 + 日期”和“消费维度:笔数 + 大于等于 + 数字”两行配置。
+- 境外消费规则移除“标签进度提示”,并为新增必填字段补充默认值、旧数据兼容和保存校验。
+- 资产大类明细调整为三行布局,限制标签1达标月与基准月控件垂直对齐,并移除标签进度提示。
+- 境外消费规则描述补充“配置?弹窗提示内容”占位。
+- 层层礼规则中的开户当月、连续达标、允许断开、是否需要对比、限制标签1达标月全部改为“是/否”下拉框。
+
+## Verification
+
+- `npm run build`: passed
+- `git diff --check`: passed
+- Browser console errors: none
+- Template switching: passed
+- Layer tab switching: passed
+- NTB rule-card DOM and visual comparison: passed
+- Layer compact layout DOM and visual comparison: passed
+- Layer overseas rule DOM and visual comparison: passed
+- Layer asset-detail layout and boolean-select verification: passed
+- Layer overseas description placeholder verification: passed
+
+final result: passed

BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/compare-layer.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/compare-mgm.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/compare-ntb.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-layer-html-style.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-layer.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-mgm.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-ntb-html-style.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/implementation-ntb.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-asset-detail-after.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-after-rule.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-after-top.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-comparison.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-reference-rule.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-compact-reference-tab.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-overseas-after.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-overseas-comparison.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-overseas-description-after.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/layer-overseas-reference.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/ntb-rule-style-after.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/ntb-rule-style-comparison.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/qa/ntb-rule-style-reference.png


+ 15 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/Layout.vue

@@ -51,6 +51,21 @@
                 券码审核
               </el-menu-item>
             </el-sub-menu>
+            <el-sub-menu index="customization-hsbc-activity">
+              <template #title>活动管理</template>
+              <el-menu-item index="/customization/hsbc/activities">
+                活动管理
+              </el-menu-item>
+              <el-menu-item index="/customization/hsbc/activity-mutex-groups">
+                活动互斥组管理
+              </el-menu-item>
+              <el-menu-item index="/customization/hsbc/activity-report">
+                活动报表/进度查询
+              </el-menu-item>
+              <el-menu-item index="/customization/hsbc/credit-card-activity-entry">
+                信用卡活动入口配置
+              </el-menu-item>
+            </el-sub-menu>
             <el-sub-menu index="customization-hsbc-campaign">
               <template #title>发放计划管理</template>
               <el-menu-item index="/customization/hsbc/equity-grant-campaign">

+ 369 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/activity/ActivityAchievementConfig.vue

@@ -0,0 +1,369 @@
+<template>
+  <div class="achievement-config">
+    <div class="template-row">
+      <el-form-item label="活动模板" required>
+        <el-select v-model="form.achievementTemplate" style="width:200px" @change="handleTemplateChange">
+          <el-option label="NTB(新客达标)" value="NTB" />
+          <el-option label="MGM(推荐有礼)" value="MGM" />
+          <el-option label="层层礼" value="LAYER" />
+        </el-select>
+      </el-form-item>
+      <el-form-item v-if="form.achievementTemplate === 'LAYER'" label="层级数量" required>
+        <el-input-number v-model="form.layerCount" :min="1" :max="5" @change="syncLayerCount" />
+      </el-form-item>
+    </div>
+
+    <template v-if="form.achievementTemplate === 'NTB'">
+      <section class="config-block">
+        <h3>达标进度提示话术配置</h3>
+        <div class="field-panel compact-copy-panel">
+          <el-form-item label="未达标提示" label-width="140px">
+            <el-input v-model="form.progressCopy.unreached" class="compact-copy-input" />
+          </el-form-item>
+          <el-form-item label="已达标提示" label-width="140px">
+            <el-input v-model="form.progressCopy.reached" class="compact-copy-input" />
+          </el-form-item>
+          <el-form-item label="达标进度提示" label-width="140px">
+            <el-input v-model="form.progressCopy.help" class="compact-copy-input" placeholder="配置问号弹窗提示内容" />
+          </el-form-item>
+        </div>
+      </section>
+
+      <section class="config-block">
+        <h3>达标规则标签配置</h3>
+        <el-button :disabled="form.ntbTimeRules.length >= 1" :icon="Plus" @click="addTimeRule">添加时间标签</el-button>
+        <div v-for="(rule, index) in form.ntbTimeRules" :key="rule.key" class="time-rule">
+          <el-form-item label="规则标签" label-width="120px" required>
+            <div class="time-rule-controls">
+              <el-select v-model="rule.field" class="compact-field-select">
+                <el-option label="开户时间" value="开户时间" />
+              </el-select>
+              <el-select v-model="rule.operator" class="compact-operator-select">
+                <el-option label="介于" value="介于" />
+              </el-select>
+              <el-date-picker
+                v-model="rule.dateRange"
+                class="compact-date-range"
+                type="daterange"
+                value-format="YYYY-MM-DD"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              />
+            </div>
+          </el-form-item>
+          <el-button class="time-rule-delete" type="danger" link :icon="Delete" title="删除时间标签" @click="form.ntbTimeRules.splice(index, 1)" />
+        </div>
+        <el-button type="primary" :icon="Plus" @click="addRule(form.ntbRules)">添加进度标签</el-button>
+        <ActivityRuleEditor v-for="(rule, index) in form.ntbRules" :key="rule.key" :rule="rule" :index="index" mode="ntb" removable @remove="form.ntbRules.splice(index, 1)" />
+      </section>
+
+      <section class="config-block reward-section">
+        <ActivityRewardEditor :reward="form.templateRewards.NTB" :grant-plans="grantPlans" />
+      </section>
+    </template>
+
+    <template v-else-if="form.achievementTemplate === 'MGM'">
+      <section class="config-block reward-section">
+        <ActivityRewardEditor :reward="form.templateRewards.MGM" :grant-plans="grantPlans" />
+      </section>
+      <section class="config-block">
+        <h3>达标规则标签配置</h3>
+        <div class="field-panel">
+          <el-form-item label="已达标展示标签" required><el-select v-model="form.mgmAchievedTag" style="width:260px"><el-option label="客户MSC Group" value="客户MSC Group" /></el-select></el-form-item>
+        </div>
+        <el-button type="primary" :icon="Plus" @click="addMgmRule">添加进度标签</el-button>
+        <ActivityRuleEditor v-for="(rule, index) in form.mgmRules" :key="rule.key" :rule="rule" :index="index" mode="mgm" :removable="form.mgmRules.length > 1" @remove="form.mgmRules.splice(index, 1)" />
+      </section>
+      <section class="config-block">
+        <h3>被推荐进度配置</h3>
+        <div class="field-panel mgm-referred-panel">
+          <el-form-item label="规则标签1描述" label-width="180px" required>
+            <el-input v-model="form.mgmReferredProgress.labelOne" class="mgm-referred-copy-input" />
+          </el-form-item>
+          <el-form-item label="规则标签2描述" label-width="180px" required>
+            <el-input v-model="form.mgmReferredProgress.labelTwo" class="mgm-referred-copy-input" />
+          </el-form-item>
+          <el-form-item label="是否有进阶奖励" label-width="180px" required>
+            <el-select v-model="form.mgmReferredProgress.advancedReward" class="boolean-select">
+              <el-option label="是" :value="true" />
+              <el-option label="否" :value="false" />
+            </el-select>
+          </el-form-item>
+          <template v-if="form.mgmReferredProgress.advancedReward">
+            <el-form-item label="进阶奖励规则标签" label-width="180px" required>
+              <div class="mgm-referred-rule-controls">
+                <el-select v-model="form.mgmReferredProgress.advancedRuleField" class="mgm-referred-field-select">
+                  <el-option label="TRB值(含家庭账户)" value="TRB值(含家庭账户)" />
+                </el-select>
+                <el-select v-model="form.mgmReferredProgress.advancedOperator" class="mgm-referred-operator-select">
+                  <el-option label="大于等于" value="大于等于" />
+                </el-select>
+                <el-input-number v-model="form.mgmReferredProgress.advancedValue" :min="0" :controls="false" class="mgm-referred-value-input" />
+              </div>
+            </el-form-item>
+            <el-form-item label="进阶奖励规则标签描述" label-width="180px" required>
+              <el-input v-model="form.mgmReferredProgress.advancedRuleText" class="mgm-referred-copy-input" />
+            </el-form-item>
+          </template>
+        </div>
+      </section>
+    </template>
+
+    <template v-else>
+      <section class="config-block">
+        <h3>达标层级配置</h3>
+        <el-tabs v-model="activeLayer" class="layer-tabs">
+          <el-tab-pane v-for="(level, levelIndex) in form.achievementLayers" :key="level.key" :label="`层级${levelIndex + 1}`" :name="String(levelIndex)">
+            <div class="layer-meta-fields">
+              <el-form-item label="可参与人群" label-width="120px">
+                <el-select v-model="level.crowdId" class="compact-copy-input">
+                  <el-option v-for="item in crowds" :key="item.id" :label="`${item.id}-${item.name}`" :value="item.id" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="层级标题" label-width="120px">
+                <el-input v-model="level.title" class="compact-copy-input" />
+              </el-form-item>
+            </div>
+            <el-button type="primary" :icon="Plus" @click="addActivity(level)">添加活动</el-button>
+
+            <div v-for="(activity, activityIndex) in level.activities" :key="activity.key" class="layer-activity">
+              <div class="activity-head">
+                <div><strong>活动{{ activityIndex + 1 }}</strong><span v-if="activityIndex > 0" class="notice">*注意:活动{{ activityIndex + 1 }}达标前提为活动{{ activityIndex }}</span></div>
+                <el-button v-if="level.activities.length > 1" type="danger" link :icon="Delete" @click="level.activities.splice(activityIndex, 1)">删除活动</el-button>
+              </div>
+              <div class="layer-activity-fields">
+                <el-form-item label="活动标题" label-width="120px">
+                  <el-input v-model="activity.title" class="compact-copy-input" />
+                </el-form-item>
+                <el-form-item label="活动描述" label-width="120px">
+                  <el-input v-model="activity.description" class="compact-copy-input" />
+                </el-form-item>
+              </div>
+              <el-button type="primary" :icon="Plus" @click="addRule(activity.rules)">添加进度标签</el-button>
+              <ActivityRuleEditor v-for="(rule, ruleIndex) in activity.rules" :key="rule.key" :rule="rule" :index="ruleIndex" mode="layer" :dependent="activityIndex > 0" :removable="activity.rules.length > 1" @remove="activity.rules.splice(ruleIndex, 1)" />
+              <div class="activity-reward"><ActivityRewardEditor :reward="activity.reward" :grant-plans="grantPlans" /></div>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+      </section>
+    </template>
+  </div>
+</template>
+
+<script>
+import { Delete, Plus } from '@element-plus/icons-vue'
+import ActivityRewardEditor from './ActivityRewardEditor.vue'
+import ActivityRuleEditor from './ActivityRuleEditor.vue'
+
+const key = (prefix) => `${prefix}-${Date.now()}-${Math.random()}`
+
+export default {
+  name: 'ActivityAchievementConfig',
+  components: { ActivityRewardEditor, ActivityRuleEditor },
+  props: {
+    form: { type: Object, required: true },
+    crowds: { type: Array, default: () => [] },
+    grantPlans: { type: Array, default: () => [] }
+  },
+  data() { return { Delete, Plus, activeLayer: '0' } },
+  created() { this.normalize() },
+  methods: {
+    reward(type) { return { type, planId: '', amount: 3200000, validity: [], grantPeriod: '整个周期', times: 1 } },
+    rule(field = 'TRB值(含家庭账户)') {
+      return {
+        key: key('rule'), field, operator: field === '开户时间' ? '介于' : '大于等于', value: field === '境外消费' ? 1 : 500000,
+        dateRange: [], openingMonthRequired: false, continuous: field === 'TRB值(含家庭账户)', months: 3, allowBreak: true,
+        labelDescription: '', reachedText: '', unreachedText: '', progressText: '', failureReason: '', afterOpeningMonths: 3, requiredMonths: 1,
+        consumptionTimeOperator: '小于等于', consumptionEndDate: '', consumptionDimension: '笔数',
+        compareRequired: true, baseMonth: '', newCategoryCount: 1, limitFirstRuleMonth: true
+      }
+    },
+    activity(index) {
+      const field = index === 1 ? '境外消费' : 'TRB值(含家庭账户)'
+      return { key: key('activity'), title: '', description: '', rules: [this.rule(field)], reward: this.reward('权益') }
+    },
+    level(index) { return { key: key('level'), crowdId: '', title: `层级${index + 1}`, activities: [this.activity(0), this.activity(1)] } },
+    normalize() {
+      if (!this.form.progressCopy) this.form.progressCopy = { unreached: '', reached: '', help: '' }
+      if (!Array.isArray(this.form.ntbTimeRules) || !this.form.ntbTimeRules.length) this.form.ntbTimeRules = [{ key: key('time'), field: '开户时间', operator: '介于', dateRange: ['2026-07-01', '2026-09-30'] }]
+      if (!Array.isArray(this.form.ntbRules) || !this.form.ntbRules.length) {
+        const ntbRule = this.rule()
+        ntbRule.openingMonthRequired = true
+        ntbRule.labelDescription = '资产'
+        ntbRule.reachedText = '已满50万'
+        ntbRule.unreachedText = '未满50万'
+        this.form.ntbRules = [ntbRule]
+      }
+      if (!Array.isArray(this.form.mgmRules) || !this.form.mgmRules.length) {
+        const opening = this.rule('开户时间')
+        opening.dateRange = ['2026-07-01', '2026-09-30']
+        opening.reachedText = '已开户'
+        opening.unreachedText = '未开户'
+        opening.failureReason = '未在指定时间内开户'
+        const trb = this.rule()
+        trb.continuous = false
+        trb.reachedText = '已升级为卓越理财客户'
+        trb.unreachedText = '未升级为卓越理财客户'
+        trb.failureReason = '未在活动期间升级为卓越理财客户'
+        this.form.mgmRules = [opening, trb]
+      }
+      if (!this.form.templateRewards) this.form.templateRewards = { NTB: this.reward('权益'), MGM: this.reward('积分') }
+      if (!this.form.templateRewards.NTB) this.form.templateRewards.NTB = this.reward('权益')
+      if (!this.form.templateRewards.MGM) this.form.templateRewards.MGM = this.reward('积分')
+      if (!this.form.mgmAchievedTag) this.form.mgmAchievedTag = '客户MSC Group'
+      if (!this.form.mgmReferredProgress) this.form.mgmReferredProgress = {}
+      if (!this.form.mgmReferredProgress.labelOne) this.form.mgmReferredProgress.labelOne = '开户时间'
+      if (!this.form.mgmReferredProgress.labelTwo) this.form.mgmReferredProgress.labelTwo = '基础资产达标|月内日均/家庭日月均人均余额≥50万'
+      if (this.form.mgmReferredProgress.advancedReward === undefined) this.form.mgmReferredProgress.advancedReward = true
+      if (!this.form.mgmReferredProgress.advancedRuleField) this.form.mgmReferredProgress.advancedRuleField = 'TRB值(含家庭账户)'
+      if (!this.form.mgmReferredProgress.advancedRuleText) this.form.mgmReferredProgress.advancedRuleText = '是否完成尊尚升级'
+      Object.assign(this.form.mgmReferredProgress, {
+        advancedOperator: this.form.mgmReferredProgress.advancedOperator || '大于等于',
+        advancedValue: this.form.mgmReferredProgress.advancedValue || 6000000
+      })
+      if (!Array.isArray(this.form.achievementLayers) || !this.form.achievementLayers.length) this.form.achievementLayers = [this.level(0), this.level(1)]
+      this.form.achievementLayers.forEach((level, index) => {
+        if (!Array.isArray(level.activities) || !level.activities.length) level.activities = [this.activity(0), this.activity(1)]
+        level.activities.forEach((activity, activityIndex) => {
+          if (!Array.isArray(activity.rules) || !activity.rules.length) activity.rules = [this.rule(activityIndex === 1 ? '境外消费' : 'TRB值(含家庭账户)')]
+          activity.rules.forEach((rule) => {
+            if (rule.field !== '境外消费') return
+            if (!rule.consumptionTimeOperator) rule.consumptionTimeOperator = '小于等于'
+            if (!rule.consumptionDimension) rule.consumptionDimension = '笔数'
+            if (!rule.operator) rule.operator = '大于等于'
+          })
+          if (activityIndex === 0) {
+            if (!activity.title) activity.title = '活动一:资产达标'
+            if (!activity.description) activity.description = '完成以下任务获取320,000汇友荟积分'
+            const firstRule = activity.rules[0]
+            if (!firstRule.field) Object.assign(firstRule, this.rule('TRB值(含家庭账户)'))
+            if (!firstRule.reachedText) firstRule.reachedText = '连续3个月资产达标'
+          } else {
+            if (!activity.title) activity.title = '活动二:信用卡加码礼赠500元微信立减金'
+            if (!activity.description) activity.description = '完成以下任务赠500元微信立减金'
+            const firstRule = activity.rules[0]
+            if (!firstRule.field || firstRule.field !== '境外消费') Object.assign(firstRule, this.rule('境外消费'))
+            if (!firstRule.reachedText) firstRule.reachedText = '需完成境外任意美元有效消费'
+            if (!firstRule.consumptionTimeOperator) firstRule.consumptionTimeOperator = '小于等于'
+            if (!firstRule.consumptionDimension) firstRule.consumptionDimension = '笔数'
+            if (!firstRule.operator) firstRule.operator = '大于等于'
+            if (!firstRule.consumptionEndDate) firstRule.consumptionEndDate = '2026-12-31'
+          }
+          if (!activity.reward) activity.reward = this.reward('权益')
+          if (activity.reward.type === '积分') {
+            activity.reward.type = '权益'
+            activity.reward.planId = ''
+          }
+          if (!activity.reward.planId) activity.reward.planId = 'AGP002'
+        })
+        if (!level.crowdId) level.crowdId = 'ACD001'
+        if (!level.title || level.title === `层级${index + 1}`) level.title = `卓越层级礼·层级${index + 1}(月日均≥50万)`
+      })
+      this.form.layerCount = this.form.layerCount || this.form.achievementLayers.length || 2
+      this.syncLayerCount(this.form.layerCount)
+    },
+    handleTemplateChange() { this.normalize() },
+    addRule(list) {
+      if (list.length >= 5) return this.$message.warning('最多添加 5 个进度标签')
+      list.push(this.rule())
+    },
+    addTimeRule() {
+      if (this.form.ntbTimeRules.length >= 1) return
+      this.form.ntbTimeRules.push({ key: key('time'), field: '开户时间', operator: '介于', dateRange: [] })
+    },
+    addMgmRule() {
+      if (this.form.mgmRules.length >= 5) return this.$message.warning('最多添加 5 个进度标签')
+      this.form.mgmRules.push(this.rule('开户时间'))
+    },
+    addActivity(level) {
+      if (level.activities.length >= 5) return this.$message.warning('每个层级最多添加 5 个活动')
+      level.activities.push(this.activity(level.activities.length))
+    },
+    syncLayerCount(value) {
+      const count = Number(value || 1)
+      while (this.form.achievementLayers.length < count) this.form.achievementLayers.push(this.level(this.form.achievementLayers.length))
+      if (this.form.achievementLayers.length > count) this.form.achievementLayers.splice(count)
+      if (Number(this.activeLayer) >= count) this.activeLayer = String(count - 1)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.achievement-config { display:flex; flex-direction:column; gap:20px; padding:4px 0 12px; }
+.template-row {
+  display:flex;
+  align-items:center;
+  gap:72px;
+  padding:20px 24px 2px;
+  background:#fff;
+  border:1px solid #f0f2f5;
+  border-radius:8px;
+  box-shadow:0 1px 3px rgba(0,0,0,.05);
+}
+.config-block {
+  padding:0 24px 24px;
+  background:#fff;
+  border:1px solid #f0f2f5;
+  border-radius:8px;
+  box-shadow:0 1px 3px rgba(0,0,0,.05);
+  overflow:hidden;
+}
+.config-block h3 {
+  margin:0 -24px 24px;
+  padding:15px 24px;
+  color:#303133;
+  background:#fafafa;
+  border-bottom:1px solid #ebeef5;
+  font-size:16px;
+  font-weight:600;
+}
+.field-panel { margin-bottom:24px; padding:20px 20px 2px; background:#f7f8fa; border:1px solid #f0f2f5; border-radius:4px; }
+.copy-panel :deep(.el-form-item__content) { max-width:640px; }
+.compact-copy-panel :deep(.el-form-item__label),
+.layer-meta-fields :deep(.el-form-item__label),
+.layer-activity-fields :deep(.el-form-item__label) { white-space:nowrap; }
+.compact-copy-input { width:480px; max-width:100%; }
+.boolean-select { width:100px; }
+.mgm-referred-panel :deep(.el-form-item__label) { white-space:nowrap; }
+.mgm-referred-copy-input { width:480px; max-width:100%; }
+.mgm-referred-rule-controls { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
+.mgm-referred-field-select { width:280px; }
+.mgm-referred-operator-select { width:160px; }
+.mgm-referred-value-input { width:180px; }
+.time-rule {
+  position:relative;
+  margin:16px 0 24px;
+  padding:18px 18px 0;
+  background:#f7f8fa;
+  border:1px solid #ebeef5;
+  border-radius:4px;
+}
+.time-rule :deep(.el-form-item__label) { white-space:nowrap; }
+.time-rule-controls { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
+.time-rule-delete { position:absolute; top:16px; right:18px; }
+.compact-field-select { width:280px; }
+.compact-operator-select { width:160px; }
+.compact-date-range { width:300px; }
+.reward-section { padding-bottom:24px; }
+.layer-tabs :deep(.el-tabs__header) { margin:0 -24px 24px; padding:0 24px; background:#fff; }
+.layer-tabs :deep(.el-tabs__nav) { width:auto; }
+.layer-tabs :deep(.el-tabs__item) { min-width:120px; height:44px; padding:0 28px; text-align:center; font-weight:500; }
+.layer-activity {
+  margin-top:24px;
+  padding:20px;
+  background:#f7f8fa;
+  border:1px solid #e4e7ed;
+  border-radius:8px;
+}
+.activity-head { margin-bottom:20px; display:flex; justify-content:space-between; align-items:center; }
+.notice { margin-left:14px; color:#f56c6c; font-size:12px; font-weight:400; }
+.activity-reward { margin-top:24px; padding:0 24px 24px; background:#fff; border:1px solid #e4e7ed; border-radius:4px; overflow:hidden; }
+.achievement-config :deep(.el-button--primary) { box-shadow:0 1px 2px rgba(64,158,255,.2); }
+@media (max-width:1200px) {
+  .template-row { gap:20px; flex-wrap:wrap; }
+  .compact-field-select { width:240px; }
+  .compact-date-range { width:300px; }
+  .achievement-config :deep(.el-col) { max-width:100%; flex:0 0 100%; }
+}
+</style>

+ 86 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/activity/ActivityRewardEditor.vue

@@ -0,0 +1,86 @@
+<template>
+  <div class="reward-config" :class="{ titled: showTitle }">
+    <div v-if="showTitle" class="block-title">达标奖励配置</div>
+    <div class="reward-type-row">
+      <el-form-item label="达标发放" label-width="130px" required>
+        <el-select v-model="reward.type" style="width:200px" @change="reward.planId = ''">
+          <el-option label="积分" value="积分" />
+          <el-option label="权益" value="权益" />
+        </el-select>
+      </el-form-item>
+    </div>
+    <div class="field-panel reward-field-panel">
+      <template v-if="reward.type === '积分'">
+        <el-form-item label="选择积分科目" label-width="130px" required>
+          <el-select v-model="reward.planId" placeholder="科目编号-科目名称" class="reward-plan-select">
+            <el-option v-for="item in pointPlans" :key="item.id" :label="`${item.id}-${item.name}`" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="积分赠送数量" label-width="130px" required>
+          <el-input-number v-model="reward.amount" :min="1" :controls="false" class="point-amount-input" />
+          <span class="unit">积分</span>
+        </el-form-item>
+      </template>
+      <template v-else>
+        <el-form-item label="选择权益" label-width="130px" required>
+          <el-select v-model="reward.planId" placeholder="权益ID-权益名称" class="reward-plan-select">
+            <el-option v-for="item in benefitPlans" :key="item.id" :label="`${item.id}-${item.name}`" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="权益有效期" label-width="130px" required>
+          <el-date-picker v-model="reward.validity" class="reward-validity-picker" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" />
+        </el-form-item>
+        <el-form-item label="权益赠送次数" label-width="130px" required>
+          <div class="reward-times-controls">
+            <el-select v-model="reward.grantPeriod" class="grant-period-select">
+              <el-option label="整个周期" value="整个周期" />
+            </el-select>
+            <el-input-number v-model="reward.times" :min="1" class="times-input" />
+            <span class="unit">次</span>
+          </div>
+        </el-form-item>
+      </template>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ActivityRewardEditor',
+  props: {
+    reward: { type: Object, required: true },
+    grantPlans: { type: Array, default: () => [] },
+    showTitle: { type: Boolean, default: true }
+  },
+  computed: {
+    pointPlans() { return this.grantPlans.filter((item) => item.rewardType === '积分') },
+    benefitPlans() { return this.grantPlans.filter((item) => item.rewardType !== '积分') }
+  }
+}
+</script>
+
+<style scoped>
+.reward-config.titled { margin:0 -24px -24px; }
+.block-title {
+  margin-bottom:20px;
+  padding:15px 24px;
+  color:#303133;
+  background:#fafafa;
+  border-bottom:1px solid #ebeef5;
+  font-size:16px;
+  font-weight:600;
+}
+.reward-type-row { padding:0 4px; }
+.reward-config.titled .reward-type-row { padding:0 24px; }
+.field-panel { padding:18px 18px 0; background:#f7f8fa; border:1px solid #f0f2f5; border-radius:4px; }
+.reward-config.titled .field-panel { margin:0 24px 24px; }
+.reward-type-row :deep(.el-form-item__label),
+.reward-field-panel :deep(.el-form-item__label) { white-space:nowrap; }
+.unit { margin-left:10px; color:#606266; }
+.reward-plan-select { width:480px; max-width:100%; }
+.reward-validity-picker { width:320px; max-width:100%; flex:0 1 320px; }
+.reward-times-controls { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
+.grant-period-select { width:150px; }
+.times-input { width:110px; }
+.point-amount-input { width:220px; }
+</style>

+ 393 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/components/activity/ActivityRuleEditor.vue

@@ -0,0 +1,393 @@
+<template>
+  <div class="rule-panel" :class="{ dependent: dependent }">
+    <template v-if="mode === 'ntb'">
+      <el-button
+        v-if="removable"
+        class="ntb-delete"
+        type="danger"
+        link
+        :icon="Delete"
+        title="删除规则标签"
+        aria-label="删除规则标签"
+        @click="$emit('remove')"
+      />
+
+      <el-form-item class="ntb-main-rule" :label="`规则标签${index + 1}`" label-width="120px" required>
+        <div class="ntb-inline">
+          <el-select v-model="rule.field" class="ntb-field-select">
+            <el-option v-for="item in fieldOptions" :key="item" :label="item" :value="item" />
+          </el-select>
+          <el-select v-model="rule.operator" class="ntb-operator-select">
+            <el-option label="大于等于" value="大于等于" />
+          </el-select>
+          <el-input-number v-model="rule.value" :min="0" :controls="false" class="ntb-value-input" />
+        </div>
+      </el-form-item>
+
+      <div class="ntb-details">
+        <el-form-item label="开户当月是否必须达标" label-width="160px" required>
+          <el-select v-model="rule.openingMonthRequired" class="ntb-boolean-select">
+            <el-option label="是" :value="true" />
+            <el-option label="否" :value="false" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="是否连续达标" label-width="160px" required>
+          <div class="ntb-inline">
+            <el-select v-model="rule.continuous" class="ntb-boolean-select">
+              <el-option label="是" :value="true" />
+              <el-option label="否" :value="false" />
+            </el-select>
+            <template v-if="rule.continuous">
+              <span>连续</span>
+              <el-input-number v-model="rule.months" :min="1" :max="36" :controls="false" class="ntb-month-input" />
+              <span>个月</span>
+              <el-divider direction="vertical" />
+              <span class="ntb-required-label">是否允许断开</span>
+              <el-select v-model="rule.allowBreak" class="ntb-boolean-select">
+                <el-option label="是" :value="true" />
+                <el-option label="否" :value="false" />
+              </el-select>
+            </template>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="标签描述" label-width="160px" required>
+          <el-input v-model="rule.labelDescription" class="ntb-wide-input" />
+        </el-form-item>
+        <el-form-item label="标签未达标提示" label-width="160px" required>
+          <el-input v-model="rule.unreachedText" class="ntb-wide-input" />
+        </el-form-item>
+        <el-form-item label="标签已达标提示" label-width="160px" required>
+          <el-input v-model="rule.reachedText" class="ntb-wide-input" />
+        </el-form-item>
+        <el-form-item label="标签进度提示" label-width="160px">
+          <el-input v-model="rule.progressText" class="ntb-wide-input" placeholder="配置?弹窗提示内容" />
+        </el-form-item>
+      </div>
+    </template>
+
+    <template v-else>
+      <div class="rule-head">
+        <strong>规则标签{{ index + 1 }}</strong>
+        <el-button v-if="removable" type="danger" link :icon="Delete" @click="$emit('remove')">删除</el-button>
+      </div>
+
+      <template v-if="mode === 'layer'">
+        <el-form-item class="layer-main-rule" label="规则标签" label-width="120px" required>
+          <div class="layer-main-controls">
+            <el-select v-model="rule.field" class="layer-field-select">
+              <el-option v-for="item in fieldOptions" :key="item" :label="item" :value="item" />
+            </el-select>
+
+            <template v-if="rule.field !== '境外消费' && rule.field !== '资产大类明细'">
+              <el-select v-model="rule.operator" class="layer-operator-select">
+                <el-option label="大于等于" value="大于等于" />
+              </el-select>
+              <el-input-number v-model="rule.value" :min="0" :controls="false" class="layer-value-input" />
+            </template>
+          </div>
+        </el-form-item>
+
+        <div v-if="rule.field === '境外消费'" class="layer-overseas-fields">
+          <el-form-item label="消费时间" label-width="120px" required>
+            <div class="layer-row-controls">
+              <el-select v-model="rule.consumptionTimeOperator" class="layer-operator-select">
+                <el-option label="小于等于" value="小于等于" />
+              </el-select>
+              <el-date-picker v-model="rule.consumptionEndDate" class="layer-consumption-date" type="date" value-format="YYYY-MM-DD" placeholder="请选择消费时间" />
+            </div>
+          </el-form-item>
+          <el-form-item label="消费维度" label-width="120px" required>
+            <div class="layer-row-controls">
+              <el-select v-model="rule.consumptionDimension" class="layer-dimension-select">
+                <el-option label="笔数" value="笔数" />
+              </el-select>
+              <el-select v-model="rule.operator" class="layer-operator-select">
+                <el-option label="大于等于" value="大于等于" />
+              </el-select>
+              <el-input-number v-model="rule.value" :min="1" :controls="false" class="layer-value-input" />
+            </div>
+          </el-form-item>
+        </div>
+
+        <div v-if="rule.field === '资产大类明细'" class="layer-asset-fields">
+          <el-form-item label="是否需要对比" label-width="150px" required>
+            <el-select v-model="rule.compareRequired" class="layer-boolean-select">
+              <el-option label="是" :value="true" />
+              <el-option label="否" :value="false" />
+            </el-select>
+          </el-form-item>
+          <div class="layer-asset-row">
+            <el-form-item label="基准月" label-width="150px" required>
+              <el-date-picker v-model="rule.baseMonth" type="month" value-format="YYYYMM" placeholder="选择月份" class="layer-date-input" />
+            </el-form-item>
+            <el-form-item label="新增类别" label-width="100px" required>
+              <el-input-number v-model="rule.newCategoryCount" :min="1" class="layer-count-input" />
+              <span class="unit">类</span>
+            </el-form-item>
+          </div>
+          <el-form-item label="限制标签1达标月" label-width="150px" required>
+            <el-select v-model="rule.limitFirstRuleMonth" class="layer-boolean-select">
+              <el-option label="是" :value="true" />
+              <el-option label="否" :value="false" />
+            </el-select>
+          </el-form-item>
+        </div>
+
+        <div v-if="showContinuous" class="layer-status-row">
+          <el-form-item label="开户当月必须达标" label-width="150px" required>
+            <el-select v-model="rule.openingMonthRequired" class="layer-boolean-select">
+              <el-option label="是" :value="true" />
+              <el-option label="否" :value="false" />
+            </el-select>
+          </el-form-item>
+          <div class="layer-continuous-group">
+            <el-form-item label="连续达标" label-width="80px" required>
+              <el-select v-model="rule.continuous" class="layer-boolean-select">
+                <el-option label="是" :value="true" />
+                <el-option label="否" :value="false" />
+              </el-select>
+            </el-form-item>
+            <el-form-item v-if="rule.continuous" label="连续月份" label-width="80px" required>
+              <el-input-number v-model="rule.months" :min="1" :max="36" class="layer-month-input" />
+              <span class="unit">个月</span>
+            </el-form-item>
+            <el-form-item v-if="rule.continuous" label="允许断开" label-width="80px" required>
+              <el-select v-model="rule.allowBreak" class="layer-boolean-select">
+                <el-option label="是" :value="true" />
+                <el-option label="否" :value="false" />
+              </el-select>
+            </el-form-item>
+          </div>
+        </div>
+
+        <div class="layer-copy-fields">
+          <el-form-item label="规则标题" label-width="120px"><el-input v-model="rule.reachedText" class="layer-copy-input" /></el-form-item>
+          <el-form-item label="规则描述" label-width="120px">
+            <el-input v-model="rule.unreachedText" class="layer-copy-input" :placeholder="rule.field === '境外消费' ? '配置?弹窗提示内容' : ''" />
+          </el-form-item>
+          <el-form-item v-if="rule.field !== '境外消费' && rule.field !== '资产大类明细'" label="标签进度提示" label-width="120px"><el-input v-model="rule.progressText" class="layer-copy-input" placeholder="配置问号弹窗提示内容" /></el-form-item>
+        </div>
+      </template>
+
+      <template v-else-if="mode === 'mgm' && rule.field === 'TRB值(含家庭账户)'">
+        <el-form-item class="layer-main-rule mgm-trb-main-rule" label="规则标签" label-width="120px" required>
+          <div class="layer-main-controls">
+            <el-select v-model="rule.field" class="layer-field-select">
+              <el-option v-for="item in fieldOptions" :key="item" :label="item" :value="item" />
+            </el-select>
+            <el-select v-model="rule.operator" class="layer-operator-select">
+              <el-option label="大于等于" value="大于等于" />
+            </el-select>
+            <el-input-number v-model="rule.value" :min="0" :controls="false" class="layer-value-input" />
+          </div>
+        </el-form-item>
+
+        <div class="mgm-trb-status">
+          <el-form-item label="是否连续达标" label-width="120px" required>
+            <el-select v-model="rule.continuous" class="layer-boolean-select">
+              <el-option label="是" :value="true" />
+              <el-option label="否" :value="false" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="开户后" label-width="80px" required>
+            <el-input-number v-model="rule.afterOpeningMonths" :min="1" class="mgm-month-input" />
+            <span class="unit">个月内</span>
+          </el-form-item>
+          <el-form-item label="达标任意" label-width="90px" required>
+            <el-input-number v-model="rule.requiredMonths" :min="1" class="mgm-month-input" />
+            <span class="unit">个月</span>
+          </el-form-item>
+        </div>
+
+        <div class="layer-copy-fields mgm-copy-fields">
+          <el-form-item label="已达标提示话术" label-width="120px" required>
+            <el-input v-model="rule.reachedText" class="layer-copy-input" />
+          </el-form-item>
+          <el-form-item label="待达标提示话术" label-width="120px" required>
+            <el-input v-model="rule.unreachedText" class="layer-copy-input" />
+          </el-form-item>
+          <el-form-item label="不达标原因" label-width="120px" required>
+            <el-input v-model="rule.failureReason" class="layer-copy-input" />
+          </el-form-item>
+        </div>
+      </template>
+
+      <template v-else-if="mode === 'mgm' && rule.field === '开户时间'">
+        <el-form-item class="layer-main-rule mgm-opening-main-rule" label="规则标签" label-width="120px" required>
+          <div class="layer-main-controls">
+            <el-select v-model="rule.field" class="layer-field-select">
+              <el-option v-for="item in fieldOptions" :key="item" :label="item" :value="item" />
+            </el-select>
+            <el-select v-model="rule.operator" class="layer-operator-select">
+              <el-option label="介于" value="介于" />
+            </el-select>
+            <el-date-picker
+              v-model="rule.dateRange"
+              class="mgm-date-range"
+              type="daterange"
+              value-format="YYYY-MM-DD"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+            />
+          </div>
+        </el-form-item>
+
+        <div class="layer-copy-fields mgm-copy-fields">
+          <el-form-item label="已达标提示话术" label-width="120px" required>
+            <el-input v-model="rule.reachedText" class="layer-copy-input" />
+          </el-form-item>
+          <el-form-item label="待达标提示话术" label-width="120px" required>
+            <el-input v-model="rule.unreachedText" class="layer-copy-input" />
+          </el-form-item>
+          <el-form-item label="不达标原因" label-width="120px" required>
+            <el-input v-model="rule.failureReason" class="layer-copy-input" />
+          </el-form-item>
+        </div>
+      </template>
+
+      <template v-else>
+        <el-row :gutter="16">
+          <el-col :span="8">
+            <el-form-item :label="`规则标签${index + 1}`" required>
+              <el-select v-model="rule.field" style="width:100%">
+                <el-option v-for="item in fieldOptions" :key="item" :label="item" :value="item" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <template v-if="rule.field === '开户时间'">
+            <el-col :span="6"><el-form-item label="判断条件" required><el-select v-model="rule.operator" style="width:100%"><el-option label="介于" value="介于" /></el-select></el-form-item></el-col>
+            <el-col :span="10"><el-form-item label="日期范围" required><el-date-picker v-model="rule.dateRange" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" style="width:100%" /></el-form-item></el-col>
+          </template>
+
+          <template v-else>
+            <el-col :span="6"><el-form-item label="判断条件" required><el-select v-model="rule.operator" style="width:100%"><el-option label="大于等于" value="大于等于" /></el-select></el-form-item></el-col>
+            <el-col :span="10"><el-form-item label="规则值" required><el-input-number v-model="rule.value" :min="0" :controls="false" style="width:100%" /></el-form-item></el-col>
+          </template>
+        </el-row>
+
+        <el-row v-if="mode === 'mgm' && rule.field !== '开户时间'" :gutter="16">
+          <el-col :span="6"><el-form-item label="是否连续达标" required><el-select v-model="rule.continuous" class="mgm-boolean-select"><el-option label="是" :value="true" /><el-option label="否" :value="false" /></el-select></el-form-item></el-col>
+          <el-col :span="9"><el-form-item label="开户后" required><el-input-number v-model="rule.afterOpeningMonths" :min="1" /><span class="unit">个月内</span></el-form-item></el-col>
+          <el-col :span="9"><el-form-item label="达标任意" required><el-input-number v-model="rule.requiredMonths" :min="1" /><span class="unit">个月</span></el-form-item></el-col>
+        </el-row>
+
+        <el-row :gutter="16">
+          <el-col :span="12"><el-form-item :label="reachedLabel" required><el-input v-model="rule.reachedText" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item :label="unreachedLabel" required><el-input v-model="rule.unreachedText" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="不达标原因" required><el-input v-model="rule.failureReason" /></el-form-item></el-col>
+        </el-row>
+      </template>
+    </template>
+  </div>
+</template>
+
+<script>
+import { Delete } from '@element-plus/icons-vue'
+
+export default {
+  name: 'ActivityRuleEditor',
+  emits: ['remove'],
+  props: {
+    rule: { type: Object, required: true },
+    index: { type: Number, required: true },
+    mode: { type: String, required: true },
+    removable: Boolean,
+    dependent: Boolean
+  },
+  data() { return { Delete } },
+  computed: {
+    fieldOptions() {
+      if (this.mode === 'mgm') return ['开户时间', 'TRB值(含家庭账户)']
+      if (this.mode === 'layer') return ['TRB值(含家庭账户)', '资产大类明细', '境外消费']
+      return ['TRB值(含家庭账户)']
+    },
+    showContinuous() {
+      return this.rule.field === 'TRB值(含家庭账户)' && this.mode !== 'mgm'
+    },
+    reachedLabel() { return this.mode === 'mgm' ? '已达标提示话术' : this.mode === 'layer' ? '规则标题' : '标签已达标提示' },
+    unreachedLabel() { return this.mode === 'mgm' ? '待达标提示话术' : this.mode === 'layer' ? '规则描述' : '标签未达标提示' }
+  }
+}
+</script>
+
+<style scoped>
+.rule-panel {
+  position:relative;
+  margin-top:16px;
+  padding:18px 18px 2px;
+  background:#f7f8fa;
+  border:1px solid #e4e7ed;
+  border-radius:4px;
+}
+.rule-panel.dependent { background:#fff1f1; border-color:#f7c6c7; }
+.rule-panel :deep(.el-form-item__label) { white-space:nowrap; }
+.rule-head { margin-bottom:14px; display:flex; justify-content:space-between; align-items:center; }
+.unit { margin-left:8px; color:#606266; }
+.ntb-delete { position:absolute; top:12px; right:14px; z-index:1; font-size:18px; }
+.ntb-main-rule { margin-right:48px; }
+.ntb-details { margin-left:100px; }
+.ntb-inline { display:flex; align-items:center; gap:14px; flex-wrap:wrap; color:#303133; }
+.ntb-field-select { width:240px; }
+.ntb-operator-select { width:160px; }
+.ntb-value-input { width:160px; }
+.ntb-boolean-select { width:120px; }
+.ntb-month-input { width:80px; }
+.ntb-wide-input { width:640px; max-width:100%; }
+.ntb-required-label::before { margin-right:8px; color:#f56c6c; content:'*'; }
+.ntb-inline :deep(.el-divider--vertical) { height:42px; margin:0 4px 0 0; }
+.layer-main-rule { margin-bottom:16px; }
+.layer-main-controls { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
+.layer-field-select { width:280px; }
+.layer-operator-select { width:160px; }
+.layer-value-input { width:180px; }
+.layer-date-input { width:180px; }
+.layer-count-input { width:120px; }
+.layer-boolean-select { width:100px; }
+.layer-overseas-fields { max-width:700px; }
+.layer-row-controls { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
+.layer-consumption-date { width:220px; }
+.layer-dimension-select { width:120px; }
+.layer-asset-fields { max-width:640px; }
+.layer-asset-row { display:flex; align-items:flex-start; gap:20px; flex-wrap:wrap; }
+.layer-status-row {
+  display:block;
+}
+.layer-status-row :deep(.el-form-item),
+.layer-asset-row :deep(.el-form-item) { margin-right:0; }
+.layer-continuous-group {
+  display:flex;
+  align-items:flex-start;
+  gap:18px;
+  flex-wrap:wrap;
+  margin-left:70px;
+}
+.layer-month-input { width:128px; }
+.layer-copy-fields { max-width:620px; }
+.layer-copy-input { width:480px; max-width:100%; }
+.mgm-boolean-select { width:120px; }
+.mgm-trb-status {
+  display:flex;
+  align-items:flex-start;
+  gap:18px;
+  flex-wrap:wrap;
+}
+.mgm-trb-status :deep(.el-form-item) { margin-right:0; }
+.mgm-trb-main-rule :deep(.el-form-item__label),
+.mgm-opening-main-rule :deep(.el-form-item__label),
+.mgm-trb-status :deep(.el-form-item__label),
+.mgm-copy-fields :deep(.el-form-item__label) { white-space:nowrap; }
+.mgm-month-input { width:128px; }
+.mgm-date-range { width:300px; }
+
+@media (max-width:1200px) {
+  .ntb-details { margin-left:0; }
+  .ntb-main-rule { margin-right:36px; }
+  .ntb-field-select { width:220px; }
+  .layer-asset-row { gap:12px; }
+  .layer-continuous-group { gap:12px; margin-left:0; }
+  .mgm-trb-status { gap:12px; }
+}
+</style>

+ 84 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/router/index.js

@@ -48,6 +48,90 @@ const routes = [
         component: () => import('@/views/CreditCardEquity.vue'),
         meta: { title: '信用卡权益' }
       },
+      {
+        path: 'customization/hsbc/activities',
+        name: 'ActivityManagement',
+        component: () => import('@/views/ActivityManagement.vue'),
+        meta: { title: '活动管理' }
+      },
+      {
+        path: 'customization/hsbc/activities/add',
+        name: 'ActivityAdd',
+        component: () => import('@/views/ActivityEdit.vue'),
+        meta: { title: '新建活动' }
+      },
+      {
+        path: 'customization/hsbc/activities/edit/:id',
+        name: 'ActivityEdit',
+        component: () => import('@/views/ActivityEdit.vue'),
+        meta: { title: '编辑活动' }
+      },
+      {
+        path: 'customization/hsbc/activities/view/:id',
+        name: 'ActivityView',
+        component: () => import('@/views/ActivityView.vue'),
+        meta: { title: '查看活动' }
+      },
+      {
+        path: 'customization/hsbc/activity-forms',
+        name: 'ActivityFormList',
+        component: () => import('@/views/ActivityConfigList.vue'),
+        meta: { title: '报名表单管理', configType: 'forms' }
+      },
+      {
+        path: 'customization/hsbc/activity-crowds',
+        name: 'ActivityCrowdManagement',
+        component: () => import('@/views/ActivityCrowdManagement.vue'),
+        meta: { title: '客群管理' }
+      },
+      {
+        path: 'customization/hsbc/activity-progress-templates',
+        name: 'ActivityProgressTemplate',
+        component: () => import('@/views/ActivityProgressTemplate.vue'),
+        meta: { title: '活动进度模板管理' }
+      },
+      {
+        path: 'customization/hsbc/activity-progress-rules',
+        name: 'ActivityProgressRuleList',
+        component: () => import('@/views/ActivityConfigList.vue'),
+        meta: { title: '达标规则管理', configType: 'progress' }
+      },
+      {
+        path: 'customization/hsbc/activity-grant-plans',
+        name: 'ActivityGrantPlan',
+        component: () => import('@/views/ActivityGrantPlan.vue'),
+        meta: { title: '权益发放计划管理' }
+      },
+      {
+        path: 'customization/hsbc/activity-mutex-groups',
+        name: 'ActivityMutexGroupList',
+        component: () => import('@/views/ActivityMutexGroup.vue'),
+        meta: { title: '活动互斥组管理' }
+      },
+      {
+        path: 'customization/hsbc/activity-launches',
+        name: 'ActivityLaunchList',
+        component: () => import('@/views/ActivityConfigList.vue'),
+        meta: { title: '活动投放管理', configType: 'launches' }
+      },
+      {
+        path: 'customization/hsbc/activity-notifications',
+        name: 'ActivityNotificationList',
+        component: () => import('@/views/ActivityConfigList.vue'),
+        meta: { title: '通知模板管理', configType: 'notifications' }
+      },
+      {
+        path: 'customization/hsbc/activity-report',
+        name: 'ActivityReport',
+        component: () => import('@/views/ActivityReport.vue'),
+        meta: { title: '活动报表/进度查询' }
+      },
+      {
+        path: 'customization/hsbc/credit-card-activity-entry',
+        name: 'CreditCardActivityEntry',
+        component: () => import('@/views/CreditCardActivityEntry.vue'),
+        meta: { title: '信用卡活动入口配置' }
+      },
       {
         path: 'customization/hsbc/crowd-tasks',
         name: 'CrowdTaskList',

+ 540 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/store/activityStore.js

@@ -0,0 +1,540 @@
+import { reactive } from 'vue'
+
+const pad = (n) => String(n).padStart(2, '0')
+
+function nowText() {
+  const d = new Date()
+  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
+}
+
+function clone(value) {
+  return JSON.parse(JSON.stringify(value))
+}
+
+function emptyActivity() {
+  return {
+    id: '',
+    internalTitle: '',
+    name: '',
+    category: '普通活动',
+    status: 'draft',
+    auditStatus: '未提交',
+    auditor: '',
+    target: '借记卡新客',
+    shareTitle: '',
+    shareImage: '',
+    cities: [],
+    description: '',
+    scrmActivityCode: '',
+    scrmChannelCode: '',
+    labels: [],
+    thumbnail: '',
+    detailImage: '',
+    displayTitle: '',
+    displaySubtitle: '',
+    buttonText: '立即报名',
+    detailButtonText: '查看活动详情',
+    signupTime: [],
+    activityTime: [],
+    displayTime: [],
+    location: '',
+    content: '',
+    showOnHome: true,
+    showOnAll: true,
+    showOnAssembly: false,
+    showOnCarousel: false,
+    assemblyPageIds: [],
+    visibleCrowds: ['全部客户'],
+    signupCrowds: ['全部客户'],
+    visibleCrowdId: '',
+    visibleCrowdIds: [],
+    signupCrowdId: '',
+    signupCrowdIds: [],
+    signupFormId: '',
+    showSignupCount: true,
+    sharable: true,
+    showOnMine: true,
+    achieveCrowds: ['全部客户'],
+    sortNo: 1,
+    visibleStartTime: '',
+    visibleEndTime: '',
+    needSignup: true,
+    needConfirm: true,
+    confirmText: '请确认您已了解活动规则,报名成功后将进入达标进度跟踪。',
+    signupSuccessTitle: '',
+    signupSuccessDescription: '',
+    quotaEnabled: true,
+    quota: 3000,
+    signedCount: 628,
+    needExtraInfo: true,
+    signupGuideEnabled: false,
+    extraForms: [
+      { name: '', placeholder: '', type: 'text', required: true, maxLength: 30 }
+    ],
+    extraInfoLink: '',
+    progressEnabled: true,
+    achievementTemplate: 'NTB',
+    layerCount: 2,
+    progressCopy: {
+      unreached: '您已报名本活动,请继续保持账户状态',
+      reached: '您已达标活动规则,可点击下方按钮查看奖励',
+      help: ''
+    },
+    ntbTimeRules: [],
+    ntbRules: [],
+    mgmRules: [],
+    templateRewards: {
+      NTB: { type: '积分', planId: 'AGP001', amount: 3200000, validity: [], grantPeriod: '整个周期', times: 1 },
+      MGM: { type: '积分', planId: 'AGP001', amount: 3200000, validity: [], grantPeriod: '整个周期', times: 1 }
+    },
+    achievementRules: [
+      {
+        key: 'default-rule-1',
+        field: 'TRB值(含家庭账户)',
+        operator: '大于等于',
+        value: 500000,
+        dateRange: [],
+        openingMonthRequired: true,
+        continuous: true,
+        months: 3,
+        allowBreak: true,
+        reachedText: '已满50万',
+        unreachedText: '未满50万',
+        progressText: '',
+        failureReason: ''
+      }
+    ],
+    mgmAchievedTag: '',
+    mgmReferredProgress: {
+      labelOne: '开户时间',
+      labelTwo: '基础资产达标|月内日均/家庭日月均人均余额≥50万',
+      advancedReward: true,
+      advancedRuleField: 'TRB值(含家庭账户)',
+      advancedOperator: '大于等于',
+      advancedValue: 6000000,
+      advancedRuleText: '是否完成尊尚升级'
+    },
+    achievementLayers: [],
+    rewardType: '积分',
+    rewardValidity: [],
+    rewardGrantTimes: 1,
+    progressTemplateId: '',
+    progressRuleId: '',
+    progressStyle: '多层级任务',
+    levels: [
+      {
+        name: '基础达标',
+        tasks: [
+          { tag: '开户', text: '完成汇丰借记卡开户', help: 'T+1 更新开户结果' },
+          { tag: '资产', text: '活动期内月末 TRB 达到 50 万元', help: '按月末批量数据计算' }
+        ]
+      }
+    ],
+    continuousRule: { enabled: false, x: 3, y: 6, z: 2 },
+    reward: {
+      name: '达标积分奖励',
+      type: '积分',
+      grantMode: '达标后自动发放',
+      buttonText: '查看积分奖励',
+      linkTarget: '积分明细'
+    },
+    rewardEnabled: true,
+    grantPlanId: '',
+    mutexEnabled: true,
+    mutexGroup: 'NTB新客互斥组',
+    mutexActivities: ['酒店新客礼遇'],
+    ntb: true,
+    mgm: false,
+    blockText: '您已参与同组新客活动,暂无法报名本活动。',
+    releaseRule: '活动结束且未达标释放',
+    smsEnabled: false,
+    smsTemplate: '',
+    signupNoticeTemplateId: '',
+    beforeStartTemplateId: '',
+    rewardTemplateId: '',
+    ownerStaffId: '',
+    beforeStartSmsEnabled: false,
+    rewardSmsEnabled: false,
+    launchChannels: ['权益首页', '活动列表'],
+    launchUrl: 'https://demo.hsbc.com.cn/activity',
+    creditCardEntry: false,
+    creator: '当前用户',
+    createdAt: nowText(),
+    updatedAt: nowText()
+  }
+}
+
+const state = reactive({
+  activities: [
+    {
+      ...emptyActivity(),
+      id: 'ACT202606001',
+      internalTitle: '2026借记卡新客开户达标活动',
+      name: '新客开户达标礼',
+      status: 'published',
+      auditStatus: '已发布',
+      activityTime: ['2026-07-01', '2026-09-30'],
+      signupTime: ['2026-06-25', '2026-09-15'],
+      signedCount: 628,
+      quota: 3000,
+      visibleCrowdId: 'ACD001',
+      signupCrowdId: 'ACD001',
+      signupFormId: 'FORM001',
+      progressTemplateId: 'TPL001',
+      progressRuleId: 'PROG001',
+      grantPlanId: 'AGP001',
+      auditor: '王审核',
+      launchChannels: ['权益首页', '全部活动', '渠道二维码']
+    },
+    {
+      ...emptyActivity(),
+      id: 'ACT202606002',
+      internalTitle: '酒店合作方新客礼遇活动',
+      name: '酒店新客礼遇',
+      status: 'draft',
+      auditStatus: '未提交',
+      target: '潜客/新客',
+      signupTime: ['2026-07-05', '2026-08-31'],
+      activityTime: ['2026-07-05', '2026-10-31'],
+      signedCount: 1200,
+      quota: 1200,
+      visibleCrowdId: 'ACD001',
+      signupCrowdId: 'ACD001',
+      signupFormId: 'FORM002',
+      progressTemplateId: 'TPL002',
+      progressRuleId: 'PROG001',
+      grantPlanId: 'AGP001',
+      extraFields: [
+        { name: '酒店会员号', placeholder: '请输入酒店会员号', type: 'text', required: true, maxLength: 30, link: '' }
+      ],
+      reward: {
+        name: '酒店权益券',
+        type: '第三方权益',
+        grantMode: '达标后跳转领取',
+        buttonText: '查看奖励',
+        linkTarget: '第三方权益页'
+      }
+    },
+    {
+      ...emptyActivity(),
+      id: 'ACT202606003',
+      internalTitle: 'MGM亲友推荐互斥演示活动',
+      name: 'MGM 亲友推荐活动',
+      category: 'MGM活动',
+      status: 'offline',
+      auditStatus: '已下线',
+      target: '推荐人与被推荐人',
+      needExtraInfo: false,
+      visibleCrowdId: 'ACD002',
+      signupCrowdId: 'ACD002',
+      progressTemplateId: 'TPL003',
+      progressRuleId: 'PROG002',
+      grantPlanId: 'AGP002',
+      auditor: '王审核',
+      mgm: true,
+      ntb: true,
+      mutexGroup: 'NTB新客互斥组',
+      activityTime: ['2026-05-01', '2026-06-15'],
+      signupTime: ['2026-05-01', '2026-06-10'],
+      progressStyle: 'MGM推荐进度',
+      reward: {
+        name: '推荐立减金',
+        type: '立减金',
+        grantMode: '达标后自动发放',
+        buttonText: '前往领取',
+        linkTarget: '立减金列表'
+      }
+    }
+  ],
+  forms: [
+    { id: 'FORM001', name: '航空会员号表单', fields: '航空会员号', status: '启用', usedBy: 1, updatedAt: nowText() },
+    { id: 'FORM002', name: '酒店会员号表单', fields: '酒店会员号', status: '启用', usedBy: 1, updatedAt: nowText() }
+  ],
+  progressRules: [
+    { id: 'PROG001', name: '新客开户多层级规则', levels: 2, tasks: 4, status: '启用', updatedAt: nowText() },
+    { id: 'PROG002', name: 'MGM推荐进度规则', levels: 1, tasks: 3, status: '启用', updatedAt: nowText() }
+  ],
+  progressTemplates: [
+    {
+      id: 'TPL001',
+      name: '多条件多次发奖模板',
+      templateType: '多条件多次发奖',
+      statusTexts: '未达标/已达标/已发奖/无法继续参与',
+      levels: '达标50万、达标100万',
+      rewardMode: '每层级独立发奖',
+      continuousEnabled: true,
+      continuousRule: '观察12个月,连续8个月达标',
+      mgmView: '否',
+      status: '启用',
+      updatedAt: nowText()
+    },
+    {
+      id: 'TPL002',
+      name: '多条件单次发奖模板',
+      templateType: '多条件单次发奖',
+      statusTexts: '未达标/已达标/已发奖',
+      levels: '开户、资产、消费',
+      rewardMode: '全部条件满足后发一次奖',
+      continuousEnabled: false,
+      continuousRule: '-',
+      mgmView: '否',
+      status: '启用',
+      updatedAt: nowText()
+    },
+    {
+      id: 'TPL003',
+      name: 'MGM专属推荐进度模板',
+      templateType: 'MGM专属',
+      statusTexts: '被推荐人未达标/已达标/互斥失败',
+      levels: '推荐关系、开户、达标',
+      rewardMode: '推荐人与被推荐人分别展示',
+      continuousEnabled: false,
+      continuousRule: '-',
+      mgmView: '是',
+      status: '启用',
+      updatedAt: nowText()
+    }
+  ],
+  mutexGroups: [
+    {
+      id: 'MTX001',
+      name: 'NTB新客互斥组',
+      mutexType: '互斥报名',
+      businessType: 'NTB/MGM',
+      scopeType: '指定活动',
+      activities: '新客开户达标礼、酒店新客礼遇、MGM 亲友推荐活动',
+      judgeBasis: ['报名记录', 'MGM标签', '开户时间', '达标状态'],
+      realtimeBlock: true,
+      tPlusOneFix: true,
+      releaseRule: '活动结束且未达标释放',
+      listStatusText: '不符合参与条件',
+      detailStatusText: '您已参与同组新客活动,暂无法报名本活动。',
+      blockText: '您已参与同组新客活动,暂无法报名本活动。',
+      conflictDisplayRule: '命中后其他活动展示不符合条件',
+      correctionText: '次日标签更新后,如已参加 MGM 或已开户,则当前活动变为无法继续参与。',
+      remark: '用于新客活动之间的实时互斥拦截和 T+1 数据修正。',
+      status: '启用',
+      updatedAt: nowText()
+    }
+  ],
+  launches: [
+    { id: 'LCH001', activityName: '新客开户达标礼', channel: '权益首页', material: '首页活动卡片', status: '已投放', updatedAt: nowText() },
+    { id: 'LCH002', activityName: '酒店新客礼遇', channel: '渠道二维码', material: '合作方落地页', status: '待投放', updatedAt: nowText() }
+  ],
+  notifications: [
+    {
+      id: 'NTF001',
+      name: '报名成功通知',
+      scene: '报名成功',
+      channel: '短信',
+      template: '【汇丰】您已成功报名${activityName},请在活动期内完成达标任务。',
+      variables: 'activityName',
+      status: '启用',
+      updatedAt: nowText()
+    },
+    {
+      id: 'NTF002',
+      name: '达标发奖通知',
+      scene: '达标发奖',
+      channel: '短信',
+      template: '【汇丰】您参与的${activityName}已达标,奖励${rewardName}已发放。',
+      variables: 'activityName,rewardName',
+      status: '启用',
+      updatedAt: nowText()
+    }
+  ],
+  activityCrowds: [
+    {
+      id: 'ACD001',
+      name: '借记卡新客可见客群',
+      crowdType: '可见客群',
+      source: '标签规则',
+      rule: '开户时间晚于2026-01-01,且未参与同组NTB活动',
+      memberCount: 35800,
+      usedBy: '新客开户达标礼',
+      status: '启用',
+      updatedAt: nowText()
+    },
+    {
+      id: 'ACD002',
+      name: 'MGM推荐报名客群',
+      crowdType: '报名客群',
+      source: '指定名单',
+      rule: '推荐人白名单 + 被推荐人潜客标签',
+      memberCount: 12600,
+      usedBy: 'MGM 亲友推荐活动',
+      status: '启用',
+      updatedAt: nowText()
+    }
+  ],
+  activityGrantPlans: [
+    {
+      id: 'AGP001',
+      name: '新客开户积分发放计划',
+      rewardType: '积分',
+      rewardName: '达标积分奖励',
+      grantMode: '达标后自动发放',
+      perCustomerLimit: 1,
+      stock: 5000,
+      usedBy: '新客开户达标礼',
+      status: '启用',
+      updatedAt: nowText()
+    },
+    {
+      id: 'AGP002',
+      name: '推荐立减金发放计划',
+      rewardType: '立减金',
+      rewardName: '推荐立减金',
+      grantMode: '达标后跳转领取',
+      perCustomerLimit: 1,
+      stock: 2000,
+      usedBy: 'MGM 亲友推荐活动',
+      status: '启用',
+      updatedAt: nowText()
+    }
+  ],
+  notificationRecords: [
+    {
+      id: 'ANR001',
+      activityName: '新客开户达标礼',
+      scene: '报名成功',
+      templateName: '报名成功通知',
+      receiverCount: 628,
+      successCount: 626,
+      failCount: 2,
+      sendTime: nowText(),
+      status: '发送完成'
+    },
+    {
+      id: 'ANR002',
+      activityName: 'MGM 亲友推荐活动',
+      scene: '达标发奖',
+      templateName: '达标发奖通知',
+      receiverCount: 86,
+      successCount: 86,
+      failCount: 0,
+      sendTime: nowText(),
+      status: '发送完成'
+    }
+  ],
+  creditCardEntry: {
+    visibleCrowdIds: [],
+    icon: '',
+    name: '信用卡活动专区',
+    tip: '温馨提示:如需查看信用卡活动进度,请点击上方“信用卡活动专区”。',
+    link: 'https://demo.hsbc.com.cn/credit-card/activity',
+    enableCreditCardTab: false,
+    updatedAt: nowText()
+  }
+})
+
+let seq = state.activities.length + 1
+
+function genActivityId() {
+  const d = new Date()
+  return `ACT${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}${pad(seq++)}`
+}
+
+function saveSimple(listName, payload) {
+  const list = state[listName]
+  const idPrefix = { forms: 'FORM', progressRules: 'PROG', progressTemplates: 'TPL', mutexGroups: 'MTX', launches: 'LCH', notifications: 'NTF', activityCrowds: 'ACD', activityGrantPlans: 'AGP', notificationRecords: 'ANR' }[listName] || 'CFG'
+  if (payload.id) {
+    const target = list.find((item) => item.id === payload.id)
+    if (target) Object.assign(target, payload, { updatedAt: nowText() })
+    return target
+  }
+  const record = { ...payload, id: `${idPrefix}${pad(list.length + 1)}`, status: payload.status || '启用', updatedAt: nowText() }
+  list.unshift(record)
+  return record
+}
+
+export default {
+  emptyActivity,
+  get activities() {
+    return state.activities
+  },
+  get forms() {
+    return state.forms
+  },
+  get progressRules() {
+    return state.progressRules
+  },
+  get progressTemplates() {
+    return state.progressTemplates
+  },
+  get mutexGroups() {
+    return state.mutexGroups
+  },
+  get launches() {
+    return state.launches
+  },
+  get notifications() {
+    return state.notifications
+  },
+  get activityCrowds() {
+    return state.activityCrowds
+  },
+  get activityGrantPlans() {
+    return state.activityGrantPlans
+  },
+  get notificationRecords() {
+    return state.notificationRecords
+  },
+  get creditCardEntry() {
+    return state.creditCardEntry
+  },
+  queryActivities(filters = {}) {
+    return state.activities.filter((item) => {
+      if (filters.name && !item.name.includes(filters.name)) return false
+      if (filters.id && !item.id.includes(filters.id)) return false
+      if (filters.status && item.status !== filters.status) return false
+      if (filters.category && item.category !== filters.category) return false
+      return true
+    })
+  },
+  getActivity(id) {
+    return state.activities.find((item) => item.id === id)
+  },
+  saveActivity(payload) {
+    if (payload.id) {
+      const target = state.activities.find((item) => item.id === payload.id)
+      if (!target) return null
+      Object.assign(target, clone(payload), { updatedAt: nowText() })
+      return target
+    }
+    const record = { ...clone(payload), id: genActivityId(), status: 'draft', auditStatus: '未提交', creator: '当前用户', createdAt: nowText(), updatedAt: nowText() }
+    state.activities.unshift(record)
+    return record
+  },
+  copyActivity(id) {
+    const source = this.getActivity(id)
+    if (!source) return null
+    const record = { ...clone(source), id: genActivityId(), name: `${source.name}-副本`, status: 'draft', auditStatus: '未提交', createdAt: nowText(), updatedAt: nowText() }
+    state.activities.unshift(record)
+    return record
+  },
+  publishActivity(id) {
+    const target = this.getActivity(id)
+    if (!target) return null
+    target.status = 'published'
+    target.auditStatus = '已发布'
+    target.updatedAt = nowText()
+    return target
+  },
+  offlineActivity(id) {
+    const target = this.getActivity(id)
+    if (!target) return null
+    target.status = 'offline'
+    target.auditStatus = '已下线'
+    target.updatedAt = nowText()
+    return target
+  },
+  removeActivity(id) {
+    const idx = state.activities.findIndex((item) => item.id === id)
+    if (idx >= 0) state.activities.splice(idx, 1)
+  },
+  saveSimple,
+  updateCreditCardEntry(payload) {
+    Object.assign(state.creditCardEntry, payload, { updatedAt: nowText() })
+  }
+}

+ 254 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityConfigList.vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="activity-config-list">
+    <el-card>
+      <template #header>
+        <div class="header">
+          <div>
+            <div class="title">{{ config.title }}</div>
+            <div class="desc">{{ config.desc }}</div>
+          </div>
+          <el-button type="primary" @click="openDialog()">
+            <el-icon><Plus /></el-icon>{{ config.addText }}
+          </el-button>
+        </div>
+      </template>
+
+      <el-table :data="rows" border stripe>
+        <el-table-column v-for="col in config.columns" :key="col.prop" :prop="col.prop" :label="col.label" :min-width="col.width || 140" show-overflow-tooltip>
+          <template #default="{ row }">
+            <span v-if="col.type === 'array'">{{ arrayText(row[col.prop]) }}</span>
+            <span v-else-if="col.type === 'bool'">{{ row[col.prop] ? '是' : '否' }}</span>
+            <span v-else>{{ row[col.prop] || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="isActive(row.status) ? 'success' : 'info'">{{ row.status || '启用' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="updatedAt" label="更新时间" width="170" />
+        <el-table-column label="操作" width="170" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="openDialog(row)">编辑</el-button>
+            <el-button type="primary" link @click="toggleStatus(row)">{{ isActive(row.status) ? '停用' : '启用' }}</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="editing.id ? `编辑${config.shortTitle}` : `新增${config.shortTitle}`" width="820px">
+      <el-form :model="editing" label-width="130px">
+        <el-row :gutter="16">
+          <el-col v-for="field in config.fields" :key="field.prop" :span="field.span || 12">
+            <el-form-item :label="field.label" :required="field.required !== false">
+              <el-input v-if="!field.type || field.type === 'text'" v-model="editing[field.prop]" :placeholder="field.placeholder" />
+              <el-input v-else-if="field.type === 'textarea'" v-model="editing[field.prop]" type="textarea" :rows="field.rows || 3" :placeholder="field.placeholder" />
+              <el-input-number v-else-if="field.type === 'number'" v-model="editing[field.prop]" :min="field.min || 0" :max="field.max || 999999" style="width: 100%" />
+              <el-switch v-else-if="field.type === 'switch'" v-model="editing[field.prop]" />
+              <el-select v-else-if="field.type === 'select'" v-model="editing[field.prop]" style="width: 100%" :multiple="field.multiple" :allow-create="field.allowCreate" filterable default-first-option>
+                <el-option v-for="option in field.options" :key="option" :label="option" :value="option" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="save">保存</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { ElMessage } from 'element-plus'
+import activityStore from '@/store/activityStore'
+
+const configs = {
+  forms: {
+    title: '报名表单管理',
+    shortTitle: '报名表单',
+    desc: '配置报名时收集的补充信息,活动中最多引用两个字段。',
+    addText: '新增表单',
+    listName: 'forms',
+    activeStatus: '启用',
+    inactiveStatus: '停用',
+    defaults: { status: '启用', fieldType: '文本', requiredFlag: true, maxLength: 30, jumpLink: '', usedBy: 0 },
+    columns: [
+      { prop: 'id', label: '表单编号', width: 120 },
+      { prop: 'name', label: '表单名称' },
+      { prop: 'fields', label: '字段名称' },
+      { prop: 'fieldType', label: '字段类型', width: 100 },
+      { prop: 'requiredFlag', label: '必填', width: 80, type: 'bool' },
+      { prop: 'maxLength', label: '最大长度', width: 90 },
+      { prop: 'jumpLink', label: '第三方跳转链接' },
+      { prop: 'usedBy', label: '引用活动数', width: 110 }
+    ],
+    fields: [
+      { prop: 'name', label: '表单名称' },
+      { prop: 'fields', label: '字段名称' },
+      { prop: 'placeholder', label: '提示文案' },
+      { prop: 'fieldType', label: '字段类型', type: 'select', options: ['文本', '数字', '手机号', '会员号'] },
+      { prop: 'requiredFlag', label: '是否必填', type: 'switch' },
+      { prop: 'maxLength', label: '最大长度', type: 'number', min: 1, max: 100 },
+      { prop: 'jumpLink', label: '第三方跳转链接', span: 24, required: false },
+      { prop: 'status', label: '状态', type: 'select', options: ['启用', '停用'] }
+    ]
+  },
+  progress: {
+    title: '达标规则管理',
+    shortTitle: '达标规则',
+    desc: '配置进度页样式、达标层级、任务条件、标签和连续达标规则。',
+    addText: '新增规则',
+    listName: 'progressRules',
+    activeStatus: '启用',
+    inactiveStatus: '停用',
+    defaults: { status: '启用', style: '多层级任务', levels: 1, tasks: 1, taskTags: [], copywriting: '', helpText: '', continuousEnabled: false, continuousX: 3, continuousY: 6, continuousZ: 2 },
+    columns: [
+      { prop: 'id', label: '规则编号', width: 120 },
+      { prop: 'name', label: '规则名称' },
+      { prop: 'style', label: '进度样式' },
+      { prop: 'levels', label: '层级数', width: 90 },
+      { prop: 'tasks', label: '任务数', width: 90 },
+      { prop: 'taskTags', label: '任务标签', type: 'array' },
+      { prop: 'continuousEnabled', label: '连续达标', type: 'bool', width: 90 }
+    ],
+    fields: [
+      { prop: 'name', label: '规则名称' },
+      { prop: 'style', label: '进度样式', type: 'select', options: ['多层级任务', '连续达标月份', 'MGM推荐进度'] },
+      { prop: 'levels', label: '层级数', type: 'number', min: 1, max: 5 },
+      { prop: 'tasks', label: '任务数', type: 'number', min: 1, max: 5 },
+      { prop: 'taskTags', label: '任务标签', type: 'select', multiple: true, allowCreate: true, options: ['开户', '资产', '消费', '推荐', 'MGM'] },
+      { prop: 'copywriting', label: '对客文案', type: 'textarea', span: 24 },
+      { prop: 'helpText', label: '问号说明', type: 'textarea', span: 24, required: false },
+      { prop: 'continuousEnabled', label: '连续达标', type: 'switch' },
+      { prop: 'continuousX', label: '观察周期X', type: 'number', min: 1, max: 24 },
+      { prop: 'continuousY', label: '达标月份Y', type: 'number', min: 1, max: 24 },
+      { prop: 'continuousZ', label: '发奖次数Z', type: 'number', min: 1, max: 24 },
+      { prop: 'status', label: '状态', type: 'select', options: ['启用', '停用'] }
+    ]
+  },
+  launches: {
+    title: '活动投放管理',
+    shortTitle: '投放配置',
+    desc: '配置渠道码、链接、二维码、MobileX/权益首页版位和合作方落地页。',
+    addText: '新增投放',
+    listName: 'launches',
+    activeStatus: '已投放',
+    inactiveStatus: '待投放',
+    defaults: { status: '待投放', channelCode: '', link: '', qrCode: true, placement: '', landingPage: '' },
+    columns: [
+      { prop: 'id', label: '投放编号', width: 120 },
+      { prop: 'activityName', label: '活动名称' },
+      { prop: 'channel', label: '投放渠道' },
+      { prop: 'channelCode', label: '渠道码' },
+      { prop: 'material', label: '投放物料' },
+      { prop: 'placement', label: '版位' },
+      { prop: 'link', label: '链接' }
+    ],
+    fields: [
+      { prop: 'activityName', label: '活动名称' },
+      { prop: 'channel', label: '投放渠道', type: 'select', options: ['权益首页', '全部活动', '渠道二维码', 'MobileX', '合作方链接'] },
+      { prop: 'channelCode', label: '渠道码' },
+      { prop: 'material', label: '投放物料' },
+      { prop: 'qrCode', label: '生成二维码', type: 'switch' },
+      { prop: 'placement', label: '版位' },
+      { prop: 'link', label: '活动链接', span: 24 },
+      { prop: 'landingPage', label: '合作方落地页', span: 24, required: false },
+      { prop: 'status', label: '状态', type: 'select', options: ['待投放', '已投放'] }
+    ]
+  },
+  notifications: {
+    title: '活动通知模板管理',
+    shortTitle: '通知模板',
+    desc: '配置报名成功、活动开始前、达标发奖等短信模板和变量。',
+    addText: '新增模板',
+    listName: 'notifications',
+    activeStatus: '启用',
+    inactiveStatus: '停用',
+    defaults: { status: '启用', channel: '短信', scene: '报名成功', variables: 'activityName' },
+    columns: [
+      { prop: 'id', label: '模板编号', width: 120 },
+      { prop: 'name', label: '模板名称' },
+      { prop: 'scene', label: '触发场景' },
+      { prop: 'channel', label: '通知渠道' },
+      { prop: 'variables', label: '变量' },
+      { prop: 'template', label: '模板内容' }
+    ],
+    fields: [
+      { prop: 'name', label: '模板名称' },
+      { prop: 'scene', label: '触发场景', type: 'select', options: ['报名成功', '活动开始前', '达标发奖', '补充信息提醒'] },
+      { prop: 'channel', label: '通知渠道', type: 'select', options: ['短信', '站内信', 'Push'] },
+      { prop: 'variables', label: '变量' },
+      { prop: 'template', label: '模板内容', type: 'textarea', span: 24, rows: 4 },
+      { prop: 'status', label: '状态', type: 'select', options: ['启用', '停用'] }
+    ]
+  }
+}
+
+export default {
+  name: 'ActivityConfigList',
+  data() {
+    return {
+      dialogVisible: false,
+      editing: {}
+    }
+  },
+  computed: {
+    type() {
+      return this.$route.meta.configType
+    },
+    config() {
+      return configs[this.type]
+    },
+    rows() {
+      return activityStore[this.config.listName] || []
+    }
+  },
+  methods: {
+    arrayText(value) {
+      return Array.isArray(value) ? value.join('、') : value || '-'
+    },
+    isActive(status) {
+      return status === this.config.activeStatus || (!status && this.config.activeStatus === '启用')
+    },
+    openDialog(row) {
+      this.editing = row ? { ...this.config.defaults, ...JSON.parse(JSON.stringify(row)) } : { ...this.config.defaults }
+      this.dialogVisible = true
+    },
+    save() {
+      const required = this.config.fields.find((field) => field.required !== false)
+      if (required && !this.editing[required.prop]) {
+        ElMessage.warning(`${required.label}必填`)
+        return
+      }
+      if (this.type === 'forms' && Number(this.editing.maxLength) <= 0) {
+        ElMessage.warning('最大长度必须大于 0')
+        return
+      }
+      if (this.type === 'progress' && Number(this.editing.tasks) > 5) {
+        ElMessage.warning('每层任务最多 5 个')
+        return
+      }
+      if (this.type === 'launches' && this.editing.status === '已投放' && !this.editing.link) {
+        ElMessage.warning('已投放状态下活动链接必填')
+        return
+      }
+      activityStore.saveSimple(this.config.listName, this.editing)
+      ElMessage.success('保存成功')
+      this.dialogVisible = false
+    },
+    toggleStatus(row) {
+      row.status = this.isActive(row.status) ? this.config.inactiveStatus : this.config.activeStatus
+      activityStore.saveSimple(this.config.listName, row)
+      ElMessage.success('状态已更新')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.header { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
+.title { font-size: 16px; font-weight: 600; color: #303133; }
+.desc { margin-top: 4px; font-size: 13px; color: #909399; }
+</style>

+ 26 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityConfigOverview.vue

@@ -0,0 +1,26 @@
+<template>
+  <el-card>
+    <template #header><div class="title">活动配置总览</div></template>
+    <el-table :data="rows" border stripe>
+      <el-table-column prop="id" label="活动编号" width="130" />
+      <el-table-column prop="name" label="活动名称" min-width="180" />
+      <el-table-column label="客群" min-width="160"><template #default="{ row }">{{ text(row.visibleCrowds) }} / {{ text(row.signupCrowds) }}</template></el-table-column>
+      <el-table-column label="报名表单" min-width="140"><template #default="{ row }">{{ row.needExtraInfo ? `${row.extraForms?.length || 0}个表单` : '无' }}</template></el-table-column>
+      <el-table-column label="达标规则" min-width="150"><template #default="{ row }">{{ row.progressEnabled ? `${row.progressStyle}` : '无' }}</template></el-table-column>
+      <el-table-column label="互斥组" min-width="150"><template #default="{ row }">{{ row.mutexEnabled ? row.mutexGroup : '无' }}</template></el-table-column>
+      <el-table-column label="发放计划" min-width="160"><template #default="{ row }">{{ row.reward?.grantPlan || row.reward?.name || '-' }}</template></el-table-column>
+      <el-table-column label="通知" min-width="120"><template #default="{ row }">{{ [row.smsEnabled && '报名', row.beforeStartSmsEnabled && '开始前', row.rewardSmsEnabled && '发奖'].filter(Boolean).join('、') || '无' }}</template></el-table-column>
+      <el-table-column label="投放" min-width="170"><template #default="{ row }">{{ text(row.launchChannels) }}</template></el-table-column>
+      <el-table-column prop="updatedAt" label="更新时间" width="170" />
+    </el-table>
+  </el-card>
+</template>
+<script>
+import activityStore from '@/store/activityStore'
+export default {
+  name: 'ActivityConfigOverview',
+  computed: { rows() { return activityStore.activities } },
+  methods: { text(v) { return Array.isArray(v) ? v.join('、') : v || '-' } }
+}
+</script>
+<style scoped>.title{font-weight:600}</style>

+ 80 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityCrowdManagement.vue

@@ -0,0 +1,80 @@
+<template>
+  <div>
+    <el-card>
+      <template #header>
+        <div class="header">
+          <div><div class="title">活动客群管理</div><div class="desc">配置展示客群、报名客群、达标客群,供活动创建时引用。</div></div>
+          <el-button type="primary" @click="openDialog()"><el-icon><Plus /></el-icon>新增客群</el-button>
+        </div>
+      </template>
+      <el-table :data="rows" border stripe>
+        <el-table-column prop="id" label="客群编号" width="120" />
+        <el-table-column prop="name" label="客群名称" min-width="180" />
+        <el-table-column prop="crowdType" label="客群类型" width="120" />
+        <el-table-column prop="source" label="来源" width="120" />
+        <el-table-column prop="rule" label="规则说明" min-width="260" show-overflow-tooltip />
+        <el-table-column prop="memberCount" label="客户数" width="100" />
+        <el-table-column prop="usedBy" label="引用活动" min-width="160" show-overflow-tooltip />
+        <el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="row.status === '启用' ? 'success' : 'info'">{{ row.status }}</el-tag></template></el-table-column>
+        <el-table-column prop="updatedAt" label="更新时间" width="170" />
+        <el-table-column label="操作" width="150" fixed="right"><template #default="{ row }"><el-button type="primary" link @click="openDialog(row)">编辑</el-button><el-button type="primary" link @click="toggle(row)">{{ row.status === '启用' ? '停用' : '启用' }}</el-button></template></el-table-column>
+      </el-table>
+    </el-card>
+    <el-dialog v-model="visible" :title="form.id ? '编辑活动客群' : '新增活动客群'" width="720px">
+      <el-form :model="form" label-width="110px">
+        <el-form-item label="客群名称" required><el-input v-model="form.name" /></el-form-item>
+        <el-form-item label="客群类型" required><el-select v-model="form.crowdType" style="width:100%"><el-option label="可见客群" value="可见客群" /><el-option label="报名客群" value="报名客群" /><el-option label="达标客群" value="达标客群" /></el-select></el-form-item>
+        <el-form-item label="来源" required><el-select v-model="form.source" style="width:100%"><el-option label="标签规则" value="标签规则" /><el-option label="指定名单" value="指定名单" /><el-option label="外部导入" value="外部导入" /></el-select></el-form-item>
+        <el-form-item label="规则说明" required><el-input v-model="form.rule" type="textarea" :rows="3" /></el-form-item>
+        <el-form-item label="客户数"><el-input-number v-model="form.memberCount" :min="0" /></el-form-item>
+        <el-form-item label="引用活动"><el-input v-model="form.usedBy" /></el-form-item>
+        <el-form-item label="名额控制">
+          <el-input-number v-model="form.quota" :min="0" placeholder="不填表示不限" />
+        </el-form-item>
+        <el-form-item label="报名成功引导语">
+          <el-switch v-model="form.enableSignupGuide" />
+        </el-form-item>
+        <template v-if="form.enableSignupGuide">
+          <el-form-item label="报名成功页引导语配置" class="signup-guide-block">
+            <div class="signup-guide-card">
+              <el-form-item label="标题" :label-width="'80px'" required>
+                <el-input v-model="form.signupGuideTitle" placeholder="请输入标题" />
+              </el-form-item>
+              <el-form-item label="描述" :label-width="'80px'">
+                <el-input v-model="form.signupGuideDesc" type="textarea" :rows="3" placeholder="请输入描述" />
+              </el-form-item>
+            </div>
+          </el-form-item>
+        </template>
+        <el-form-item label="状态"><el-select v-model="form.status" style="width:100%"><el-option label="启用" value="启用" /><el-option label="停用" value="停用" /></el-select></el-form-item>
+      </el-form>
+      <template #footer><el-button @click="visible=false">取消</el-button><el-button type="primary" @click="save">保存</el-button></template>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { ElMessage } from 'element-plus'
+import activityStore from '@/store/activityStore'
+export default {
+  name: 'ActivityCrowdManagement',
+  data() { return { visible: false, form: {} } },
+  computed: { rows() { return activityStore.activityCrowds } },
+  methods: {
+    openDialog(row) { this.form = row ? { ...row } : { crowdType: '可见客群', source: '标签规则', memberCount: 0, status: '启用', quota: 0, enableSignupGuide: false, signupGuideTitle: '', signupGuideDesc: '' }; this.visible = true },
+    save() {
+      if (!this.form.name || !this.form.rule) return ElMessage.warning('客群名称和规则说明必填');
+      if (this.form.enableSignupGuide && !this.form.signupGuideTitle) return ElMessage.warning('开启报名成功引导语时标题必填');
+      activityStore.saveSimple('activityCrowds', this.form);
+      this.visible = false;
+      ElMessage.success('保存成功');
+    },
+    toggle(row) { row.status = row.status === '启用' ? '停用' : '启用'; activityStore.saveSimple('activityCrowds', row) }
+  }
+}
+</script>
+<style scoped>
+.header { display:flex; justify-content:space-between; align-items:center; gap:16px; }
+.title { font-size:16px; font-weight:600; }
+.desc { margin-top:4px; color:#909399; font-size:13px; }
+
+</style>

+ 767 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityEdit.vue

@@ -0,0 +1,767 @@
+<template>
+  <div class="activity-edit">
+    <el-card>
+      <template #header><div class="page-header"><span>{{ isEdit ? '编辑活动' : '新建活动' }}</span><el-button @click="goBack">返回</el-button></div></template>
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="150px">
+        <el-steps :active="activeStep" simple class="activity-steps">
+          <el-step v-for="item in stepItems" :key="item.title" :title="item.title">
+            <template #icon><el-icon><component :is="item.icon" /></el-icon></template>
+          </el-step>
+        </el-steps>
+
+        <div v-show="activeStep === 0" class="step-pane">
+          <el-card shadow="never" class="section-card">
+            <template #header><div class="section-title">基础信息</div></template>
+            <el-row :gutter="20">
+              <el-col :span="12"><el-form-item label="活动标题(对内)" prop="internalTitle"><el-input v-model="form.internalTitle" maxlength="50" show-word-limit /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="活动名称(对客)" prop="name"><el-input v-model="form.name" maxlength="50" show-word-limit /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="微信分享名称"><el-input v-model="form.shareTitle" placeholder="为空默认取活动名称" /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="活动类型" prop="category"><el-select v-model="form.category" style="width:100%"><el-option label="普通活动" value="普通活动" /><el-option label="卓越迎新礼" value="卓越迎新礼" /><el-option label="层层礼" value="层层礼" /><el-option label="MGM" value="MGM" /><el-option label="信用卡活动" value="信用卡活动" /></el-select></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="活动城市"><el-select v-model="form.cities" multiple allow-create filterable default-first-option style="width:100%"><el-option label="全国" value="全国" /><el-option label="上海" value="上海" /><el-option label="北京" value="北京" /><el-option label="广州" value="广州" /></el-select></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="活动地点"><el-input v-model="form.location" placeholder="线下活动填写" /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="活动小图" prop="thumbnail"><el-input v-model="form.thumbnail" placeholder="列表卡片图片 URL" /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="活动头图" prop="detailImage"><el-input v-model="form.detailImage" placeholder="详情页头图 URL" /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="分享图片"><el-input v-model="form.shareImage" placeholder="为空默认取小图或头图" /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="是否可转发"><el-switch v-model="form.sharable" /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="SCRM activity code"><el-input v-model="form.scrmActivityCode" /></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="SCRM channel code"><el-input v-model="form.scrmChannelCode" /></el-form-item></el-col>
+              <el-col :span="24"><el-form-item label="Label"><el-select v-model="form.labels" multiple allow-create filterable default-first-option style="width:100%"><el-option label="interest-新客" value="interest-新客" /><el-option label="interest-MGM" value="interest-MGM" /></el-select></el-form-item></el-col>
+              <el-col :span="24"><el-form-item label="活动描述"><el-input v-model="form.description" type="textarea" :rows="2" /></el-form-item></el-col>
+              <el-col :span="24"><el-form-item label="活动详情" prop="content"><el-input v-model="form.content" type="textarea" :rows="5" /></el-form-item></el-col>
+            </el-row>
+          </el-card>
+        </div>
+
+        <div v-show="activeStep === 1" class="step-pane">
+          <el-card shadow="never" class="section-card">
+            <template #header><div class="section-title">展示规则设置</div></template>
+            <el-row :gutter="20">
+              <el-col :span="24">
+                <el-form-item label="活动时间" prop="activityTime">
+                  <el-date-picker v-model="form.activityTime" type="daterange" value-format="YYYY-MM-DD" class="date-range-field" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="展示时间">
+                  <el-date-picker v-model="form.displayTime" type="daterange" value-format="YYYY-MM-DD" class="date-range-field" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="展示客群">
+                  <el-select
+                    v-model="form.visibleCrowdIds"
+                    multiple
+                    collapse-tags
+                    collapse-tags-tooltip
+                    clearable
+                    placeholder="请选择展示客群,不选代表全部用户展示"
+                    class="field-md"
+                  >
+                    <el-option v-for="item in crowds" :key="item.id" :label="item.name" :value="item.id" />
+                  </el-select>
+                  <div class="form-tip">不选代表全部用户展示(全部展示不包括潜客)</div>
+                </el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="权益首页">
+                  <el-radio-group v-model="form.showOnHome">
+                    <el-radio :value="true">显示</el-radio>
+                    <el-radio :value="false">不显示</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="组装页">
+                  <el-radio-group v-model="form.showOnAssembly">
+                    <el-radio :value="true">显示</el-radio>
+                    <el-radio :value="false">不显示</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+                <div v-if="form.showOnAssembly" class="assembly-select-panel">
+                  <el-select
+                    v-model="form.assemblyPageIds"
+                    multiple
+                    collapse-tags
+                    collapse-tags-tooltip
+                    clearable
+                    placeholder="请选择组装页"
+                    class="field-md"
+                  >
+                    <el-option v-for="item in assemblyPages" :key="item.id" :label="`${item.name}(${item.id})`" :value="item.id" />
+                  </el-select>
+                </div>
+              </el-col>
+              <el-col :span="24">
+                <el-form-item label="活动轮播图">
+                  <el-radio-group v-model="form.showOnCarousel">
+                    <el-radio :value="true">显示</el-radio>
+                    <el-radio :value="false">不显示</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-card>
+        </div>
+
+        <div v-show="activeStep === 2" class="step-pane">
+          <el-card shadow="never" class="section-card">
+            <template #header><div class="section-title">客群与报名</div></template>
+            <el-row :gutter="16" class="signup-row">
+              <el-col :span="24">
+                <el-form-item label="是否需要报名"><el-switch v-model="form.needSignup" /></el-form-item>
+              </el-col>
+              <template v-if="form.needSignup">
+                <el-col :span="24">
+                  <el-form-item label="报名客群">
+                    <el-select
+                      v-model="form.signupCrowdIds"
+                      multiple
+                      collapse-tags
+                      collapse-tags-tooltip
+                      clearable
+                      placeholder="请选择报名客群,数据来自人群管理"
+                      class="field-md"
+                    >
+                      <el-option v-for="item in crowds" :key="item.id" :label="item.name" :value="item.id" />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="24">
+                  <el-form-item label="报名时间" required>
+                    <el-date-picker v-model="form.signupTime" type="daterange" value-format="YYYY-MM-DD" class="date-range-field" />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="24">
+                  <el-form-item label="报名确认"><el-switch v-model="form.needConfirm" /></el-form-item>
+                </el-col>
+                <el-col v-if="form.needConfirm" :span="24">
+                  <el-form-item label="报名确认文案">
+                    <el-input v-model="form.confirmText" placeholder="请输入报名确认文案" class="field-lg" />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="24">
+                  <el-form-item label="补充信息"><el-switch v-model="form.needExtraInfo" @change="handleNeedExtraInfoChange" /></el-form-item>
+                </el-col>
+                <el-col v-if="form.needExtraInfo" :span="24">
+                  <div class="extra-forms-header">
+                    <div>
+                      <span class="extra-forms-title">补充信息配置</span>
+                      <span class="extra-forms-limit">最多配置 2 项</span>
+                    </div>
+                    <el-button v-if="form.extraForms.length < 2" type="primary" link :icon="Plus" @click="addExtraForm">添加补充信息</el-button>
+                  </div>
+                  <div v-for="(extra, extraIndex) in form.extraForms" :key="extraIndex" class="extra-form-panel">
+                    <div class="extra-form-head">
+                      <span>补充信息{{ extraIndex + 1 }}</span>
+                      <el-button v-if="form.extraForms.length > 1" type="danger" link :icon="Delete" @click="removeExtraForm(extraIndex)">删除补充信息</el-button>
+                    </div>
+                    <el-row :gutter="16">
+                      <el-col :span="12"><el-form-item label="字段名称" :prop="`extraForms.${extraIndex}.name`" required><el-input v-model="extra.name" class="field-md" /></el-form-item></el-col>
+                      <el-col :span="12"><el-form-item label="提示文案" :prop="`extraForms.${extraIndex}.placeholder`" required><el-input v-model="extra.placeholder" class="field-md" /></el-form-item></el-col>
+                      <el-col :span="12"><el-form-item label="字段类型" :prop="`extraForms.${extraIndex}.type`" required>
+                        <el-select v-model="extra.type" class="field-md"><el-option label="文本" value="text" /><el-option label="数字" value="number" /></el-select>
+                      </el-form-item></el-col>
+                      <el-col :span="6"><el-form-item label="是否必填" :prop="`extraForms.${extraIndex}.required`" required><el-switch v-model="extra.required" /></el-form-item></el-col>
+                      <el-col :span="6"><el-form-item label="最大长度" :prop="`extraForms.${extraIndex}.maxLength`" required><el-input-number v-model="extra.maxLength" :min="1" :max="500" :controls="true" class="field-xs" /></el-form-item></el-col>
+                    </el-row>
+                  </div>
+                  <div class="extra-link-panel">
+                    <el-form-item label="第三方跳转链接" label-width="140px">
+                      <el-input v-model="form.extraInfoLink" placeholder="例如:https://example.com/third-party" class="field-lg" />
+                    </el-form-item>
+                  </div>
+                </el-col>
+                <el-col :span="24">
+                  <el-form-item label="名额控制"><el-switch v-model="form.quotaEnabled" /></el-form-item>
+                </el-col>
+                <template v-if="form.quotaEnabled">
+                  <el-col :span="12"><el-form-item label="报名人数上限"><el-input-number v-model="form.quota" :min="0" class="field-md" /></el-form-item></el-col>
+                  <el-col :span="12"><el-form-item label="展示报名人数"><el-switch v-model="form.showSignupCount" /></el-form-item></el-col>
+                  <el-col v-if="isEdit" :span="24"><el-form-item label="已报名人数"><el-input-number v-model="form.signedCount" :min="0" disabled class="field-md" /></el-form-item></el-col>
+                </template>
+                <el-col :span="24">
+                  <el-form-item label="报名成功引导语"><el-switch v-model="form.signupGuideEnabled" /></el-form-item>
+                </el-col>
+                <el-col v-if="form.signupGuideEnabled" :span="24">
+                  <div class="signup-success-panel">
+                    <div class="signup-success-title">报名成功页引导语配置</div>
+                    <el-form-item label="标题" label-width="140px" required>
+                      <el-input
+                        v-model="form.signupSuccessTitle"
+                        maxlength="30"
+                        show-word-limit
+                        placeholder="请输入移动端报名成功页标题"
+                        class="field-md"
+                      />
+                    </el-form-item>
+                    <el-form-item label="描述" label-width="140px">
+                      <el-input
+                        v-model="form.signupSuccessDescription"
+                        type="textarea"
+                        :rows="3"
+                        maxlength="200"
+                      show-word-limit
+                      placeholder="请输入移动端报名成功页描述"
+                    />
+                  </el-form-item>
+                </div>
+              </el-col>
+              </template>
+            </el-row>
+          </el-card>
+        </div>
+
+        <div v-show="activeStep === 3" class="step-pane">
+          <el-card shadow="never" class="section-card">
+            <template #header><div class="section-title">进度与奖励</div></template>
+            <ActivityAchievementConfig :form="form" :crowds="crowds" :grant-plans="grantPlans" />
+            <div v-if="false" class="progress-config">
+              <div class="config-section template-section">
+                <div class="section-body">
+                  <el-row :gutter="20" align="middle">
+                    <el-col :span="8">
+                      <el-form-item label="进度与奖励" class="compact-item">
+                        <el-switch v-model="form.progressEnabled" />
+                      </el-form-item>
+                    </el-col>
+                    <el-col v-if="form.progressEnabled" :span="10">
+                      <el-form-item label="活动模板" required class="compact-item">
+                        <el-select v-model="form.achievementTemplate" placeholder="请选择" style="width:100%" @change="handleAchievementTemplateChange">
+                          <el-option label="NTB(新客达标)" value="NTB" />
+                          <el-option label="MGM(推荐有礼)" value="MGM" />
+                          <el-option label="层层礼" value="LAYER" />
+                        </el-select>
+                      </el-form-item>
+                    </el-col>
+                    <el-col v-if="form.progressEnabled && form.achievementTemplate === 'LAYER'" :span="6">
+                      <el-form-item label="层级数量" required class="compact-item">
+                        <el-input-number v-model="form.layerCount" :min="1" :max="5" @change="syncLayerCount" />
+                      </el-form-item>
+                    </el-col>
+                  </el-row>
+                </div>
+              </div>
+
+              <el-empty v-if="!form.progressEnabled || !form.achievementTemplate" description="当前未配置达标进度与奖励" :image-size="72" />
+
+              <template v-else>
+                <div v-if="form.achievementTemplate === 'NTB'" class="config-section">
+                  <div class="section-header">达标进度提示话术配置</div>
+                  <div class="section-body narrow-form">
+                    <el-form-item label="未达标提示"><el-input v-model="form.progressCopy.unreached" maxlength="100" show-word-limit /></el-form-item>
+                    <el-form-item label="已达标提示"><el-input v-model="form.progressCopy.reached" maxlength="100" show-word-limit /></el-form-item>
+                    <el-form-item label="达标进度提示"><el-input v-model="form.progressCopy.help" maxlength="100" show-word-limit placeholder="配置问号弹窗提示内容" /></el-form-item>
+                  </div>
+                </div>
+
+                <div class="config-section">
+                  <div class="section-header">
+                    <span>{{ form.achievementTemplate === 'LAYER' ? '达标层级配置' : '达标规则标签配置' }}</span>
+                    <el-button v-if="form.achievementTemplate !== 'LAYER'" type="primary" :icon="Plus" @click="addAchievementRule">添加进度标签</el-button>
+                  </div>
+                  <div class="section-body">
+                    <template v-if="form.achievementTemplate === 'MGM'">
+                      <el-form-item label="已达标展示标签" required class="limit-field">
+                        <el-select v-model="form.mgmAchievedTag" placeholder="请选择">
+                          <el-option label="客户MSC Group" value="客户MSC Group" />
+                        </el-select>
+                      </el-form-item>
+                    </template>
+
+                    <template v-if="form.achievementTemplate !== 'LAYER'">
+                      <div v-for="(rule, index) in form.achievementRules" :key="rule.key" class="rule-block">
+                        <div class="rule-title">
+                          <strong>规则标签{{ index + 1 }}</strong>
+                          <el-button v-if="form.achievementRules.length > 1" type="danger" link :icon="Delete" @click="removeAchievementRule(index)">删除</el-button>
+                        </div>
+                        <el-row :gutter="16">
+                          <el-col :span="8">
+                            <el-form-item label="规则标签" required>
+                              <el-select v-model="rule.field" placeholder="请选择" style="width:100%">
+                                <el-option v-for="item in ruleFieldOptions" :key="item" :label="item" :value="item" />
+                              </el-select>
+                            </el-form-item>
+                          </el-col>
+                          <el-col :span="6">
+                            <el-form-item label="判断条件" required>
+                              <el-select v-model="rule.operator" placeholder="请选择" style="width:100%">
+                                <el-option v-for="item in operatorOptions" :key="item" :label="item" :value="item" />
+                              </el-select>
+                            </el-form-item>
+                          </el-col>
+                          <el-col :span="10">
+                            <el-form-item label="规则值" required>
+                              <el-date-picker v-if="rule.field === '开户时间' && rule.operator === '介于'" v-model="rule.dateRange" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" style="width:100%" />
+                              <el-input-number v-else v-model="rule.value" :min="0" :controls="false" style="width:100%" />
+                            </el-form-item>
+                          </el-col>
+                        </el-row>
+                        <el-row v-if="rule.field !== '开户时间'" :gutter="16">
+                          <el-col :span="8"><el-form-item label="开户当月必须达标"><el-switch v-model="rule.openingMonthRequired" /></el-form-item></el-col>
+                          <el-col :span="8"><el-form-item label="连续达标"><el-switch v-model="rule.continuous" /></el-form-item></el-col>
+                          <el-col v-if="rule.continuous" :span="8"><el-form-item label="连续月份"><el-input-number v-model="rule.months" :min="1" :max="36" /></el-form-item></el-col>
+                          <el-col v-if="rule.continuous" :span="8"><el-form-item label="允许断开"><el-switch v-model="rule.allowBreak" /></el-form-item></el-col>
+                        </el-row>
+                        <el-row :gutter="16">
+                          <el-col :span="12"><el-form-item :label="form.achievementTemplate === 'MGM' ? '已达标提示话术' : '标签描述'" required><el-input v-model="rule.reachedText" /></el-form-item></el-col>
+                          <el-col :span="12"><el-form-item :label="form.achievementTemplate === 'MGM' ? '待达标提示话术' : '标签未达标提示'" required><el-input v-model="rule.unreachedText" /></el-form-item></el-col>
+                          <el-col v-if="form.achievementTemplate === 'MGM'" :span="12"><el-form-item label="不达标原因" required><el-input v-model="rule.failureReason" /></el-form-item></el-col>
+                          <el-col v-else :span="12"><el-form-item label="标签进度提示"><el-input v-model="rule.progressText" placeholder="配置问号弹窗提示内容" /></el-form-item></el-col>
+                        </el-row>
+                      </div>
+                    </template>
+
+                    <template v-else>
+                      <el-tabs v-model="layerActiveTab" class="layer-tabs">
+                        <el-tab-pane v-for="(level, levelIndex) in form.achievementLayers" :key="level.key" :label="`层级${levelIndex + 1}`" :name="String(levelIndex)">
+                          <div class="level-panel">
+                            <el-row :gutter="16">
+                              <el-col :span="12"><el-form-item label="可参与人群"><el-select v-model="level.crowdId" clearable style="width:100%"><el-option v-for="item in crowds" :key="item.id" :label="`${item.id}-${item.name}`" :value="item.id" /></el-select></el-form-item></el-col>
+                              <el-col :span="12"><el-form-item label="层级标题"><el-input v-model="level.title" /></el-form-item></el-col>
+                            </el-row>
+                            <div class="subsection-actions">
+                              <el-button type="primary" :icon="Plus" @click="addLayerActivity(level)">添加活动</el-button>
+                            </div>
+
+                            <div v-for="(activity, activityIndex) in level.activities" :key="activity.key" class="layer-activity">
+                              <div class="activity-heading">
+                                <div>
+                                  <strong>活动 {{ activityIndex + 1 }}</strong>
+                                  <span v-if="activityIndex > 0" class="prerequisite-note">*注意:活动{{ activityIndex + 1 }}达标前提为活动{{ activityIndex }}</span>
+                                </div>
+                                <el-button v-if="level.activities.length > 1" type="danger" link :icon="Delete" @click="removeLayerActivity(level, activityIndex)">删除活动</el-button>
+                              </div>
+                              <el-row :gutter="16">
+                                <el-col :span="12"><el-form-item label="活动标题"><el-input v-model="activity.title" /></el-form-item></el-col>
+                                <el-col :span="12"><el-form-item label="活动描述"><el-input v-model="activity.description" /></el-form-item></el-col>
+                              </el-row>
+                              <div class="subsection-actions rule-actions"><el-button type="primary" plain :icon="Plus" @click="addLayerRule(activity)">添加进度标签</el-button></div>
+                              <div v-for="(rule, ruleIndex) in activity.rules" :key="rule.key" class="rule-block inner-rule" :class="{ 'dependent-rule': activityIndex > 0 }">
+                                <div class="rule-title"><strong>规则标签{{ ruleIndex + 1 }}</strong><el-button v-if="activity.rules.length > 1" type="danger" link :icon="Delete" @click="activity.rules.splice(ruleIndex, 1)">删除</el-button></div>
+                                <el-row :gutter="16">
+                                  <el-col :span="8"><el-form-item label="规则标签" required><el-select v-model="rule.field" style="width:100%"><el-option v-for="item in ruleFieldOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item></el-col>
+                                  <template v-if="rule.field === '境外消费'">
+                                    <el-col :span="8"><el-form-item label="消费时间" required><el-date-picker v-model="rule.consumptionEndDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择日期" style="width:100%" /></el-form-item></el-col>
+                                    <el-col :span="8"><el-form-item label="消费维度" required><el-select v-model="rule.consumptionDimension" style="width:100%"><el-option label="笔数" value="笔数" /></el-select></el-form-item></el-col>
+                                    <el-col :span="8"><el-form-item label="判断条件" required><el-select v-model="rule.operator" style="width:100%"><el-option label="大于等于" value="大于等于" /></el-select></el-form-item></el-col>
+                                    <el-col :span="8"><el-form-item label="消费笔数" required><el-input-number v-model="rule.value" :min="1" style="width:100%" /></el-form-item></el-col>
+                                  </template>
+                                  <template v-else>
+                                    <el-col :span="6"><el-form-item label="判断条件" required><el-select v-model="rule.operator" style="width:100%"><el-option v-for="item in operatorOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item></el-col>
+                                    <el-col :span="10"><el-form-item label="规则值" required>
+                                      <el-date-picker v-if="rule.field === '开户时间' && rule.operator === '介于'" v-model="rule.dateRange" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" style="width:100%" />
+                                      <el-input-number v-else v-model="rule.value" :min="0" :controls="false" style="width:100%" />
+                                    </el-form-item></el-col>
+                                    <el-col v-if="rule.field !== '开户时间'" :span="8"><el-form-item label="开户当月必须达标"><el-switch v-model="rule.openingMonthRequired" /></el-form-item></el-col>
+                                    <el-col v-if="rule.field !== '开户时间'" :span="8"><el-form-item label="连续达标"><el-switch v-model="rule.continuous" /></el-form-item></el-col>
+                                    <el-col v-if="rule.field !== '开户时间' && rule.continuous" :span="8"><el-form-item label="连续月份"><el-input-number v-model="rule.months" :min="1" :max="36" /></el-form-item></el-col>
+                                    <el-col v-if="rule.field !== '开户时间' && rule.continuous" :span="8"><el-form-item label="允许断开"><el-switch v-model="rule.allowBreak" /></el-form-item></el-col>
+                                  </template>
+                                  <el-col :span="12"><el-form-item label="规则标题"><el-input v-model="rule.reachedText" /></el-form-item></el-col>
+                                  <el-col :span="12"><el-form-item label="规则描述"><el-input v-model="rule.unreachedText" /></el-form-item></el-col>
+                                  <el-col :span="12"><el-form-item label="标签进度提示"><el-input v-model="rule.progressText" placeholder="配置问号弹窗提示内容" /></el-form-item></el-col>
+                                </el-row>
+                              </div>
+                              <div class="inline-reward">
+                                <el-form-item label="达标发放计划" required>
+                                  <el-select v-model="activity.grantPlanId" placeholder="请选择" style="width:100%">
+                                    <el-option v-for="item in grantPlans" :key="item.id" :label="`${item.id}-${item.name}`" :value="item.id" />
+                                  </el-select>
+                                </el-form-item>
+                              </div>
+                            </div>
+                          </div>
+                        </el-tab-pane>
+                      </el-tabs>
+                    </template>
+                  </div>
+                </div>
+
+                <div v-if="form.achievementTemplate === 'MGM'" class="config-section">
+                  <div class="section-header">被推荐进度配置</div>
+                  <div class="section-body narrow-form">
+                    <el-form-item label="规则标签1描述" required><el-input v-model="form.mgmReferredProgress.labelOne" /></el-form-item>
+                    <el-form-item label="规则标签2描述" required><el-input v-model="form.mgmReferredProgress.labelTwo" /></el-form-item>
+                    <el-form-item label="是否有进阶奖励" required><el-switch v-model="form.mgmReferredProgress.advancedReward" /></el-form-item>
+                    <el-form-item v-if="form.mgmReferredProgress.advancedReward" label="进阶奖励规则标签" required>
+                      <el-select v-model="form.mgmReferredProgress.advancedRuleField" style="width:320px"><el-option label="TRB值(含家庭账户)" value="TRB值(含家庭账户)" /></el-select>
+                    </el-form-item>
+                    <el-form-item v-if="form.mgmReferredProgress.advancedReward" label="进阶奖励规则描述" required><el-input v-model="form.mgmReferredProgress.advancedRuleText" /></el-form-item>
+                  </div>
+                </div>
+
+                <div v-if="form.achievementTemplate !== 'LAYER'" class="config-section">
+                  <div class="section-header">达标奖励配置</div>
+                  <div class="section-body">
+                    <el-row :gutter="20">
+                      <el-col :span="8"><el-form-item label="需要发放奖励"><el-switch v-model="form.rewardEnabled" /></el-form-item></el-col>
+                      <el-col v-if="form.rewardEnabled" :span="8"><el-form-item label="奖励类型" required><el-select v-model="form.rewardType" style="width:100%"><el-option label="积分" value="积分" /><el-option label="权益" value="权益" /></el-select></el-form-item></el-col>
+                      <el-col v-if="form.rewardEnabled" :span="8"><el-form-item label="权益发放计划" required><el-select v-model="form.grantPlanId" clearable placeholder="请选择" style="width:100%"><el-option v-for="item in filteredGrantPlans" :key="item.id" :label="`${item.id}-${item.name}`" :value="item.id" /></el-select></el-form-item></el-col>
+                      <el-col v-if="form.rewardEnabled && form.rewardType === '权益'" :span="12"><el-form-item label="权益有效期" required><el-date-picker v-model="form.rewardValidity" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" style="width:100%" /></el-form-item></el-col>
+                      <el-col v-if="form.rewardEnabled && form.rewardType === '权益'" :span="12"><el-form-item label="赠送次数" required><el-input-number v-model="form.rewardGrantTimes" :min="1" /></el-form-item></el-col>
+                    </el-row>
+                  </div>
+                </div>
+              </template>
+            </div>
+          </el-card>
+        </div>
+
+        <div v-show="activeStep === 4" class="step-pane">
+          <el-card shadow="never" class="section-card">
+            <template #header><div class="section-title">互斥与通知</div></template>
+            <el-row :gutter="20">
+              <el-col :span="8"><el-form-item label="是否参与互斥"><el-switch v-model="form.mutexEnabled" /></el-form-item></el-col>
+              <el-col :span="16"><el-form-item label="互斥组" :required="form.mutexEnabled"><el-select v-model="form.mutexGroup" clearable style="width:100%"><el-option v-for="item in mutexGroups" :key="item.id" :label="item.name" :value="item.name" /></el-select></el-form-item></el-col>
+              <el-col :span="8"><el-form-item label="报名成功通知"><el-switch v-model="form.smsEnabled" /></el-form-item></el-col>
+              <el-col :span="16"><el-form-item label="报名成功模板"><el-select v-model="form.signupNoticeTemplateId" clearable style="width:100%"><el-option v-for="item in notifications" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="开始前提醒模板"><el-select v-model="form.beforeStartTemplateId" clearable style="width:100%"><el-option v-for="item in notifications" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="达标发奖通知模板"><el-select v-model="form.rewardTemplateId" clearable style="width:100%"><el-option v-for="item in notifications" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
+              <el-col :span="12"><el-form-item label="活动 owner staff ID"><el-input v-model="form.ownerStaffId" /></el-form-item></el-col>
+            </el-row>
+          </el-card>
+        </div>
+
+      </el-form>
+      <div class="action-bar">
+        <div class="action-left">
+          <el-button v-if="activeStep > 0 && activeStep < stepItems.length - 1" @click="prevStep"><el-icon><ArrowLeft /></el-icon>上一步</el-button>
+        </div>
+        <div class="action-center">
+          <el-button @click="goBack">取消</el-button>
+          <el-button type="primary" plain @click="saveDraft">保存草稿</el-button>
+          <el-button v-if="activeStep === stepItems.length - 1" type="primary" @click="markPending">提交审核</el-button>
+        </div>
+        <div class="action-right">
+          <el-button v-if="activeStep < stepItems.length - 1" type="primary" @click="nextStep">下一步<el-icon class="el-icon--right"><ArrowRight /></el-icon></el-button>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { ElMessage } from 'element-plus'
+import { ArrowLeft, ArrowRight, Bell, InfoFilled, Medal, Plus, Setting, User, Delete } from '@element-plus/icons-vue'
+import request from '@/utils/request'
+import activityStore from '@/store/activityStore'
+import ActivityAchievementConfig from '@/components/activity/ActivityAchievementConfig.vue'
+
+export default {
+  name: 'ActivityEdit',
+  components: { ActivityAchievementConfig },
+  data() {
+    return {
+      Delete,
+      Plus,
+      ArrowLeft,
+      ArrowRight,
+      activeStep: 0,
+      stepItems: [
+        { title: '基础信息', icon: InfoFilled },
+        { title: '展示规则', icon: Setting },
+        { title: '客群与报名', icon: User },
+        { title: '进度与奖励', icon: Medal },
+        { title: '互斥与通知', icon: Bell }
+      ],
+      layerActiveTab: '0',
+      assemblyPages: [],
+      ruleFieldOptions: ['开户时间', 'TRB值(含家庭账户)', '客户MSC Group', '境外消费'],
+      operatorOptions: ['介于', '大于等于', '小于等于'],
+      form: activityStore.emptyActivity(),
+      rules: {
+        internalTitle: [{ required: true, message: '请输入活动标题(对内)', trigger: 'blur' }],
+        name: [{ required: true, message: '请输入活动名称(对客)', trigger: 'blur' }],
+        category: [{ required: true, message: '请选择活动类型', trigger: 'change' }],
+        thumbnail: [{ required: true, message: '请输入活动小图', trigger: 'blur' }],
+        detailImage: [{ required: true, message: '请输入活动头图', trigger: 'blur' }],
+        content: [{ required: true, message: '请输入活动详情', trigger: 'blur' }],
+        activityTime: [{ type: 'array', required: true, message: '请选择活动时间', trigger: 'change' }]
+      }
+    }
+  },
+  computed: {
+    isEdit() { return this.$route.name === 'ActivityEdit' },
+    crowds() { return activityStore.activityCrowds },
+    forms() { return activityStore.forms },
+    progressTemplates() { return activityStore.progressTemplates },
+    progressRules() { return activityStore.progressRules },
+    grantPlans() { return activityStore.activityGrantPlans },
+    filteredGrantPlans() {
+      if (!this.form.rewardType) return this.grantPlans
+      if (this.form.rewardType === '积分') return this.grantPlans.filter((item) => item.rewardType === '积分')
+      return this.grantPlans.filter((item) => item.rewardType !== '积分')
+    },
+    mutexGroups() { return activityStore.mutexGroups },
+    notifications() { return activityStore.notifications }
+  },
+  created() {
+    if (this.isEdit) {
+      const row = activityStore.getActivity(this.$route.params.id)
+      if (!row) {
+        ElMessage.error('活动不存在')
+        return this.goBack()
+      }
+      this.form = { ...activityStore.emptyActivity(), ...JSON.parse(JSON.stringify(row)) }
+    }
+    this.normalizeProgressConfig()
+    this.normalizeExtraForms()
+    this.fetchAssemblyPages()
+  },
+  methods: {
+    async fetchAssemblyPages() {
+      try {
+        const res = await request.get('/api/assembly/list', { params: { name: '' } })
+        this.assemblyPages = Array.isArray(res?.data) ? res.data : []
+      } catch (e) {
+        this.assemblyPages = []
+      }
+    },
+    newAchievementRule() {
+      return {
+        key: `${Date.now()}-${Math.random()}`,
+        field: '',
+        operator: '',
+        value: 0,
+        dateRange: [],
+        openingMonthRequired: false,
+        continuous: false,
+        months: 1,
+        allowBreak: false,
+        reachedText: '',
+        unreachedText: '',
+        progressText: '',
+        failureReason: '',
+        consumptionEndDate: '',
+        consumptionDimension: '笔数'
+      }
+    },
+    newLayerActivity(index) {
+      const rule = this.newAchievementRule()
+      if (index === 1) {
+        rule.field = '境外消费'
+        rule.operator = '大于等于'
+        rule.value = 1
+      }
+      return {
+        key: `${Date.now()}-activity-${index}-${Math.random()}`,
+        title: '',
+        description: '',
+        rules: [rule],
+        grantPlanId: ''
+      }
+    },
+    newAchievementLayer(index) {
+      return {
+        key: `${Date.now()}-${index}-${Math.random()}`,
+        crowdId: '',
+        title: `层级${index + 1}`,
+        activities: [this.newLayerActivity(0), this.newLayerActivity(1)]
+      }
+    },
+    normalizeProgressConfig() {
+      if (!this.form.progressCopy) this.form.progressCopy = { unreached: '', reached: '', help: '' }
+      if (!Array.isArray(this.form.achievementRules) || !this.form.achievementRules.length) this.form.achievementRules = [this.newAchievementRule()]
+      if (!this.form.mgmReferredProgress) this.form.mgmReferredProgress = { labelOne: '', labelTwo: '', advancedReward: false, advancedRuleField: '', advancedRuleText: '' }
+      if (!Array.isArray(this.form.achievementLayers) || !this.form.achievementLayers.length) this.form.achievementLayers = [this.newAchievementLayer(0)]
+      this.form.achievementLayers.forEach((level) => {
+        if (!Array.isArray(level.activities) || !level.activities.length) {
+          const firstActivity = this.newLayerActivity(0)
+          firstActivity.title = level.activityTitle || ''
+          firstActivity.description = level.description || ''
+          firstActivity.rules = Array.isArray(level.rules) && level.rules.length ? level.rules : [this.newAchievementRule()]
+          firstActivity.grantPlanId = level.grantPlanId || ''
+          level.activities = [firstActivity, this.newLayerActivity(1)]
+        }
+      })
+      this.syncLayerCount(this.form.layerCount || 1)
+    },
+    handleAchievementTemplateChange() {
+      if (this.form.achievementTemplate === 'LAYER') this.syncLayerCount(this.form.layerCount || 1)
+    },
+    newExtraForm() {
+      return { name: '', placeholder: '', type: 'text', required: true, maxLength: 30 }
+    },
+    normalizeExtraForms() {
+      const legacyForms = Array.isArray(this.form.extraForms) ? this.form.extraForms : []
+      if (this.form.extraInfoLink === undefined) {
+        this.form.extraInfoLink = legacyForms.find((item) => item?.link)?.link || ''
+      }
+      const normalized = legacyForms.flatMap((item) => {
+        if (Array.isArray(item?.fields)) return item.fields
+        return item ? [item] : []
+      }).map((item) => ({
+        name: item.name || '',
+        placeholder: item.placeholder || '',
+        type: item.type || 'text',
+        required: item.required !== false,
+        maxLength: Number(item.maxLength || 30)
+      })).slice(0, 2)
+      this.form.extraForms = normalized.length ? normalized : [this.newExtraForm()]
+    },
+    addExtraForm() {
+      if (this.form.extraForms.length >= 2) return ElMessage.warning('最多配置 2 项补充信息')
+      this.form.extraForms.push(this.newExtraForm())
+    },
+    removeExtraForm(index) {
+      if (this.form.extraForms.length <= 1) return
+      this.form.extraForms.splice(index, 1)
+    },
+    handleNeedExtraInfoChange(value) {
+      if (value && (!Array.isArray(this.form.extraForms) || !this.form.extraForms.length)) this.normalizeExtraForms()
+    },
+    addAchievementRule() {
+      if (this.form.achievementRules.length >= 5) return ElMessage.warning('最多添加 5 个进度标签')
+      this.form.achievementRules.push(this.newAchievementRule())
+    },
+    removeAchievementRule(index) {
+      this.form.achievementRules.splice(index, 1)
+    },
+    addLayerActivity(level) {
+      if (level.activities.length >= 5) return ElMessage.warning('每个层级最多添加 5 个活动')
+      level.activities.push(this.newLayerActivity(level.activities.length))
+    },
+    removeLayerActivity(level, index) {
+      level.activities.splice(index, 1)
+    },
+    addLayerRule(activity) {
+      if (activity.rules.length >= 5) return ElMessage.warning('每个活动最多添加 5 个进度标签')
+      activity.rules.push(this.newAchievementRule())
+    },
+    syncLayerCount(value) {
+      const count = Number(value || 1)
+      if (!Array.isArray(this.form.achievementLayers)) this.form.achievementLayers = []
+      while (this.form.achievementLayers.length < count) this.form.achievementLayers.push(this.newAchievementLayer(this.form.achievementLayers.length))
+      if (this.form.achievementLayers.length > count) this.form.achievementLayers.splice(count)
+      if (Number(this.layerActiveTab) >= count) this.layerActiveTab = String(count - 1)
+    },
+    nextStep() {
+      if (this.activeStep < this.stepItems.length - 1) this.activeStep += 1
+    },
+    prevStep() {
+      if (this.activeStep > 0) this.activeStep -= 1
+    },
+    saveDraft() { this.submit(false) },
+    markPending() { this.submit(true) },
+    submit(toAudit) {
+      this.$refs.formRef.validate((valid) => {
+        if (!valid) return
+        const error = this.validateBusiness()
+        if (error) return ElMessage.warning(error)
+        if (toAudit) {
+          this.form.status = 'pending'
+          this.form.auditStatus = '待审核'
+        }
+        activityStore.saveActivity(this.form)
+        ElMessage.success(toAudit ? '已保存并提交审核' : '保存成功')
+        this.goBack()
+      })
+    },
+    validateBusiness() {
+      if (this.form.activityTime?.length === 2 && this.form.activityTime[0] > this.form.activityTime[1]) return '活动结束时间必须晚于开始时间'
+      if (this.form.needSignup && (!this.form.signupTime || this.form.signupTime.length !== 2)) return '需要报名时,报名时间必填'
+      if (this.form.signupGuideEnabled && !this.form.signupSuccessTitle) return '开启报名成功引导语时标题必填'
+      if (this.form.signupTime?.length === 2 && this.form.signupTime[0] > this.form.signupTime[1]) return '报名结束时间必须晚于开始时间'
+      if (this.form.needExtraInfo) {
+        if (!this.form.extraForms?.length) return '开启补充信息时必须至少配置一个表单'
+        if (this.form.extraForms.length > 2) return '补充信息最多配置 2 项'
+        for (let i = 0; i < this.form.extraForms.length; i++) {
+          const ef = this.form.extraForms[i]
+          if (!ef.name) return `请填写补充信息${i + 1}的字段名称`
+          if (!ef.placeholder) return `请填写补充信息${i + 1}的提示文案`
+          if (!ef.type) return `请选择补充信息${i + 1}的字段类型`
+          if (!ef.maxLength || ef.maxLength < 1) return `请填写补充信息${i + 1}的最大长度`
+        }
+      }
+      if (this.form.quotaEnabled && Number(this.form.quota) < Number(this.form.signedCount || 0)) return '报名人数上限不能小于已报名人数'
+      if (this.form.progressEnabled && !this.form.achievementTemplate) return '开启进度与奖励后必须选择活动模板'
+      if (this.form.progressEnabled && this.form.achievementTemplate === 'NTB' && (!this.form.ntbRules?.length || this.form.ntbRules.some((rule) => !rule.field || !rule.operator))) return '请完整配置 NTB 达标规则标签'
+      if (this.form.progressEnabled && this.form.achievementTemplate === 'MGM' && this.form.mgmRules?.some((rule) => !rule.field || !rule.operator)) return '请完整配置 MGM 达标规则标签'
+      if (this.form.progressEnabled && this.form.achievementTemplate === 'LAYER') {
+        const invalidLayerConfig = this.form.achievementLayers.some((level) => level.activities.some((activity) => {
+          const invalidRule = activity.rules.some((rule) => !rule.field || (
+            rule.field === '境外消费' &&
+            (!rule.consumptionTimeOperator || !rule.consumptionEndDate || !rule.consumptionDimension || !rule.operator || Number(rule.value) < 1)
+          ))
+          return invalidRule || !activity.reward?.planId
+        }))
+        if (invalidLayerConfig) return '请完整配置每个层级活动的规则和达标奖励'
+      }
+      if (this.form.progressEnabled && this.form.achievementTemplate !== 'LAYER' && !this.form.templateRewards?.[this.form.achievementTemplate]?.planId) return '请选择达标奖励'
+      if (this.form.mutexEnabled && !this.form.mutexGroup) return '参与互斥时必须选择互斥组'
+      return ''
+    },
+    preview() { ElMessage.success('已生成移动端预览效果') },
+    statusText(status) { return { draft: '草稿', pending: '待审核', approved: '审核通过', published: '已发布', offline: '已下线' }[status] || status },
+    goBack() { this.$router.push('/customization/hsbc/activities') }
+  }
+}
+</script>
+
+<style scoped>
+.activity-edit { padding:20px; }
+.page-header { display:flex; justify-content:space-between; align-items:center; font-weight:600; }
+.activity-steps { margin-bottom:20px; }
+.step-pane { min-height:360px; }
+.section-card { margin-bottom:16px; border:none; }
+.section-card :deep(.el-card__header) { padding:12px 20px; background:#fafafa; border-bottom:1px solid #ebeef5; }
+.section-title { color:#303133; font-size:16px; font-weight:600; }
+.form-tip { margin-top:4px; color:#909399; font-size:12px; line-height:1.4; }
+.signup-row :deep(.el-form-item) { margin-bottom:16px; }
+.signup-row :deep(.el-form-item__content) { display:flex; align-items:center; }
+.field-md { width:240px; max-width:100%; }
+.field-lg { width:320px; max-width:100%; }
+.field-xs { width:140px; max-width:100%; }
+.date-range-field { width:300px; max-width:100%; }
+.activity-edit :deep(.compact-date-range),
+.activity-edit :deep(.mgm-date-range) { width:300px; max-width:100%; }
+.activity-edit :deep(.reward-validity-picker) { width:320px; max-width:100%; flex:0 1 320px; }
+.assembly-select-panel { padding:12px 14px; margin-bottom:16px; background:#fafafa; border:1px solid #ebeef5; border-radius:4px; }
+.action-bar { display:flex; align-items:center; justify-content:space-between; margin-top:20px; padding-top:20px; border-top:1px solid #ebeef5; }
+.action-left, .action-right { width:120px; }
+.action-center { display:flex; justify-content:center; gap:12px; }
+.action-center .el-button { min-width:100px; }
+.audit-actions { margin-top:16px; text-align:center; }
+.progress-config { display:flex; flex-direction:column; gap:16px; }
+.config-section { border:1px solid #ebeef5; border-radius:4px; background:#fff; overflow:hidden; }
+.template-section { background:#fafafa; }
+.section-header { min-height:48px; padding:0 20px; display:flex; align-items:center; justify-content:space-between; gap:16px; border-bottom:1px solid #ebeef5; background:#f5f7fa; color:#303133; font-size:15px; font-weight:600; }
+.section-body { padding:20px; }
+.compact-item { margin-bottom:0; }
+.narrow-form :deep(.el-form-item__content) { max-width:720px; }
+.limit-field { max-width:540px; }
+.rule-block { position:relative; margin-top:16px; padding:16px 16px 0; border:1px solid #dcdfe6; border-radius:4px; background:#fafafa; }
+.rule-block:first-child { margin-top:0; }
+.rule-title { margin-bottom:16px; display:flex; justify-content:space-between; align-items:center; color:#606266; }
+.layer-tabs :deep(.el-tabs__header) { margin:-20px -20px 20px; padding:0 20px; background:#fafafa; }
+.layer-tabs :deep(.el-tabs__nav-wrap::after) { height:1px; }
+.layer-tabs :deep(.el-tabs__item) { min-width:96px; padding:0 24px; text-align:center; }
+.level-panel { padding-top:4px; }
+.layer-activity { margin-top:18px; padding:18px; border:1px solid #dcdfe6; border-radius:4px; background:#fafafa; }
+.activity-heading { margin-bottom:18px; display:flex; align-items:center; justify-content:space-between; gap:16px; color:#303133; }
+.prerequisite-note { margin-left:12px; color:#f56c6c; font-size:12px; font-weight:400; }
+.rule-actions { padding-top:14px; border-top:1px solid #ebeef5; }
+.dependent-rule { border-color:#fbc4c4; background:#fef0f0; }
+.inner-rule { background:#f5f7fa; }
+.subsection-actions { margin:4px 0 12px; }
+.inline-reward { margin-top:16px; padding:16px 16px 0; border-top:1px solid #ebeef5; }
+.extra-forms-header { margin-bottom:12px; display:flex; align-items:center; justify-content:space-between; gap:12px; }
+.extra-forms-title { font-size:15px; font-weight:600; color:#303133; }
+.extra-forms-limit { margin-left:12px; color:#909399; font-size:12px; }
+.extra-form-panel { margin-bottom:16px; padding:16px 18px; border:1px solid #dcdfe6; border-radius:4px; background:#fafafa; }
+.extra-form-head { margin-bottom:14px; display:flex; align-items:center; justify-content:space-between; color:#303133; font-size:14px; font-weight:600; }
+.extra-link-panel { padding:16px 18px 0; border:1px solid #dcdfe6; border-radius:4px; background:#fafafa; }
+.signup-success-panel { margin-bottom:18px; padding:0 18px 2px; border:1px solid #ebeef5; border-radius:4px; background:#fafafa; }
+.signup-success-panel :deep(.el-form-item) { margin-bottom:18px; }
+.signup-success-panel :deep(.el-form-item:last-child) { margin-bottom:6px; }
+.signup-success-title { margin:0 -18px 18px; padding:14px 18px; border-bottom:1px solid #ebeef5; background:#f5f7fa; color:#303133; font-size:15px; font-weight:600; }
+.signup-success-panel :deep(.el-form-item__content) { max-width:640px; }
+@media (max-width: 768px) {
+  .signup-success-panel :deep(.el-form-item) { display:block; }
+  .signup-success-panel :deep(.el-form-item__label) { width:auto !important; height:auto; margin-bottom:8px; justify-content:flex-start; line-height:22px; }
+  .signup-success-panel :deep(.el-form-item__content) { margin-left:0 !important; }
+}
+@media (max-width: 1200px) {
+  .section-body :deep(.el-col) { max-width:100%; flex:0 0 100%; }
+}
+</style>

+ 38 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityGrantPlan.vue

@@ -0,0 +1,38 @@
+<template>
+  <div>
+    <el-card>
+      <template #header><div class="header"><div><div class="title">活动权益发放计划</div><div class="desc">配置达标后发什么奖品、怎么发、库存和单客户上限。</div></div><el-button type="primary" @click="openDialog()"><el-icon><Plus /></el-icon>新增计划</el-button></div></template>
+      <el-table :data="rows" border stripe>
+        <el-table-column prop="id" label="计划编号" width="120" /><el-table-column prop="name" label="计划名称" min-width="180" /><el-table-column prop="rewardType" label="奖品类型" width="110" /><el-table-column prop="rewardName" label="奖品名称" min-width="150" /><el-table-column prop="grantMode" label="发放方式" min-width="160" /><el-table-column prop="perCustomerLimit" label="单客户上限" width="110" /><el-table-column prop="stock" label="库存" width="100" /><el-table-column prop="usedBy" label="引用活动" min-width="160" /><el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="row.status === '启用' ? 'success' : 'info'">{{ row.status }}</el-tag></template></el-table-column><el-table-column prop="updatedAt" label="更新时间" width="170" /><el-table-column label="操作" width="150" fixed="right"><template #default="{ row }"><el-button type="primary" link @click="openDialog(row)">编辑</el-button><el-button type="primary" link @click="toggle(row)">{{ row.status === '启用' ? '停用' : '启用' }}</el-button></template></el-table-column>
+      </el-table>
+    </el-card>
+    <el-dialog v-model="visible" :title="form.id ? '编辑发放计划' : '新增发放计划'" width="720px">
+      <el-form :model="form" label-width="120px">
+        <el-form-item label="计划名称" required><el-input v-model="form.name" /></el-form-item>
+        <el-form-item label="奖品类型"><el-select v-model="form.rewardType" style="width:100%"><el-option label="积分" value="积分" /><el-option label="立减金" value="立减金" /><el-option label="卡券" value="卡券" /><el-option label="信用卡权益" value="信用卡权益" /><el-option label="第三方权益" value="第三方权益" /></el-select></el-form-item>
+        <el-form-item label="奖品名称" required><el-input v-model="form.rewardName" /></el-form-item>
+        <el-form-item label="发放方式"><el-select v-model="form.grantMode" style="width:100%"><el-option label="达标后自动发放" value="达标后自动发放" /><el-option label="达标后跳转领取" value="达标后跳转领取" /><el-option label="报名成功后直接发放" value="报名成功后直接发放" /></el-select></el-form-item>
+        <el-form-item label="单客户上限"><el-input-number v-model="form.perCustomerLimit" :min="1" /></el-form-item>
+        <el-form-item label="库存"><el-input-number v-model="form.stock" :min="0" /></el-form-item>
+        <el-form-item label="引用活动"><el-input v-model="form.usedBy" /></el-form-item>
+        <el-form-item label="状态"><el-select v-model="form.status" style="width:100%"><el-option label="启用" value="启用" /><el-option label="停用" value="停用" /></el-select></el-form-item>
+      </el-form>
+      <template #footer><el-button @click="visible=false">取消</el-button><el-button type="primary" @click="save">保存</el-button></template>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { ElMessage } from 'element-plus'
+import activityStore from '@/store/activityStore'
+export default {
+  name: 'ActivityGrantPlan',
+  data() { return { visible: false, form: {} } },
+  computed: { rows() { return activityStore.activityGrantPlans } },
+  methods: {
+    openDialog(row) { this.form = row ? { ...row } : { rewardType: '积分', grantMode: '达标后自动发放', perCustomerLimit: 1, stock: 0, status: '启用' }; this.visible = true },
+    save() { if (!this.form.name || !this.form.rewardName) return ElMessage.warning('计划名称和奖品名称必填'); activityStore.saveSimple('activityGrantPlans', this.form); this.visible = false; ElMessage.success('保存成功') },
+    toggle(row) { row.status = row.status === '启用' ? '停用' : '启用'; activityStore.saveSimple('activityGrantPlans', row) }
+  }
+}
+</script>
+<style scoped>.header{display:flex;justify-content:space-between;align-items:center;gap:16px}.title{font-size:16px;font-weight:600}.desc{margin-top:4px;color:#909399;font-size:13px}</style>

+ 153 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityManagement.vue

@@ -0,0 +1,153 @@
+<template>
+  <div class="activity-management">
+    <el-card>
+      <template #header>
+        <div class="toolbar">
+          <el-form :model="searchForm" inline label-width="86px">
+            <el-form-item label="活动 ID">
+              <el-input v-model="searchForm.id" clearable placeholder="精确查询活动编号" style="width: 180px" />
+            </el-form-item>
+            <el-form-item label="活动名称">
+              <el-input v-model="searchForm.name" clearable placeholder="对内标题/对客名称" style="width: 220px" />
+            </el-form-item>
+            <el-form-item label="活动类型">
+              <el-select v-model="searchForm.category" clearable placeholder="全部" style="width: 150px">
+                <el-option label="普通活动" value="普通活动" />
+                <el-option label="卓越迎新礼" value="卓越迎新礼" />
+                <el-option label="层层礼" value="层层礼" />
+                <el-option label="MGM" value="MGM" />
+                <el-option label="信用卡活动" value="信用卡活动" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="活动状态">
+              <el-select v-model="searchForm.status" clearable placeholder="全部" style="width: 140px">
+                <el-option label="草稿" value="draft" />
+                <el-option label="待审核" value="pending" />
+                <el-option label="审核通过" value="approved" />
+                <el-option label="已发布" value="published" />
+                <el-option label="已下线" value="offline" />
+                <el-option label="已结束" value="ended" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="创建人">
+              <el-input v-model="searchForm.creator" clearable placeholder="创建人" style="width: 130px" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </el-form-item>
+          </el-form>
+          <el-button type="primary" @click="handleCreate"><el-icon><Plus /></el-icon>新建活动</el-button>
+        </div>
+      </template>
+
+      <el-table :data="tableData" border stripe>
+        <el-table-column label="操作" width="310" fixed="left">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="handleView(row)">查看</el-button>
+            <el-button v-if="['draft', 'rejected', 'approved'].includes(row.status)" type="primary" link @click="handleEdit(row)">编辑</el-button>
+            <el-button type="primary" link @click="preview(row)">预览</el-button>
+            <el-button v-if="row.status === 'draft'" type="primary" link @click="submitAudit(row)">提交审核</el-button>
+            <el-button v-if="row.status === 'pending'" type="success" link @click="approve(row)">审核</el-button>
+            <el-button v-if="row.status === 'approved'" type="success" link @click="publish(row)">发布</el-button>
+            <el-button v-if="row.status === 'published'" type="warning" link @click="offline(row)">下线</el-button>
+          </template>
+        </el-table-column>
+        <el-table-column prop="id" label="活动 ID" width="130" />
+        <el-table-column prop="internalTitle" label="活动标题(对内)" min-width="170" show-overflow-tooltip />
+        <el-table-column prop="name" label="活动名称(对客)" min-width="170" show-overflow-tooltip />
+        <el-table-column prop="category" label="活动类型" width="120" />
+        <el-table-column label="活动时间" width="210"><template #default="{ row }">{{ rangeText(row.activityTime) }}</template></el-table-column>
+        <el-table-column label="报名时间" width="210"><template #default="{ row }">{{ rangeText(row.signupTime) }}</template></el-table-column>
+        <el-table-column label="展示状态" width="110">
+          <template #default="{ row }">
+            <el-tag :type="row.showOnHome || row.showOnAll ? 'success' : 'info'">{{ row.showOnHome || row.showOnAll ? '对客展示' : '不展示' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="活动状态" width="110"><template #default="{ row }"><el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag></template></el-table-column>
+        <el-table-column prop="auditStatus" label="审核状态" width="110" />
+        <el-table-column prop="creator" label="创建人" width="110" />
+        <el-table-column prop="auditor" label="审核人" width="110" />
+        <el-table-column prop="createdAt" label="创建时间" width="170" />
+        <el-table-column prop="updatedAt" label="更新时间" width="170" />
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { ElMessage, ElMessageBox } from 'element-plus'
+import activityStore from '@/store/activityStore'
+
+export default {
+  name: 'ActivityManagement',
+  data() {
+    return { searchForm: { id: '', name: '', category: '', status: '', creator: '' }, tableData: [] }
+  },
+  created() { this.handleSearch() },
+  activated() { this.handleSearch() },
+  methods: {
+    handleSearch() {
+      this.tableData = activityStore.queryActivities(this.searchForm).filter((item) => {
+        if (this.searchForm.creator && !String(item.creator || '').includes(this.searchForm.creator)) return false
+        return true
+      })
+    },
+    handleReset() {
+      this.searchForm = { id: '', name: '', category: '', status: '', creator: '' }
+      this.handleSearch()
+    },
+    handleCreate() { this.$router.push('/customization/hsbc/activities/add') },
+    handleEdit(row) { this.$router.push(`/customization/hsbc/activities/edit/${row.id}`) },
+    handleView(row) { this.$router.push(`/customization/hsbc/activities/view/${row.id}`) },
+    preview(row) { ElMessage.success(`已生成移动端预览:${row.name}`) },
+    submitAudit(row) {
+      const error = this.validateRequired(row)
+      if (error) return ElMessage.warning(error)
+      row.status = 'pending'
+      row.auditStatus = '待审核'
+      ElMessage.success('已提交审核')
+      this.handleSearch()
+    },
+    approve(row) {
+      row.status = 'approved'
+      row.auditStatus = '审核通过'
+      row.auditor = '当前审核人'
+      ElMessage.success('审核通过')
+      this.handleSearch()
+    },
+    async publish(row) {
+      await ElMessageBox.confirm(`确定发布活动「${row.name}」吗?`, '发布活动', { type: 'warning' })
+      activityStore.publishActivity(row.id)
+      this.handleSearch()
+    },
+    async offline(row) {
+      await ElMessageBox.confirm(`确定下线活动「${row.name}」吗?`, '下线活动', { type: 'warning' })
+      activityStore.offlineActivity(row.id)
+      this.handleSearch()
+    },
+    validateRequired(row) {
+      if (!row.name) return '活动名称(对客)必填'
+      if (!row.internalTitle) return '活动标题(对内)必填'
+      if (!row.activityTime || row.activityTime.length !== 2) return '活动时间必填'
+      if (row.needSignup && (!row.signupTime || row.signupTime.length !== 2)) return '报名时间必填'
+      if (row.progressEnabled && !row.progressTemplateId) return '开启进度跟踪后必须选择进度模板'
+      if (row.progressEnabled && !row.progressRuleId) return '开启进度跟踪后必须选择达标规则'
+      if (row.rewardEnabled && !row.grantPlanId) return '需要权益发放时必须选择发放计划'
+      if (row.mutexEnabled && !row.mutexGroup) return '参与互斥时必须选择互斥组'
+      return ''
+    },
+    rangeText(range) { return range && range.length === 2 ? `${range[0]} 至 ${range[1]}` : '-' },
+    statusText(status) {
+      return { draft: '草稿', pending: '待审核', approved: '审核通过', published: '已发布', offline: '已下线', ended: '已结束', rejected: '已驳回' }[status] || status
+    },
+    statusType(status) {
+      return { draft: 'info', pending: 'warning', approved: 'success', published: 'success', offline: 'danger', ended: 'info', rejected: 'danger' }[status] || ''
+    }
+  }
+}
+</script>
+
+<style scoped>
+.toolbar { display:flex; justify-content:space-between; align-items:flex-start; gap:16px; }
+</style>

+ 245 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityMutexGroup.vue

@@ -0,0 +1,245 @@
+<template>
+  <div class="activity-mutex-group">
+    <el-card>
+      <template #header>
+        <div class="header">
+          <div>
+            <div class="title">活动互斥组管理</div>
+            <div class="desc">配置 NTB/MGM 等活动之间的实时拦截、T+1 修正、释放规则和对客状态文案。</div>
+          </div>
+          <el-button type="primary" @click="openDialog()">
+            <el-icon><Plus /></el-icon>新增互斥组
+          </el-button>
+        </div>
+      </template>
+
+      <el-table :data="rows" border stripe>
+        <el-table-column prop="id" label="互斥组编号" width="120" />
+        <el-table-column prop="name" label="互斥组名称" min-width="160" show-overflow-tooltip />
+        <el-table-column prop="mutexType" label="互斥类型" width="120" />
+        <el-table-column prop="businessType" label="业务类型" width="120" />
+        <el-table-column prop="scopeType" label="互斥活动范围" width="130" />
+        <el-table-column prop="activities" label="互斥活动" min-width="220" show-overflow-tooltip />
+        <el-table-column label="判断依据" min-width="190">
+          <template #default="{ row }">{{ basisText(row.judgeBasis) }}</template>
+        </el-table-column>
+        <el-table-column label="实时拦截" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="row.realtimeBlock ? 'success' : 'info'">{{ row.realtimeBlock ? '开启' : '关闭' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="T+1修正" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="row.tPlusOneFix ? 'warning' : 'info'">{{ row.tPlusOneFix ? '开启' : '关闭' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="releaseRule" label="释放规则" min-width="170" show-overflow-tooltip />
+        <el-table-column prop="listStatusText" label="列表状态文案" min-width="140" show-overflow-tooltip />
+        <el-table-column label="状态" width="90" align="center">
+          <template #default="{ row }">
+            <el-tag :type="row.status === '启用' ? 'success' : 'info'">{{ row.status }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="updatedAt" label="更新时间" width="170" />
+        <el-table-column label="操作" width="170" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="openDialog(row)">编辑</el-button>
+            <el-button type="primary" link @click="toggleStatus(row)">{{ row.status === '启用' ? '停用' : '启用' }}</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-dialog v-model="dialogVisible" :title="editing.id ? '编辑互斥组' : '新增互斥组'" width="860px">
+      <el-form :model="editing" label-width="130px">
+        <el-divider content-position="left">基础规则</el-divider>
+        <el-row :gutter="16">
+          <el-col :span="12">
+            <el-form-item label="互斥组名称" required>
+              <el-input v-model="editing.name" placeholder="如:NTB新客互斥组" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="互斥类型" required>
+              <el-select v-model="editing.mutexType" style="width: 100%">
+                <el-option label="互斥报名" value="互斥报名" />
+                <el-option label="互斥继续参与" value="互斥继续参与" />
+                <el-option label="互斥展示" value="互斥展示" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="业务类型" required>
+              <el-select v-model="editing.businessType" style="width: 100%">
+                <el-option label="NTB/MGM" value="NTB/MGM" />
+                <el-option label="NTB" value="NTB" />
+                <el-option label="MGM" value="MGM" />
+                <el-option label="普通活动" value="普通活动" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="互斥活动范围" required>
+              <el-select v-model="editing.scopeType" style="width: 100%">
+                <el-option label="指定活动" value="指定活动" />
+                <el-option label="指定活动类型" value="指定活动类型" />
+                <el-option label="同业务类型全部活动" value="同业务类型全部活动" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="互斥活动" required>
+              <el-input v-model="editing.activities" placeholder="填写活动名称,多个用顿号或逗号分隔" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">判断与拦截</el-divider>
+        <el-form-item label="判断依据" required>
+          <el-checkbox-group v-model="editing.judgeBasis">
+            <el-checkbox label="报名记录">报名记录</el-checkbox>
+            <el-checkbox label="MGM标签">MGM标签</el-checkbox>
+            <el-checkbox label="开户时间">开户时间</el-checkbox>
+            <el-checkbox label="达标状态">达标状态</el-checkbox>
+            <el-checkbox label="活动状态">活动状态</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+        <el-row :gutter="16">
+          <el-col :span="12">
+            <el-form-item label="报名实时拦截" required>
+              <el-switch v-model="editing.realtimeBlock" active-text="开启" inactive-text="关闭" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="T+1标签修正">
+              <el-switch v-model="editing.tPlusOneFix" active-text="开启" inactive-text="关闭" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="拦截文案" required>
+          <el-input v-model="editing.blockText" type="textarea" :rows="3" placeholder="点击报名时弹出的拦截提示" />
+        </el-form-item>
+        <el-form-item label="T+1修正文案">
+          <el-input v-model="editing.correctionText" type="textarea" :rows="3" placeholder="次日标签更新后活动状态修正说明" />
+        </el-form-item>
+
+        <el-divider content-position="left">释放与展示</el-divider>
+        <el-row :gutter="16">
+          <el-col :span="12">
+            <el-form-item label="释放规则" required>
+              <el-select v-model="editing.releaseRule" style="width: 100%">
+                <el-option label="活动结束且未达标释放" value="活动结束且未达标释放" />
+                <el-option label="活动结束后释放" value="活动结束后释放" />
+                <el-option label="报名失败立即释放" value="报名失败立即释放" />
+                <el-option label="不自动释放" value="不自动释放" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="列表状态文案">
+              <el-input v-model="editing.listStatusText" placeholder="如:不符合参与条件" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="详情状态文案">
+              <el-input v-model="editing.detailStatusText" type="textarea" :rows="2" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="命中后展示规则">
+              <el-input v-model="editing.conflictDisplayRule" placeholder="如:命中后其他活动展示不符合条件" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态" required>
+              <el-select v-model="editing.status" style="width: 100%">
+                <el-option label="启用" value="启用" />
+                <el-option label="停用" value="停用" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="备注">
+              <el-input v-model="editing.remark" type="textarea" :rows="2" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="save">保存</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { ElMessage } from 'element-plus'
+import activityStore from '@/store/activityStore'
+
+function emptyMutex() {
+  return {
+    name: '',
+    mutexType: '互斥报名',
+    businessType: 'NTB/MGM',
+    scopeType: '指定活动',
+    activities: '',
+    judgeBasis: ['报名记录'],
+    realtimeBlock: true,
+    tPlusOneFix: false,
+    releaseRule: '活动结束且未达标释放',
+    listStatusText: '不符合参与条件',
+    detailStatusText: '',
+    blockText: '',
+    conflictDisplayRule: '命中后其他活动展示不符合条件',
+    correctionText: '',
+    remark: '',
+    status: '启用'
+  }
+}
+
+export default {
+  name: 'ActivityMutexGroup',
+  data() {
+    return {
+      dialogVisible: false,
+      editing: emptyMutex()
+    }
+  },
+  computed: {
+    rows() {
+      return activityStore.mutexGroups
+    }
+  },
+  methods: {
+    basisText(value) {
+      return Array.isArray(value) ? value.join('、') : value || '-'
+    },
+    openDialog(row) {
+      this.editing = row ? { ...emptyMutex(), ...JSON.parse(JSON.stringify(row)) } : emptyMutex()
+      if (!Array.isArray(this.editing.judgeBasis)) this.editing.judgeBasis = []
+      this.dialogVisible = true
+    },
+    save() {
+      if (!this.editing.name) return ElMessage.warning('互斥组名称必填')
+      if (!this.editing.activities) return ElMessage.warning('互斥活动必填')
+      if (!this.editing.judgeBasis.length) return ElMessage.warning('判断依据至少选择一项')
+      if (this.editing.realtimeBlock && !this.editing.blockText) return ElMessage.warning('开启报名实时拦截时,拦截文案必填')
+      activityStore.saveSimple('mutexGroups', this.editing)
+      ElMessage.success('保存成功')
+      this.dialogVisible = false
+    },
+    toggleStatus(row) {
+      row.status = row.status === '启用' ? '停用' : '启用'
+      activityStore.saveSimple('mutexGroups', row)
+      ElMessage.success('状态已更新')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.header { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
+.title { font-size: 16px; font-weight: 600; color: #303133; }
+.desc { margin-top: 4px; font-size: 13px; color: #909399; }
+</style>

+ 21 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityNotificationRecord.vue

@@ -0,0 +1,21 @@
+<template>
+  <el-card>
+    <template #header><div class="title">活动通知发送记录</div></template>
+    <el-table :data="rows" border stripe>
+      <el-table-column prop="id" label="记录编号" width="120" />
+      <el-table-column prop="activityName" label="活动名称" min-width="180" />
+      <el-table-column prop="scene" label="触发场景" width="120" />
+      <el-table-column prop="templateName" label="模板名称" min-width="160" />
+      <el-table-column prop="receiverCount" label="发送人数" width="100" />
+      <el-table-column prop="successCount" label="成功" width="90" />
+      <el-table-column prop="failCount" label="失败" width="90" />
+      <el-table-column prop="sendTime" label="发送时间" width="170" />
+      <el-table-column prop="status" label="状态" width="110" />
+    </el-table>
+  </el-card>
+</template>
+<script>
+import activityStore from '@/store/activityStore'
+export default { name: 'ActivityNotificationRecord', computed: { rows() { return activityStore.notificationRecords } } }
+</script>
+<style scoped>.title{font-weight:600}</style>

+ 126 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityProgressTemplate.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="activity-progress-template">
+    <el-card>
+      <template #header>
+        <div class="header">
+          <div>
+            <div class="title">活动进度模板管理</div>
+            <div class="desc">配置移动端达标进度页展示样式,和达标规则、权益发放计划分开维护。</div>
+          </div>
+          <el-button type="primary" @click="openDialog()"><el-icon><Plus /></el-icon>新增模板</el-button>
+        </div>
+      </template>
+      <el-table :data="rows" border stripe>
+        <el-table-column prop="id" label="模板编号" width="120" />
+        <el-table-column prop="name" label="模板名称" min-width="180" />
+        <el-table-column prop="templateType" label="进度类型" width="150" />
+        <el-table-column prop="statusTexts" label="状态文案" min-width="210" show-overflow-tooltip />
+        <el-table-column prop="levels" label="层级/任务展示" min-width="190" show-overflow-tooltip />
+        <el-table-column prop="rewardMode" label="发奖展示规则" min-width="170" show-overflow-tooltip />
+        <el-table-column label="连续达标" width="100" align="center">
+          <template #default="{ row }"><el-tag :type="row.continuousEnabled ? 'warning' : 'info'">{{ row.continuousEnabled ? '是' : '否' }}</el-tag></template>
+        </el-table-column>
+        <el-table-column prop="mgmView" label="MGM视角" width="100" />
+        <el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="row.status === '启用' ? 'success' : 'info'">{{ row.status }}</el-tag></template></el-table-column>
+        <el-table-column prop="updatedAt" label="更新时间" width="170" />
+        <el-table-column label="操作" width="150" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="openDialog(row)">编辑</el-button>
+            <el-button type="primary" link @click="toggle(row)">{{ row.status === '启用' ? '停用' : '启用' }}</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-dialog v-model="visible" :title="form.id ? '编辑进度模板' : '新增进度模板'" width="860px">
+      <el-form :model="form" label-width="130px">
+        <el-divider content-position="left">基础信息</el-divider>
+        <el-row :gutter="16">
+          <el-col :span="12"><el-form-item label="模板名称" required><el-input v-model="form.name" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="进度类型" required><el-select v-model="form.templateType" style="width:100%"><el-option label="多条件多次发奖" value="多条件多次发奖" /><el-option label="多条件单次发奖" value="多条件单次发奖" /><el-option label="MGM专属" value="MGM专属" /></el-select></el-form-item></el-col>
+          <el-col :span="24"><el-form-item label="状态文案" required><el-input v-model="form.statusTexts" placeholder="未达标/已达标/已发奖/无法继续参与" /></el-form-item></el-col>
+        </el-row>
+
+        <el-divider content-position="left">层级与奖励展示</el-divider>
+        <el-form-item label="层级/任务展示" required><el-input v-model="form.levels" type="textarea" :rows="3" placeholder="如:达标50万、达标100万;开户、资产、消费" /></el-form-item>
+        <el-form-item label="发奖展示规则" required><el-input v-model="form.rewardMode" placeholder="如:每层级独立发奖、全部条件满足后发一次奖" /></el-form-item>
+        <el-form-item label="未达标按钮文案"><el-input v-model="form.unreachedButtonText" placeholder="默认:查看达标详情" /></el-form-item>
+
+        <el-divider content-position="left">连续达标展示</el-divider>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="是否连续达标"><el-switch v-model="form.continuousEnabled" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="观察周期X"><el-input-number v-model="form.continuousX" :min="1" :max="24" /></el-form-item></el-col>
+          <el-col :span="8"><el-form-item label="达标月份Y"><el-input-number v-model="form.continuousY" :min="1" :max="24" /></el-form-item></el-col>
+          <el-col :span="24"><el-form-item label="连续规则说明"><el-input v-model="form.continuousRule" /></el-form-item></el-col>
+        </el-row>
+
+        <el-divider content-position="left">MGM专属展示</el-divider>
+        <el-row :gutter="16">
+          <el-col :span="8"><el-form-item label="MGM视角"><el-select v-model="form.mgmView" style="width:100%"><el-option label="是" value="是" /><el-option label="否" value="否" /></el-select></el-form-item></el-col>
+          <el-col :span="16"><el-form-item label="被推荐人状态来源"><el-input v-model="form.referredStatusSource" placeholder="MGM标签、推荐关系、活动报名记录" /></el-form-item></el-col>
+          <el-col :span="24"><el-form-item label="未达标原因文案"><el-input v-model="form.unreachedReasonText" type="textarea" :rows="2" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="推荐人视角按钮"><el-input v-model="form.referrerButtonText" placeholder="查看达标详情" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="状态"><el-select v-model="form.status" style="width:100%"><el-option label="启用" value="启用" /><el-option label="停用" value="停用" /></el-select></el-form-item></el-col>
+        </el-row>
+      </el-form>
+      <template #footer><el-button @click="visible=false">取消</el-button><el-button type="primary" @click="save">保存</el-button></template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { ElMessage } from 'element-plus'
+import activityStore from '@/store/activityStore'
+
+export default {
+  name: 'ActivityProgressTemplate',
+  data() {
+    return { visible: false, form: {} }
+  },
+  computed: {
+    rows() { return activityStore.progressTemplates }
+  },
+  methods: {
+    openDialog(row) {
+      this.form = row ? { ...row } : {
+        templateType: '多条件多次发奖',
+        statusTexts: '未达标/已达标/已发奖/无法继续参与',
+        levels: '',
+        rewardMode: '',
+        unreachedButtonText: '查看达标详情',
+        continuousEnabled: false,
+        continuousX: 12,
+        continuousY: 8,
+        continuousRule: '',
+        mgmView: '否',
+        referredStatusSource: '',
+        unreachedReasonText: '',
+        referrerButtonText: '查看达标详情',
+        status: '启用'
+      }
+      this.visible = true
+    },
+    save() {
+      if (!this.form.name || !this.form.templateType || !this.form.levels || !this.form.rewardMode) {
+        return ElMessage.warning('模板名称、进度类型、层级展示和发奖展示规则必填')
+      }
+      if (this.form.continuousEnabled && Number(this.form.continuousY) > Number(this.form.continuousX)) {
+        return ElMessage.warning('连续达标月份Y不能大于观察周期X')
+      }
+      activityStore.saveSimple('progressTemplates', this.form)
+      this.visible = false
+      ElMessage.success('保存成功')
+    },
+    toggle(row) {
+      row.status = row.status === '启用' ? '停用' : '启用'
+      activityStore.saveSimple('progressTemplates', row)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.header { display:flex; justify-content:space-between; align-items:center; gap:16px; }
+.title { font-size:16px; font-weight:600; }
+.desc { margin-top:4px; color:#909399; font-size:13px; }
+</style>

+ 76 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityReport.vue

@@ -0,0 +1,76 @@
+<template>
+  <div class="activity-report">
+    <el-row :gutter="16" class="summary-row">
+      <el-col :span="6"><el-card><div class="metric"><span>活动总数</span><strong>{{ activities.length }}</strong></div></el-card></el-col>
+      <el-col :span="6"><el-card><div class="metric"><span>已发布</span><strong>{{ publishedCount }}</strong></div></el-card></el-col>
+      <el-col :span="6"><el-card><div class="metric"><span>累计报名</span><strong>{{ totalSigned }}</strong></div></el-card></el-col>
+      <el-col :span="6"><el-card><div class="metric"><span>互斥活动</span><strong>{{ mutexCount }}</strong></div></el-card></el-col>
+    </el-row>
+
+    <el-card>
+      <template #header>
+        <div class="title">活动报表/进度查询</div>
+      </template>
+      <el-table :data="activities" border stripe>
+        <el-table-column prop="id" label="活动编号" width="130" />
+        <el-table-column prop="name" label="活动名称" min-width="180" />
+        <el-table-column label="报名进度" width="220">
+          <template #default="{ row }">
+            <el-progress :percentage="signupPercent(row)" />
+          </template>
+        </el-table-column>
+        <el-table-column label="达标配置" min-width="160">
+          <template #default="{ row }">{{ row.progressEnabled ? `${row.levels.length} 层级 / ${taskCount(row)} 任务` : '未开启' }}</template>
+        </el-table-column>
+        <el-table-column label="奖品" min-width="160">
+          <template #default="{ row }">{{ row.reward.name }}({{ row.reward.type }})</template>
+        </el-table-column>
+        <el-table-column label="互斥状态" min-width="180">
+          <template #default="{ row }">
+            <el-tag v-if="row.mutexEnabled" type="warning">{{ row.mutexGroup }}</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="updatedAt" label="更新时间" width="170" />
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import activityStore from '@/store/activityStore'
+
+export default {
+  name: 'ActivityReport',
+  computed: {
+    activities() {
+      return activityStore.activities
+    },
+    publishedCount() {
+      return this.activities.filter((item) => item.status === 'published').length
+    },
+    totalSigned() {
+      return this.activities.reduce((sum, item) => sum + Number(item.signedCount || 0), 0)
+    },
+    mutexCount() {
+      return this.activities.filter((item) => item.mutexEnabled).length
+    }
+  },
+  methods: {
+    signupPercent(row) {
+      if (!row.quotaEnabled || !row.quota) return 100
+      return Math.min(100, Math.round((row.signedCount / row.quota) * 100))
+    },
+    taskCount(row) {
+      return row.levels.reduce((sum, level) => sum + level.tasks.length, 0)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.summary-row { margin-bottom: 16px; }
+.metric { display: flex; flex-direction: column; gap: 8px; color: #606266; }
+.metric strong { font-size: 28px; color: #303133; }
+.title { font-weight: 600; }
+</style>

+ 70 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/ActivityView.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="activity-view">
+    <el-card v-if="activity">
+      <template #header>
+        <div class="page-header">
+          <span>查看活动</span>
+          <el-button @click="$router.push('/customization/hsbc/activities')">返回</el-button>
+        </div>
+      </template>
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="活动编号">{{ activity.id }}</el-descriptions-item>
+        <el-descriptions-item label="活动名称">{{ activity.name }}</el-descriptions-item>
+        <el-descriptions-item label="活动分类">{{ activity.category }}</el-descriptions-item>
+        <el-descriptions-item label="状态">{{ statusText(activity.status) }}</el-descriptions-item>
+        <el-descriptions-item label="活动对象">{{ activity.target }}</el-descriptions-item>
+        <el-descriptions-item label="报名人数">{{ activity.signedCount }} / {{ activity.quotaEnabled ? activity.quota : '不限' }}</el-descriptions-item>
+        <el-descriptions-item label="报名时间">{{ rangeText(activity.signupTime) }}</el-descriptions-item>
+        <el-descriptions-item label="活动时间">{{ rangeText(activity.activityTime) }}</el-descriptions-item>
+        <el-descriptions-item label="展示规则" :span="2">
+          <el-tag v-if="activity.showOnHome">权益首页</el-tag>
+          <el-tag v-if="activity.showOnAll" type="success">全部活动</el-tag>
+          <el-tag v-for="item in activity.launchChannels" :key="item" type="info">{{ item }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="报名规则" :span="2">
+          {{ activity.needSignup ? '需要报名' : '无需报名' }},
+          {{ activity.needConfirm ? '需要二次确认' : '无需二次确认' }},
+          {{ activity.needExtraInfo ? `补充信息 ${activity.extraForms?.length || 0} 项` : '无需补充信息' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="报名成功标题">{{ activity.signupSuccessTitle || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="报名成功描述">{{ activity.signupSuccessDescription || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="达标规则" :span="2">
+          {{ activity.progressEnabled ? `${activity.progressStyle},${activity.levels.length} 个层级` : '不跟踪进度' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="奖品配置" :span="2">
+          {{ activity.reward.name }} / {{ activity.reward.type }} / {{ activity.reward.buttonText }} -> {{ activity.reward.linkTarget }}
+        </el-descriptions-item>
+        <el-descriptions-item label="互斥规则" :span="2">
+          {{ activity.mutexEnabled ? `${activity.mutexGroup},${activity.blockText}` : '不参与互斥' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="活动内容" :span="2">{{ activity.content || '-' }}</el-descriptions-item>
+      </el-descriptions>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import activityStore from '@/store/activityStore'
+
+export default {
+  name: 'ActivityView',
+  computed: {
+    activity() {
+      return activityStore.getActivity(this.$route.params.id)
+    }
+  },
+  methods: {
+    rangeText(range) {
+      return range && range.length === 2 ? `${range[0]} 至 ${range[1]}` : '-'
+    },
+    statusText(status) {
+      return { draft: '草稿', published: '已发布', offline: '已下线' }[status] || status
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page-header { display: flex; justify-content: space-between; align-items: center; font-weight: 600; }
+.el-tag { margin-right: 6px; }
+</style>

+ 98 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/CreditCardActivityEntry.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="credit-card-activity-entry">
+    <el-card>
+      <template #header><div class="title">信用卡活动入口配置</div></template>
+      <el-form :model="form" label-width="170px" style="max-width: 780px">
+        <el-form-item label="展示客群" required>
+          <el-select
+            v-model="form.visibleCrowdIds"
+            multiple
+            collapse-tags
+            collapse-tags-tooltip
+            clearable
+            placeholder="请选择展示客群,不选代表全部用户展示"
+            class="field-md"
+          >
+            <el-option v-for="item in crowds" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+          <div class="form-tip">数据来自人群管理,不选代表全部用户展示(全部展示不包括潜客)</div>
+        </el-form-item>
+        <el-form-item label="活动图标">
+          <el-upload
+            class="entry-icon-uploader"
+            :show-file-list="false"
+            :auto-upload="false"
+            :on-change="handleIconChange"
+            accept="image/*"
+          >
+            <img v-if="form.icon" :src="form.icon" class="entry-icon-preview" />
+            <el-icon v-else class="entry-icon-placeholder"><Plus /></el-icon>
+          </el-upload>
+          <div class="form-tip">建议尺寸 100 × 100 px,支持 PNG / JPG</div>
+        </el-form-item>
+        <el-form-item label="入口名称" required>
+          <el-input v-model="form.name" placeholder="移动端入口标题" class="field-md" />
+        </el-form-item>
+        <el-form-item label="提示文案">
+          <el-input v-model="form.tip" type="textarea" :rows="3" placeholder="温馨提示:如需查看信用卡活动进度,请点击上方“信用卡活动专区”。" class="field-lg" />
+        </el-form-item>
+        <el-form-item label="跳转地址" required>
+          <el-input v-model="form.link" placeholder="CVP/信用卡权益平台 URL" />
+        </el-form-item>
+        <el-form-item label="启用信用卡活动 tab">
+          <el-switch v-model="form.enableCreditCardTab" active-text="启用" inactive-text="不启用" />
+        </el-form-item>
+        <el-form-item label="更新时间">
+          <span>{{ form.updatedAt }}</span>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="save">保存</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { ElMessage } from 'element-plus'
+import { Plus } from '@element-plus/icons-vue'
+import activityStore from '@/store/activityStore'
+
+export default {
+  name: 'CreditCardActivityEntry',
+  data() {
+    return {
+      Plus,
+      form: { visibleCrowdIds: [], icon: '', enableCreditCardTab: false, ...activityStore.creditCardEntry }
+    }
+  },
+  computed: {
+    crowds() { return activityStore.activityCrowds }
+  },
+  methods: {
+    handleIconChange(file) {
+      const reader = new FileReader()
+      reader.onload = (e) => { this.form.icon = e.target.result }
+      reader.readAsDataURL(file.raw)
+    },
+    save() {
+      if (!this.form.name) return ElMessage.warning('入口名称必填')
+      if (!this.form.link) return ElMessage.warning('跳转地址必填')
+      activityStore.updateCreditCardEntry(this.form)
+      this.form = { ...activityStore.creditCardEntry }
+      ElMessage.success('保存成功')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.title { font-weight: 600; }
+.form-tip { margin-top:4px; color:#909399; font-size:12px; line-height:1.4; }
+.field-md { width:240px; max-width:100%; }
+.field-lg { width:360px; max-width:100%; }
+.entry-icon-uploader :deep(.el-upload) { border:1px dashed #dcdfe6; border-radius:6px; cursor:pointer; width:100px; height:100px; display:flex; align-items:center; justify-content:center; overflow:hidden; background:#fafafa; transition:border-color .2s; }
+.entry-icon-uploader :deep(.el-upload:hover) { border-color:#409eff; }
+.entry-icon-preview { width:100%; height:100%; object-fit:cover; }
+.entry-icon-placeholder { font-size:24px; color:#909399; }
+</style>

+ 2 - 2
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/PageAssembly.vue

@@ -301,7 +301,7 @@
                     </el-row>
                     <el-form-item v-if="btn.linkType === 'app'">
                       <template #label>pageId <span class="required-star">*</span></template>
-                      <el-input v-model="btn.appPageId" size="small" placeholder="例如:moneyPulling" @input="handleButtonJumpFieldChange(btn)" />
+                      <el-input v-model="btn.appPageId" size="small" placeholder="例如:moneyPulling / wechatCoupon / homeMGM" @input="handleButtonJumpFieldChange(btn)" />
                     </el-form-item>
                   </div>
                   <!-- 弹窗配置 -->
@@ -366,7 +366,7 @@
                       </el-row>
                       <el-form-item v-if="btn.dialog.confirmLinkType === 'app'">
                         <template #label>pageId <span class="required-star">*</span></template>
-                        <el-input v-model="btn.dialog.confirmAppPageId" size="small" placeholder="例如:moneyPulling" @input="handleDialogJumpFieldChange(btn.dialog)" />
+                        <el-input v-model="btn.dialog.confirmAppPageId" size="small" placeholder="例如:moneyPulling / wechatCoupon / homeMGM" @input="handleDialogJumpFieldChange(btn.dialog)" />
                       </el-form-item>
                     </div>
                   </div>

+ 1 - 1
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/src/views/PageZoneConfig.vue

@@ -219,7 +219,7 @@
               </el-row>
               <el-form-item v-if="item.jumpType === 'app'">
                 <template #label>pageId <span class="required-star">*</span></template>
-                <el-input v-model="item.appPageId" placeholder="例如:moneyPulling" @input="markModified" />
+                <el-input v-model="item.appPageId" placeholder="例如:moneyPulling / wechatCoupon / homeMGM" @input="markModified" />
               </el-form-item>
             </el-form>
           </div>

+ 760 - 0
工作空间/产研/权益/汇丰全行/产品原型/pc-demo/活动管理-达标与进度.html

@@ -0,0 +1,760 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>进度与奖励配置 - CUPD</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <script src="https://unpkg.com/@phosphor-icons/web"></script>
+    <style>
+        body { background-color: #f0f2f5; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; }
+        .input-focus-ring:focus { outline: none; border-color: #1677ff; box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.2); }
+        .no-scrollbar::-webkit-scrollbar { display: none; }
+        .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
+        .fade-in { animation: fadeIn 0.3s ease-in-out; }
+        @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
+    </style>
+</head>
+<body class="text-gray-800 h-screen flex flex-col overflow-hidden">
+
+    <!-- 顶部步骤条模拟 -->
+    <header class="bg-white shadow-sm shrink-0 z-10 border-b border-gray-200">
+        <div class="max-w-4xl mx-auto py-4">
+            <div class="flex items-center justify-between text-sm w-full bg-gray-50 rounded-lg p-2 border border-gray-100">
+                <div class="flex-1 text-center py-2 text-gray-400">基本信息</div>
+                <div class="text-gray-300"><i class="ph ph-caret-right"></i></div>
+                <div class="flex-1 text-center py-2 text-gray-400">活动规则</div>
+                <div class="text-gray-300"><i class="ph ph-caret-right"></i></div>
+                <div class="flex-1 text-center py-2 font-medium text-blue-600 bg-white shadow-sm rounded">达标进度 (进度与奖励)</div>
+                <div class="text-gray-300"><i class="ph ph-caret-right"></i></div>
+                <div class="flex-1 text-center py-2 text-gray-400">预览</div>
+            </div>
+        </div>
+    </header>
+
+    <!-- 主内容滚动区 -->
+    <main class="flex-1 overflow-y-auto p-6 relative no-scrollbar">
+        <div class="max-w-5xl mx-auto space-y-6 pb-24">
+            
+            <!-- 全局模板切换 -->
+            <div class="bg-white p-6 rounded-lg shadow-sm border border-gray-100 flex flex-wrap items-center gap-8">
+                <div class="flex items-center gap-4">
+                    <label class="font-medium text-gray-800"><span class="text-red-500 mr-1">*</span>活动模板</label>
+                    <select id="templateSelector" class="w-64 px-4 py-2 border border-gray-300 rounded-md text-sm input-focus-ring bg-white font-medium" onchange="switchTemplate()">
+                        <option value="" selected>请选择</option>
+                        <option value="NTB">NTB (新客达标)</option>
+                        <option value="MGM">MGM (推荐有礼)</option>
+                        <option value="LAYER">层层礼</option>
+                    </select>
+                </div>
+
+                <!-- 层层礼:动态显示的层级数量 -->
+                <div id="layerCountContainer" class="hidden flex items-center gap-4 border-l border-gray-200 pl-8">
+                    <label class="font-medium text-gray-800"><span class="text-red-500 mr-1">*</span>层级数量</label>
+                    <input type="number" value="2" min="1" max="10" class="w-32 px-4 py-2 border border-gray-300 rounded-md text-sm input-focus-ring bg-white">
+                </div>
+            </div>
+
+            <!-- ========================================== -->
+            <!-- 模板内容渲染区 -->
+            <!-- ========================================== -->
+            <div id="templateContainer">
+                <!-- 模板内容会注入到这里 -->
+            </div>
+
+        </div>
+    </main>
+
+    <!-- 底部悬浮操作栏 -->
+    <div class="fixed bottom-0 right-0 left-0 bg-white border-t border-gray-200 p-4 px-8 flex justify-center gap-3 z-20 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)]">
+        <button class="px-6 py-2 border border-gray-300 text-gray-700 bg-white rounded shadow-sm hover:bg-gray-50 font-medium text-sm transition-colors w-32">
+            上一步
+        </button>
+        <button onclick="alert('保存成功,进入预览阶段!')" class="px-6 py-2 bg-[#1677ff] text-white rounded shadow-sm hover:bg-blue-600 font-medium text-sm transition-colors w-40 flex items-center justify-center gap-1.5">
+            保存,下一步 <i class="ph-bold ph-arrow-right"></i>
+        </button>
+    </div>
+
+    <!-- ========================================== -->
+    <!-- 以下为隐藏的 HTML 模板片段,用于 JS 动态渲染 -->
+    <!-- ========================================== -->
+
+    <!-- 【组件】空状态 -->
+    <template id="tpl_empty">
+        <div class="bg-white p-16 rounded-lg shadow-sm border border-gray-100 flex flex-col items-center justify-center text-gray-400 fade-in">
+            <i class="ph-light ph-folder-dashed text-6xl mb-4 text-gray-300"></i>
+            <p class="text-base font-medium text-gray-600">当前未配置达标进度与奖励</p>
+            <p class="text-sm mt-1 text-gray-400">若本活动无需配置,可直接点击“保存,下一步”跳过</p>
+        </div>
+    </template>
+
+    <!-- 【组件】通用:奖励配置模块 (积分/权益 切换) -->
+    <template id="tpl_reward_config">
+        <!-- 移除了最外层容器边框,只提供内部排版,避免双层嵌套标题 -->
+        <div class="reward-wrapper">
+            <div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50 header-area">
+                <h3 class="text-base font-semibold text-gray-800">达标奖励配置</h3>
+            </div>
+            <div class="p-6">
+                <!-- 奖励类型选择 -->
+                <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center mb-6">
+                    <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right"><span class="text-red-500 mr-1">*</span>达标发放</label>
+                    <div class="md:col-span-10">
+                        <select class="w-64 px-3 py-1.5 border border-blue-400 rounded text-sm bg-white focus:ring-2 focus:ring-blue-100 outline-none" onchange="toggleRewardType(this)">
+                            <option value="points" selected>积分</option>
+                            <option value="rights">权益</option>
+                        </select>
+                    </div>
+                </div>
+
+                <!-- 积分配置区 (使用 Grid 防止折行混乱) -->
+                <div class="reward-points-area grid grid-cols-1 md:grid-cols-12 gap-6 items-center bg-gray-50 p-4 rounded border border-gray-100">
+                    <div class="md:col-span-6 flex items-center gap-3">
+                        <label class="text-sm font-medium text-gray-700 w-24 text-right shrink-0"><span class="text-red-500 mr-1">*</span>选择积分科目</label>
+                        <select class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white min-w-[200px]">
+                            <option>P001-开户迎新积分</option>
+                            <option>P002-资产达标积分</option>
+                        </select>
+                    </div>
+                    <div class="md:col-span-6 flex items-center gap-3">
+                        <label class="text-sm font-medium text-gray-700 w-24 text-right shrink-0"><span class="text-red-500 mr-1">*</span>积分赠送数量</label>
+                        <div class="flex-1 flex items-center gap-2">
+                            <input type="text" value="3200000" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm">
+                            <span class="text-sm text-gray-500 shrink-0">积分</span>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 权益配置区 (默认隐藏) -->
+                <div class="reward-rights-area hidden space-y-4 bg-gray-50 p-4 rounded border border-gray-100">
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-6 items-center">
+                        <div class="md:col-span-6 flex items-center gap-3">
+                            <label class="text-sm font-medium text-gray-700 w-24 text-right shrink-0"><span class="text-red-500 mr-1">*</span>选择权益</label>
+                            <select class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white min-w-[200px]">
+                                <option>权益ID-权益名称</option>
+                            </select>
+                        </div>
+                        <div class="md:col-span-6 flex items-center gap-3">
+                            <label class="text-sm font-medium text-gray-700 w-24 text-right shrink-0"><span class="text-red-500 mr-1">*</span>权益有效期</label>
+                            <div class="flex-1 flex items-center gap-2">
+                                <input type="text" placeholder="请选择开始时间" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm">
+                                <span class="text-gray-400">-</span>
+                                <input type="text" placeholder="请选择结束时间" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm">
+                            </div>
+                        </div>
+                    </div>
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-6 items-center pt-2">
+                        <div class="md:col-span-6 flex items-center gap-3">
+                            <label class="text-sm font-medium text-gray-700 w-24 text-right shrink-0"><span class="text-red-500 mr-1">*</span>权益赠送次数</label>
+                            <div class="flex-1 flex items-center gap-2">
+                                <select class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white shrink-0">
+                                    <option>整个周期</option>
+                                </select>
+                                <input type="text" value="1" class="w-16 px-3 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                <span class="text-sm text-gray-500 shrink-0">次</span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </template>
+
+    <!-- 模板 1: NTB (新客) -->
+    <template id="tpl_ntb">
+        <div class="space-y-6 fade-in">
+            <!-- 达标进度提示话术 -->
+            <div class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden">
+                <div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50">
+                    <h3 class="text-base font-semibold text-gray-800">达标进度提示话术配置</h3>
+                </div>
+                <div class="p-6 space-y-4">
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">未达标提示</label>
+                        <div class="md:col-span-10">
+                            <input type="text" value="您已报名本活动,请继续保持账户状态" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl">
+                        </div>
+                    </div>
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">已达标提示</label>
+                        <div class="md:col-span-10">
+                            <input type="text" value="您已达标活动规则,可点击下方按钮领取权益" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl">
+                        </div>
+                    </div>
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">达标进度提示</label>
+                        <div class="md:col-span-10">
+                            <input type="text" placeholder="配置?弹窗提示内容" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl">
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 达标规则标签配置 -->
+            <div class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden">
+                <div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50">
+                    <h3 class="text-base font-semibold text-gray-800">达标规则标签配置</h3>
+                </div>
+                <div class="p-6">
+                    <div class="mb-4">
+                        <button type="button" class="px-4 py-1.5 bg-gray-300 text-gray-600 rounded text-sm shadow-sm flex items-center gap-1 cursor-not-allowed">
+                            <i class="ph ph-plus"></i> 添加时间标签
+                        </button>
+                    </div>
+
+                    <div class="p-4 bg-gray-50 rounded border border-gray-200 relative mb-6">
+                        <button class="absolute top-4 right-4 text-red-500 hover:text-red-700"><i class="ph-bold ph-trash text-lg"></i></button>
+                        <div class="flex items-center gap-2">
+                            <span class="text-red-500">*</span><span class="font-medium text-gray-700 text-sm">规则标签</span>
+                            <div class="flex items-center gap-3 flex-1 ml-4">
+                                <select class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>开户时间</option>
+                                </select>
+                                <select class="w-24 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>介于</option>
+                                </select>
+                                <div class="flex items-center gap-2">
+                                    <input type="text" value="20260701" class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                    <span class="text-gray-400">-</span>
+                                    <input type="text" value="20260930" class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="mb-4 border-t border-gray-100 pt-6">
+                        <button type="button" class="px-4 py-1.5 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 shadow-sm flex items-center gap-1">
+                            <i class="ph ph-plus"></i> 添加进度标签
+                        </button>
+                    </div>
+
+                    <div class="p-4 bg-gray-50 rounded border border-gray-200 relative">
+                        <button class="absolute top-4 right-4 text-red-500 hover:text-red-700"><i class="ph-bold ph-trash text-lg"></i></button>
+                        <div class="flex items-center gap-2 mb-4">
+                            <span class="text-red-500">*</span><span class="font-medium text-gray-700 text-sm">规则标签1</span>
+                            <div class="flex items-center gap-3 flex-1 ml-3">
+                                <select class="w-48 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>TRB值(含家庭账户)</option>
+                                </select>
+                                <select class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>大于等于</option>
+                                </select>
+                                <input type="text" value="500000" class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm">
+                            </div>
+                        </div>
+
+                        <div class="space-y-3 pl-20">
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-32 shrink-0">开户当月是否必须达标</label>
+                                <select class="w-24 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>是</option>
+                                </select>
+                            </div>
+                            <div class="flex items-center gap-6 mt-2 flex-wrap">
+                                <div class="flex items-center gap-2">
+                                    <span class="text-red-500">*</span><label class="text-sm text-gray-600">是否连续达标</label>
+                                    <select class="w-24 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                        <option>是</option>
+                                    </select>
+                                </div>
+                                <div class="flex items-center gap-2">
+                                    <span class="text-sm text-gray-600">连续</span>
+                                    <input type="text" value="3" class="w-16 px-2 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                    <span class="text-sm text-gray-600">个月</span>
+                                </div>
+                                <div class="flex items-center gap-2 border-l border-gray-300 pl-4">
+                                    <span class="text-red-500">*</span><label class="text-sm text-gray-600">是否允许断开</label>
+                                    <select class="w-24 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                        <option>是</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="flex items-center gap-3 pt-2">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">标签描述</label>
+                                <input type="text" value="资产" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">标签未达标提示</label>
+                                <input type="text" value="未满50万" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">标签已达标提示</label>
+                                <input type="text" value="已满50万" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                            <div class="flex items-center gap-3">
+                                <span class="text-transparent">*</span><label class="text-sm text-gray-600 w-24 shrink-0">标签进度提示</label>
+                                <input type="text" placeholder="配置?弹窗提示内容" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 注入达标奖励配置 -->
+            <div id="ntbRewardContainer" class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden">
+                <!-- 奖励配置模块将注入于此 -->
+            </div>
+        </div>
+    </template>
+
+    <!-- 模板 2: MGM -->
+    <template id="tpl_mgm">
+        <div class="space-y-6 fade-in">
+            <!-- 注入达标奖励配置的容器 (MGM 将直接包含奖励模块,无外层边框重复) -->
+            <div id="mgmRewardContainer" class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden">
+                <!-- 奖励配置模块将注入于此 -->
+            </div>
+
+            <!-- 达标规则标签配置 -->
+            <div class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden">
+                <div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50 flex items-center gap-4">
+                    <h3 class="text-base font-semibold text-gray-800">达标规则标签配置</h3>
+                    <div class="flex items-center gap-2 text-sm ml-8">
+                        <span class="text-red-500">*</span><span class="text-gray-700">已达标展示标签</span>
+                        <select class="px-3 py-1 border border-gray-300 rounded bg-white w-40">
+                            <option>客户MSC Group</option>
+                        </select>
+                    </div>
+                </div>
+                <div class="p-6">
+                    <div class="mb-4 border-b border-gray-100 pb-4">
+                        <button type="button" class="px-4 py-1.5 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 shadow-sm flex items-center gap-1">
+                            <i class="ph ph-plus"></i> 添加进度标签
+                        </button>
+                    </div>
+
+                    <!-- 规则标签 1 -->
+                    <div class="p-4 bg-gray-50 rounded border border-gray-200 relative mb-4">
+                        <button class="absolute top-4 right-4 text-red-500 hover:text-red-700"><i class="ph-bold ph-trash text-lg"></i></button>
+                        <div class="flex items-center gap-2 mb-4">
+                            <span class="text-red-500">*</span><span class="font-medium text-gray-700 text-sm w-20 shrink-0">规则标签1</span>
+                            <div class="flex-1 grid grid-cols-1 md:grid-cols-12 gap-4">
+                                <select class="col-span-1 md:col-span-3 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>开户时间</option>
+                                </select>
+                                <select class="col-span-1 md:col-span-2 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>介于</option>
+                                </select>
+                                <div class="col-span-1 md:col-span-7 flex items-center gap-2">
+                                    <input type="text" value="20260701" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                    <span class="text-gray-400">-</span>
+                                    <input type="text" value="20260930" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="space-y-3 pl-[5.5rem]">
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">已达标提示话术</label>
+                                <input type="text" value="已开户" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">待达标提示话术</label>
+                                <input type="text" value="未开户" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">不达标原因</label>
+                                <input type="text" value="未在指定时间内开户" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                        </div>
+                    </div>
+
+                    <!-- 规则标签 2 -->
+                    <div class="p-4 bg-gray-50 rounded border border-gray-200 relative">
+                        <button class="absolute top-4 right-4 text-red-500 hover:text-red-700"><i class="ph-bold ph-trash text-lg"></i></button>
+                        <div class="flex items-center gap-2 mb-4">
+                            <span class="text-red-500">*</span><span class="font-medium text-gray-700 text-sm w-20 shrink-0">规则标签2</span>
+                            <div class="flex-1 grid grid-cols-1 md:grid-cols-12 gap-4">
+                                <select class="col-span-1 md:col-span-4 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>TRB值(含家庭账户)</option>
+                                </select>
+                                <select class="col-span-1 md:col-span-3 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                    <option>大于等于</option>
+                                </select>
+                                <input type="text" value="500000" class="col-span-1 md:col-span-5 px-3 py-1.5 border border-gray-300 rounded text-sm">
+                            </div>
+                        </div>
+                        <div class="flex items-center gap-4 mb-4 pl-[5.5rem] flex-wrap">
+                            <div class="flex items-center gap-2">
+                                <span class="text-red-500">*</span><span class="text-sm text-gray-600">是否连续达标</span>
+                                <select class="px-3 py-1.5 border border-gray-300 rounded text-sm bg-white w-24">
+                                    <option>否</option>
+                                </select>
+                            </div>
+                            <div class="flex items-center gap-2 border-l border-gray-300 pl-4">
+                                <span class="text-red-500">*</span><span class="text-sm text-gray-600">开户后</span>
+                                <input type="text" value="3" class="w-16 px-2 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                <span class="text-sm text-gray-600">个月内,达标任意</span>
+                                <input type="text" value="1" class="w-16 px-2 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                <span class="text-sm text-gray-600">个月</span>
+                            </div>
+                        </div>
+                        <div class="space-y-3 pl-[5.5rem]">
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">已达标提示话术</label>
+                                <input type="text" value="已升级为卓越理财客户" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">待达标提示话术</label>
+                                <input type="text" value="未升级为卓越理财客户" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                            <div class="flex items-center gap-3">
+                                <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-24 shrink-0">不达标原因</label>
+                                <input type="text" value="未在活动期间升级为卓越理财客户" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 被推荐进度配置 (MGM专属) -->
+            <div class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden">
+                <div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50">
+                    <h3 class="text-base font-semibold text-gray-800">被推荐进度配置</h3>
+                </div>
+                <div class="p-6 space-y-4">
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right"><span class="text-red-500 mr-1">*</span>规则标签1描述</label>
+                        <div class="md:col-span-10">
+                            <input type="text" value="开户时间" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl">
+                        </div>
+                    </div>
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right"><span class="text-red-500 mr-1">*</span>规则标签2描述</label>
+                        <div class="md:col-span-10">
+                            <input type="text" value="基础资产达标 | 月内日均/家庭月日均人均余额≥50万" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl">
+                        </div>
+                    </div>
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right"><span class="text-red-500 mr-1">*</span>是否有进阶奖励</label>
+                        <div class="md:col-span-10">
+                            <select class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                <option>是</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center bg-gray-50 p-4 rounded border border-gray-200">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right"><span class="text-red-500 mr-1">*</span>进阶奖励规则标签</label>
+                        <div class="md:col-span-10 flex flex-wrap items-center gap-3">
+                            <select class="px-3 py-1.5 border border-gray-300 rounded text-sm bg-white w-48">
+                                <option>TRB值(含家庭账户)</option>
+                            </select>
+                            <select class="px-3 py-1.5 border border-gray-300 rounded text-sm bg-white w-32">
+                                <option>大于等于</option>
+                            </select>
+                            <input type="text" value="6000000" class="px-3 py-1.5 border border-gray-300 rounded text-sm w-40">
+                        </div>
+                    </div>
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right"><span class="text-red-500 mr-1">*</span>进阶奖励规则标签描述</label>
+                        <div class="md:col-span-10">
+                            <input type="text" value="是否完成尊尚升级" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl">
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </template>
+
+    <!-- 模板 3: 层层礼 -->
+    <template id="tpl_layer">
+        <div class="space-y-6 fade-in">
+            <!-- 达标层级配置主体 (使用 Tabs) -->
+            <div class="bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden">
+                <div class="px-6 py-4 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
+                    <h3 class="text-base font-semibold text-gray-800">达标层级配置</h3>
+                </div>
+                
+                <!-- Tabs 导航 -->
+                <div class="flex border-b border-gray-200 px-6">
+                    <button class="layer-tab px-6 py-3 font-medium text-sm text-blue-600 border-b-2 border-blue-600 bg-blue-50/30" onclick="switchLayerTab(this)">层级1</button>
+                    <button class="layer-tab px-6 py-3 font-medium text-sm text-gray-500 hover:text-gray-700" onclick="switchLayerTab(this)">层级2</button>
+                </div>
+
+                <div class="p-6">
+                    <!-- 层级基础信息 -->
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center mb-6">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">可参与人群</label>
+                        <div class="md:col-span-10">
+                            <select class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-xl">
+                                <option>人群编号-人群名称</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center mb-8">
+                        <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">层级标题</label>
+                        <div class="md:col-span-10">
+                            <input type="text" value="卓越层级礼. 层级1(月日均≥50万)" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl">
+                        </div>
+                    </div>
+
+                    <div class="mb-4">
+                        <button type="button" class="px-4 py-1.5 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 shadow-sm flex items-center gap-1">
+                            <i class="ph ph-plus"></i> 添加活动
+                        </button>
+                    </div>
+
+                    <!-- 活动1 卡片 -->
+                    <div class="bg-gray-50 rounded-lg border border-gray-200 p-6 mb-8 relative">
+                        <h4 class="text-base font-semibold text-gray-800 mb-6">活动 1</h4>
+                        
+                        <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center mb-4">
+                            <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">活动标题</label>
+                            <div class="md:col-span-10">
+                                <input type="text" value="活动一:资产达标" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl bg-white">
+                            </div>
+                        </div>
+                        <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center mb-6">
+                            <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">活动描述</label>
+                            <div class="md:col-span-10">
+                                <input type="text" value="完成以下任务获取320, 000汇友荟积分" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl bg-white">
+                            </div>
+                        </div>
+
+                        <div class="mb-4 border-t border-gray-200 pt-4">
+                            <button type="button" class="px-4 py-1.5 bg-blue-500 text-white rounded text-sm hover:bg-blue-600 shadow-sm flex items-center gap-1">
+                                <i class="ph ph-plus"></i> 添加进度标签
+                            </button>
+                        </div>
+
+                        <!-- 活动1 - 规则标签1 -->
+                        <div class="bg-white p-4 rounded border border-gray-200 mb-6 relative">
+                            <button class="absolute top-4 right-4 text-red-500 hover:text-red-700"><i class="ph-bold ph-trash text-lg"></i></button>
+                            <div class="flex items-center gap-2 mb-4">
+                                <span class="text-red-500">*</span><span class="font-medium text-gray-700 text-sm">规则标签1</span>
+                                <div class="flex items-center gap-3 flex-1 ml-3">
+                                    <select class="w-48 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                        <option>TRB值(含家庭账户)</option>
+                                    </select>
+                                    <select class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                        <option>大于等于</option>
+                                    </select>
+                                    <input type="text" value="500000" class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm">
+                                </div>
+                            </div>
+                            <div class="space-y-3 pl-20">
+                                <div class="flex items-center gap-3">
+                                    <span class="text-red-500">*</span><label class="text-sm text-gray-600 w-32 shrink-0">开户当月是否必须达标</label>
+                                    <select class="w-24 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                        <option>否</option>
+                                    </select>
+                                </div>
+                                <div class="flex items-center gap-6 mt-2 flex-wrap">
+                                    <div class="flex items-center gap-2">
+                                        <span class="text-red-500">*</span><label class="text-sm text-gray-600">是否连续达标</label>
+                                        <select class="w-24 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                            <option>是</option>
+                                        </select>
+                                    </div>
+                                    <div class="flex items-center gap-2">
+                                        <span class="text-sm text-gray-600">连续</span>
+                                        <input type="text" value="3" class="w-16 px-2 py-1.5 border border-gray-300 rounded text-sm text-center">
+                                        <span class="text-sm text-gray-600">个月</span>
+                                    </div>
+                                    <div class="flex items-center gap-2 border-l border-gray-300 pl-4">
+                                        <span class="text-red-500">*</span><label class="text-sm text-gray-600">是否允许断开</label>
+                                        <select class="w-24 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                            <option>是</option>
+                                        </select>
+                                    </div>
+                                </div>
+                                <div class="flex items-center gap-3 pt-2">
+                                    <span class="text-transparent">*</span><label class="text-sm text-gray-600 w-24 shrink-0">规则标题</label>
+                                    <input type="text" value="连续3个月资产达标" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                                </div>
+                                <div class="flex items-center gap-3">
+                                    <span class="text-transparent">*</span><label class="text-sm text-gray-600 w-24 shrink-0">规则描述</label>
+                                    <input type="text" placeholder="配置?弹窗提示内容" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                                </div>
+                                <div class="flex items-center gap-3">
+                                    <span class="text-transparent">*</span><label class="text-sm text-gray-600 w-24 shrink-0">标签进度提示</label>
+                                    <input type="text" placeholder="配置?弹窗提示内容" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                                </div>
+                            </div>
+                        </div>
+
+                        <!-- 注入:活动 1 的奖励配置 -->
+                        <div id="layerActivity1Reward" class="bg-white rounded border border-gray-200">
+                            <!-- JS注入区 -->
+                        </div>
+                    </div>
+
+                    <!-- 活动2 卡片 -->
+                    <div class="bg-gray-50 rounded-lg border border-gray-200 p-6 relative">
+                        <div class="flex items-center gap-3 mb-6">
+                            <h4 class="text-base font-semibold text-gray-800">活动 2</h4>
+                            <span class="text-xs text-red-500">*注意:活动2达标前提为活动1</span>
+                        </div>
+                        
+                        <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center mb-4">
+                            <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">活动标题</label>
+                            <div class="md:col-span-10">
+                                <input type="text" value="活动二:信用卡加码礼赠500元微信立减金" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl bg-white">
+                            </div>
+                        </div>
+                        <div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-center mb-6">
+                            <label class="md:col-span-2 text-sm font-medium text-gray-700 text-right">活动描述</label>
+                            <div class="md:col-span-10">
+                                <input type="text" value="完成以下任务赠500元微信立减金" class="w-full px-3 py-1.5 border border-gray-300 rounded text-sm max-w-xl bg-white">
+                            </div>
+                        </div>
+
+                        <div class="mb-4 border-t border-gray-200 pt-4">
+                            <button type="button" class="px-4 py-1.5 bg-blue-500 text-white rounded text-sm hover:bg-blue-600 shadow-sm flex items-center gap-1">
+                                <i class="ph ph-plus"></i> 添加进度标签
+                            </button>
+                        </div>
+
+                        <!-- 活动2 - 规则标签1 -->
+                        <div class="bg-red-50 p-4 rounded border border-red-100 mb-6 relative">
+                            <button class="absolute top-4 right-4 text-red-500 hover:text-red-700"><i class="ph-bold ph-trash text-lg"></i></button>
+                            <div class="flex items-center gap-2 mb-4">
+                                <span class="text-red-500">*</span><span class="font-medium text-gray-700 text-sm">规则标签1</span>
+                                <div class="flex items-center gap-3 flex-1 ml-3">
+                                    <select class="w-48 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                        <option>境外消费</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="space-y-3 pl-20">
+                                <div class="flex items-center gap-3 flex-wrap">
+                                    <div class="flex items-center gap-2">
+                                        <span class="text-red-500">*</span><label class="text-sm text-gray-600">消费时间</label>
+                                        <select class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                            <option>小于等于</option>
+                                        </select>
+                                        <input type="text" value="20261231" class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm">
+                                    </div>
+                                </div>
+                                <div class="flex items-center gap-3 flex-wrap mt-2">
+                                    <div class="flex items-center gap-2">
+                                        <span class="text-red-500">*</span><label class="text-sm text-gray-600">消费维度</label>
+                                        <select class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white">
+                                            <option>笔数</option>
+                                        </select>
+                                        <select class="w-32 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white ml-2">
+                                            <option>大于等于</option>
+                                        </select>
+                                        <input type="text" value="1" class="w-24 px-3 py-1.5 border border-gray-300 rounded text-sm">
+                                    </div>
+                                </div>
+                                <div class="flex items-center gap-3 pt-2">
+                                    <span class="text-transparent">*</span><label class="text-sm text-gray-600 w-24 shrink-0">规则标题</label>
+                                    <input type="text" value="需完成境外任意美元有效消费" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                                </div>
+                                <div class="flex items-center gap-3">
+                                    <span class="text-transparent">*</span><label class="text-sm text-gray-600 w-24 shrink-0">规则描述</label>
+                                    <input type="text" placeholder="配置?弹窗提示内容" class="flex-1 px-3 py-1.5 border border-gray-300 rounded text-sm bg-white max-w-lg">
+                                </div>
+                            </div>
+                        </div>
+
+                        <!-- 注入:活动 2 的奖励配置 -->
+                        <div id="layerActivity2Reward" class="bg-white rounded border border-gray-200">
+                            <!-- JS注入区 -->
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </template>
+
+    <script>
+        // 初始化
+        document.addEventListener('DOMContentLoaded', () => {
+            switchTemplate(); 
+        });
+
+        // 切换模板主逻辑
+        function switchTemplate() {
+            const selector = document.getElementById('templateSelector');
+            const container = document.getElementById('templateContainer');
+            const layerCountContainer = document.getElementById('layerCountContainer');
+            const value = selector.value;
+            
+            // 处理层级数量输入框显示
+            if (value === 'LAYER') {
+                layerCountContainer.classList.remove('hidden');
+            } else {
+                layerCountContainer.classList.add('hidden');
+            }
+
+            // 清空并重新注入主内容
+            container.innerHTML = '';
+            
+            let tplId = 'tpl_empty';
+            if (value === 'NTB') tplId = 'tpl_ntb';
+            else if (value === 'MGM') tplId = 'tpl_mgm';
+            else if (value === 'LAYER') tplId = 'tpl_layer';
+
+            // 抓取并注入 HTML
+            const templateElement = document.getElementById(tplId);
+            if(templateElement) {
+                container.innerHTML = templateElement.innerHTML;
+            }
+
+            // 二次注入奖励模块复用组件
+            if (value !== '') {
+                injectRewardModules(value);
+            }
+        }
+
+        // 动态注入通用的奖励模块 (积分/权益)
+        function injectRewardModules(templateType) {
+            const rewardTemplateHtml = document.getElementById('tpl_reward_config').innerHTML;
+
+            if (templateType === 'NTB') {
+                const ntbC = document.getElementById('ntbRewardContainer');
+                if(ntbC) ntbC.innerHTML = rewardTemplateHtml;
+            } else if (templateType === 'MGM') {
+                const mgmC = document.getElementById('mgmRewardContainer');
+                if(mgmC) {
+                    mgmC.innerHTML = rewardTemplateHtml;
+                    // MGM 如果设计图不要“达标奖励配置”这个小标题,可以隐藏 header-area (目前保留)
+                }
+            } else if (templateType === 'LAYER') {
+                const act1 = document.getElementById('layerActivity1Reward');
+                const act2 = document.getElementById('layerActivity2Reward');
+                if(act1) act1.innerHTML = rewardTemplateHtml;
+                if(act2) {
+                    act2.innerHTML = rewardTemplateHtml;
+                    // 层层礼中,第二个奖励设计图为“权益”,我们通过 JS 自动触发切换
+                    const act2Select = act2.querySelector('select');
+                    if(act2Select) {
+                        act2Select.value = 'rights';
+                        toggleRewardType(act2Select);
+                    }
+                }
+            }
+        }
+
+        // 切换奖励类型 (积分 or 权益)
+        function toggleRewardType(selectElement) {
+            // 使用 closest 寻找当前属于哪个奖励区块,避免影响同页面的其他区块
+            const wrapper = selectElement.closest('.reward-wrapper');
+            if (!wrapper) return;
+
+            const pointsArea = wrapper.querySelector('.reward-points-area');
+            const rightsArea = wrapper.querySelector('.reward-rights-area');
+
+            if (selectElement.value === 'points') {
+                if(pointsArea) pointsArea.classList.remove('hidden');
+                if(rightsArea) rightsArea.classList.add('hidden');
+            } else {
+                if(pointsArea) pointsArea.classList.add('hidden');
+                if(rightsArea) rightsArea.classList.remove('hidden');
+            }
+        }
+
+        // 层层礼:简单的 Tabs 切换交互
+        function switchLayerTab(clickedTab) {
+            const tabs = document.querySelectorAll('.layer-tab');
+            tabs.forEach(tab => {
+                tab.classList.remove('text-blue-600', 'border-b-2', 'border-blue-600', 'bg-blue-50/30');
+                tab.classList.add('text-gray-500');
+            });
+            clickedTab.classList.remove('text-gray-500');
+            clickedTab.classList.add('text-blue-600', 'border-b-2', 'border-blue-600', 'bg-blue-50/30');
+        }
+    </script>
+</body>
+</html>

BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo截图/MGM.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo截图/NTB.png


BIN
工作空间/产研/权益/汇丰全行/产品原型/pc-demo截图/层层礼.png


BIN
工作空间/产研/权益/汇丰全行/测试用例/渠道管理_测试用例.xlsx


Fichier diff supprimé car celui-ci est trop grand
+ 27 - 0
工作空间/产研/权益/汇丰全行/测试用例/渠道管理_测试用例.xlsx.inspect.ndjson


BIN
工作空间/产研/权益/汇丰全行/测试用例/组装页2_测试用例_更新版.xlsx


Fichier diff supprimé car celui-ci est trop grand
+ 33 - 0
工作空间/产研/权益/汇丰全行/测试用例/组装页2_测试用例_更新版.xlsx.inspect.ndjson


BIN
工作空间/产研/权益/汇丰全行/测试用例/组装页_测试用例_基于原表更新版.xlsx


Fichier diff supprimé car celui-ci est trop grand
+ 2 - 0
工作空间/产研/权益/汇丰全行/测试用例/组装页_测试用例_基于原表更新版.xlsx.inspect.ndjson


BIN
工作空间/产研/权益/汇丰全行/需求文档/Copy of CUPD管理台0108-问题提出版_260119 (002).xlsx


BIN
工作空间/产研/权益/汇丰全行/需求文档/Reward Platform活动模块方案细化(1).pptx


+ 0 - 0
工作空间/产研/权益/汇丰全行/需求文档/新建 文本文档.txt


+ 883 - 0
工作空间/产研/权益/汇丰全行/需求文档/权益平台活动模块_产品文档_后台菜单进度模板版.md

@@ -0,0 +1,883 @@
+# 权益平台活动模块产品文档(后台菜单 + 进度模板版)
+
+> 本版重点修订后台配置部分:将后台从“功能模块堆叠”改为“菜单化配置”,并新增“活动进度模板管理”,用于承接 PPT 中三种活动进度页样式。  
+> 资料依据:用户上传的 PPT、视频讲解、Excel 后台表格《活动管理/客群管理/内容管理/权益管理/渠道管理/活动进度查询》,以及上一版《权益平台活动模块\_产品文档.md》。
+
+* * *
+
+## 1\. 本版核心结论
+
+### 1.1 这版解决什么问题
+
+上一版后台配置的方向是对的,但仍有一个关键问题:**后台配置和移动端页面之间的关系还不够清楚,尤其是“活动进度页”没有单独抽象出来。**
+
+PPT 中移动端不仅有活动列表、活动详情、报名链路,还包含复杂的活动进度页:
+
+1.  多个达标条件,多次发奖;
+2.  多个达标条件,单次发奖;
+3.  MGM 专属进度展示。
+
+这三种不是简单字段,而是不同的**移动端进度页模板**。如果全部塞进“活动管理-编辑页”,会导致活动编辑页过重,也不利于后续复用和维护。
+
+因此,本版后台设计改为:
+
+```text
+活动管理 = 活动主菜单,只做活动编排
+客群管理 = 配置谁能看、谁能报名、谁能达标
+报名表单管理 = 配置报名时额外收集什么信息
+活动进度模板管理 = 配置移动端进度页长什么样
+达标规则管理 = 配置系统怎么判断是否达标
+权益发放计划管理 = 配置达标后发什么、怎么跳转
+活动互斥组管理 = 配置哪些活动互斥、命中后怎么提示
+活动投放管理 = 配置链接、二维码、渠道、首页/MobileX版位
+通知模板管理 = 配置报名、活动提醒、达标发奖通知
+活动报表/进度查询 = 查询报名、达标、发放结果
+```
+
+活动创建时不直接写死复杂规则,而是**选择引用**这些菜单中已配置好的内容。
+
+* * *
+
+### 1.2 后台能不能配置出 PPT 的移动端内容
+
+结论:**可以覆盖大部分 PPT 移动端要求,但必须新增“活动进度模板管理”,并把“达标规则”和“进度页展示”拆开。**
+
+| PPT 移动端内容 | 是否可配置 | 对应后台菜单 | 说明  |
+| --- | --- | --- | --- |
+| 权益首页活动模块 | 可配置 | 活动管理 / 活动投放管理 | 配置是否展示到首页、展示时间、展示客群、版位。 |
+| 全部活动列表 | 可配置 | 活动管理 | 配置是否展示到全部活动、活动小图、名称、时间。 |
+| 我的活动-进行中/已结束 | 可配置 | 活动管理 + 活动状态规则 | 报名活动报名后进入;无需报名活动活动期间自动进入。 |
+| 活动详情页-未报名 | 可配置 | 活动管理 | 配置头图、名称、对象、报名时间、活动时间、地点、内容、达标层级摘要。 |
+| 报名确认页 | 可配置 | 活动管理 | 配置是否需要报名确认、确认文案。 |
+| 补充信息收集页 | 可配置 | 报名表单管理 | 配置最多 2 个补充信息字段,例如航司会员号。 |
+| 报名成功/失败 | 可配置 + 系统判断 | 活动管理 / 报名规则 / 互斥组 | 名额满、不符合条件、互斥拦截等失败原因由系统判断。 |
+| 活动详情页-已报名/无需报名 | 可配置 | 活动管理 + 活动进度模板管理 + 权益发放计划 | 决定按钮展示“查看更多活动/查看达标进度/查看权益”。 |
+| 达标进度页三种样式 | 需要新增菜单 | 活动进度模板管理 | 多条件多次发奖、多条件单次发奖、MGM 专属。 |
+| 达标任务与标签判断 | 可配置 | 达标规则管理 / 客群管理 | 配置标签、条件、连续达标 X/Y/Z。 |
+| 奖励按钮与跳转 | 可配置 | 权益发放计划管理 | 根据奖励类型映射“查看积分奖励/前往领取/查看奖励”。 |
+| 互斥 Case A/B/C | 可配置 | 活动互斥组管理 | 配置互斥组、判断依据、拦截文案、T+1 修正。 |
+| 信用卡活动入口 | 可配置 | 信用卡活动入口配置 / 活动投放管理 | 先作为入口配置,不建议第一版完整纳入活动体系。 |
+
+* * *
+
+### 1.3 哪些是 PPT 明确有的,哪些是补齐的
+
+| 类型  | 内容  | 说明  |
+| --- | --- | --- |
+| PPT 明确有 | 活动列表、我的活动、活动详情、报名确认、补充信息、达标进度、互斥 Case、信用卡入口 | 这是移动端必须支持的展示和流程。 |
+| Excel 后台表已有 | 活动管理、客群管理、内容管理、权益管理、渠道管理、活动进度查询 | Excel 中已经出现大量后台字段,但部分字段混在同一张“活动管理”表里。 |
+| 本版补齐 | 活动进度模板管理、活动互斥组管理、报名表单管理、活动投放管理、通知模板管理 | 这些是为了让后台配置可复用、可维护、可被活动选择引用。 |
+| 本版拆分 | 进度模板 ≠ 达标规则 ≠ 权益发放计划 | 避免把移动端展示、达标判断、奖励发放三类不同能力混在一起。 |
+
+* * *
+
+## 2\. 后台菜单总览
+
+### 2.1 菜单结构
+
+| 菜单  | 是否独立 | 菜单定位 | 活动创建时怎么用 |
+| --- | --- | --- | --- |
+| 活动管理 | 必须独立 | 创建活动、编辑活动、审核发布、关联其他配置 | 主菜单,选择客群、表单、进度模板、达标规则、权益计划、互斥组。 |
+| 客群管理 | 必须独立 | 配置客户范围 | 活动中选择展示客群、报名客群;达标规则中选择达标客群。 |
+| 报名表单管理 | 必须独立 | 配置报名补充信息 | 活动报名设置中选择表单。 |
+| 活动进度模板管理 | 必须独立 | 配置移动端进度页样式 | 活动开启进度跟踪后选择进度模板。 |
+| 达标规则管理 | 必须独立 | 配置系统如何判断达标 | 活动或进度模板中选择达标规则。 |
+| 权益发放计划管理 | 必须独立或放在权益管理下 | 配置达标后发什么奖励 | 活动/达标层级中选择发放计划。 |
+| 活动互斥组管理 | 必须独立 | 配置活动互斥关系 | 活动报名设置中选择互斥组。 |
+| 活动投放管理 | 建议独立 | 配置链接、二维码、渠道、版位 | 选择活动后生成渠道链接和投放物料。 |
+| 通知模板管理 | 可放内容管理下 | 配置短信/通知模板 | 活动或发放计划中选择模板。 |
+| 活动报表/进度查询 | 必须独立 | 查询报名、达标、发放结果 | 不参与活动创建,只做运营查询。 |
+| 信用卡活动入口配置 | 可放投放或系统配置下 | 控制信用卡专区入口 | 配置是否展示、入口文案、跳转地址。 |
+
+* * *
+
+### 2.2 后台配置的核心关系
+
+```text
+活动管理
+  ├─ 选择展示客群 → 控制首页/全部活动是否可见
+  ├─ 选择报名客群 → 控制活动详情是否可报名
+  ├─ 选择报名表单 → 控制补充信息页展示什么字段
+  ├─ 选择进度模板 → 控制报名后进度页长什么样
+  ├─ 选择达标规则 → 控制系统怎么判断是否达标
+  ├─ 选择权益发放计划 → 控制达标后展示什么奖励按钮、跳哪里
+  ├─ 选择互斥组 → 控制是否拦截其他新客活动
+  ├─ 选择通知模板 → 控制是否发报名/开始/发奖通知
+  └─ 选择投放配置 → 控制链接、二维码、MobileX/权益首页版位
+```
+
+* * *
+
+## 3\. 菜单一:活动管理
+
+### 3.1 菜单定位
+
+活动管理是整个活动模块的主菜单,负责创建一个活动,并把其他菜单中的配置组合起来。活动管理不应该承载所有复杂规则,否则字段会非常多,且无法复用。
+
+活动管理主要解决 4 个问题:
+
+1.  这个活动是什么;
+2.  这个活动在哪里展示;
+3.  用户能不能报名、怎么报名;
+4.  报名后是否有关联进度、奖励、互斥、通知。
+
+* * *
+
+### 3.2 活动管理 - 列表页
+
+查询条件
+
+| 查询项 | 类型  | 说明  |
+| --- | --- | --- |
+| 活动 ID | 输入框 | 精确查询活动编号。 |
+| 活动名称 | 输入框 | 支持活动标题(对内)或活动名称(对客)模糊查询。 |
+| 活动类型 | 下拉框 | 普通活动、卓越迎新礼、层层礼、MGM、信用卡活动等。 |
+| 活动状态 | 下拉框 | 草稿、待审核、审核通过、已发布、已下线、已结束、无效。 |
+| 审核状态 | 下拉框 | 未提交、待审核、审核通过、审核驳回。 |
+| 活动时间 | 日期范围 | 按活动开始/结束时间筛选。 |
+| 创建人 | 输入框 | 查询某运营创建的活动。 |
+
+列表字段
+
+| 字段名称 | 字段含义 | 移动端关系 |
+| --- | --- | --- |
+| 活动 ID | 系统生成唯一编号 | 移动端查询活动详情时使用。 |
+| 活动标题(对内) | 后台识别名称 | 不对客展示。 |
+| 活动名称(对客) | 客户看到的活动名称 | 展示在首页、全部活动、我的活动、详情页。 |
+| 活动类型 | 活动分类 | 影响后台筛选、互斥组选择、报表归类。 |
+| 活动时间 | 活动进行时间 | 展示在列表和详情页。 |
+| 报名时间 | 可报名时间 | 影响报名按钮是否可点击。 |
+| 展示状态 | 是否对客展示 | 控制首页/全部活动是否可见。 |
+| 活动状态 | 生命周期状态 | 控制是否可编辑、发布、下线。 |
+| 审核状态 | 审核流转状态 | 待审核活动可置顶给审核人。 |
+| 创建人/审核人 | 操作人信息 | 不对客展示。 |
+| 创建时间/更新时间 | 后台记录 | 不对客展示。 |
+| 操作  | 查看/编辑/预览/提交审核/审核/发布/下线 | 预览用于查看移动端效果。 |
+
+操作按钮
+
+| 操作  | 显示条件 | 点击后逻辑 |
+| --- | --- | --- |
+| 新建活动 | 有活动创建权限 | 进入活动新建页。 |
+| 查看  | 有查看权限 | 进入只读详情页。 |
+| 编辑  | 草稿/驳回/未发布 | 进入编辑页。已发布活动哪些字段可改需确认。 |
+| 预览  | 活动已保存 | 生成移动端预览页。若有渠道码,预览链接携带追踪码。 |
+| 提交审核 | 必填项完整 | 弹出审核人选择,提交后变为待审核。 |
+| 审核  | 当前用户为审核人 | 审核通过或驳回。 |
+| 发布  | 审核通过且有发布权限 | 发布后按展示规则对客展示。 |
+| 下线  | 已发布活动 | 下线后前端不再新增展示;已报名用户是否仍可查看需确认。 |
+
+* * *
+
+### 3.3 活动管理 - 新建/编辑页
+
+建议使用 Tab 结构,避免一个页面字段过多。
+
+Tab 1:基础信息
+
+| 字段  | 类型  | 是否必填 | 移动端影响 | 说明  |
+| --- | --- | --- | --- | --- |
+| 活动标题(对内) | 文本  | 是   | 不展示 | 后台列表、审核、报表识别。 |
+| 活动名称(对客) | 文本  | 是   | 首页/全部活动/我的活动/详情页 | 建议限制 1-50 字。 |
+| 活动微信分享名称 | 文本  | 否   | 微信分享卡片 | 为空时默认取活动名称。 |
+| 活动描述 | 多行文本 | 否   | 可作为列表副标题或分享描述 | 是否移动端展示需确认。 |
+| 活动详情 | 富文本 | 是   | 活动详情页“活动内容” | 支持文字、图片、链接;链接需白名单。 |
+| 活动类型 | 下拉框 | 是   | 影响展示/互斥/报表 | 示例:卓越迎新礼、层层礼、MGM、普通活动。 |
+| 活动城市 | 下拉/多选 | 否   | 详情页展示 | 如地点足够,可不单独配置。 |
+| 活动地点 | 文本  | 否   | 详情页展示 | 线下活动展示,线上可为空。 |
+| 活动小图 | 图片  | 是   | 首页/列表/我的活动卡片 | 用于活动卡片展示。 |
+| 活动头图 | 图片  | 是   | 活动详情页顶部 | 与小图可不同。 |
+| 分享图片 | 图片  | 否   | 微信分享卡片 | 为空时默认取小图或头图。 |
+| 是否可转发 | 开关  | 是   | 是否允许转发 | PPT 中活动可配置“可转发/不可转发”。 |
+| SCRM activity code | 文本  | 否   | 不展示 | 后续同步/报表使用。 |
+| SCRM channel code | 文本  | 否   | 不展示 | 渠道归因使用。 |
+| Label | 多选  | 否   | 不直接展示 | 需提供 interest 列表。 |
+
+Tab 2:时间与展示
+
+| 字段  | 类型  | 是否必填 | 移动端影响 | 说明  |
+| --- | --- | --- | --- | --- |
+| 活动开始时间 | 日期时间 | 是   | 活动时间展示、状态判断 | 需早于结束时间。 |
+| 活动结束时间 | 日期时间 | 是   | 活动结束后进入已结束 | 已结束活动置灰但可点击查看。 |
+| 展示开始时间 | 日期时间 | 否   | 控制首页/全部活动是否展示 | 为空默认发布后可展示。 |
+| 展示结束时间 | 日期时间 | 否   | 控制活动何时隐藏 | 可与活动结束时间不同。 |
+| 是否展示到权益首页 | 开关  | 是   | 首页活动模块是否展示 | 还需满足展示时间、展示客群、发布状态。 |
+| 是否展示到全部活动 | 开关  | 是   | 全部活动列表是否展示 | 关闭后可仅通过链接/二维码进入。 |
+| 是否展示到我的活动 | 系统规则/开关 | 是   | 我的活动是否显示 | 报名活动报名后显示;无需报名活动活动期间自动显示。 |
+| 是否展示报名人数 | 开关  | 否   | 详情页报名人数区域 | PPT 中写报名人数前端是否展示可选。 |
+
+Tab 3:客群与报名
+
+| 字段  | 类型  | 是否必填 | 移动端影响 | 说明  |
+| --- | --- | --- | --- | --- |
+| 展示客群 | 客群选择 | 是   | 决定客户能否看到活动 | 引用客群管理。展示客群不代表可报名。 |
+| 报名客群 | 客群选择 | 需要报名时是 | 决定详情页是否显示报名按钮 | Excel 备注报名客群为单个,是否支持多选需确认。 |
+| 是否需要报名 | 开关  | 是   | 决定是否走报名链路 | 否:进入报名后/无需报名详情版本。 |
+| 报名开始时间 | 日期时间 | 需要报名时是 | 控制报名按钮 | 是否必须晚于活动开始时间需确认。 |
+| 报名结束时间 | 日期时间 | 需要报名时是 | 超过后不可报名 | 需晚于报名开始时间。 |
+| 是否需要报名确认 | 开关  | 否   | 点击报名后是否进入确认页 | PPT 写可配置不需要该 journey。 |
+| 报名确认文案 | 富文本 | 开启确认时是 | 报名确认页展示 | 支持活动名、互斥提示等变量。 |
+| 是否留资 | 开关  | 否   | 是否进入留资链路 | Excel 中区分留资与补充信息,需确认。 |
+| 关联留资表 | 选择/链接 | 开启留资时是 | 跳转或关联外部留资表 | 可能属于 AO 原生表单。 |
+| 是否需要补充信息 | 开关  | 否   | 是否展示补充信息页/弹窗 | 如航司会员号、酒店会员号。 |
+| 补充信息表 | 表单选择 | 开启补充信息时是 | 决定收集字段 | 引用报名表单管理。 |
+| 第三方跳转链接 | URL | 否   | 提交后跳第三方 | 域名需在白名单。 |
+| 报名人数上限 | 数字/不限 | 否   | 名额满提示 | 并发报名需实时判断。 |
+
+Tab 4:进度与奖励
+
+> 本 Tab 只做选择引用,不在活动管理里配置全部进度细节。
+
+| 字段  | 类型  | 是否必填 | 移动端影响 | 说明  |
+| --- | --- | --- | --- | --- |
+| 是否有进度跟踪 | 开关  | 是   | 已报名详情页按钮是否为“查看达标进度” | 开启后必须选择进度模板和达标规则。 |
+| 进度模板 | 模板选择 | 有进度时是 | 决定进度页样式 | 引用活动进度模板管理。 |
+| 达标规则 | 规则选择 | 有进度时是 | 决定系统如何判断达标 | 引用达标规则管理。 |
+| 是否需要权益发放 | 开关  | 否   | 是否展示奖励入口 | 无奖励活动不展示奖励按钮。 |
+| 权益发放计划 | 计划选择 | 需要发放时是 | 决定奖励按钮和跳转 | 引用权益发放计划管理。 |
+| 无进度是否直接发奖 | 开关  | 否   | 详情页按钮为“查看权益” | 对应 PPT 中“无进度跟踪直接发奖”。 |
+
+Tab 5:互斥与通知
+
+| 字段  | 类型  | 是否必填 | 移动端影响 | 说明  |
+| --- | --- | --- | --- | --- |
+| 是否参与互斥 | 开关  | 否   | 报名时可能被拦截 | 开启后选择互斥组。 |
+| 互斥组 | 互斥组选择 | 开启互斥时是 | 列表不符合条件、报名拦截、无法继续参与 | 引用活动互斥组管理。 |
+| 是否报名成功通知 | 开关  | 否   | 不影响页面 | 选择通知模板。 |
+| 报名成功通知模板 | 模板选择 | 否   | 短信通知 | 引用通知模板管理。 |
+| 活动开始前提醒模板 | 模板选择 | 否   | 短信通知 | 需配置触发时间。 |
+| 达标发奖通知模板 | 模板选择 | 否   | 短信通知 | 和发奖状态关联。 |
+| 活动 owner staff ID | 文本  | 否   | 不展示 | 用于通知、审核或同步。 |
+
+Tab 6:审核发布
+
+| 字段/操作 | 说明  |
+| --- | --- |
+| 审核人 | 提交审核时选择业务审核人。 |
+| 提交审核 | 必填项完整后可提交。 |
+| 审核通过 | 业务审核通过后可发布。 |
+| 审核驳回 | 返回编辑状态,需填写驳回原因。 |
+| 发布  | 发布后活动进入前端展示逻辑。 |
+| 下线  | 下线后前端不再新增展示。 |
+
+* * *
+
+## 4\. 菜单二:客群管理
+
+### 4.1 菜单定位
+
+客群管理用于配置“哪些客户能看到活动、哪些客户能报名、哪些客户算达标”。它不应该在每个活动里重复配置。
+
+### 4.2 客群管理 - 列表页
+
+| 字段  | 说明  |
+| --- | --- |
+| 客群编号 | 系统生成,供活动引用。 |
+| 客群名称 | 如“Premier 新客”“MGM 被推荐人”。 |
+| 客群描述 | 说明客群用途。 |
+| 创建人 | Excel 中提到仅创建人可编辑。 |
+| 最后更新时间 | 最近更新时间。 |
+| 操作  | 编辑/查看/预览名单。 |
+
+### 4.3 客群管理 - 新建/编辑页
+
+| 字段  | 类型  | 是否必填 | 说明  |
+| --- | --- | --- | --- |
+| 客群名称 | 文本  | 是   | 后台识别和活动选择使用。 |
+| 客群描述 | 多行文本 | 否   | 说明适用场景。 |
+| 标签组合 | 标签+条件 | 是   | 支持多个标签组合。 |
+| 操作符 | 下拉  | 是   | 等于、大于、小于、包含等。 |
+| 规则值 | 输入/枚举 | 是   | 根据标签类型决定。 |
+| 排序  | 升序/降序 | 否   | 是否需要传接口需确认。 |
+| Sizing | 数字  | 否   | 最大圈选客户数量。 |
+| 白名单/黑名单 | 文件上传/选择 | 否   | 与标签结果做交集或排除。 |
+| 人群预览 | 查询结果 | 否   | 显示 Customer ID,供 sample check。 |
+
+### 4.4 与移动端关系
+
+| 使用位置 | 移动端影响 |
+| --- | --- |
+| 展示客群 | 决定客户是否能在首页/全部活动看到活动。 |
+| 报名客群 | 决定详情页展示“报名”还是“不符合报名条件”。 |
+| 达标客群 | 决定客户是否在进度页达标。 |
+| 互斥特殊客群 | 可用于 MGM 被推荐人、NTB 新客等判断。 |
+
+* * *
+
+## 5\. 菜单三:报名表单管理
+
+### 5.1 菜单定位
+
+报名表单管理用于配置报名时额外收集的信息,例如航司会员号、酒店会员号。活动创建时只选择表单,不在活动编辑页临时创建字段。
+
+### 5.2 报名表单管理 - 列表页
+
+| 字段  | 说明  |
+| --- | --- |
+| 表单 ID | 系统生成。 |
+| 表单名称 | 后台识别,例如“航司会员号收集表”。 |
+| 表单标题 | 对客展示标题,例如“报名需要您提供 IHG 会员号”。 |
+| 字段数量 | 当前表单包含几个字段,最多建议 2 个。 |
+| 创建人 | 创建账号。 |
+| 更新时间 | 最近更新时间。 |
+| 操作  | 编辑/查看。被活动引用后是否允许编辑需确认。 |
+
+### 5.3 报名表单管理 - 新建/编辑页
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 表单名称 | 文本  | 是   | 不对客展示。 |
+| 表单标题 | 文本  | 是   | 补充信息页/弹窗顶部展示。 |
+| 收集字段名称 1 | 文本  | 是   | 输入框标题,如“航司会员号”。 |
+| 字段 1 输入提示 | 文本  | 否   | placeholder 或说明。 |
+| 字段 1 校验规则 | 规则配置 | 否   | 控制提交校验。 |
+| 收集字段名称 2 | 文本  | 否   | PPT 写最多收集 2 个字段。 |
+| 字段 2 输入提示 | 文本  | 否   | placeholder 或说明。 |
+| 字段 2 校验规则 | 规则配置 | 否   | 控制提交校验。 |
+| 提交按钮文案 | 文本  | 否   | 默认“提交”。 |
+
+### 5.4 需要确认
+
+| 问题  | 原因  |
+| --- | --- |
+| 留资表和补充信息表是否两个概念 | Excel 中同时出现“是否留资/关联留资表”和“补充信息表”。 |
+| 关闭补充信息弹窗是否算报名成功 | PPT 中有“关闭仍判断报名成功,下次进入继续弹窗”的逻辑。 |
+| 第三方跳转后是否需要回调 | 如果点击提交跳第三方就算报名成功,需要确认是否不校验第三方完成情况。 |
+
+* * *
+
+## 6\. 菜单四:活动进度模板管理
+
+### 6.1 为什么必须单独成菜单
+
+PPT 中的“达标进度展示样式”不是普通字段,而是移动端页面模板。至少包含三种类型:
+
+1.  多个达标条件,多次发奖;
+2.  多个达标条件,单次发奖;
+3.  MGM 专属进度展示。
+
+如果放在活动管理里,会导致以下问题:
+
+| 问题  | 影响  |
+| --- | --- |
+| 活动编辑页字段过多 | 配置复杂,运营难理解。 |
+| 进度样式无法复用 | 每个活动都要重新配置一遍。 |
+| MGM 逻辑污染普通活动 | 推荐人/被推荐人视角会让普通进度配置很乱。 |
+| 后续新增样式困难 | 比如新增“阶梯任务 + 排名”样式时需要大改活动管理。 |
+
+因此建议新增:**活动进度模板管理**。
+
+* * *
+
+### 6.2 活动进度模板管理 - 列表页
+
+| 字段  | 说明  |
+| --- | --- |
+| 模板 ID | 系统生成。 |
+| 模板名称 | 后台识别,例如“卓越新户多层达标模板”。 |
+| 进度类型 | 多条件多次发奖 / 多条件单次发奖 / MGM 专属。 |
+| 适用活动类型 | NTB、MGM、层层礼、普通活动等。 |
+| 是否连续达标 | 是/否。 |
+| 层级数量 | 当前模板配置几个进度层级。 |
+| 奖励展示方式 | 单次发奖 / 多次发奖。 |
+| 状态  | 启用 / 停用。 |
+| 关联活动数 | 当前模板被多少活动引用。 |
+| 更新时间 | 最近更新时间。 |
+| 操作  | 查看 / 编辑 / 复制 / 停用 / 预览。 |
+
+* * *
+
+### 6.3 活动进度模板管理 - 新建/编辑页
+
+配置区 1:基础信息
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 模板名称 | 文本  | 是   | 不对客展示。 |
+| 进度类型 | 单选  | 是   | 决定移动端进度页结构。 |
+| 适用活动类型 | 多选  | 否   | 限制哪些活动可选择该模板。 |
+| 对客标题 | 文本  | 否   | 进度页标题。 |
+| 对客说明 | 多行文本 | 否   | 进度页顶部说明。 |
+| 是否启用 | 开关  | 是   | 停用后活动不可选择。 |
+
+配置区 2:进度页状态文案
+
+| 状态  | 配置内容 | 移动端影响 |
+| --- | --- | --- |
+| 进行中 | 页面提示文案 | 进度页顶部提示区。 |
+| 未达标 | 未达标提示文案 | 任务未完成时展示。 |
+| 已达标 | 已达标提示文案 | 达标但未发奖时展示。 |
+| 已发奖 | 已发奖提示文案 | 发奖后展示。 |
+| 已结束 | 活动结束提示文案 | 已结束活动仍可查看。 |
+| 无法继续参与 | 资格关闭提示文案 | 互斥或 T+1 修正后展示。 |
+
+配置区 3:层级展示配置
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 是否展示层级 Tab | 开关  | 否   | 多层级时展示 tab;单层级不展示。 |
+| 层级名称 | 文本  | 是   | 如“达标 50 万”。 |
+| 层级排序 | 数字  | 是   | 控制展示顺序。 |
+| 层级对客文案 | 文本  | 是   | 进度页展示。 |
+| 问号说明 | 富文本 | 否   | 点击问号展示补充说明。 |
+| 点击对比内容 | 富文本 | 否   | 点击对比时展示弹窗。 |
+| 未达标按钮文案 | 文本  | 否   | 默认“查看达标详情”。 |
+| 达标后按钮规则 | 系统规则 | 是   | 根据权益发放计划自动生成。 |
+
+配置区 4:连续达标展示
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 是否连续达标 | 开关  | 否   | 决定是否按月展示进度。 |
+| 活动观察周期 X | 数字  | 连续达标时是 | 例如 12 个月。 |
+| 连续达标月份 Y | 数字  | 连续达标时是 | 例如连续 8 个月。 |
+| 最大观察周期 | 系统限制 | 是   | 建议最多 36 个月。 |
+| 最大连续达标月份 | 系统限制 | 是   | 建议最多 24 个月。 |
+| 月份展示方式 | 系统规则 | 是   | 超过 3 个月横向滑动。 |
+
+配置区 5:MGM 专属展示
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 是否展示推荐人进度 | 开关  | MGM 模板是 | 推荐人可查看被推荐人状态。 |
+| 是否展示被推荐人进度 | 开关  | MGM 模板是 | 被推荐人进度区展示。 |
+| 被推荐人无奖励时是否展示 | 开关  | 否   | PPT 中写无奖励则不展示进度页。 |
+| 未达标原因文案 | 富文本 | 否   | 例如“您的被推荐人已报名参与其他新客活动”。 |
+| 推荐人视角按钮 | 文本  | 否   | 默认“查看达标详情”。 |
+| 被推荐人状态来源 | 规则选择 | 是   | MGM 标签、推荐关系、活动报名记录。 |
+
+* * *
+
+### 6.4 三种进度类型如何配置
+
+| 进度类型 | 适用场景 | 配置重点 | 移动端表现 |
+| --- | --- | --- | --- |
+| 多条件多次发奖 | 层层礼、阶梯式奖励 | 多层级、多奖励计划、每层独立判断 | 达到一层发一层奖励,多个奖励可能依次亮起。 |
+| 多条件单次发奖 | 多任务达标但只发一次奖 | 多条件组合、最终只关联一个奖励计划 | 所有或指定条件满足后,只出现一次奖励按钮。 |
+| MGM 专属 | 亲友推荐 | 推荐人/被推荐人视角、互斥失败文案、推荐关系状态 | 推荐人可看到被推荐人是否达标或被互斥。 |
+
+* * *
+
+### 6.5 与活动管理的关系
+
+活动管理中只需要配置:
+
+| 活动管理字段 | 说明  |
+| --- | --- |
+| 是否有进度跟踪 | 开启后才显示进度模板选择。 |
+| 进度模板 | 选择本菜单中已启用模板。 |
+| 达标规则 | 选择达标规则管理中的规则。 |
+| 权益发放计划 | 选择发放计划,控制按钮和跳转。 |
+
+* * *
+
+## 7\. 菜单五:达标规则管理
+
+### 7.1 菜单定位
+
+达标规则管理负责“系统怎么判断客户是否达标”。它不负责页面长什么样,页面展示由“活动进度模板管理”控制。
+
+简单说:
+
+```text
+活动进度模板 = 前端怎么展示
+达标规则 = 后台怎么判断
+权益发放计划 = 达标后发什么
+```
+
+* * *
+
+### 7.2 达标规则管理 - 列表页
+
+| 字段  | 说明  |
+| --- | --- |
+| 规则 ID | 系统生成。 |
+| 规则名称 | 如“TRB 连续 8 个月达标规则”。 |
+| 规则类型 | 标签规则 / 资产规则 / 连续达标 / 组合规则。 |
+| 适用活动类型 | NTB、层层礼、MGM 等。 |
+| 条件数量 | 当前规则包含几个条件。 |
+| 是否连续达标 | 是/否。 |
+| 状态  | 启用/停用。 |
+| 关联模板数 | 被多少进度模板引用。 |
+| 关联活动数 | 被多少活动引用。 |
+| 操作  | 查看/编辑/复制/停用。 |
+
+* * *
+
+### 7.3 达标规则管理 - 新建/编辑页
+
+| 配置区 | 字段  | 类型  | 是否必填 | 说明  |
+| --- | --- | --- | --- | --- |
+| 基础信息 | 规则名称 | 文本  | 是   | 后台识别。 |
+| 基础信息 | 规则类型 | 下拉  | 是   | 标签规则、连续达标、组合规则。 |
+| 基础信息 | 适用活动类型 | 多选  | 否   | 限制可选择范围。 |
+| 条件配置 | 条件名称 | 文本  | 是   | 如“TRB 达标”。 |
+| 条件配置 | 数据来源 | 下拉  | 是   | 标签、资产、订单、积分、报名记录等。 |
+| 条件配置 | 标签/字段 | 选择  | 是   | 用于判断的标签或字段。 |
+| 条件配置 | 操作符 | 下拉  | 是   | 等于、大于、小于、包含、不包含等。 |
+| 条件配置 | 规则值 | 输入/枚举 | 是   | 例如 TRB >= 500000。 |
+| 条件配置 | 条件关系 | AND/OR | 是   | 多条件组合关系。 |
+| 连续达标 | 观察周期 X | 数字  | 视规则而定 | 例如 12 个月。 |
+| 连续达标 | 连续达标月份 Y | 数字  | 视规则而定 | 例如 8 个月。 |
+| 连续达标 | 达标条件 Z | 条件配置 | 视规则而定 | TRB 口径等。 |
+| 输出结果 | 达标层级 | 下拉/数字 | 否   | 输出给进度模板展示。 |
+| 输出结果 | 达标状态编码 | 枚举  | 是   | 未达标、已达标、已发奖等。 |
+
+### 7.4 与移动端关系
+
+| 达标规则输出 | 移动端展示 |
+| --- | --- |
+| 未达标 | 进度页任务未完成,按钮为“查看达标详情”。 |
+| 已达标未发奖 | 进度页达标状态亮起,但奖励按钮可能未出现或不可点击。 |
+| 已发奖 | 按奖励类型展示“查看积分奖励/前往领取/查看奖励”。 |
+| 无法继续参与 | 进度页或详情页展示资格关闭文案。 |
+
+* * *
+
+## 8\. 菜单六:权益发放计划管理
+
+### 8.1 菜单定位
+
+权益发放计划负责“达标后发什么”。它可放在已有“权益管理”下,但活动不应直接配置券码和发放细节,而是选择发放计划。
+
+### 8.2 发放计划列表页
+
+| 字段  | 说明  |
+| --- | --- |
+| 发放计划 ID | 系统生成。 |
+| 发放计划名称 | 如“达标 50 万发 500 积分”。 |
+| 奖励类型 | 积分、立减金、卡券、电子券、信用卡权益、第三方权益。 |
+| 发放方式 | 自动/手工/第三方。 |
+| 总数量 | 总库存。 |
+| 剩余数量 | 剩余库存。 |
+| 发放状态 | 草稿、已生效、已停用。 |
+| 关联活动数 | 当前被多少活动引用。 |
+| 操作  | 查看/编辑/停用。 |
+
+### 8.3 发放计划编辑页
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 发放计划名称 | 文本  | 是   | 不对客展示。 |
+| 奖励类型 | 下拉  | 是   | 决定按钮文案。 |
+| 权益/礼品 ID | 选择  | 是   | 决定跳转目标。 |
+| 单人发放数量 | 数字  | 是   | 决定客户获得数量。 |
+| 总库存 | 数字  | 否   | 库存不足影响发放。 |
+| 每日发放上限 | 数字  | 否   | 控制发放节奏。 |
+| 是否需要客户领取 | 开关  | 否   | 影响按钮为“前往领取”还是“查看奖励”。 |
+| 跳转地址 | URL/路由 | 否   | 第三方权益或外部权益使用。 |
+| 是否通知 | 开关  | 否   | 发奖后是否发通知。 |
+| 通知模板 | 选择  | 开启通知时是 | 引用通知模板管理。 |
+
+### 8.4 奖励类型与移动端按钮映射
+
+| 奖励类型 | 移动端按钮 | 跳转目标 |
+| --- | --- | --- |
+| 积分  | 查看积分奖励 | 积分明细页。 |
+| 立减金 | 前往领取 / 领取奖励 | 立减金列表或领取页。 |
+| 卡券/电子券 | 查看奖励 | 权益详情页或卡券详情页。 |
+| 信用卡权益 | 查看奖励 | 信用卡权益详情页。 |
+| 第三方发放权益 | 查看奖励 | 第三方权益说明页或权益详情页。 |
+
+* * *
+
+## 9\. 菜单七:活动互斥组管理
+
+### 9.1 菜单定位
+
+活动互斥不建议只作为“活动管理”里的一个开关。它应该独立成互斥组菜单,活动创建时选择互斥组。
+
+互斥组解决的问题是:客户已经参加某个新客活动后,是否还能参加同组其他活动;MGM 与非 MGM 新客活动如何互斥;T+1 标签更新后如何修正客户资格。
+
+* * *
+
+### 9.2 活动互斥组管理 - 列表页
+
+| 字段  | 说明  |
+| --- | --- |
+| 互斥组 ID | 系统生成。 |
+| 互斥组名称 | 如“卓越理财新户活动互斥组”。 |
+| 互斥类型 | NTB 新客互斥、MGM 互斥、自定义互斥。 |
+| 判断依据 | 报名记录、MGM 标签、开户时间、达标状态等。 |
+| 关联活动数 | 当前组下有多少活动。 |
+| 生效状态 | 启用/停用。 |
+| 更新时间 | 最近更新时间。 |
+| 操作  | 查看/编辑/复制/停用。 |
+
+* * *
+
+### 9.3 活动互斥组管理 - 新建/编辑页
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 互斥组名称 | 文本  | 是   | 不对客展示。 |
+| 互斥类型 | 下拉  | 是   | 决定判断逻辑。 |
+| 互斥活动范围 | 活动类型/活动选择 | 是   | 命中后其他活动不可报名。 |
+| 判断依据 | 多选  | 是   | 报名记录、MGM 标签、开户时间等。 |
+| 报名实时拦截 | 开关  | 是   | 点击报名时弹出拦截提示。 |
+| T+1 标签修正 | 开关  | MGM/NTB 场景是 | 次日可能关闭已报名资格。 |
+| 释放规则 | 枚举  | 是   | 活动结束未达标后是否释放资格。 |
+| 列表状态文案 | 文本  | 否   | 如“不符合参与条件”。 |
+| 拦截弹窗文案 | 富文本 | 是   | 报名拦截时展示。 |
+| 无法继续参与文案 | 富文本 | 是   | T+1 后资格关闭时展示。 |
+| 推荐人侧文案 | 富文本 | MGM 场景是 | 推荐人看到被推荐人互斥失败原因。 |
+
+* * *
+
+### 9.4 与 PPT 互斥 Case 的关系
+
+| PPT 场景 | 后台配置来源 | 移动端结果 |
+| --- | --- | --- |
+| Case A:先报名非 MGM,后被推荐 | 互斥组 + 报名记录 + MGM 标签 | 被推荐人无法继续参与 MGM,推荐人侧展示“被推荐人已参与其他新客活动”。 |
+| Case B:先被推荐,后报名 NTB | 互斥组 + MGM 标签 | 第二天起其他 NTB 活动展示不符合参与条件;当天极限情况次日关闭资格。 |
+| Case C:先报名航司,再报名酒店 | 同一互斥组内报名记录 | 第二个活动报名时实时拦截。 |
+
+* * *
+
+## 10\. 菜单八:活动投放管理
+
+### 10.1 菜单定位
+
+活动投放管理用于生成活动链接、二维码、太阳码,并配置活动在 MobileX、权益首页等渠道和版位的投放。它不应完全塞在活动基础信息里,因为一个活动可能有多个渠道、多个版位、多个上线时间。
+
+### 10.2 活动投放管理 - 列表页
+
+| 字段  | 说明  |
+| --- | --- |
+| 投放 ID | 系统生成。 |
+| 活动名称 | 关联活动。 |
+| Campaign ID | 活动归因 ID。 |
+| Channel ID | 渠道归因 ID。 |
+| 投放渠道 | MobileX、微信服务号、短信、Paid Media 等。 |
+| 投放版位 | MobileX 版位、权益首页 Banner 位等。 |
+| 上线时间 | 投放开始时间。 |
+| 下线时间 | 投放结束时间。 |
+| 状态  | 草稿、已发布、已下线。 |
+| 操作  | 编辑/查看/生成链接/生成二维码。 |
+
+### 10.3 活动投放管理 - 新建/编辑页
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 选择活动 | 活动选择 | 是   | 决定链接进入哪个活动。 |
+| Campaign ID | 搜索选择 | 否   | 链接携带,用于统计。 |
+| Channel ID | 搜索选择 | 否   | 链接携带,用于统计。 |
+| 投放渠道 | 下拉  | 是   | 决定生成物料类型。 |
+| 投放 MobileX 版位 | 下拉  | 否   | 在 MobileX 指定位置展示。 |
+| 投放权益首页版位 | 下拉  | 否   | 在权益首页 1-8 位置展示。 |
+| 投放开始时间 | 日期时间 | 是   | 控制版位何时展示。 |
+| 投放结束时间 | 日期时间 | 否   | 控制版位何时隐藏。 |
+| 生成链接 | 按钮  | \-  | 生成 H5 链接。 |
+| 生成二维码/太阳码 | 按钮  | \-  | 用于线下或微信投放。 |
+
+* * *
+
+## 11\. 菜单九:通知模板管理
+
+### 11.1 菜单定位
+
+通知模板可放在内容管理下。活动管理和发放计划中只选择模板,不重复维护短信内容。
+
+### 11.2 通知模板列表页
+
+| 字段  | 说明  |
+| --- | --- |
+| 模板编号 | 系统生成。 |
+| 模板名称 | 如“活动报名成功提醒”。 |
+| 类型  | SMS/其他,Excel 中目前主要是 SMS。 |
+| 内容  | 通知正文。 |
+| 创建人 | 创建账号。 |
+| 操作  | 编辑/查看。 |
+
+### 11.3 通知模板编辑页
+
+| 字段  | 类型  | 是否必填 | 说明  |
+| --- | --- | --- | --- |
+| 模板名称 | 文本  | 是   | 后台识别。 |
+| 模板类型 | 下拉  | 是   | SMS。 |
+| 模板内容 | 多行文本 | 是   | 是否支持活动名称、报名时间、奖励名称等变量需确认。 |
+| 提交  | 按钮  | \-  | 保存后可被活动选择。 |
+
+* * *
+
+## 12\. 菜单十:活动报表/活动进度查询
+
+### 12.1 菜单定位
+
+活动报表/进度查询不参与活动创建配置,只用于运营查询客户报名、达标、发放、积分、立减金、信用卡权益领取等结果。
+
+### 12.2 建议查询页
+
+| 查询页 | 主要字段 | 用途  |
+| --- | --- | --- |
+| 客户活动报名查询 | 报名时间、活动名称、活动编号、客户来源渠道、Customer ID、客户账户类型 | 查询客户是否报名、从哪个渠道报名。 |
+| 客户达标情况查询 | 客户号、现 MSC、升级 MSC entry date、是否首次升级、月份、已达标层级、规则/活动 | 查询客户达标进度和层级。 |
+| 客户权益发放查询 | 发放时间、权益名称、权益编号、客户来源渠道、Customer ID、客户账户类型 | 查询权益是否发放。 |
+| 积分查询 | 客户号、积分科目、积分变动额、积分总额、清算时间、交易描述 | 查询积分奖励明细。 |
+| 立减金发放明细 | 客户号、立减金类型、发放日期、金额、有效期、是否领取、是否使用 | 查询立减金领取和使用状态。 |
+| 信用卡权益领取及使用 | 客户号、卡号、客户等级、权益领取明细、商品编号、商品名称、商品数量 | 查询信用卡权益领取情况。 |
+
+* * *
+
+## 13\. 信用卡活动入口配置
+
+### 13.1 菜单定位
+
+信用卡活动在 PPT 中更像入口配置,而不是完整纳入本活动系统。第一版建议先做入口,不建议重做 CVP 信用卡活动体系。
+
+### 13.2 配置字段
+
+| 字段  | 类型  | 是否必填 | 移动端影响 |
+| --- | --- | --- | --- |
+| 是否展示信用卡入口 | 开关  | 是   | 全部活动列表底部或信用卡 tab 是否展示。 |
+| 入口名称 | 文本  | 是   | 移动端入口标题。 |
+| 提示文案 | 文本  | 否   | 如“如需查看信用卡活动进度,请点击信用卡活动专区”。 |
+| 跳转地址 | URL | 是   | 点击后跳转 CVP/信用卡权益平台。 |
+| 是否启用信用卡活动 tab | 开关  | 否   | 如启用,活动列表增加 tab;无活动时不展示。 |
+
+* * *
+
+## 14\. 后台菜单与移动端页面映射表
+
+| 后台菜单/配置 | 控制的移动端页面 | 影响内容 |
+| --- | --- | --- |
+| 活动管理-基础信息 | 首页活动模块、全部活动、我的活动、活动详情 | 活动名称、图片、时间、地点、活动内容。 |
+| 活动管理-时间与展示 | 首页、全部活动、我的活动 | 是否展示、何时展示、是否进入已结束。 |
+| 客群管理 | 首页、全部活动、活动详情、进度页 | 谁能看、谁能报名、谁能达标。 |
+| 报名表单管理 | 补充信息页/弹窗 | 收集字段、校验规则、提交逻辑。 |
+| 活动管理-报名设置 | 活动详情、报名确认页、报名结果页 | 是否报名、是否确认、名额、报名成功/失败。 |
+| 活动进度模板管理 | 活动达标进度页 | 三种进度样式、层级展示、状态文案、MGM 专属展示。 |
+| 达标规则管理 | 活动达标进度页 | 标签/资产/连续达标判断,输出达标状态。 |
+| 权益发放计划管理 | 达标进度页、奖励详情页 | 奖励类型、按钮文案、跳转目标、发放状态。 |
+| 活动互斥组管理 | 活动列表、活动详情、报名失败、MGM 进度页 | 不符合参与条件、报名拦截、无法继续参与、推荐人侧提示。 |
+| 活动投放管理 | 活动入口、Banner、二维码/链接访问 | 活动从哪个渠道进入,是否携带追踪码。 |
+| 通知模板管理 | 无直接页面 | 报名成功、开始前提醒、达标发奖通知。 |
+| 信用卡活动入口配置 | 全部活动列表底部/信用卡 tab | 信用卡活动专区入口和跳转。 |
+
+* * *
+
+## 15\. 第一版后台建议范围
+
+### 15.1 必须做
+
+| 菜单/能力 | 原因  |
+| --- | --- |
+| 活动管理 | 没有它无法创建活动和驱动移动端展示。 |
+| 客群管理 | 活动可见、报名、达标都依赖客群。 |
+| 报名表单管理 | PPT 有补充信息收集,不同活动字段不同。 |
+| 活动进度模板管理 | PPT 有三种进度页样式,必须独立配置。 |
+| 达标规则管理 | 需要把“怎么判断达标”从页面样式中拆出来。 |
+| 权益发放计划管理 | 达标后按钮和跳转必须有配置来源。 |
+| 活动互斥组管理 | NTB/MGM 互斥是核心规则,必须复用配置。 |
+| 活动报表/进度查询 | 运营需要查询报名、达标、发放结果。 |
+
+### 15.2 可以后续做
+
+| 菜单/能力 | 原因  |
+| --- | --- |
+| 活动投放多渠道效果分析 | 第一版先生成链接和渠道码,效果看板可后续。 |
+| 信用卡活动独立 tab | PPT 中仍是待讨论,第一版可先做固定入口。 |
+| 通知模板变量高级配置 | 第一版可以先支持简单模板。 |
+| 进度模板版本管理 | 有价值,但第一版可先支持复制、编辑、停用。 |
+| 投入产出比分析 | 字段口径复杂,可后续单独评估。 |
+
+### 15.3 不建议第一版做
+
+| 功能  | 原因  |
+| --- | --- |
+| 把所有配置都堆在活动管理一个编辑页里 | 字段过多,移动端和后台关系会变乱。 |
+| 让运营自由写复杂逻辑表达式 | 容易配置错误,建议先用标签/规则模板选择。 |
+| 信用卡活动完整纳入本活动体系 | 信用卡活动依赖 CVP,第一版做入口更稳。 |
+| 已发布活动随意编辑互斥组或达标规则 | 会影响已报名客户状态,容易引发投诉。 |
+| 真实接入全部发奖系统 | Demo 阶段可先模拟发奖状态和跳转。 |
+
+* * *
+
+## 16\. 仍需确认的问题
+
+| 优先级 | 待确认问题 | 影响范围 |
+| --- | --- | --- |
+| P0  | 活动时间、报名时间、展示时间三者关系如何定义 | 前端展示、报名按钮、我的活动状态。 |
+| P0  | 报名时间是否一定晚于活动开始时间 | Excel 中有该备注,但业务上可能存在提前报名。 |
+| P0  | 展示客群、报名客群、达标客群是否都支持多选 | 客群字段结构和接口结构。 |
+| P0  | 留资表和补充信息表是否两个独立概念 | 报名链路和后台菜单拆分。 |
+| P0  | 补充信息弹窗关闭后是否算报名成功 | 报名记录、我的活动、后续补填逻辑。 |
+| P0  | 进度页达标层级最多 4 层还是 5 层 | PPT 中口径不一致,影响进度模板和达标规则。 |
+| P0  | 三种进度类型是否固定,后续是否还会新增 | 影响活动进度模板管理的数据结构。 |
+| P0  | 进度模板和达标规则是一对一还是一对多 | 影响活动创建时选择逻辑。 |
+| P0  | 互斥是按活动类型自动互斥,还是按互斥组手动配置 | 影响互斥组菜单设计。 |
+| P0  | MGM 标签 T+1 后关闭资格时,已报名记录是否保留 | 我的活动、活动详情、进度页状态。 |
+| P1  | 已发布活动哪些字段允许编辑 | 审核、版本、已报名客户状态。 |
+| P1  | 活动下线后,已报名客户是否仍可在我的活动查看 | 我的活动和已结束列表。 |
+| P1  | 投放权益首页版位是否银行可自助配置 | 活动投放管理是否能完整做。 |
+| P1  | 发放计划被多个活动引用时,是否允许修改 | 奖励发放稳定性。 |
+| P1  | 互斥组被多个活动引用时是否允许编辑 | 已报名客户状态稳定性。 |
+| P2  | 是否需要活动配置版本记录 | 审计和问题追溯。 |
+| P2  | 通知模板变量范围 | 短信模板配置复杂度。 |
+
+* * *
+
+## 17\. 给 Codex / OpenCode 的开发提示词
+
+```text
+你需要基于现有后台 demo 项目,新增“权益平台活动模块”的后台配置 demo。请不要从零开发完整系统,而是在现有后台风格基础上新增菜单和页面。
+
+一、项目目标
+实现一个可演示的权益平台活动后台配置系统,用来配置移动端活动首页、全部活动、我的活动、活动详情、报名链路、达标进度页、奖励按钮、活动互斥和信用卡活动入口。
+
+二、菜单范围
+请新增或模拟以下后台菜单:
+1. 活动管理
+2. 客群管理
+3. 报名表单管理
+4. 活动进度模板管理
+5. 达标规则管理
+6. 权益发放计划管理
+7. 活动互斥组管理
+8. 活动投放管理
+9. 通知模板管理
+10. 活动报表/进度查询
+11. 信用卡活动入口配置
+
+三、重点要求
+1. 活动管理是主菜单,只做活动编排,不要把所有复杂规则都塞进活动编辑页。
+2. 客群、报名表单、进度模板、达标规则、权益发放计划、互斥组都要能独立配置,并在活动新建/编辑页中选择引用。
+3. 必须新增“活动进度模板管理”,支持三种进度类型:
+   - 多条件多次发奖
+   - 多条件单次发奖
+   - MGM 专属
+4. “活动进度模板”负责移动端进度页怎么展示;“达标规则管理”负责系统怎么判断是否达标;“权益发放计划管理”负责达标后发什么奖励。
+5. 活动互斥组需要独立菜单配置,活动创建时选择互斥组,不要只做一个是否互斥开关。
+
+四、活动管理页面要求
+活动管理需要有列表页和新建/编辑页。
+列表页字段包括:活动ID、活动名称、活动类型、活动时间、报名时间、活动状态、审核状态、创建人、审核人、创建时间、更新时间、操作。
+操作包括:查看、编辑、预览、提交审核、审核、发布、下线。
+
+活动新建/编辑页使用 Tab 结构:
+1. 基础信息
+2. 时间与展示
+3. 客群与报名
+4. 进度与奖励
+5. 互斥与通知
+6. 审核发布
+
+五、移动端联动要求
+后台配置需要能模拟影响移动端展示:
+1. 是否展示到首页影响权益首页活动模块。
+2. 是否展示到全部活动影响全部活动列表。
+3. 展示客群影响客户是否能看到活动。
+4. 报名客群影响详情页是否可报名。
+5. 报名表单影响补充信息页字段。
+6. 进度模板影响达标进度页样式。
+7. 达标规则影响进度状态:未达标、已达标、已发奖、无法继续参与。
+8. 权益发放计划影响达标后按钮:查看积分奖励、前往领取、查看奖励。
+9. 互斥组影响列表状态、报名拦截弹窗、无法继续参与提示、MGM推荐人侧文案。
+
+六、交付物
+1. 后台菜单页面 demo。
+2. 每个菜单至少有列表页和新建/编辑页。
+3. 使用 mock 数据即可,不需要真实接口。
+4. 页面风格保持现有后台系统风格。
+5. 不需要实现真实银行标签、真实发奖、真实短信发送。
+```

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff