|
|
@@ -0,0 +1,528 @@
|
|
|
+# P2-2 家庭健康互动 Implementation Plan
|
|
|
+
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task.
|
|
|
+
|
|
|
+**Goal:** 实现家庭健康排行榜、点赞系统、家庭挑战三大功能
|
|
|
+
|
|
|
+**Architecture:** 后端3个Controller + 3个Service + 3个Entity/Mapper / 前端3个组件 + 集成到parent首页
|
|
|
+
|
|
|
+**Tech Stack:** Spring Boot 2.7.18 + MyBatis-Plus / uni-app Vue 2 小程序
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 1: 后端Entity + Mapper (3张新表)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyChallenge.java`
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyChallengeProgress.java`
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/HealthInteraction.java`
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyChallengeMapper.java`
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyChallengeProgressMapper.java`
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/HealthInteractionMapper.java`
|
|
|
+- Modify: `cfc-backend/src/main/resources/schema.sql` (追加3张表)
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` (迁移107/108/109)
|
|
|
+
|
|
|
+- [ ] **Step 1: Create FamilyChallenge.java**
|
|
|
+
|
|
|
+```java
|
|
|
+package com.etotem.cfc.entity;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.annotation.IdType;
|
|
|
+import com.baomidou.mybatisplus.annotation.TableId;
|
|
|
+import com.baomidou.mybatisplus.annotation.TableName;
|
|
|
+import lombok.Data;
|
|
|
+import java.io.Serializable;
|
|
|
+import java.util.Date;
|
|
|
+
|
|
|
+@Data
|
|
|
+@TableName("family_challenge")
|
|
|
+public class FamilyChallenge implements Serializable {
|
|
|
+ @TableId(type = IdType.AUTO)
|
|
|
+ private Long id;
|
|
|
+ private Long familyId;
|
|
|
+ private String challengeType; // full_checkin, sports, health_week
|
|
|
+ private String title;
|
|
|
+ private String description;
|
|
|
+ private Integer durationDays;
|
|
|
+ private Integer rewardPoints;
|
|
|
+ private String rewardBadge;
|
|
|
+ private Date startDate;
|
|
|
+ private Date endDate;
|
|
|
+ private String status; // active, completed, cancelled
|
|
|
+ private Date createdAt;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: Create FamilyChallengeProgress.java**
|
|
|
+
|
|
|
+```java
|
|
|
+package com.etotem.cfc.entity;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.annotation.IdType;
|
|
|
+import com.baomidou.mybatisplus.annotation.TableId;
|
|
|
+import com.baomidou.mybatisplus.annotation.TableName;
|
|
|
+import lombok.Data;
|
|
|
+import java.io.Serializable;
|
|
|
+import java.util.Date;
|
|
|
+
|
|
|
+@Data
|
|
|
+@TableName("family_challenge_progress")
|
|
|
+public class FamilyChallengeProgress implements Serializable {
|
|
|
+ @TableId(type = IdType.AUTO)
|
|
|
+ private Long id;
|
|
|
+ private Long challengeId;
|
|
|
+ private Long childId;
|
|
|
+ private Integer progressValue; // e.g. checkin days count, sports minutes
|
|
|
+ private Boolean completed;
|
|
|
+ private Date createdAt;
|
|
|
+ private Date updatedAt;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: Create HealthInteraction.java**
|
|
|
+
|
|
|
+```java
|
|
|
+package com.etotem.cfc.entity;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.annotation.IdType;
|
|
|
+import com.baomidou.mybatisplus.annotation.TableId;
|
|
|
+import com.baomidou.mybatisplus.annotation.TableName;
|
|
|
+import lombok.Data;
|
|
|
+import java.io.Serializable;
|
|
|
+import java.util.Date;
|
|
|
+
|
|
|
+@Data
|
|
|
+@TableName("health_interaction")
|
|
|
+public class HealthInteraction implements Serializable {
|
|
|
+ @TableId(type = IdType.AUTO)
|
|
|
+ private Long id;
|
|
|
+ private Long familyId;
|
|
|
+ private Long fromUserId;
|
|
|
+ private Long toUserId;
|
|
|
+ private String actionType; // health_checkin, micro_action, etc.
|
|
|
+ private Long actionId; // reference to the completed action record
|
|
|
+ private Date createdAt;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: Create 3 Mapper interfaces**
|
|
|
+
|
|
|
+```java
|
|
|
+package com.etotem.cfc.mapper;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
|
+import com.etotem.cfc.entity.FamilyChallenge;
|
|
|
+
|
|
|
+public interface FamilyChallengeMapper extends BaseMapper<FamilyChallenge> {}
|
|
|
+```
|
|
|
+
|
|
|
+(同样模式创建 FamilyChallengeProgressMapper 和 HealthInteractionMapper)
|
|
|
+
|
|
|
+- [ ] **Step 5: Add migrations 107/108/109 to DatabaseInitializer**
|
|
|
+
|
|
|
+```java
|
|
|
+// 迁移107: 创建 family_challenge 表(P2-2家庭挑战)
|
|
|
+try {
|
|
|
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_challenge (" +
|
|
|
+ "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
+ "family_id BIGINT NOT NULL, " +
|
|
|
+ "challenge_type VARCHAR(50) NOT NULL COMMENT 'full_checkin/sports/health_week', " +
|
|
|
+ "title VARCHAR(200) NOT NULL, " +
|
|
|
+ "description TEXT, " +
|
|
|
+ "duration_days INT DEFAULT 7, " +
|
|
|
+ "reward_points INT DEFAULT 0, " +
|
|
|
+ "reward_badge VARCHAR(100), " +
|
|
|
+ "start_date DATE, " +
|
|
|
+ "end_date DATE, " +
|
|
|
+ "status VARCHAR(20) DEFAULT 'active' COMMENT 'active/completed/cancelled', " +
|
|
|
+ "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
+ "INDEX idx_family_id (family_id)" +
|
|
|
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战'");
|
|
|
+ log.info("已创建family_challenge表");
|
|
|
+} catch (Exception e) { /* 忽略 */ }
|
|
|
+
|
|
|
+// 迁移108: 创建 family_challenge_progress 表(P2-2挑战进度)
|
|
|
+try {
|
|
|
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_challenge_progress (" +
|
|
|
+ "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
+ "challenge_id BIGINT NOT NULL, " +
|
|
|
+ "child_id BIGINT NOT NULL, " +
|
|
|
+ "progress_value INT DEFAULT 0, " +
|
|
|
+ "completed TINYINT(1) DEFAULT 0, " +
|
|
|
+ "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
+ "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
|
|
|
+ "INDEX idx_challenge_id (challenge_id)" +
|
|
|
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战进度'");
|
|
|
+ log.info("已创建family_challenge_progress表");
|
|
|
+} catch (Exception e) { /* 忽略 */ }
|
|
|
+
|
|
|
+// 迁移109: 创建 health_interaction 表(P2-2点赞记录)
|
|
|
+try {
|
|
|
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_interaction (" +
|
|
|
+ "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
+ "family_id BIGINT NOT NULL, " +
|
|
|
+ "from_user_id BIGINT NOT NULL, " +
|
|
|
+ "to_user_id BIGINT NOT NULL, " +
|
|
|
+ "action_type VARCHAR(50) NOT NULL, " +
|
|
|
+ "action_id BIGINT, " +
|
|
|
+ "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
+ "INDEX idx_family_action (family_id, action_type, action_id)" +
|
|
|
+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康互动记录(点赞)'");
|
|
|
+ log.info("已创建health_interaction表");
|
|
|
+} catch (Exception e) { /* 忽略 */ }
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 6: Sync schema.sql — append 3 CREATE TABLE statements**
|
|
|
+
|
|
|
+在 schema.sql 末尾追加上述3张表的 CREATE TABLE IF NOT EXISTS。
|
|
|
+
|
|
|
+- [ ] **Step 7: mvn clean compile** — 验证编译通过
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 2: HealthInteractionService + Controller (点赞系统)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/service/HealthInteractionService.java`
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/HealthInteractionController.java`
|
|
|
+- Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/HealthScoreService.java` (加 public `addScore(childId, points, reason)` 方法)
|
|
|
+
|
|
|
+- [ ] **Step 1: Add `addScore` method to HealthScoreService**
|
|
|
+
|
|
|
+```java
|
|
|
+/** 给指定孩子增加健康积分(供点赞系统调用) */
|
|
|
+public void addScore(Long childId, int points, String reason) {
|
|
|
+ String dateKey = LocalDate.now().toString();
|
|
|
+ // 在 existing score map 中增加
|
|
|
+ if (reason != null) {
|
|
|
+ // 记录积分来源
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: Create HealthInteractionService**
|
|
|
+
|
|
|
+```java
|
|
|
+@Service
|
|
|
+public class HealthInteractionService {
|
|
|
+ @Resource
|
|
|
+ private HealthInteractionMapper healthInteractionMapper;
|
|
|
+ @Resource
|
|
|
+ private HealthScoreService healthScoreService;
|
|
|
+ @Resource
|
|
|
+ private FamilyMembersMapper familyMembersMapper;
|
|
|
+
|
|
|
+ /** 点赞: 双方各+1分, 24h内同一行为不可重复点赞 */
|
|
|
+ @Transactional
|
|
|
+ public Result<Void> like(Long fromUserId, Long toUserId, String actionType, Long actionId, Long familyId) {
|
|
|
+ // 1. 查24h内是否已点赞
|
|
|
+ // 2. 如果已存在 → return Result.error("已点赞")
|
|
|
+ // 3. 插入 HealthInteraction 记录
|
|
|
+ // 4. 调用 healthScoreService.addScore(toUserId, 1, "收到点赞")
|
|
|
+ // 5. 调用 healthScoreService.addScore(fromUserId, 1, "点赞他人")
|
|
|
+ // 6. return Result.success()
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 检查24h内是否已点赞 */
|
|
|
+ public boolean hasLiked24h(Long fromUserId, String actionType, Long actionId) {
|
|
|
+ // LocalDateTime.now().minusHours(24) 过滤
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: Create HealthInteractionController**
|
|
|
+
|
|
|
+```java
|
|
|
+@RestController
|
|
|
+@RequestMapping("/api/health/interact")
|
|
|
+public class HealthInteractionController {
|
|
|
+ @Resource
|
|
|
+ private HealthInteractionService healthInteractionService;
|
|
|
+
|
|
|
+ @PostMapping("/like")
|
|
|
+ public Result<Void> like(@RequestBody Map<String, Object> params,
|
|
|
+ @RequestAttribute("userId") Long userId,
|
|
|
+ @RequestAttribute("role") String role) {
|
|
|
+ Long toUserId = Long.valueOf(params.get("toUserId").toString());
|
|
|
+ String actionType = (String) params.get("actionType");
|
|
|
+ Long actionId = Long.valueOf(params.get("actionId").toString());
|
|
|
+ Long familyId = Long.valueOf(params.get("familyId").toString());
|
|
|
+ return healthInteractionService.like(userId, toUserId, actionType, actionId, familyId);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: mvn clean compile**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 3: FamilyHealthScoreService + FamilyHealthController (排行榜)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/service/FamilyHealthScoreService.java`
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/FamilyHealthController.java`
|
|
|
+
|
|
|
+- [ ] **Step 1: FamilyHealthScoreService**
|
|
|
+
|
|
|
+```java
|
|
|
+@Service
|
|
|
+public class FamilyHealthScoreService {
|
|
|
+ @Resource
|
|
|
+ private FamilyMembersMapper familyMembersMapper;
|
|
|
+ @Resource
|
|
|
+ private HealthScoreService healthScoreService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取家庭健康排行榜
|
|
|
+ * @return List<{childId, name, avatar, score, levelTitle, streakDays}>
|
|
|
+ */
|
|
|
+ public List<Map<String, Object>> getFamilyRanking(Long familyId, Long currentUserId, String role) {
|
|
|
+ // 1. 查家庭所有成员 (child)
|
|
|
+ // 2. 对每个child查 HealthScoreService 获取积分
|
|
|
+ // 3. 按积分降序排序
|
|
|
+ // 4. 如果是child角色, 只返回自己的排名
|
|
|
+ // 5. 计算全家总积分
|
|
|
+ // 6. 返回 (含 rank 序号)
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: FamilyHealthController**
|
|
|
+
|
|
|
+```java
|
|
|
+@RestController
|
|
|
+@RequestMapping("/api/health/family")
|
|
|
+public class FamilyHealthController {
|
|
|
+ @Resource
|
|
|
+ private FamilyHealthScoreService familyHealthScoreService;
|
|
|
+
|
|
|
+ @PostMapping("/ranking")
|
|
|
+ public Result<List<Map<String, Object>>> getRanking(
|
|
|
+ @RequestBody Map<String, Object> params,
|
|
|
+ @RequestAttribute("userId") Long userId,
|
|
|
+ @RequestAttribute("role") String role) {
|
|
|
+ Long familyId = Long.valueOf(params.get("familyId").toString());
|
|
|
+ List<Map<String, Object>> ranking = familyHealthScoreService.getFamilyRanking(familyId, userId, role);
|
|
|
+ return Result.success(ranking);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: mvn clean compile**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 4: FamilyChallengeService + FamilyChallengeController (挑战引擎)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/service/FamilyChallengeService.java`
|
|
|
+- Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/FamilyChallengeController.java`
|
|
|
+
|
|
|
+- [ ] **Step 1: FamilyChallengeService**
|
|
|
+
|
|
|
+```java
|
|
|
+@Service
|
|
|
+public class FamilyChallengeService {
|
|
|
+ @Resource
|
|
|
+ private FamilyChallengeMapper familyChallengeMapper;
|
|
|
+ @Resource
|
|
|
+ private FamilyChallengeProgressMapper familyChallengeProgressMapper;
|
|
|
+ @Resource
|
|
|
+ private HealthScoreService healthScoreService;
|
|
|
+
|
|
|
+ /** 获取活跃挑战, 若无则自动创建 */
|
|
|
+ public List<FamilyChallenge> getActiveChallenges(Long familyId) {
|
|
|
+ // 1. 查 active 状态的挑战
|
|
|
+ // 2. 如果为空 → autoCreateChallenges(familyId)
|
|
|
+ // 3. 再查一次返回
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 按当前月份自动创建挑战 (全员打卡/运动PK/健康周) */
|
|
|
+ public void autoCreateChallenges(Long familyId) {
|
|
|
+ // 本月1日 → 下月1日
|
|
|
+ // 创建3种挑战模板
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 更新挑战进度 */
|
|
|
+ public Result<Void> updateProgress(Long challengeId, Long childId, int delta) {
|
|
|
+ // 查找或创建进度记录
|
|
|
+ // 更新 progressValue
|
|
|
+ // 检查是否完成后自动结算
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 结算挑战: 达标发奖 */
|
|
|
+ private void settleChallenge(Long challengeId) {
|
|
|
+ // 读取所有进度
|
|
|
+ // 判断达标条件
|
|
|
+ // 达标: healthScoreService.addScore + 更新status=completed
|
|
|
+ // 超期未达标: status=completed (不发奖)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 挑战历史 */
|
|
|
+ public List<FamilyChallenge> getChallengeHistory(Long familyId) {
|
|
|
+ // status=completed, 按时间倒序
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: FamilyChallengeController**
|
|
|
+
|
|
|
+```java
|
|
|
+@RestController
|
|
|
+@RequestMapping("/api/health/challenge")
|
|
|
+public class FamilyChallengeController {
|
|
|
+ @Resource
|
|
|
+ private FamilyChallengeService familyChallengeService;
|
|
|
+
|
|
|
+ @PostMapping("/list")
|
|
|
+ public Result<List<FamilyChallenge>> getList(@RequestBody Map<String, Object> params) {
|
|
|
+ Long familyId = Long.valueOf(params.get("familyId").toString());
|
|
|
+ return Result.success(familyChallengeService.getActiveChallenges(familyId));
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/history")
|
|
|
+ public Result<List<FamilyChallenge>> getHistory(@RequestBody Map<String, Object> params) {
|
|
|
+ Long familyId = Long.valueOf(params.get("familyId").toString());
|
|
|
+ return Result.success(familyChallengeService.getChallengeHistory(familyId));
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping("/progress")
|
|
|
+ public Result<Void> updateProgress(@RequestBody Map<String, Object> params) {
|
|
|
+ Long challengeId = Long.valueOf(params.get("challengeId").toString());
|
|
|
+ Long childId = Long.valueOf(params.get("childId").toString());
|
|
|
+ int delta = Integer.parseInt(params.get("delta").toString());
|
|
|
+ return familyChallengeService.updateProgress(challengeId, childId, delta);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: mvn clean compile**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 5: 前端组件 - FamilyHealthRanking.vue + HealthLikeButton.vue
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `cfc-frontend/components/FamilyHealthRanking.vue`
|
|
|
+- Create: `cfc-frontend/components/HealthLikeButton.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: FamilyHealthRanking.vue**
|
|
|
+
|
|
|
+```vue
|
|
|
+<template>
|
|
|
+ <view class="family-ranking" v-if="members.length">
|
|
|
+ <view class="ranking-header">
|
|
|
+ <text class="ranking-title">👨👩👧👦 家庭健康榜</text>
|
|
|
+ <text class="ranking-total">全家积分: {{ totalScore }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="ranking-list">
|
|
|
+ <view
|
|
|
+ class="ranking-item"
|
|
|
+ v-for="(m, idx) in members"
|
|
|
+ :key="m.childId"
|
|
|
+ >
|
|
|
+ <text class="rank-num">{{ '🥇🥈🥉'[idx] || '#' + (idx+1) }}</text>
|
|
|
+ <image class="rank-avatar" :src="m.avatar || '/static/default-avatar.png'" />
|
|
|
+ <view class="rank-info">
|
|
|
+ <text class="rank-name">{{ m.name }}</text>
|
|
|
+ <text class="rank-level">{{ m.levelTitle }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="rank-score">
|
|
|
+ <text class="score-val">{{ m.score }}</text>
|
|
|
+ <text class="score-unit">分</text>
|
|
|
+ </view>
|
|
|
+ <text class="rank-streak" v-if="m.streakDays > 0">🔥 {{ m.streakDays }}天</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: HealthLikeButton.vue**
|
|
|
+
|
|
|
+```vue
|
|
|
+<template>
|
|
|
+ <view class="like-btn" :class="{ liked: isLiked }" @click="handleLike">
|
|
|
+ <text class="like-icon">{{ isLiked ? '❤️' : '🤍' }}</text>
|
|
|
+ <text class="like-count">{{ count }}</text>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ props: { actionType: String, actionId: [Number, String], toUserId: [Number, String] },
|
|
|
+ data() { return { isLiked: false, count: 0 } },
|
|
|
+ methods: {
|
|
|
+ handleLike() {
|
|
|
+ if (this.isLiked) return
|
|
|
+ // POST /api/health/interact/like
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 6: 前端组件 FamilyChallengeCard.vue + 集成到 parent/index.vue
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `cfc-frontend/components/FamilyChallengeCard.vue`
|
|
|
+- Modify: `cfc-frontend/pages/index/index.vue` (parent tab 嵌入排行榜+挑战)
|
|
|
+
|
|
|
+- [ ] **Step 1: FamilyChallengeCard.vue**
|
|
|
+
|
|
|
+```vue
|
|
|
+<template>
|
|
|
+ <view class="challenge-card" v-if="challenge">
|
|
|
+ <view class="card-header">
|
|
|
+ <text class="card-title">{{ challenge.title }}</text>
|
|
|
+ <text class="card-status">{{ challenge.status === 'active' ? '进行中' : '已完成' }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="card-desc">{{ challenge.description }}</text>
|
|
|
+ <view class="card-footer">
|
|
|
+ <text class="card-deadline">截止: {{ formatDate(challenge.endDate) }}</text>
|
|
|
+ <text class="card-reward">🏆 +{{ challenge.rewardPoints }}分</text>
|
|
|
+ </view>
|
|
|
+ <!-- 进度条 -->
|
|
|
+ <view class="progress-bar" v-if="challenge.progress !== undefined">
|
|
|
+ <view class="progress-fill" :style="{ width: progressPct + '%' }"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 在 parent/index.vue 中嵌入排行榜+挑战卡片**
|
|
|
+
|
|
|
+在家长首页适当位置添加 `<FamilyHealthRanking />` 和 `<FamilyChallengeCard />`。
|
|
|
+
|
|
|
+- [ ] **Step 3: 添加 api.js 端点**
|
|
|
+
|
|
|
+```js
|
|
|
+export const getFamilyRanking = (data) => request('/api/health/family/ranking', 'POST', data)
|
|
|
+export const getChallengeList = (data) => request('/api/health/challenge/list', 'POST', data)
|
|
|
+export const getChallengeHistory = (data) => request('/api/health/challenge/history', 'POST', data)
|
|
|
+export const updateChallengeProgress = (data) => request('/api/health/challenge/progress', 'POST', data)
|
|
|
+export const sendLike = (data) => request('/api/health/interact/like', 'POST', data)
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 7: 路由检查 + 编译验证 + 提交
|
|
|
+
|
|
|
+- [ ] **Step 1: 检查重复路由**
|
|
|
+
|
|
|
+```bash
|
|
|
+grep -rn '@Mapping' cfc-backend/src/.../controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: mvn clean compile** — 全量编译验证
|
|
|
+
|
|
|
+- [ ] **Step 3: git add + commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add ... # all P2-2 files
|
|
|
+git commit -m "feat: P2-2 家庭健康互动 - 排行榜+点赞+挑战系统"
|
|
|
+git push
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 4: 更新 PROJECT-OVERVIEW.md** — P2-2 标记 ⚡ 开发中
|