基于
phase1-product-plan.md与1.0产品对齐方案.md,每个用户故事包含:标题、描述、验收标准、优先级
作为 数字能量师/C端用户
我希望 输入姓名和出生年月日,发起一次命盘咨询
以便 获取命盘展示和AI解读服务
验收标准(输入表单):
POST /api/consultation/start 发起咨询验收标准(咨询会话管理):
CalculatorService 完成,前端只负责展示(calculator.js 保留为降级兜底,不作为主路径)作为 数字能量师
我希望 看到一个清晰、美观的数字三角形,每个位置都标有数字
以便 直接用于客户讲解
验收标准:
内部三角形布局严格遵循(新字母命名):
O
/ \
M N
/ \ / \
I J K L
底部输入框布局:日(2位)| 月(2位)| 年(4位),从左到右排列(已改为"日月年"顺序)
外部三组以T形线段连接三角内部对应位置:
外部三组使用独立的计算值,非内部数字的直接重复
每个数字使用单独的圆形/方形卡片展示,字体清晰易读
五区使用不同背景色区分(左区/中区/右区/父源/母源/主性格)
数字颜色:1-9 每个数字有独立颜色(参考能量数字配色)
卓越数(11/22/33)用特殊标记突出
底部显示姓名和出生日期信息
作为 开发团队
我希望 有一份完整的位置命名与计算规则对照表作为开发基准
以便 前后端计算逻辑一致,避免歧义
| 字母 | 来源 | 示例 1990-01-15 | 说明 |
|---|---|---|---|
| A | 年份千位 | 1 | 年[0] |
| B | 年份百位 | 9 | 年[1] |
| C | 年份十位 | 9 | 年[2] |
| D | 年份个位 | 0 | 年[3] |
| E | 月份十位 | 0 | 月[0] |
| F | 月份个位 | 1 | 月[1] |
| G | 日期十位 | 1 | 日[0] |
| H | 日期个位 | 5 | 日[1] |
| 字母 | 公式 | 中文名 | 旧名对照 | 视觉层级 |
|---|---|---|---|---|
| I | reduce(E+F) |
月能量 | 旧 F | 底部左1 |
| J | reduce(G+H) |
日能量 | 旧 G | 底部左2 |
| K | reduce(A+B) |
年前半 | 旧 H | 底部右1 |
| L | reduce(C+D) |
年后半 | 旧 I | 底部右2 |
| M | reduce(I+J) |
青年综合数 | 不变 | 中层左 |
| N | reduce(K+L) |
晚年综合数 | 不变 | 中层右 |
| O | reduce(M+N) |
主性格数 | 不变 | 顶层 |
| 字母 | 公式 | 中文名 | 年龄区间 | 所属组 |
|---|---|---|---|---|
| P | reduce(I+M) |
左侧左子(left-L) | 21-40 | 左侧 |
| Q | reduce(J+M) |
左侧右子(left-R) | 21-40 | 左侧 |
| R | reduce(P+Q) |
左侧主数(left) | 21-40 | 左侧 |
| S | reduce(K+N) |
右侧左子(right-L) | 61+ | 右侧 |
| T | reduce(L+N) |
右侧右子(right-R) | 61+ | 右侧 |
| U | reduce(S+T) |
右侧主数(right) | 61+ | 右侧 |
| V | reduce(M+O) |
顶部左子(top-L) | 41-60 | 顶部 |
| W | reduce(N+O) |
顶部右子(top-R) | 41-60 | 顶部 |
| X | reduce(V+W) |
顶部主数(top) | 41-60 | 顶部 |
X
↙ ↘
V W
↙ ↘
M ───────────── N
↙ ↘ ↙ ↘
P Q S T
↙ ↘ ↙ ↘
I ─────────── J K ─────────── L
R (21-40) U (61+)
底部输入: 日 G H | 月 E F | 年 A B C D
| 组 | 位置 | 年龄区间 | 关联内部位 |
|---|---|---|---|
| 左侧组 (R, P, Q) | 三角形左外侧 | 21-40岁 | 基于 I, J, M |
| 顶部组 (X, V, W) | 三角形顶外侧 | 41-60岁 | 基于 M, N, O |
| 右侧组 (U, S, T) | 三角形右外侧 | 61+岁 | 基于 K, L, N |
reduce(N) 定义:各位数字相加归约至个位,但 11/22/33 保留为卓越数+ 操作符均先执行普通加法,再调用 reduce() 归约POST /api/consultation/start 响应示例){
"recordId": 42,
"isNew": false,
"chartData": {
"positions": {
"I": 1, "J": 2, "K": 3, "L": 4,
"M": 5, "N": 6, "O": 7,
"P": 8, "Q": 9, "R": 1,
"S": 2, "T": 3, "U": 4,
"V": 5, "W": 6, "X": 7
},
"mainCharacter": 7,
"isMasterNumber": false
},
"messages": []
}
作为 数字能量师
我希望 点击三角形中的每个数字可以查看该位置的含义和能量解读
以便 快速向客户解释命盘
验收标准:
作为 数字能量师
我希望 将命盘生成为一张精美的图片,分享到微信或保存到相册
以便 发给客户或在朋友圈展示
验收标准:
作为 数字能量师
我希望 将命盘导出为 PDF 文件,可以直接打印或微信发送
以便 客户获得正式的纸质/电子版报告
验收标准:
作为 数字能量师
我希望 在命盘生成后,看到由AI自动生成的完整文字解读
以便 直接发给客户或稍作润色后使用,节省自己查资料写解读的时间
验收标准:
作为 平台运营者
我希望 控制AI解读功能的免费与付费边界
以便 激励用户订阅C端年费或能量师年费
验收标准:
作为 平台运营者
我希望 控制AI问答互动功能的免费次数,且限定在同一个命盘上
以便 防止用户通过切换生日绕过每日限制,同时保证体验清晰可预期
验收标准:
lastQuotaDate 跨日时自动归零作为 系统
我希望 同一命盘的重复解读请求不重复调用LLM API
以便 控制成本,避免同一命盘每次查看都消耗API费用
验收标准:
作为 数字能量师
我希望 看到清晰的定价方案并进行年费支付
以便 获得AI完整解读、分销推广等全部功能
验收标准:
POST /api/pricing/current 获取)vipEndTime += 365天)作为 平台
我希望 系统自动判断用户是否享受种子价
以便 运营策略自动化,无需人工干预
验收标准:
POST /api/pricing/current 返回,前端不做判断orders 表中 product_type='practitioner' 且 status='paid' 的记录数sys_config.pricing.practitioner.seed_limit(默认 300)时,返回种子价pricing.practitioner.seed_limit)作为 付费能量师
我希望 在个人中心看到我的订阅状态、到期日,并能续费
以便 管理我的会员身份
验收标准:
作为 能量师
我希望 使用微信一键登录
以便 不需要注册账号,降低使用门槛
验收标准:
作为 能量师
我希望 查看我过去的所有咨询记录
以便 回顾和继续之前的咨询
验收标准:
(userId, birthday) 联合唯一)作为 付费用户(C端 / 能量师)
我希望 在首次付费后自动获得专属推广码
以便 分享给微信好友来发展下级并赚取佣金
验收标准:
O/0/I/1(即 ABCDEFGHJKLMNPQRSTUVWXYZ23456789)作为 平台
我希望 当用户通过推广链接/扫码打开小程序时,自动记录上下级关系
以便 为佣金分成打好基础
验收标准:
分享链路:
/pages/index/index?ref=ABC123(6位推广码)ref 参数前端缓存策略:
onLaunch(options) 读取 options.query.ref,转为大写后写入缓存 pending_referrerpending_referrer,以用户最后一次点击的链接为准pending_referrer 永不过期,直到注册成功后才清除pending_referrer 传入注册接口后端注册逻辑(UserService.loginOrRegister()):
referrerCode 有效 → invitedBy = 上级IDreferrerCode 无效 → invitedBy = null(静默降级,不报错)referrerCode 为空 → invitedBy = null(正常注册)referrerCode 为自己的新openid → invitedBy = null(防止自邀请)referrerCode,不覆盖已有 invitedBy(关系一次性锁定)referrerCode 查询不到对应上级 → 静默跳过,不抛异常数据库更新:
inviter.directCount += 1(实时更新统计字段)边界与异常:
pending_referrer,下次触发登录仍携带pending_referrer,重试时继续携带作为 平台
我希望 当能量师A的下级B升级为能量师时,自动结算固定金额佣金
以便 能量师获得推广收益
验收标准:
触发条件:
productType = "practitioner" 且支付成功 → 进入 B端佣金结算流程productType = "annual" → 走 US-6.4 C端佣金(互斥分支)佣金计算:
sys_config 读取 commission.practitioner.l1(默认 ¥500 = 50000分)sys_config 读取 commission.practitioner.l2(默认 ¥100 = 10000分)收款人资格:
buyer.invitedBy 对应的上级存在 → 创建佣金(不需要校验VIP是否有效)invitedBy 对应的上上级存在 → 创建佣金状态与幂等:
status 直接写入 "settled"(即时到账,无 pending 冷静期)outTradeNo 重复回调 → 幂等处理,不创建重复佣金统计更新:
inviter.directCount += 1,inviter.convertedCount += 1inviter2.indirectCount += 1配置项(后台可配):
| 配置键 | 默认值 | 说明 |
|--------|--------|------|
| commission.practitioner.l1 | 50000 | B端一级佣金(分) |
| commission.practitioner.l2 | 10000 | B端二级佣金(分) |
作为 平台
我希望 当C端用户B通过上级A的链接注册并付费 ¥131 时,自动按比例分成
以便 激励所有付费用户推广
验收标准:
触发条件:
productType = "annual" 且支付成功 → 进入 C端佣金结算流程佣金计算:
totalFee × directRate / 10000,从 sys_config 读取 commission.annual.direct_rate(默认 4000 = 40%)totalFee × upstreamRate / 10000,从 sys_config 读取 commission.annual.upstream_rate(默认 500 = 5%)收款人资格(与 B端不同):
buyer.invitedBy 对应的上级必须已付费(referralCode != null),才创建佣金invitedBy 对应的上上级必须已付费(referralCode != null),才创建佣金状态与幂等:
status 直接写入 "settled"与 B端的差异:
directCount/convertedCount/indirectCount(这些统计仅用于B端"升级能量师")每笔 ¥131 分配示例: | 分配对象 | 比例 | 金额 | |---------|------|------| | 直接上级 | 40% | ¥52.40 | | 上上级 | 5% | ¥6.55 | | 平台 | 55% | ¥72.05 |
配置项(后台可配):
| 配置键 | 默认值 | 说明 |
|--------|--------|------|
| commission.annual.direct_rate | 4000 | C端直接佣金比例(万分比,4000=40%) |
| commission.annual.upstream_rate | 500 | C端上级佣金比例(万分比,500=5%) |
作为 能量师
我希望 在分销面板中查看我的推广收益和团队数据
以便 追踪推广效果,激励持续推广
验收标准:
入口权限:
vipEndTime > now),普通用户看不到入口页面结构(自上而下):
① 推广码卡片:
我的推广码: ABC123 + 复制按钮② 收益统计卡片(三列等宽):
status = settled)/100)③ 团队统计(三列):
directCountindirectCountconvertedCount④ 佣金明细列表(最近50条):
⑤ 推广工具区:
页面状态:
后端 API 需求:
| 接口 | 说明 |
|------|------|
| POST /api/profile/info | 含 referralCode(已有) |
| POST /api/commission/list | 佣金明细列表(已有) |
| POST /api/commission/stats | 新增:总收益+可提现+今日新增+团队统计 |
作为 免费用户(C端)
我希望 在AI解读页底部看到 ¥131 开通完整解读的引导
以便 一键订阅获取完整服务
验收标准:
入口位置(用户已确认选项1——命盘解读页底部):
chart/index.vue 底部(聊天区下方)固定显示订阅卡片(免费用户)交互流程(未登录):
交互流程(已登录未付费):
product=annual,金额 ¥131交互流程(已付费):
支付页适配:
payment/index.vue 支持 URL 参数 product:
product=annual → 显示 C端年费 ¥131 和对应权益列表product=practitioner → 显示能量师价格(通过 API 获取种子/标准价)createOrder 接口接收 productType 参数后端适应:
productType 字段("annual" | "practitioner")vipType 字段("annual" | "practitioner" | null)paySuccess() 按 productType 分支佣金逻辑:
practitioner → US-6.3 固定金额annual → US-6.4 比例分成作为 能量师
我希望 在命盘图上添加文字批注或标记
以便 为每个客户记录个性化的解读要点
验收标准:
作为 能量师
我希望 为命盘记录添加简单的标签或分组
以便 对客户进行分类管理
验收标准:
作为 运营管理员
我希望 在管理后台可视化编辑定价和佣金参数
以便 灵活调整运营策略,无需开发介入
DDL & Entity:
新建 sys_config 表,结构如下:
CREATE TABLE `sys_config` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`config_key` VARCHAR(64) NOT NULL COMMENT '配置键(点分命名法)',
`config_value` VARCHAR(255) DEFAULT NULL COMMENT '配置值(始终使用字符串存储)',
`description` VARCHAR(255) DEFAULT NULL COMMENT '中文说明',
`value_type` VARCHAR(20) NOT NULL DEFAULT 'string' COMMENT 'int | price | percent | string',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_config_key` (`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
预置 8 条种子数据(见下)。
后端:SysConfigService + 内存缓存:
@PostConstruct loadAllToCache():启动时将全量配置读入 ConcurrentHashMap<String, String>getString(key, default)、getInt(key, default) 两个读方法,O(1) 取值key 不存在时 → 返回代码中传入的 defaultValue,永不抛异常updateConfig(key, value):更新 DB + 同步写入缓存,无需重启batchUpdate(Map<String, String>):循环调用 updateConfig,整个操作在 @Transactional 中管理后台 API:
POST /api/admin/config/list → 返回 List<SysConfig>(含全部字段)POST /api/admin/config/update → 接收 { "configKey": "value", ... } → 批量更新小程序端 API:POST /api/pricing/current:
// Request: 无参数
// Response:
{
"code": 0,
"data": {
"seedPrice": 131400, // pricing.practitioner.seed(分)
"standardPrice": 198600, // pricing.practitioner.standard(分)
"annualFee": 13100, // pricing.annual(分)
"isSeedPrice": true, // 当前用户是否能享受种子价
"remainingSeats": 298, // 种子名额余量
"seedLimit": 300 // 名额上限
}
}
isSeedPrice 的计算逻辑:统计 orders 表中 product_type='practitioner' AND status='paid' 的记录数,小于 seedLimit 则为 true管理后台页面(src/views/settings/index.vue):
/settings,图标 ⚙️定价配置卡片:
countByProductTypeAndStatus)B端佣金配置卡片:
C端佣金配置卡片:
保存逻辑:
POST /api/admin/config/updateel-form 的 reset 行为 —— 用户手动刷新页面可恢复到上次保存的值错误与边界:
el-input-number 的 min 属性直接阻止输入POST /api/admin/config/list
// Response:
{
"code": 0,
"data": [
{
"id": 1,
"configKey": "pricing.practitioner.seed",
"configValue": "131400",
"description": "能量师种子价(分)",
"valueType": "price",
"updatedAt": "2026-05-28T10:00:00"
}
// ... 共 8 条
]
}
POST /api/admin/config/update
// Request:
{
"pricing.practitioner.seed": "131400",
"commission.practitioner.l1": "60000"
}
// Response: { "code": 0 }
POST /api/admin/config/reset(可选,单条重置)
// Request: { "configKey": "commission.practitioner.l1" }
// Response: { "code": 0, "data": { "configKey": "commission.practitioner.l1", "configValue": "50000" } }
| 类名 | 位置 | 说明 |
|---|---|---|
SysConfig |
entity/ |
JPA 实体 |
SysConfigRepository |
repository/ |
JPA 接口 |
SysConfigService |
service/ |
配置 CRUD + 内存缓存 |
AdminConfigController |
controller/ |
/api/admin/config/* 端点 |
PricingController |
controller/ |
/api/pricing/current 小程序端端点 |
OrderRepository |
repository/ |
新增 countByProductTypeAndStatus() |
SysConfigService 核心代码:
@Service
public class SysConfigService {
private final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
private final SysConfigRepository repository;
@PostConstruct
public void loadAllToCache() {
cache.clear();
repository.findAllByOrderByConfigKeyAsc()
.forEach(cfg -> cache.put(cfg.getConfigKey(), cfg.getConfigValue()));
}
public String getString(String key, String defaultValue) {
return cache.getOrDefault(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
String val = cache.get(key);
if (val == null) return defaultValue;
try { return Integer.parseInt(val); }
catch (NumberFormatException e) { return defaultValue; }
}
@Transactional
public void updateConfig(String key, String value) {
SysConfig cfg = repository.findByConfigKey(key)
.orElseGet(() -> new SysConfig(key, value, "auto", "string"));
cfg.setConfigValue(value);
cfg.setUpdatedAt(LocalDateTime.now());
repository.save(cfg);
cache.put(key, value);
}
public void batchUpdate(Map<String, String> configs) {
configs.forEach(this::updateConfig);
}
}
| 文件 | 操作 | 说明 |
|---|---|---|
src/views/settings/index.vue |
新增 | 系统配置页面(~200行) |
src/router/index.ts |
修改 | 新增 /settings 路由 |
src/layout/index.vue |
修改 | 新增菜单项 |
侧边栏菜单新增:
<el-menu-item index="/settings">
<el-icon><Setting /></el-icon>
<span>系统配置</span>
</el-menu-item>
页面组件结构:
settings/index.vue
├── el-card (header-toolbar: "系统配置" + [保存全部配置])
├── el-card (定价配置)
│ ├── el-form-item: 能量师种子价 → el-input-number(min=0, max=999999, step=100)
│ ├── el-form-item: 能量师标准价 → el-input-number(...)
│ ├── el-form-item: 种子价名额上限 → el-input-number(min=1, max=99999, step=50)
│ └── el-form-item: C端年费 → el-input-number(...)
├── el-card (B端佣金配置)
│ ├── el-form-item: L1 佣金 → el-input-number
│ ├── el-form-item: L2 佣金 → el-input-number
│ └── 实时比例显示
└── el-card (C端佣金配置)
├── el-form-item: 直接佣金比例 → el-input-number(min=0, max=10000, step=500)
├── el-form-item: 上级佣金比例 → el-input-number(min=0, max=5000, step=100)
└── 实时分配预览
表单校验规则:
| 字段 | 校验规则 |
|---|---|
| 所有金额(元) | > 0,整数,≤ 999999 |
| 比例(%) | 0-100,整数 |
| 名额上限 | ≥ 1,≤ 99999 |
INSERT INTO `sys_config` (`config_key`, `config_value`, `description`, `value_type`) VALUES
('pricing.practitioner.seed', '131400', '能量师种子价(分)', 'price'),
('pricing.practitioner.standard', '198600', '能量师标准价(分)', 'price'),
('pricing.practitioner.seed_limit', '300', '种子价名额上限', 'int'),
('pricing.annual', '13100', 'C端年费(分)', 'price'),
('commission.practitioner.l1', '50000', 'B端一级佣金(分)', 'price'),
('commission.practitioner.l2', '10000', 'B端二级佣金(分)', 'price'),
('commission.annual.direct_rate', '4000', 'C端直接佣金比例(万分比)', 'percent'),
('commission.annual.upstream_rate', '500', 'C端上级佣金比例(万分比)', 'percent');
| # | 场景 | 步骤 | 预期 |
|---|---|---|---|
| 1 | 初始加载 | 打开系统配置页 | 8个字段显示正确的默认值 |
| 2 | 修改定价 | 种子价改为 ¥2,000 → 保存 | 下次 POST /api/pricing/current 返回 seedPrice=200000 |
| 3 | 修改佣金 | L1 改为 ¥600 → 保存 | 新订单的佣金 amount=60000 |
| 4 | 比例联动 | 直接佣金改为 50% | 下方显示"每笔¥131:直接¥65.50 / 上级¥6.55 / 平台¥58.95" |
| 5 | 比例联动反向 | 种子价改为 ¥2,000 | B端卡片"L1占种子价%"从38.1%变为25.0% |
| 6 | 输入校验 | 在金额框输入 -100 | el-input-number 阻止输入,值保持为 0 |
| 7 | 保存失败 | 网络断开后保存 | 显示"保存失败",输入值保留不丢失 |
| 8 | 保存后立即生效 | 改种子价为 ¥2,000 → 保存 → 新用户打开支付页 | 显示 ¥2,000 |
| 9 | 缓存同步 | API 直接改 DB → 调用 getInt() | 返回值仍为旧值(下次 loadAllToCache 或调用 updateConfig 后更新) |
| 10 | 种子名额展示 | 3笔已支付 practitioner 订单 | 显示"已使用:3 / 300" |
作为 运营管理员
我希望 查看所有订单记录,按产品类型和状态筛选
以便 核对收入和对账
后端 API(补全 AdminController.listOrders()):
POST /api/admin/orders → 接收筛选参数,返回分页订单列表
// Request:
{
"productType": "", // 筛选:"" 全部 | "practitioner" | "annual"
"status": "", // 筛选:"" 全部 | "paid" | "pending"
"page": 1,
"pageSize": 20
}
// Response:
{
"code": 0,
"data": {
"list": [
{
"id": 1,
"userId": 42,
"outTradeNo": "202605280001",
"totalFee": 131400,
"productType": "practitioner",
"isSeedPrice": true,
"status": "paid",
"payType": "wxpay",
"paidAt": "2026-05-28T10:00:00",
"createdAt": "2026-05-28T09:59:00"
}
],
"total": 128,
"page": 1,
"pageSize": 20
}
}
后端在 OrderRepository 中新增分页+动态筛选查询方法(使用 Specification 或 @Query 动态拼接)
前端页面:
当前代码待补全:
AdminController.listOrders() 目前返回 Result.success(null) → 需替换为真实数据Order 实体和 OrderRepository 已有,但缺 productType 和 isSeedPrice 字段 → 需新增orders 需增加 product_type 和 is_seed_price 列Pageable + Specification(或简单用 PageRequest)| 类名 | 变更 | 说明 |
|---|---|---|
Order.java |
新增字段 | productType (String)、isSeedPrice (Boolean) |
OrderRepository |
新增方法 | countByProductTypeAndStatus(productType, status) |
OrderRepository |
新增方法 | findAll(Specification, Pageable) 或 findByProductTypeAndStatus |
AdminController |
重写 listOrders() |
支持筛选+分页 |
作为 运营管理员
我希望 查看所有佣金记录和统计
以便 了解分销推广效果
后端 API(增强现有):
POST /api/admin/commissions → 支持分页和按级别筛选
// Request:
{
"level": 0, // 0: 全部 | 1: L1 | 2: L2
"page": 1,
"pageSize": 20
}
// Response:
{
"code": 0,
"data": {
"list": [
{
"id": 1,
"orderId": 101,
"fromUserId": 42,
"toUserId": 10,
"level": 1,
"amount": 50000,
"status": "settled",
"remark": "直接推荐升级能量师",
"createdAt": "2026-05-28T10:00:00"
}
],
"total": 56,
"page": 1,
"pageSize": 20,
"summary": {
"settledTotal": 350000, // 已结算总佣金(分)
"pendingTotal": 0, // 待结算总佣金(分,Phase 1 始终为 0)
"grandTotal": 350000, // 总佣金(分)
"countByLevel": { // 各级别笔数
"1": 12,
"2": 8
}
}
}
}
当前 AdminController.listAllCommissions() 返回 commissionRepository.findAll()(无分页)→ 需重写
前端页面(增强现有 commissions/index.vue):
:total="commissions.length" 是前端假分页,改为后端真分页| 当前状态 | 目标状态 | 工作量 |
|---|---|---|
AdminController.listAllCommissions() 无分页,无筛选 |
分页 + level 筛选 + summary 统计 | M |
CommissionRepository 无分页方法 |
新增 findAll(Specification, Pageable) |
S |
| 前端假分页(全量拉取) | 后端真分页 | M |
| 前端无 level 筛选 | 新增 el-select 筛选器 | S |
| 前端无备注列 | 新增 remark 列 | S |
| 前端无笔数统计卡片 | 新增第四个统计卡片 | S |
已决策:推迟,当前不做。
| 用户故事 | 优先级 | 预估工期 | 依赖 |
|---|---|---|---|
| US-1.1 生日输入与咨询发起 | P0 | 3天 | US-5.1(需登录) |
| US-1.2 三角形可视化 | P0 | 3天 | US-1.1 |
| US-1.4 数字点击查看含义 | P2 | 1天 | US-1.2 |
| US-2.1 分享图片 | P0 | 2天 | US-1.2 |
| US-2.2 PDF导出 | P0 | 2天 | US-1.2 |
| US-3.1 AI解读展示 | P0 | 3天 | US-1.1 + LLM API接入 |
| US-3.2 AI解读免费/付费控制 | P0 | 1天 | US-3.1 |
| US-3.3 解读内容缓存 | P0 | 1天 | US-3.1 |
| US-3.4 AI交互问答(免费/付费控制) | P0 | 1天 | US-1.1 |
| US-4.1 能量师付费订阅 | P0 | 2天 | US-5.1 |
| US-4.2 种子价自动判断 | P0 | 1天 | US-8.1 |
| US-4.3 订阅状态与续费 | P0 | 1天 | US-4.1 |
| US-5.1 微信登录 | P1 | 2天 | 无 |
| US-5.2 历史咨询记录 | P1 | 2天 | US-5.1 |
| US-6.1 推广码生成 | P0 | 1天 | US-4.1 |
| US-6.2 分享带参注册与上下级绑定 | P0 | 2天 | US-5.1 |
| US-6.3 B端佣金结算 | P0 | 2天 | US-4.1 + US-6.2 + US-8.1 |
| US-6.4 C端佣金结算 | P0 | 1天 | US-6.6 + US-6.3 + US-8.1 |
| US-6.5 分销面板 | P1 | 2天 | US-6.1 + US-6.3 |
| US-6.6 C端年费订阅入口 | P0 | 1.5天 | US-3.2 + US-4.1 |
| US-7.1 批注功能 | P2 | 2天 | P0完成 |
| US-7.2 客户分组 | P2 | 2天 | P0完成 |
| US-8.1 系统配置管理 | P0 | 2天 | 无(独立基础设施) |
| US-8.2 订单管理 | P1 | 1.5天 | US-4.1 + Order 新增字段 |
| US-8.3 佣金查看 | P1 | 1天 | US-6.3 |
US-5.1 微信登录
│
▼
US-1.1 生日输入与咨询发起(后端查重(userId+birthday)→确认弹窗→后台计算→返回展示)
│
├──► US-1.2 三角形可视化(24位置新命名 I-X)
├──► US-3.4 AI交互问答(每日3轮,绑定当前命盘)
│
▼
US-5.2 历史咨询记录(按 userId+birthday 唯一归组,可恢复聊天历史)
US-8.1 系统配置管理(独立基础设施——最先完成)
│
├──► US-4.2 种子价判断
├──► US-6.3 B端佣金(读取 L1/L2 金额)
└──► US-6.4 C端佣金(读取直接/上级比例)
US-5.1 微信登录
│
▼
US-6.2 分享带参注册(需要 openid)
│
▼ 绑定上下级关系
│
US-4.1 能量师付费 + US-6.6 C端年费订阅
│ │
▼ ▼
US-6.1 推广码生成 US-6.4 C端佣金结算
│ │
▼ │
US-6.3 B端佣金结算 ◄──────────────┘(共享 paySuccess() 分支)
│
▼
US-6.5 分销面板(前端展示所有数据)
| 周次 | 交付内容 |
|---|---|
| Week 1 | US-8.1 系统配置 → US-4.2 种子价 → US-4.1 能量师付费 |
| Week 2 | US-6.2 带参注册 → US-6.1 推广码 → US-6.3 B端佣金 |
| Week 3 | US-6.6 C端订阅入口 → US-6.4 C端佣金 → US-6.5 分销面板 |
| Week 4 | 联调测试 + 管理后台补全(US-8.2 / US-8.3)+ BUG修复 |
Phase 1 分销核心 P0 合计:约 10天(2周) 加上命盘、AI解读、支付等已有模块联调,总计约 4周。
以下清单基于新命名体系(A-X)和咨询会话管理需求,列出所有需修改的代码文件及变更内容。
| 文件 | 变更类型 | 变更内容 |
|---|---|---|
CalculatorService.java |
重写 | 内部7位从 F,G,H,I,M,N,O → I,J,K,L,M,N,O;新增外部9位 P,Q,R,S,T,U,V,W,X 的独立计算;新增 calculateFullTriangle() 返回全部24个位置 |
ChartService.java |
改造 | 新增 startConsultation(userId, birthday, name, questions) 方法,含:①查 userId+birthday 是否已有记录;②已有则直接返回+聊天历史;③无则服务器端调 CalculatorService 计算+创建记录+可选首次AI解读 |
ChartController.java |
改造 | 保留 /api/chart/create(兼容旧版);新增 POST /api/chart/start → 调 startConsultation() |
ChartRecord.java |
微调 | 新增 lastInteractionAt 字段(LocalDateTime,用于列表排序);加 @Table(uniqueConstraints=...) 定义 (userId, birthday) 联合唯一 |
ChartRecordRepository.java |
新增 | Optional<ChartRecord> findByUserIdAndBirthday(Long userId, String birthday) |
UserService.java |
微调 | checkDailyQuota() 和 consumeQuota() 已有逻辑不变(3次/天全局计数),但新增注释明确"非VIP每日3次绑定当前命盘,切换生日不重置" |
ProfileController.java |
微调 | 咨询列表接口按 lastInteractionAt 倒序 |
| 文件 | 变更类型 | 变更内容 |
|---|---|---|
client/utils/calculator.js |
废弃/降级 | calculateTriangle() 保留为离线兜底,不再作为主路径调用;内部命名改为 I,J,K,L,M,N,O;新增 calculateFullTriangle() 含外部 P-X;主入口加注释 @deprecated 请使用后端计算 |
client/stores/chart.js |
改造 | computeChart() 不再调 calculateTriangle(),改为调 consultationApi.start();返回数据中的 positions 直接写入 currentChart;不再调 analyzeTriangle()(由后端返回) |
client/components/TriangleChart.vue |
重写 matrix() | innerBottom 从 [d.F,d.G,d.H,d.I] → [d.I,d.J,d.K,d.L];outerLeft 从 {main:d.M, sub:[d.F,d.G]} → {main:d.R, sub:[d.P,d.Q]};outerRight 从 {main:d.N, sub:[d.H,d.I]} → {main:d.U, sub:[d.S,d.T]};outerTop 从 {main:d.O, sub:[d.M,d.N]} → {main:d.X, sub:[d.V,d.W]} |
client/pages/index/index.vue |
改造 | onAnalyze() 改为:①调 POST /api/chart/start(传 birthday + name + questions);②若返回 isNew=true 且 requireConfirmation=true 则弹确认框;③确认后再次请求;④成功后跳转 chart/index;⑤"想了解的问题"加 maxlength=100 + 实时字数显示 |
client/utils/api.js |
新增 | consultationApi:start(data) → POST /api/chart/start;detail(id) → POST /api/chart/detail |
client/pages/chart/index.vue |
微调 | 适配新的 API 返回结构(chartData.positions 含全部 I-X);onMounted 加载历史消息逻辑不变 |
client/pages/records/index.vue |
微调 | 列表按 lastInteractionAt 排序;详情页恢复聊天历史逻辑不变 |
| 变更 | 说明 |
|---|---|
chart_records 表加联合唯一索引 |
UNIQUE INDEX uk_user_birthday (user_id, birthday),确保同用户+同生日只有一条咨询记录 |
chart_records 表加字段 |
last_interaction_at DATETIME,默认等于 created_at,每次 AI 聊天时更新,用于列表排序 |
chart_records 表现有字段 |
chart_data 列存储的 JSON 结构从 7个旧位置 → 16个新位置(内部 I-O + 外部 P-X) |
Step 1: US-1.3(本对照表)→ 开发团队通读,确保理解命名对应关系
Step 2: 数据库迁移 → 加索引+字段
Step 3: CalculatorService.java → 重写为新命名+外部计算
Step 4: ChartService.java + ChartController.java → 新增 startConsultation()
Step 5: stores/chart.js + api.js → 前端调后端新接口
Step 6: TriangleChart.vue → matrix() 改用新命名+独立外部值
Step 7: pages/index/index.vue → 确认弹窗 + 100字限制
Step 8: 联调 → 前后端打通 end-to-end