Ver Fonte

feat(web): 规划师端健康方案管理页 + v1.0.1233 版本升级

iwt há 3 semanas atrás
pai
commit
a9a541165c

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-a1add80b794e96ef5dd730f35ab493943943f908
+9af6b0f35cf6b679c289ff6d778c751a3cd3539c

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

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1231",
+  "version": "1.0.1233",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1231",
+      "version": "1.0.1233",
       "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.1232",
+  "version": "1.0.1233",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

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

@@ -4,6 +4,29 @@
 
 ---
 
+## v1.0.1233 (2026-08-28)
+
+### 文档
+- 登记生成方案标准内容格式实现计划(PROJECT-OVERVIEW 🟢 已实施)
+
+### Bug 修复
+- 健康方案 submitPlan 补 tasks 字段 + regenerate 更新 tasks
+- 海报扫码进入文章/活动详情页解析 scene 参数,修复参数错误
+- 首页自检卡跳转改为五维自检页 self-check
+
+### 新功能
+- 规划师端健康方案结构化任务条目编辑
+- 支持分享到朋友圈(onShareTimeline)
+
+### 其他
+- 优化修复
+- 优化修复
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- 优化修复
+- 优化修复
+
+
 ## v1.0.1232 (2026-08-28)
 
 ### Bug 修复

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

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1232
+> 当前版本: v1.0.1233
 
 ## 历史版本
 
@@ -8,6 +8,29 @@
 
 ---
 
+## v1.0.1233 (2026-08-28)
+
+### 文档
+- 登记生成方案标准内容格式实现计划(PROJECT-OVERVIEW 🟢 已实施)
+
+### Bug 修复
+- 健康方案 submitPlan 补 tasks 字段 + regenerate 更新 tasks
+- 海报扫码进入文章/活动详情页解析 scene 参数,修复参数错误
+- 首页自检卡跳转改为五维自检页 self-check
+
+### 新功能
+- 规划师端健康方案结构化任务条目编辑
+- 支持分享到朋友圈(onShareTimeline)
+
+### 其他
+- 优化修复
+- 优化修复
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- 优化修复
+- 优化修复
+
+
 ## v1.0.1232 (2026-08-28)
 
 ### Bug 修复

+ 244 - 0
cfc-web/src/views/admin/HealthPlanManage.vue

