Browse Source

chore: auto bump version and changelog [skip ci]

iwt 1 month ago
parent
commit
e876ceeeef

+ 40 - 13
cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideTeamController.java

@@ -10,6 +10,7 @@ import com.etotem.cfc.service.GuideHierarchyService;
 import com.etotem.cfc.service.GuideInviteCodeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
@@ -30,6 +31,9 @@ public class GuideTeamController {
     @Resource
     private UserMapper userMapper;
 
+    @Resource
+    private JdbcTemplate jdbcTemplate;
+
     @Operation(summary = "获取我的团队")
     @PostMapping("/my-team")
     public Result<Map<String, Object>> getMyTeam(@RequestAttribute("userId") Long guideId) {
@@ -38,7 +42,7 @@ public class GuideTeamController {
         List<User> teamMembers = new ArrayList<>();
         if (inviteCode != null) {
             List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>()
-                    .like(User::getTeacherNo, inviteCode.getCode()));
+                    .eq(User::getTeacherNo, inviteCode.getCode()));
             teamMembers = users;
         }
 
@@ -75,7 +79,7 @@ public class GuideTeamController {
             return Result.success(new ArrayList<>());
         }
         List<User> members = userMapper.selectList(new LambdaQueryWrapper<User>()
-                .like(User::getTeacherNo, inviteCode.getCode()));
+                .eq(User::getTeacherNo, inviteCode.getCode()));
         return Result.success(members);
     }
 
@@ -199,21 +203,44 @@ public class GuideTeamController {
     public Result<Map<String, Object>> getTeamStats(@RequestAttribute("userId") Long guideId) {
         GuideInviteCode inviteCode = guideInviteCodeService.getByGuideId(guideId);
         Map<String, Object> stats = new HashMap<>();
-        
+
         if (inviteCode == null) {
-            stats.put("totalMembers", 0);
-            stats.put("totalFamilies", 0);
-            stats.put("totalChildren", 0);
+            stats.put("totalSales", 0);
+            stats.put("myCommission", 0);
+            stats.put("familyCount", 0);
+            stats.put("memberCount", 0);
             return Result.success(stats);
         }
-        
+
         List<User> members = userMapper.selectList(new LambdaQueryWrapper<User>()
-                .like(User::getTeacherNo, inviteCode.getCode()));
-        
-        stats.put("totalMembers", members.size());
-        stats.put("totalFamilies", 0);
-        stats.put("totalChildren", 0);
-        
+                .eq(User::getTeacherNo, inviteCode.getCode()));
+
+        long totalSales = 0L;
+        for (User member : members) {
+            Object sum = jdbcTemplate.queryForObject(
+                    "SELECT COALESCE(SUM(amount),0) FROM guide_orders WHERE teacher_id = ? AND status IN ('PAID','COMPLETED')",
+                    Long.class, member.getId());
+            if (sum != null) totalSales += ((Number) sum).longValue();
+        }
+
+        Object myComm = jdbcTemplate.queryForObject(
+                "SELECT COALESCE(SUM(amount),0) FROM guide_commissions WHERE teacher_id = ? AND status = 'COMPLETED'",
+                Long.class, guideId);
+        long myCommission = myComm != null ? ((Number) myComm).longValue() : 0L;
+
+        Set<Long> familyIds = new HashSet<>();
+        for (User member : members) {
+            List<Long> ids = jdbcTemplate.queryForList(
+                    "SELECT DISTINCT family_id FROM guide_orders WHERE teacher_id = ? AND status IN ('PAID','COMPLETED')",
+                    Long.class, member.getId());
+            if (ids != null) familyIds.addAll(ids);
+        }
+
+        stats.put("totalSales", totalSales);
+        stats.put("myCommission", myCommission);
+        stats.put("familyCount", familyIds.size());
+        stats.put("memberCount", members.size());
+
         return Result.success(stats);
     }
 }

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-903c2c7146c2f974b041666f705e2edbd0014f17
+ba73fbc3db8d2db2cba1bc07acc91ff1da1d6650

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1386",
+  "version": "1.0.1387",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1386",
+      "version": "1.0.1387",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1387",
+  "version": "1.0.1388",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 16 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,22 @@
 
 ---
 
