2026-08-08-family-challenge-participation-design.md 14 KB

家庭挑战参与与同意机制设计(含方向性关系评分)

版本:v1.1(✅ 已确认) 日期:2026-08-08 状态:✅ 设计已确认(2026-08-08 用户拍板:C3 只扣沟通,其余按建议默认) 范围:家庭挑战(family_challenge)参与机制重构:任意成员发起、可选成员、全体同意后开始、方向性关系评分。圈子挑战(circle_challenge)不在范围。 前置基线2026-08-06-family-challenge-flow-design.md(现状断点清单) 输入确认:brainstorming 6 轮澄清 + 3 轮追问(作用对象/时机/方向性语义)


1. 背景与目标

现状一句话(来自基线文档):挑战只能由家长手动创建,创建即 active、默认全员参与、无角色检查、无打卡 UI、无奖励发放。

本次要解决的核心问题

# 问题 现状
1 挑战只能家长发起 孩子/其他成员无发起入口
2 创建即开始,无成员确认环节 无"同意"概念,强制全员参与
3 无法选择参与成员 固定全员
4 参与行为无任何激励/反馈 发起/同意/拒绝不产生分数变化
5 关系分数无方向性 family_members 只有成员级分数,A对B=B对A

目标

  • 任何家庭成员可发起挑战,可选择全员或部分成员参与
  • 挑战创建后进入 pending(待同意) 状态,所有参与成员同意后才开始
  • 发起/同意/拒绝即时结算方向性关系评分(信任/亲近/沟通)+ 行能量,数值后台可配置
  • 结算/打卡只统计参与成员(排除拒绝者)

2. 已确认规则(brainstorming 结论,用户拍板)

