|
|
@@ -0,0 +1,252 @@
|
|
|
+<template>
|
|
|
+ <div class="health-plan-manage admin-page">
|
|
|
+ <el-card>
|
|
|
+ <div slot="header" class="admin-page-header">
|
|
|
+ <span class="admin-page-title">健康方案管理</span>
|
|
|
+ <div class="flex items-center gap-sm">
|
|
|
+ <el-input
|
|
|
+ v-model="keyword"
|
|
|
+ placeholder="搜索成员/目标"
|
|
|
+ prefix-icon="el-icon-search"
|
|
|
+ clearable
|
|
|
+ style="width:200px"
|
|
|
+ @keyup.enter.native="handleSearch"
|
|
|
+ @clear="handleSearch"
|
|
|
+ />
|
|
|
+ <el-select v-model="statusFilter" placeholder="状态" clearable style="width:130px" @change="handleSearch">
|
|
|
+ <el-option label="草稿" value="draft" />
|
|
|
+ <el-option label="待审核" value="pending_review" />
|
|
|
+ <el-option label="已发布" value="published" />
|
|
|
+ <el-option label="已驳回" value="rejected" />
|
|
|
+ </el-select>
|
|
|
+ <el-input
|
|
|
+ v-model="familyIdFilter"
|
|
|
+ placeholder="家庭ID"
|
|
|
+ clearable
|
|
|
+ style="width:120px"
|
|
|
+ @keyup.enter.native="handleSearch"
|
|
|
+ @clear="handleSearch"
|
|
|
+ />
|
|
|
+ <el-button type="primary" size="mini" @click="handleSearch">查询</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="table-scroll-wrap">
|
|
|
+ <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
|
|
|
+ <el-table-column prop="id" label="ID" width="70" />
|
|
|
+ <el-table-column label="家庭" min-width="120" show-overflow-tooltip>
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ {{ row.familyName || ('家庭' + (row.familyId || '-')) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="成员" width="140" show-overflow-tooltip>
|
|
|
+ <template slot-scope="{ row }">{{ row.memberName || row.memberIds || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="目标" min-width="180" show-overflow-tooltip>
|
|
|
+ <template slot-scope="{ row }">{{ row.goal || '-' }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="维度" width="140">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag
|
|
|
+ v-for="(d, i) in splitDimensions(row.dimensions)"
|
|
|
+ :key="i"
|
|
|
+ size="mini"
|
|
|
+ style="margin-right:4px"
|
|
|
+ >{{ d }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" width="100">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="statusType(row.status)" size="mini">{{ statusLabel(row.status) }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="创建时间" width="170">
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="100" fixed="right">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button size="mini" type="text" @click="showDetail(row)">详情</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-pagination
|
|
|
+ style="margin-top:16px;text-align:right"
|
|
|
+ layout="total, prev, pager, next, sizes"
|
|
|
+ :current-page.sync="page"
|
|
|
+ :page-size.sync="size"
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
+ :total="total"
|
|
|
+ @current-change="loadList"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ />
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-dialog title="健康方案详情" :visible.sync="detailVisible" width="720px" destroy-on-close>
|
|
|
+ <div v-loading="detailLoading" v-if="currentPlan">
|
|
|
+ <el-descriptions :column="2" border size="small">
|
|
|
+ <el-descriptions-item label="方案ID">{{ currentPlan.id }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="状态">
|
|
|
+ <el-tag :type="statusType(currentPlan.status)" size="mini">{{ statusLabel(currentPlan.status) }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="家庭">
|
|
|
+ {{ currentPlan.familyName || ('家庭' + (currentPlan.familyId || '-')) }}
|
|
|
+ <span v-if="currentPlan.familyId" style="color:#999;margin-left:6px">(#{{ currentPlan.familyId }})</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="成员">{{ currentPlan.memberName || currentPlan.memberIds || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="维度" :span="2">{{ currentPlan.dimensions || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="目标" :span="2">{{ currentPlan.goal || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="创建时间">{{ formatTime(currentPlan.createdAt) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="更新时间">{{ formatTime(currentPlan.updatedAt) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item v-if="currentPlan.reviewComment" label="审核意见" :span="2">
|
|
|
+ {{ currentPlan.reviewComment }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <div class="detail-section-title">方案内容</div>
|
|
|
+ <pre class="plan-content">{{ currentPlan.planContent || '暂无内容' }}</pre>
|
|
|
+
|
|
|
+ <div v-if="currentPlan.tasks && currentPlan.tasks.length" class="detail-section-title">
|
|
|
+ 分解任务({{ currentPlan.tasks.length }})
|
|
|
+ </div>
|
|
|
+ <el-table
|
|
|
+ v-if="currentPlan.tasks && currentPlan.tasks.length"
|
|
|
+ :data="currentPlan.tasks"
|
|
|
+ border
|
|
|
+ size="mini"
|
|
|
+ style="margin-top:8px"
|
|
|
+ >
|
|
|
+ <el-table-column prop="id" label="ID" width="70" />
|
|
|
+ <el-table-column prop="title" label="任务" min-width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="status" label="状态" width="100" />
|
|
|
+ <el-table-column label="截止时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.deadline) }}</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 - 320
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ detailLoading: false,
|
|
|
+ list: [],
|
|
|
+ total: 0,
|
|
|
+ page: 1,
|
|
|
+ size: 20,
|
|
|
+ keyword: '',
|
|
|
+ statusFilter: '',
|
|
|
+ familyIdFilter: '',
|
|
|
+ detailVisible: false,
|
|
|
+ currentPlan: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ formatTime(t) {
|
|
|
+ return t ? String(t).replace('T', ' ').substring(0, 19) : '-'
|
|
|
+ },
|
|
|
+ splitDimensions(dims) {
|
|
|
+ if (!dims) return []
|
|
|
+ return String(dims).split(',').map(function (s) { return s.trim() }).filter(Boolean)
|
|
|
+ },
|
|
|
+ statusType(s) {
|
|
|
+ var map = { pending_review: 'warning', draft: 'info', published: 'success', rejected: 'danger' }
|
|
|
+ return map[s] || 'info'
|
|
|
+ },
|
|
|
+ statusLabel(s) {
|
|
|
+ var map = { pending_review: '待审核', draft: '草稿', published: '已发布', rejected: '已驳回' }
|
|
|
+ return map[s] || s || '-'
|
|
|
+ },
|
|
|
+ handleSearch() {
|
|
|
+ this.page = 1
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ handleSizeChange() {
|
|
|
+ this.page = 1
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ async loadList() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ var familyId = this.familyIdFilter ? Number(this.familyIdFilter) : null
|
|
|
+ if (familyId !== null && isNaN(familyId)) familyId = null
|
|
|
+ var res = await adminListHealthPlans({
|
|
|
+ page: this.page,
|
|
|
+ size: this.size,
|
|
|
+ keyword: this.keyword || null,
|
|
|
+ status: this.statusFilter || null,
|
|
|
+ familyId: familyId
|
|
|
+ })
|
|
|
+ if (res.code === 200) {
|
|
|
+ var data = res.data || {}
|
|
|
+ this.list = data.records || []
|
|
|
+ this.total = data.total || 0
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.message || '加载失败')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('加载失败')
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async showDetail(row) {
|
|
|
+ this.detailVisible = true
|
|
|
+ this.currentPlan = row
|
|
|
+ this.detailLoading = true
|
|
|
+ try {
|
|
|
+ var res = await adminGetHealthPlanDetail({ planId: row.id })
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.currentPlan = res.data
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.message || '加载详情失败')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('加载详情失败')
|
|
|
+ } finally {
|
|
|
+ this.detailLoading = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.detail-section-title {
|
|
|
+ margin: 16px 0 8px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+.plan-content {
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-word;
|
|
|
+ background: #f5f7fa;
|
|
|
+ padding: 12px;
|
|
|
+ border-radius: 6px;
|
|
|
+ max-height: 360px;
|
|
|
+ overflow: auto;
|
|
|
+ margin: 0;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.6;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+</style>
|