+## v1.0.1388 (2026-09-13)
+
+### 新功能
+- 五维手动填分 + MBTI 完整体系(后端/小程序/管理端)
+
+### 其他
+-   - MbtiController: MBTI 测评入口/提交/结果页
+-   - ManualDimensionScoreController: 家长手动填家庭成员五维分数(0-100)
+-   - MbtiProfileController: 16型人格画像管理
+- - 数据库: mbti_questions / mbti_results / mbti_profiles 三表 + 种子数据
+- - 小程序: mbti-entry / mbti-test / mbti-profile / mbti-result / manual-score-entry 5页
+- - Web管理端: MbtiProfiles.vue + mbti.js API
+- - 计划文档: docs/superpowers/plans/2026-09-10-manual-dimension-mbti-plan.md
+- 
+
+
 ## v1.0.1387 (2026-09-13)
 
 ### 新功能

+ 17 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1387
+> 当前版本: v1.0.1388
 
 ## 历史版本
 
@@ -8,6 +8,22 @@
 
 ---
 
+## v1.0.1388 (2026-09-13)
+
+### 新功能
+- 五维手动填分 + MBTI 完整体系(后端/小程序/管理端)
+
+### 其他
+-   - MbtiController: MBTI 测评入口/提交/结果页
+-   - ManualDimensionScoreController: 家长手动填家庭成员五维分数(0-100)
+-   - MbtiProfileController: 16型人格画像管理
+- - 数据库: mbti_questions / mbti_results / mbti_profiles 三表 + 种子数据
+- - 小程序: mbti-entry / mbti-test / mbti-profile / mbti-result / manual-score-entry 5页
+- - Web管理端: MbtiProfiles.vue + mbti.js API
+- - 计划文档: docs/superpowers/plans/2026-09-10-manual-dimension-mbti-plan.md
+- 
+
+
 ## v1.0.1387 (2026-09-13)
 
 ### 新功能

+ 220 - 0
docs/superpowers/specs/2026-09-13-report-quick-analyze-extend-design.md