# 规则 结论
1 发起人 任何家庭成员(家长/孩子/规划师)均可发起
2 参与成员选择 创建时可选全员或部分成员
3 发起人 自动同意,必参与,自动计入参与成员
4 拒绝 拒绝者剔除出挑战,其余参与成员同意后开始
5 同意/拒绝入口 首页挑战卡片直接操作(不用进管理页)
6 结算口径 只统计参与成员(agreed,排除 rejected)
7 打卡 仅参与成员可打卡;pending 成员不可打卡
8 管理页 放开门控,所有家庭成员可进入创建
9 待同意状态 不限时(pending 可长期挂起)
10 评分时机 动作发生时立即结算
11 评分数值 默认 ±1 分,后台可配置
12 方向性 信任/亲近/沟通是有方向的成对分数:A对B ≠ B对A
13 发起评分 能量+、沟通+、亲近+(不加信任
14 同意评分 能量+、沟通+、亲近+、信任+(四项全加
15 拒绝评分 能量-;发起人→拒绝人 信任/亲近/沟通全 -;拒绝人→发起人 仅沟通 +;已同意成员→拒绝人 仅沟通 -(见 §4.3)

3. 数据模型

3.1 新表 family_challenge_participant(参与成员表)

family_challenge_participant
├── id               BIGINT AUTO_INCREMENT PK
├── challenge_id     BIGINT NOT NULL          -- 挑战ID
├── member_id        BIGINT NOT NULL          -- 成员ID
├── status           VARCHAR(20) NOT NULL     -- pending / agreed / rejected
├── agreed_at        DATETIME NULL            -- 同意时间(rejected 可复用记录拒绝时间)
├── created_at       DATETIME DEFAULT NOW
├── updated_at       DATETIME DEFAULT NOW ON UPDATE NOW
├── UNIQUE KEY uk_challenge_member (challenge_id, member_id)
└── INDEX idx_member (member_id)

用途:替代"创建即全员参与"的隐式模型,显式记录每个成员在挑战中的参与状态。

3.2 新表 family_relationship_scores(方向性关系分数表)

family_relationship_scores
├── id               BIGINT AUTO_INCREMENT PK
├── family_id        BIGINT NOT NULL
├── from_member_id   BIGINT NOT NULL          -- 分数持有方(A对B的分,记在 A→B 行)
├── to_member_id     BIGINT NOT NULL          -- 分数对象方
├── trust_score      DECIMAL(5,2) DEFAULT 0   -- A对B的信任
├── intimacy_score   DECIMAL(5,2) DEFAULT 0   -- A对B的亲近
├── communication_score DECIMAL(5,2) DEFAULT 0 -- A对B的沟通
├── created_at       DATETIME DEFAULT NOW
├── updated_at       DATETIME DEFAULT NOW ON UPDATE NOW
├── UNIQUE KEY uk_from_to (from_member_id, to_member_id)
└── INDEX idx_family (family_id)

为什么要新表

  • 现有 family_members 的三列是成员级、无方向(问卷提交时被 updateMemberScores 压平)
  • 现有 family_relationships 是成对表但只存辈分/关系标签,无分数列,且语义是"族谱"
  • 方向性分数(A对B ≠ B对A)需要独立成对存储,新表是最小侵入方案
  • 初始化:成员首次被挑战动作触及时,若无方向行则用该成员在 family_members 的对应成员级分数作为初始基线;基线缺失则默认 60.00

3.3 family_challenge 表变更

变更 说明
状态机 active / completed / cancelled 之外新增 pending(待同意)
兼容 存量 active 挑战无 participant 行 → 迁移时为其补全 participant 行(全员 agreed),不破坏旧数据

3.4 评分配置(后台可配置)

方案:新建 family_challenge_score_config 简单配置表 + cfc-web 管理页(复用 AdminEnergyConfigController 风格):

family_challenge_score_config
├── id             BIGINT AUTO_INCREMENT PK
├── action         VARCHAR(20) NOT NULL    -- initiate / agree / reject
├── score_type     VARCHAR(20) NOT NULL    -- energy / trust / intimacy / communication
├── value          INT NOT NULL DEFAULT 0  -- 增减值(可正可负,默认 ±1)
├── enabled        TINYINT DEFAULT 1
├── updated_at     DATETIME
└── UNIQUE KEY uk_action_type (action, score_type)

默认值(按已确认规则 13-15):

action energy trust intimacy communication
initiate +1 0 +1 +1
agree +1 +1 +1 +1
reject -1 -1 -1 +1

说明:reject 行的通信方向由代码固定(拒绝人→发起人 +、发起人→拒绝人 -;已同意成员→拒绝人 仅沟通 -1,见 §4.3 C3),value 只控制增减幅度。能量非方向属性,作用于个人。


4. 评分规则详解

4.1 基本约定

  • 方向性分数(trust/intimacy/communication):成对存储于 family_relationship_scores,行 (A→B) 表示"A 对 B 的分数"。动作执行者(actor)永远是被加/被减的主体——加分/减分落点在 actor 的行上。
  • 能量(行能量):非方向属性,作用于 actor 本人(EnergyService.awardEnergy / deductEnergyByCode,维度 code action)。

4.2 同意(agree)— 已确认

用户原话:"同意只是加同意人相对其他成员的,不影响其他成员对同意人的"

成员 X 同意挑战时:

落点 trust intimacy communication energy
X→其他每个参与成员 +1 +1 +1
X 本人(能量) +1
其他成员→X(反向) 0 0 0

即:只有 X 的"出向"行加分,反向行不动。

4.3 拒绝(reject)— 已确认

用户原话:"拒绝人对发起人的沟通加,发起人对拒绝人的沟通减" 用户拍板(2026-08-08):C1 ✅ -1、C2 ✅ 不变、C3 ✅ 只扣沟通、C4 ✅ I+A 每人 -1

成员 X 拒绝、发起人为 I、已同意成员集合为 A:

落点 trust intimacy communication energy
I→X -1 -1 -1
X→I 0 0 +1
A 中每个成员→X 0 0 -1(只扣沟通)
I / A 能量 -1(I+A 每人 -1)

4.4 发起(initiate)— 已确认

Q1 结论:"发起/同意时所有参与成员一起加"。方向性语义下,发起人与同意人的模式完全平行(发起人出向加分)。

发起人 I 创建挑战(参与成员集合 P,含 I):

落点 trust intimacy communication energy
I→P 中每个其他成员 0(不加信任) +1 +1
I 本人(能量) +1(仅发起人)
P 中其他成员→I(反向) 0 0 0

5. 接口设计

全部 @PostMapping(项目规范)。前缀沿用 /api/health/challenge/

5.1 创建挑战(改造)

POST /api/health/challenge/create

{
  "title": "全家运动周",
  "type": "sports",
  "goalMode": "aggregate",
  "goalValue": 100,
  "rewardPoints": 50,
  "startDate": "2026-08-10",
  "endDate": "2026-08-17",
  "participantMemberIds": [1001, 1002, 1003]   // 新增:参与成员,含发起人自身
}
  • 校验:发起人必须是家庭成员;participantMemberIds 必须包含发起人且均属本家庭
  • 行为:写入 family_challenge(status=pending)→ 写 participant 行(发起人 agreed,其余 pending)→ 立即结算发起评分(§4.4)→ 发起人能量 +1

5.2 同意 / 拒绝(新增)

POST /api/health/challenge/respond

{
  "challengeId": 123,
  "action": "agree"          // 或 "reject"
}
  • 校验:调用者必须是该挑战 participant 且 status=pending;挑战必须处于 pending
  • agree:status→agreed、agreed_at=now → 立即结算同意评分(§4.2)→ 若除 rejected 外的参与成员全部 agreed → 挑战 status→active
  • reject:status→rejected立即结算拒绝评分(§4.3)→ 若除发起人外无剩余参与成员 → 挑战自动 cancelled(待确认,见 §6 Q6)

5.3 列表(改造)

POST /api/health/challenge/list

  • 返回每张卡片我的参与状态myStatus: pending/agreed/rejected),前端据此渲染同意/拒绝按钮
  • pending 状态挑战:显示"等待成员确认(X/Y)"进度文案

5.4 打卡(改造校验)

POST /api/health/challenge/progress

  • 新增校验:调用者必须是对应挑战 participant 且 status=agreed;pending/rejected 返回"仅参与成员可打卡"

5.5 结算(改造口径)

  • settleChallenge():参与成员集合 = status=agreed 的成员(排除 rejected)
  • 结算仅发放给 agreed 成员

5.6 评分配置(管理端)

POST /api/admin/challenge-score-config/list / update(cfc-web 新增管理页,风格参照 EnergyBehaviorConfig.vue


6. 确认清单 ✅(2026-08-08 已全部拍板)

# 问题 结论
C1 拒绝时 I→X 的 trust/intimacy 是否也 -1 ✅ -1(与 communication 一致)
C2 拒绝时 X→I 的 trust/intimacy 是否不变 ✅ 不变(只加沟通)
C3 拒绝时已同意成员 A→X 方向是否也扣 只扣沟通 -1(trust/intimacy 不扣)
C4 拒绝时能量 -1 落点 ✅ I+A 每人 -1
D1 发起时 I→其他成员 是否 +1(intimacy/communication) ✅ +1(平行同意模式)
D2 发起时其他成员→I 反向是否不动 ✅ 不动
D3 发起时能量 +1 是仅发起人还是全员 ✅ 仅发起人("发起人加能量")
Q6 除发起人外全员拒绝时挑战如何处置 ✅ 自动 cancelled

7. 前端改动

文件 改动
pages/health/challenge-manage.vue ①移除角色门控(L173-174);②创建表单新增"参与成员多选"(默认全选);③展示 pending 状态
components/FamilyChallengeCard.vue ①pending 状态渲染:"等待确认(已同意 x/y)";②同意/拒绝按钮(仅我的状态=pending 时显示);③rejected 成员卡片显示"已拒绝"徽标
pages/index-home/index.vue 已加"管理 →"入口(已完成 7bdf6aa8
utils/api.js 新增 respondChallenge(challengeId, action);list 响应解析 myStatus

8. 后端改动清单

文件 改动
entity/FamilyChallengeParticipant.java 新建实体
entity/FamilyRelationshipScore.java 新建实体(方向分数)
entity/FamilyChallengeScoreConfig.java 新建实体(评分配置)
mapper/ 对应 3 个 Mapper 新建
service/FamilyChallengeService.java createChallenge 改 pending+participant 落库;新增 respondChallenge();打卡/结算校验接入 participant
service/ChallengeScoreService.java 新建:方向分数读取/初始化/增减 + 能量发放/扣减(读 config 表),供发起/同意/拒绝调用
controller/FamilyChallengeController.java 新增 respond 端点;create 校验 participant 列表;list 返回 myStatus
controller/admin/AdminChallengeScoreConfigController.java 新建:配置 CRUD
config/DatabaseInitializer.java 迁移:3 张新表 + family_challenge.status 兼容说明
resources/schema.sql 同步 3 张新表 DDL
cfc-web/src/views/admin/ChallengeScoreConfig.vue 新建管理页

9. 验收标准

  • 任意角色成员可发起挑战并选择参与成员
  • 创建后挑战为 pending,发起人自动 agreed
  • 参与成员在首页卡片可同意/拒绝,状态即时更新
  • 全部参与成员同意后挑战自动转 active
  • 除发起人外全员拒绝 → 挑战自动取消(按 Q6 确认结果)
  • 发起/同意/拒绝时方向性分数与能量按 §4 矩阵立即落账
  • 分数矩阵单元格与配置表一致(后台改 value 立即生效)
  • pending/rejected 成员打卡被拒绝;结算只统计 agreed 成员
  • 存量 active 挑战迁移后正常显示(participant 补全)

10. 范围外(后续迭代)

  • 挑战完成奖励(rewardPoints 能量发放、徽章)— 与本次参与机制正交,另起 spec
  • 到期自动结算定时任务
  • 打卡 UI(本次只做校验收紧,打卡按钮另起)
  • 通知(邀请同意/开始提醒)
  • 方向性分数在其他页面(家庭关系图/互动记录)的展示打通