|
|
@@ -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, '&')
|
|
|
+ .replace(/</g, '<')
|
|
|
+ .replace(/>/g, '>')
|
|
|
+ .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>
|