@@ -0,0 +1,220 @@
+# 报告快速分析扩展:上传后即可用
+
+日期:2026-09-13
+状态:设计中
+
+## 1. 背景与目标
+
+当前「报告快速分析」功能(`/api/ai/report/analyze`)仅在 `report-detail.vue`(view 模式)可用。用户上传报告后:
+- 菌群报告 → 跳转 `gut-flora-detail.vue`(无快速分析入口)
+- 两阶段入库 → 停留 `report-confirm.vue`(无快速分析入口)
+
+**目标**:在 `gut-flora-detail.vue` 和 `report-confirm.vue` 也提供「快速分析」入口,支持已入库的 `reportId` 和未入库的 `draftId`。
+
+---
+
+## 2. 架构设计
+
+### 2.1 后端扩展(cfc-backend)
+
+```
+POST /api/ai/report/analyze
+Request: { query, reportId?, draftId?, conversationId?, memberId? }
+```
+
+- 端点同时接受 `reportId` 和 `draftId`(至少一个非空)
+- 后端判断:优先用 `reportId`(已入库,完整上下文),其次用 `draftId`(草稿,读 payloadJson)
+- 免费版 Redis 计数、会员限制、fail-open 逻辑保持不变
+- LangGraph `chat_graph` 零改动
+
+### 2.2 数据流
+
+```
+前端页面(gut-flora-detail / report-confirm)
+    │ POST /api/ai/report/analyze { query, reportId/draftId }
+    ▼
+AIChatController.reportAnalyze()
+    ├─ 1. 会员级别检查(user.memberLevel)
+    ├─ 2. FREE → Redis 计数(fail-open)
+    ├─ 3. buildChatInputs(userId, params)
+    │     ├─ 有 reportId → familyContextService.buildContext(userId, reportId)
+    │     └─ 只有 draftId → familyContextService.buildContextFromDraft(userId, draftId) [新增]
+    └─ 4. aiService.sendMessage() → LangGraph chat_graph
+```
+
+### 2.3 前端扩展(cfc-frontend)
+
+| 页面 | 入口条件 | 传参 |
+|------|---------|------|
+| `report-detail.vue` | view 模式 + reportId | reportId(已有) |
+| `gut-flora-detail.vue` | (!isEditing && !isDraftMode && reportId) \|\| (isDraftMode && draftId) | reportId 或 draftId |
+| `report-confirm.vue` | !parsing && draftId | draftId |
+
+---
+
+## 3. 改动清单
+
+### 3.1 后端(cfc-backend)
+
+| 文件 | 改动 |
+|------|------|
+| `FamilyContextService.java` | 新增 `buildContextFromDraft(Long userId, Long draftId)`,注入 `HealthReportDraftService`,查草稿 → 解析 `payloadJson` → 返回含 `reportDetail` 的上下文 |
+| `AIChatController.java` | 1. `buildChatInputs`:检测 `params.get("draftId")`,调用新方法<br>2. `reportAnalyze`:校验改为 `reportId || draftId`,空则报错 |
+
+### 3.2 前端(cfc-frontend)
+
+| 文件 | 改动 |
+|------|------|
+| `utils/api.js` | `reportQuickAnalyze` 新增可选参数 `draftId` |
+| `pages/health/gut-flora-detail.vue` | 1. import 新增 `reportQuickAnalyze`、`getMyMembership`<br>2. data 新增 `quickAnalyze` 对象<br>3. computed 新增 `quickQuestions`<br>4. methods 新增 `openQuickAnalyze`、`closeQuickAnalyze`、`sendQuickQuestion`、`sendQuickAnalyze`<br>5. template:FAB 区域(第 229 行附近)加「🎯 快速分析」按钮 + 浮层结构<br>6. style:复用 report-detail.vue 样式(`.quick-analyze-*` 类) |
+| `pages/health/report-confirm.vue` | 同 gut-flora-detail.vue,但入口条件为 `!parsing && draftId` |
+
+---
+
+## 4. 关键实现细节
+
+### 4.1 FamilyContextService.buildContextFromDraft
+
+```java
+/**
+ * 基于草稿 ID 构建上下文(用于快速分析)
+ * @param userId 当前用户
+ * @param draftId 草稿 ID
+ * @return 上下文 Map,包含 reportDetail(从 payloadJson 还原)
+ */
+public Map<String, Object> buildContextFromDraft(Long userId, Long draftId) {
+    Map<String, Object> ctx = buildContext(userId);
+
+    if (draftId == null) return ctx;
+
+    HealthReportDraft draft = healthReportDraftService.getDraftById(draftId);
+    if (draft == null || !draft.getUserId().equals(userId)) {
+        return ctx; // 权限不通过静默返回基础上下文
+    }
+
+    // 解析 payloadJson
+    Map<String, Object> reportDetail = null;
+    try {
+        if (draft.getPayloadJson() != null && !draft.getPayloadJson().isEmpty()) {
+            reportDetail = objectMapper.readValue(draft.getPayloadJson(), Map.class);
+        }
+    } catch (Exception e) {
+        log.warn("解析草稿 payload 失败: {}", e.getMessage());
+    }
+    if (reportDetail != null) {
+        ctx.put("reportDetail", reportDetail);
+    }
+
+    return ctx;
+}
+```
+
+### 4.2 AIChatController.buildChatInputs 修改
+
+```java
+private Map<String, Object> buildChatInputs(Long userId, Map<String, String> params) {
+    // ... 现有 mascot、memberId、surveyId、selfCheckId 逻辑不变 ...
+
+    Long reportId = null;
+    Long draftId = null;
+    if (reportIdStr != null && !reportIdStr.trim().isEmpty()) {
+        reportId = Long.valueOf(reportIdStr);
+    }
+    String draftIdStr = params.get("draftId");
+    if (draftIdStr != null && !draftIdStr.trim().isEmpty()) {
+        draftId = Long.valueOf(draftIdStr);
+    }
+
+    Map<String, Object> inputs;
+    if (reportId != null) {
+        inputs = familyContextService.buildContext(userId, reportId);
+    } else if (draftId != null) {
+        inputs = familyContextService.buildContextFromDraft(userId, draftId);
+    } else {
+        inputs = familyContextService.buildContext(userId);
+    }
+    // ... mascot、portrait、selfCheck、memory 后续逻辑不变 ...
+}
+```
+
+### 4.3 AIChatController.reportAnalyze 校验修改
+
+```java
+String reportIdStr = params.get("reportId");
+String draftIdStr = params.get("draftId");
+if ((reportIdStr == null || reportIdStr.trim().isEmpty())
+        && (draftIdStr == null || draftIdStr.trim().isEmpty())) {
+    return Result.error("报告ID或草稿ID不能为空");
+}
+```
+
+### 4.4 api.js 扩展
+
+```javascript
+// 报告快速分析(免费版每日1次,付费版多轮)
+export const reportQuickAnalyze = (query, reportId, conversationId, draftId) => {
+  var data = { query: query }
+  if (reportId) data.reportId = reportId
+  if (draftId) data.draftId = draftId
+  if (conversationId) data.conversationId = conversationId
+  return request('/api/ai/report/analyze', 'POST', data)
+}
+```
+
+### 4.5 gut-flora-detail.vue 入口条件
+
+```html
+<!-- 仅查看模式:reportId 或 draftId 存在时 -->
+<view class="quick-analyze-fab" v-if="!isEditing && (reportId || draftId)" @tap="openQuickAnalyze">
+```
+
+### 4.6 report-confirm.vue 入口条件
+
+```html
+<!-- 解析完成、非解析中 -->
+<view class="quick-analyze-fab" v-if="!parsing && draftId" @tap="openQuickAnalyze">
+```
+
+---
+
+## 5. 会员限制与免费版策略
+
+- 完全复用现有逻辑:`user.memberLevel` 判定 FREE/FAMILY/PROVIDER
+- FREE:每日 1 次(Redis 计数 key 唯一,`report:analyze:free:userId:date`,无区分 reportId/draftId)
+- FREE:前端不传 `conversationId` → LangGraph 每次新建会话 → 锁死多轮
+- 付费版:可传 `conversationId` 实现多轮
+
+---
+
+## 6. 错误处理
+
+| 场景 | 行为 |
+|------|------|
+| query 为空 | `Result.error("消息不能为空")` |
+| reportId 和 draftId 均空 | `Result.error("报告ID或草稿ID不能为空")` |
+| 草稿不存在/无权限 | 静默降级,仅返回基础上下文(AI 仅做通用回答) |
+| 免费版超限 | `Result.error("免费版每日仅限 1 次快速分析,升级会员解锁更多")` |
+| Redis 异常 | fail-open 放行 + warn 日志 |
+| AI 服务不可用 | `Result.error("AI 服务暂不可用,请稍后重试")` |
+
+---
+
+## 7. 验收标准
+
+| 场景 | 预期 |
+|------|------|
+| gut-flora-detail(reportId)点击快速分析 | 正常返回 answer |
+| gut-flora-detail(draftId)点击快速分析 | 正常返回 answer(基于草稿 payload) |
+| report-confirm(draftId)点击快速分析 | 正常返回 answer |
+| 免费版第 2 次(任意页面) | 返回超限错误 |
+| 付费版多轮追问 | 带 conversationId,AI 记得上文 |
+| Redis 停掉 | 免费版仍可用(fail-open) |
+
+---
+
+## 8. 风险与注意事项
+
+1. **草稿 payload 结构**:`payloadJson` 是 `ParsedReportPayload.Payload` 的 JSON,结构与 `getReportDetail` 返回的 `reportDetail` 兼容(含 summary/indicators/gutFlora/diseaseRisks 等)
+2. **权限校验**:草稿属于上传用户,`getDraftById` 后需校验 `draft.getUserId().equals(userId)`
+3. **Redis 计数 key**:复用现有 key,同一用户同一天无论 reportId 还是 draftId 共用 1 次额度
+4. **样式复用**:直接复制 report-detail.vue 的 `.quick-analyze-*` CSS 类名,避免重复维护