Jelajahi Sumber

fix: 商品图片上传两项关键修复

Xiaogang Liao 2 bulan lalu
induk
melakukan
b3c7974bda

+ 1 - 1
cfc-web/.env.production

@@ -1,2 +1,2 @@
-VUE_APP_BASE_API=
+VUE_APP_BASE_API=api
 VUE_APP_SKIP_CAPTCHA=true

+ 7 - 0
cfc-web/src/views/Layout.vue

@@ -207,6 +207,13 @@ export default {
         
           ]},
 
+      // ===== 6.5 活动管理(独立模块) =====
+      { title: '活动管理', icon: 'el-icon-date', perm: 'activity:*', children: [
+        { path: '/activities', label: '活动列表', icon: 'el-icon-date', perm: 'activity:list' },
+        { path: '/activity-review', label: '活动审核', icon: 'el-icon-document-checked', perm: 'activity:review' },
+        { path: '/activity-registration-review', label: '活动报名审核', icon: 'el-icon-document-checked', perm: 'activity:review' },
+      ]},
+
         // ===== 7. 健康饮食 (admin + nutritionist) =====
         { title: '健康饮食', icon: 'el-icon-first-aid-kit', perm: 'health',
           children: [

+ 11 - 10
cfc-web/src/views/admin/ProductEdit.vue

@@ -87,13 +87,14 @@
               <img :src="img" class="image-preview" />
               <span class="image-remove" @click="removeImage(idx)">×</span>
             </div>
-            <el-upload
-              :action="uploadActionUrl"
-              :headers="uploadHeaders"
-              :show-file-list="false"
-              :on-success="handleImageSuccess"
-              :before-upload="beforeUpload"
-              accept="image/*"
+        <el-upload
+          :action="uploadActionUrl"
+          :headers="uploadHeaders"
+          :show-file-list="false"
+          :on-success="handleImageSuccess"
+          :before-upload="beforeUpload"
+          accept="image/*"
+          multiple
             >
               <i class="el-icon-plus image-add-icon"></i>
             </el-upload>
@@ -280,9 +281,9 @@ export default {
       const roles = rolesStr ? JSON.parse(rolesStr) : []
       return roles.includes('supplier_admin')
     },
-    uploadActionUrl() {
-      const baseURL = process.env.VUE_APP_BASE_API || 'http://localhost:9082'
-      return baseURL + '/api/admin/articles/upload/image'
+  uploadActionUrl() {
+    const baseURL = process.env.VUE_APP_BASE_API || ''
+    return baseURL + '/api/admin/articles/upload/image'
     },
     uploadHeaders() {
       const token = localStorage.getItem('token')

+ 2 - 2
cfc-web/src/views/admin/components/ProductSkuEditor.vue

@@ -125,8 +125,8 @@ export default {
       return this.editingId ? '编辑SKU' : '添加SKU'
     },
     uploadActionUrl() {
-      const baseURL = process.env.VUE_APP_BASE_API || 'http://localhost:9082'
-      return baseURL + '/api/admin/articles/upload/image'
+const baseURL = process.env.VUE_APP_BASE_API || ''
+    return baseURL + '/api/admin/articles/upload/image'
     },
     uploadHeaders() {
       const token = localStorage.getItem('token')

+ 409 - 0
docs/数据与AI结合技术方案.md

@@ -0,0 +1,409 @@
+# 数据与 AI 结合技术方案
+
+## 一、现有系统分析
+
+### 1.1 Dify AI 聊天架构
+
+```
+前端 (ai/chat.vue) → AIChatController → AIService → Dify Chatbot API (http://dify.bianwoyou.cn/v1)
+                                         ↕
+                                    FamilyContextService (家庭上下文)
+                                    AiConversationSummary (记忆层)
+                                    AiUserFact (用户事实)
+```
+
+**核心数据流:**
+1. 用户发消息 → `POST /api/ai/chat/send` (参数: query + conversationId + reportId/surveyId)
+2. `FamilyContextService.buildContext(userId, reportId, surveyId)` 组装家庭上下文
+3. 注入 mascot 角色信息到 Dify inputs
+4. `AiService.enrichInputsWithMemory()` 注入记忆层(最近摘要 + 关键事实)
+5. 调用 Dify `/chat-messages` API
+6. 返回 answer → 前端展示
+
+**已有能力:**
+- 家庭上下文注入(用户信息、孩子列表、任务完成率、健康报告、五维能量分数、认知测评结果、已购商品标签)
+- 报告上下文注入(reportId → reportDetail + surveyData)
+- Mascot 人格注入(浠宝 🌟/福宝 💪)
+- AI 记忆层(会话摘要 + 关键事实,通过 Dify Workflow 异步生成)
+- 两个 Dify 应用:通用家庭助手 + 精准营养助手(不同 API Key)
+
+### 1.2 健康报告体系
+
+```
+报告上传 (report-upload.vue)
+  ├── 体检报告 (physical_exam) → HealthReportService
+  ├── 肠道菌群报告 (gut_flora)  → HealthAnalysisService 分析 → 营养推荐
+  └── 舌诊拍照 (tongue)         → AIService.sendTongueDiagnosis()
+
+DAN 报告上传 (DanReportController)
+  └── Upload PDF → DanReportParseService 解析 → 编辑/确认 → 入库到 DanAssessmentResult
+      └── 确认后刷新五维能量分数
+```
+
+**健康分析流程:**
+- `HealthAnalysisService.analyze(reportId)` → 分析菌群、指标 → 生成 findings/suggestions/nutritionTags
+- 分析后异步关联推荐商品(`ProductRecommendationLog`)
+- 分析结果注入 Dify inputs → 精准营养助手
+
+### 1.3 推荐系统
+
+```java
+RecommendationService.search(RecommendationQuery)
+  ├── searchProducts(tags, limit)    → Product 表(状态=上架, stock>0, 名称/描述模糊匹配)
+  ├── searchActivities(tags, limit)  → Activity 表(状态=published, 未过期)
+  ├── searchArticles(tags, limit)    → Article 表(状态=published)
+  ├── searchViaDify(tags, limit)     → Dify 知识库 fallback
+  └── getDimensionProducts(dimensionCode) → ProductDimensionMapping 维度匹配
+
+// 营养模式下:Dify 返回 [RECOMMEND:{"tags":["益生菌","膳食纤维"],"types":["product"],"limit":3}]
+// AIChatController 解析 → RecommendationService.search → 返回推荐列表
+```
+
+### 1.4 任务生成
+
+- `GrowthTaskService` 管理预定义 DAILY/NEWBIE 任务(如 DAILY_AI = 每日 AI 对话)
+- `AIChatController` 在每次 AI 对话后调用 `growthTaskService.updateProgress(userId, "DAILY_AI", 1)`
+- 不支持 AI 动态生成任务
+
+### 1.5 饮食推荐
+
+- `MealRecommendController` → `MealRecommendService` → 聚合用户上下文 → 调用 Dify 营养助手生成食谱
+
+---
+
+## 二、目标方案设计
+
+### 核心需求
+
+1. **报告上传 → AI 解读**:用户上传健康报告/测评报告后,自动触发浠宝/福宝互动解读
+2. **上下文增强 AI**:AI 对话中携带用户关键数据(报告、任务、能量、历史对话)
+3. **AI 生成任务**:AI 解读过程中可生成待办任务
+4. **AI 内容推荐**:解读过程中推荐相关商品/活动/文章
+
+### 2.1 报告上传 → AI 解读
+
+**触发场景:**
+
+| 场景 | 入口 | 报告类型 | 解读方式 |
+|------|------|----------|----------|
+| 健康报告上传 | `report-upload.vue` | health_report | 上传后弹窗"去问浠宝/福宝" → 跳转 chat.vue?mode=report&reportId=X |
+| 菌群报告完成 | `gut-index.vue` | gut_flora | 分析完成后自动弹出 AI 解读入口 |
+| DAN 报告确认 | `DanReportController.confirm` | dan_assessment | 确认入库后自动触发 AI 解读任务 |
+| 舌诊完成 | `tongue-index.vue` | tongue | 舌诊结果页面→点击"AI 分析"→跳转 chat |
+
+**流程图:**
+
+```
+用户上传报告
+    ↓
+解析/保存成功
+    ↓
+页面显示"AI 解读"按钮 (浠宝/福宝 头像 + "让XX帮你解读")
+    ↓
+用户点击 → 跳转 chat.vue?mode=report&reportId=X&dimension=body
+    ↓
+AIChatController.sendMessage(reportId=X)
+    ↓
+FamilyContextService.buildContext(userId, reportId) 注入报告详情
+    ↓
+Dify 根据报告数据生成个性化解读 + 建议
+```
+
+**前端改动:**
+
+每个报告详情页底部增加"AI 解读"入口,使用用户设置的 mascot 角色:
+
+```vue
+<view class="ai-entry" @click="goAIChat">
+  <text class="ai-entry-icon">{{ mascotIcon }}</text>
+  <text class="ai-entry-text">让 {{ mascotName }} 帮你解读这份报告</text>
+</view>
+```
+
+### 2.2 Dify AI 应用整合
+
+**当前问题:** 通用助手、营养助手、舌诊分析各自独立,上下文隔离。
+
+**目标架构:** 单一增强型 Dify Chatbot,通过 inputs 实现场景路由:
+
+```
+Dify Workflow:
+  User Input → 场景分类节点 → 
+    ├─ 报告解读 → 注入报告上下文 → 专业解读
+    ├─ 营养建议 → 注入健康分析 → 营养推荐
+    ├─ 日常对话 → 家庭上下文 → 通用回答
+    ├─ 任务生成 → 检测任务意图 → 生成任务
+    └─ 内容推荐 → 检测推荐意图 → 返回推荐标记
+```
+
+**inputs 注入数据层级:**
+
+```json
+{
+  // === 基础层(每次必带)===
+  "user": {"nickname": "...", "role": "parent"},
+  "mascot_name": "XIBAO",
+  "mascot_gender": "女", 
+  "mascot_persona": "温柔可爱的浠宝小女孩",
+  "children": [{"name": "小明", "age": 8, "taskCompletionRate": 0.8}],
+  "dimensionScores": {"body": 85, "mind": 72, "wisdom": 90, "action": 78, "wealth": 65},
+  
+  // === 场景层(按需注入)===
+  // 报告解读场景
+  "reportDetail": { /* 完整报告数据 */ },
+  "reportSurvey": { /* 问卷数据 */ },
+  
+  // 健康分析场景  
+  "health_analysis": { /* findings, suggestions, nutritionTags */ },
+  
+  // 对话记忆层
+  "conversation_summary": "家长很关心孩子的肠道健康...",
+  "user_facts": "小明对乳糖不耐受\n小明喜欢运动",
+  
+  // 任务上下文
+  "recentTasks": [{"title": "每日阅读30分钟", "status": "in_progress"}]
+}
+```
+
+### 2.3 AI 生成任务
+
+**方案:** 在 Dify Workflow 中增加"任务生成"节点,当检测到用户意图包含"任务"、"计划"、"目标"等关键词时,按约定格式返回任务数据。
+
+**Dify 返回格式约定:**
+
+```
+[TASK:{"title":"每日阅读30分钟","description":"选择一本感兴趣的课外书","deadline":"2026-07-25","dimension":"wisdom","rewardPoints":10}]
+```
+
+**后端解析流程(新增 TaskParseService):**
+
+```
+AIChatController
+  ├── 调用 Dify → 获取 answer
+  ├── 解析 [TASK:...] 标记 → TaskParseService
+  │   ├── 校验参数完整性
+  │   ├── 调用 GrowthTaskService.createDynamicTask() → 插入 growth_tasks 表(新增 type=AI_GENERATED)
+  │   └── 返回 taskId
+  ├── 从 answer 移除 TASK 标记
+  └── 返回 result { answer, tasks: [...] }
+```
+
+**GrowthTaskService 新增方法:**
+
+```java
+@Transactional
+public Long createDynamicTask(Long userId, String title, String description,
+                               String deadline, String dimension, Integer rewardPoints) {
+    // 新增 growth_tasks 记录(type=AI_GENERATED)
+    GrowthTask task = new GrowthTask();
+    task.setType("AI_GENERATED");
+    task.setTitle(title);
+    task.setDescription(description);
+    // 自动创建 GrowthTaskLog 并分配给对应用户
+    // 返回 taskId
+}
+```
+
+**数据库:**
+
+```sql
+-- growth_tasks 新增 AI_GENERATED 类型
+ALTER TABLE growth_tasks MODIFY COLUMN type 
+  ENUM('DAILY','NEWBIE','AI_GENERATED') NOT NULL DEFAULT 'DAILY';
+
+-- 新增字段
+ALTER TABLE growth_tasks ADD COLUMN dimension VARCHAR(20) NULL COMMENT '五维维度(body/mind/wisdom/action/wealth)';
+ALTER TABLE growth_tasks ADD COLUMN source_conversation_id VARCHAR(100) NULL COMMENT '来源AI会话ID';
+```
+
+**前端展示:**
+
+AI 消息气泡中增加任务卡片,显示任务标题+描述+截止时间,点击"接受任务"按钮:
+
+```vue
+<view class="task-card" v-if="msg.tasks && msg.tasks.length > 0">
+  <view class="task-item" v-for="(task, ti) in msg.tasks" :key="ti">
+    <text class="task-title">📋 {{ task.title }}</text>
+    <text class="task-desc">{{ task.description }}</text>
+    <text class="task-deadline" v-if="task.deadline">截止:{{ task.deadline }}</text>
+    <button class="task-accept-btn" @click="acceptTask(task)">接受任务</button>
+  </view>
+</view>
+```
+
+### 2.4 AI 内容推荐增强
+
+**当前限制:** 仅营养模式下支持 `[RECOMMEND]` 标记,普通对话模式不支持推荐。
+
+**增强方案:** 所有 Dify 回复统一支持推荐标记
+
+```java
+// 通用推荐解析(AIChatController.sendMessage 中统一处理)
+if (answer.contains("[RECOMMEND]")) {
+    // 解析推荐标记 → RecommendationService.search
+    // 返回 recommendations 列表
+}
+```
+
+**推荐范围扩展:**
+
+| 推荐类型 | 匹配方式 | 数据源 |
+|----------|----------|--------|
+| product | 营养标签 → 商品名称/描述 | Product(state=上架) |
+| activity | 营养标签 → 活动标题/描述 | Activity(已发布+未过期) |
+| article | 营养标签 → 文章标题/摘要/标签 | Article(已发布) |
+| meal | 营养标签+健康分析 → Dify 生成 | MealRecommend |
+
+**推荐排序优化:**
+```
+RecommendationService.search 增强:
+  1. 标签匹配度排序(匹配标签数多的优先)
+  2. 用户已购商品降权(不重复推荐已购买商品)
+  3. 五维维度匹配加分(与用户当前关注维度对齐的优先)
+  4. 时效性排序
+```
+
+### 2.5 Dify Workflow 改造设计
+
+**当前 Dify 应用:**
+- App1: 通用家庭助手 (api-key-1)
+- App2: 精准营养助手 (api-key-nutrition)
+
+**目标:** 合并为单一应用 + 场景路由
+
+```
+Dify Workflow 设计:
+
+[Start] User Input + Inputs (family context)
+  │
+  ▼
+[Classifier Node] 场景分类(基于 query + context)
+  ├── "report" → [Report Analysis Agent] 
+  │   ├── 解读报告数据
+  │   ├── 生成个性化建议
+  │   ├── [Recommend Items] → 输出 [RECOMMEND...]
+  │   └── [Generate Tasks] → 输出 [TASK...]
+  │
+  ├── "nutrition" → [Nutrition Advisor Agent]
+  │   ├── 基于 health_analysis 给出营养建议
+  │   ├── [Recommend Items]
+  │   └── [Generate Tasks]
+  │
+  ├── "meal" → [Meal Planner Agent]
+  │   ├── 根据健康数据和候选食材生成食谱
+  │   └── [Generate Tasks]
+  │
+  ├── "task" → [Task Manager Agent]
+  │   ├── 创建/修改/查询任务
+  │   └── [TASK...]
+  │
+  └── "general" → [Family Assistant Agent]
+      ├── 基于家庭上下文的日常问答
+      └── [Recommend Items] (可选)
+
+所有 Agent 输出 → [Post Processor] 
+  ├── 合并 RECOMMEND / TASK 标记
+  └── 返回最终 answer
+```
+
+### 2.6 前端 chat.vue 改造
+
+**新增能力:**
+
+| 特性 | 实现方式 |
+|------|----------|
+| 报告解读模式 | `mode=report&reportId=X` → 自动注入报告上下文 |
+| 任务卡片渲染 | 解析 `msg.tasks` → 展示任务卡片 + 接受按钮 |
+| 推荐卡片增强 | 全模式支持推荐展示(目前仅 nutrition) |
+| 上下文来源显示 | 消息气泡上标注"基于你的健康报告"等来源标签 |
+| Mascot 动效 | 浠宝/福宝各有不同的动画和语气 |
+
+**消息类型扩展:**
+
+```javascript
+// 消息结构
+{
+  id: 'msg_xxx',
+  role: 'assistant',
+  content: '根据你的肠道报告...',
+  type: 'report_analysis' | 'nutrition' | 'general' | 'task',
+  recommendations: [
+    { type: 'product', id: 1, name: '益生菌', ... }
+  ],
+  tasks: [
+    { id: 'task_1', title: '每日补充益生菌', description: '...', deadline: '...' }
+  ],
+  sourceReport: { id: 123, type: 'gut_flora', score: 85 }
+}
+```
+
+---
+
+## 三、实施优先级与里程碑
+
+### Phase 1: 基础能力(2周)
+1. **报告上传后的 AI 解读入口** — 各报告详情页增加"AI 解读"按钮
+2. **通用推荐解析** — 普通对话模式也支持 `[RECOMMEND]` 标记
+3. **消息类型扩展** — 前端支持任务卡片渲染
+
+### Phase 2: 智能增强(2周)
+4. **AI 动态任务生成** — TaskParseService + GrowthTaskService.createDynamicTask
+5. **Dify Workflow 合并** — 单一应用 + 场景分类节点
+6. **任务展示与接受流程** — 前端完整交互
+
+### Phase 3: 体验优化(持续)
+7. **Mascot 动效与个性化** — 浠宝/福宝各有特色
+8. **推荐排序优化** — 标签权重 + 用户画像
+9. **A/B 测试** — AI 解读 vs 无 AI 解读的转化率对比
+
+---
+
+## 四、涉及文件清单
+
+### 后端新增/修改
+| 文件 | 改动类型 | 说明 |
+|------|----------|------|
+| `AIChatController.java` | 修改 | 通用化推荐解析、增加 task 标记解析 |
+| `AIService.java` | 修改 | 支持新 Dify Workflow 格式 |
+| `GrowthTaskService.java` | 修改 | 新增 `createDynamicTask()` 方法 |
+| `FamilyContextService.java` | 修改 | 增加任务上下文注入 |
+| `RecommendationService.java` | 修改 | 推荐排序优化 |
+| `service/TaskParseService.java` | 新增 | 解析 `[TASK]` 标记 |
+| `entity/GrowthTask.java` | 修改 | 新增 dimension/sourceConversationId 字段 |
+| `schema.sql` | 修改 | 新增字段定义 |
+| `DatabaseInitializer.java` | 修改 | 数据库迁移 |
+
+### 前端新增/修改
+| 文件 | 改动类型 | 说明 |
+|------|----------|------|
+| `pages/ai/chat.vue` | 修改 | 任务卡片渲染、全模式推荐展示 |
+| `pages/health/report-upload.vue` | 修改 | 上传后 AI 解读入口 |
+| `pages/body-detail/health-report.vue` | 修改 | 报告详情页 AI 入口 |
+| `pages/health/gut-index.vue` | 修改 | 菌群报告 AI 入口 |
+| `pages/health/tongue-index.vue` | 修改 | 舌诊 AI 入口 |
+| `utils/api.js` | 修改 | 新增 chat 相关 API 类型 |
+| `components/AITaskCard.vue` | 新增 | 任务卡片组件 |
+
+### Dify 平台配置
+| 配置 | 说明 |
+|------|------|
+| Dify Workflow 改造 | 合并为单一应用+场景分类 |
+| 新增 Agent 节点 | Report Analysis / Task Manager |
+| API Key 统一 | 由多个 API Key 合并为单一 Key |
+
+---
+
+## 五、关键设计决策
+
+1. **Dify 端做场景分类 vs 后端分类**
+   - 选择:**Dify Workflow 做场景分类** — 减少后端耦合,场景路由规则可灵活调整
+   - 后端只做通用接口 + 标记解析
+
+2. **任务结构:growth_tasks vs 独立任务表**
+   - 选择:**复用 growth_tasks**(新增 AI_GENERATED 类型)— 统一任务展示入口,复用积分/能量奖励体系
+
+3. **推荐触发:Dify 标记 vs 后端规则**
+   - 选择:**Dify 标记 + 后端兜底** — Dify 根据语义判断何时推荐,后端在无推荐时基于标签做兜底推荐
+
+4. **AI 记忆:Dify 自带 vs 本地存储**
+   - 选择:**两者结合** — Dify 会话级记忆 + 本地 AiConversationSummary/AiUserFact 做跨会话持久记忆