@@ -0,0 +1,244 @@
+<template>
+  <div class="health-plan-manage">
+    <div class="page-header">
+      <h2>健康方案管理</h2>
+      <div class="filters">
+        <el-input v-model="keyword" placeholder="搜索成员名/目标" style="width:200px" clearable @keyup.enter="loadPlans" />
+        <el-select v-model="statusFilter" placeholder="状态筛选" clearable style="width:140px;margin-left:10px" @change="loadPlans">
+          <el-option label="草稿" value="draft" />
+          <el-option label="待审核" value="pending_review" />
+          <el-option label="已发布" value="published" />
+          <el-option label="已驳回" value="rejected" />
+        </el-select>
+        <el-button type="primary" @click="loadPlans" style="margin-left:10px">查询</el-button>
+      </div>
+    </div>
+
+    <el-table :data="plans" border stripe style="width:100%" v-loading="loading" :max-height="tableHeight">
+      <el-table-column prop="id" label="方案ID" width="80" />
+      <el-table-column prop="familyName" label="家庭" width="140">
+        <template slot-scope="{ row }">{{ row.familyName || row.familyId }}</template>
+      </el-table-column>
+      <el-table-column prop="memberName" label="成员" width="120">
+        <template slot-scope="{ row }">{{ row.memberName || row.memberIds || '-' }}</template>
+      </el-table-column>
+      <el-table-column label="维度" width="160">
+        <template slot-scope="{ row }">
+          <el-tag v-for="(d, i) in (row.dimensions || '').split(',')" :key="i" size="mini" style="margin-right:3px">{{ d }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="目标" min-width="200">
+        <template slot-scope="{ row }">
+          {{ row.goal ? (row.goal.length > 50 ? row.goal.substring(0, 50) + '...' : row.goal) : '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" width="100">
+        <template slot-scope="{ row }">
+          <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="reviewComment" label="审核意见" width="150">
+        <template slot-scope="{ row }">{{ row.reviewComment || '-' }}</template>
+      </el-table-column>
+      <el-table-column prop="createdAt" label="创建时间" width="160">
+        <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
+      </el-table-column>
+      <el-table-column label="操作" width="100">
+        <template slot-scope="{ row }">
+          <el-button size="mini" type="primary" @click="showDetail(row)">详情</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <el-pagination
+      style="margin-top:16px;text-align:right"
+      background
+      layout="total, prev, pager, next"
+      :total="total"
+      :page-size="pageSize"
+      :current-page="pageNum"
+      @current-change="handlePageChange"
+    />
+
+    <!-- 详情弹窗 -->
+    <el-dialog title="健康方案详情" :visible.sync="detailVisible" width="800px" destroy-on-close>
+      <div v-if="currentPlan" v-loading="detailLoading">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="方案ID">{{ currentPlan.id }}</el-descriptions-item>
+          <el-descriptions-item label="家庭">{{ currentPlan.familyName || currentPlan.familyId }}</el-descriptions-item>
+          <el-descriptions-item label="成员">{{ currentPlan.memberName || currentPlan.memberIds || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="维度">{{ currentPlan.dimensions || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="目标" :span="2">{{ currentPlan.goal || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="状态">
+            <el-tag :type="statusType(currentPlan.status)">{{ statusLabel(currentPlan.status) }}</el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="规划师ID">{{ currentPlan.teacherId || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="审核人ID">{{ currentPlan.reviewedBy || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="审核时间">{{ formatTime(currentPlan.reviewedAt) }}</el-descriptions-item>
+          <el-descriptions-item label="审核意见">{{ currentPlan.reviewComment || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="创建时间" :span="2">{{ formatTime(currentPlan.createdAt) }}</el-descriptions-item>
+        </el-descriptions>
+
+        <el-divider content-position="left">方案内容</el-divider>
+        <div class="plan-content" v-html="renderPlanContent(currentPlan.planContent)"></div>
+
+        <el-divider content-position="left">分解任务</el-divider>
+        <el-table :data="tasks" border stripe size="small" v-loading="tasksLoading">
+          <el-table-column prop="title" label="任务标题" min-width="180" />
+          <el-table-column prop="actionType" label="类型" width="100">
+            <template slot-scope="{ row }">{{ actionTypeLabel(row.actionType) }}</template>
+          </el-table-column>
+          <el-table-column prop="dimension" label="维度" width="80">
+            <template slot-scope="{ row }">
+              <el-tag size="mini">{{ dimensionLabel(row.dimension) }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="frequency" label="频率" width="80">
+            <template slot-scope="{ row }">{{ frequencyLabel(row.frequency) }}</template>
+          </el-table-column>
+          <el-table-column prop="notes" label="备注" width="150" />
+          <el-table-column prop="status" label="状态" width="80">
+            <template slot-scope="{ row }">
+              <el-tag :type="taskStatusType(row.status)" size="mini">{{ taskStatusLabel(row.status) }}</el-tag>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div slot="footer">
+        <el-button @click="detailVisible = false">关闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { adminListHealthPlans, adminGetHealthPlanDetail } from '@/api/healthPlan'
+
+export default {
+  name: 'HealthPlanManage',
+  computed: {
+    tableHeight() {
+      return window.innerHeight - 280
+    }
+  },
+  data() {
+    return {
+      plans: [],
+      total: 0,
+      pageNum: 1,
+      pageSize: 20,
+      keyword: '',
+      statusFilter: '',
+      loading: false,
+      detailVisible: false,
+      currentPlan: null,
+      detailLoading: false,
+      tasks: [],
+      tasksLoading: false
+    }
+  },
+  created() {
+    this.loadPlans()
+  },
+  methods: {
+    async loadPlans() {
+      this.loading = true
+      try {
+        const res = await adminListHealthPlans({
+          page: this.pageNum,
+          size: this.pageSize,
+          keyword: this.keyword || null,
+          status: this.statusFilter || null
+        })
+        if (res.code === 200) {
+          this.plans = res.data?.records || []
+          this.total = res.data?.total || 0
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    handlePageChange(page) {
+      this.pageNum = page
+      this.loadPlans()
+    },
+    async showDetail(row) {
+      this.detailVisible = true
+      this.detailLoading = true
+      this.tasksLoading = true
+      this.currentPlan = row
+      this.tasks = []
+      try {
+        const res = await adminGetHealthPlanDetail({ planId: row.id })
+        if (res.code === 200) {
+          this.currentPlan = res.data
+          this.tasks = res.data.tasks || []
+        }
+      } catch (e) {
+        this.$message.error('加载详情失败')
+      } finally {
+        this.detailLoading = false
+        this.tasksLoading = false
+      }
+    },
+    formatTime(t) {
+      if (!t) return '-'
+      return t.replace('T', ' ').substring(0, 19)
+    },
+    statusType(s) {
+      const map = { draft: 'info', pending_review: 'warning', published: 'success', rejected: 'danger' }
+      return map[s] || 'info'
+    },
+    statusLabel(s) {
+      const map = { draft: '草稿', pending_review: '待审核', published: '已发布', rejected: '已驳回' }
+      return map[s] || s
+    },
+    actionTypeLabel(type) {
+      const map = { buy: '购买', read: '阅读', exercise: '运动', checkin: '打卡', diet: '饮食', activity: '活动' }
+      return map[type] || type || '-'
+    },
+    dimensionLabel(d) {
+      const map = { body: '身', mind: '心', wisdom: '智', action: '行', wealth: '富' }
+      return map[d] || d || '-'
+    },
+    frequencyLabel(f) {
+      const map = { once: '一次性', daily: '每日' }
+      return map[f] || f || '-'
+    },
+    taskStatusType(s) {
+      const map = { pending: 'warning', completed: 'success', failed: 'danger' }
+      return map[s] || 'info'
+    },
+    taskStatusLabel(s) {
+      const map = { pending: '待完成', completed: '已完成', failed: '未完成' }
+      return map[s] || s || '-'
+    },
+    renderPlanContent(content) {
+      if (!content) return '<span style="color:#999">暂无内容</span>'
+      return content
+        .replace(/&/g, '&amp;')
+        .replace(/</g, '&lt;')
+        .replace(/>/g, '&gt;')
+        .replace(/\n/g, '<br>')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.health-plan-manage { padding: 20px; }
+.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
+.page-header h2 { margin: 0; font-size: 20px; }
+.filters { display: flex; align-items: center; }
+.plan-content {
+  background: #f5f7fa;
+  border-radius: 4px;
+  padding: 16px;
+  min-height: 80px;
+  line-height: 1.8;
+  white-space: pre-wrap;
+  font-size: 14px;
+}
+</style>