|
|
@@ -0,0 +1,230 @@
|
|
|
+<template>
|
|
|
+ <el-dialog
|
|
|
+ :visible.sync="dialogVisible"
|
|
|
+ title="家庭信息"
|
|
|
+ width="900px"
|
|
|
+ top="6vh"
|
|
|
+ @open="load"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ >
|
|
|
+ <div v-loading="loading">
|
|
|
+ <template v-if="overview && overview.family">
|
|
|
+ <el-descriptions :column="2" border>
|
|
|
+ <el-descriptions-item label="家庭名称">{{ overview.family.name }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="邀请码">{{ overview.family.inviteCode || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="主账号">{{ overview.creatorRealName || overview.creatorName || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="创建时间">{{ formatDate(overview.family.createdAt) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <div class="count-cards">
|
|
|
+ <StatCard label="报告" :value="counts.reports" color="primary" clickable @click="activeTab = 'reports'" />
|
|
|
+ <StatCard label="方案" :value="counts.plans" color="success" clickable @click="activeTab = 'plans'" />
|
|
|
+ <StatCard label="任务" :value="counts.tasks" color="warning" clickable @click="activeTab = 'tasks'" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-tabs v-model="activeTab">
|
|
|
+ <el-tab-pane label="报告" name="reports">
|
|
|
+ <el-table :data="overview.reports || []" border max-height="300">
|
|
|
+ <el-table-column label="类型">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <StatusTag :options="reportTypeOptions" :value="row.reportType" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="reportNumber" label="报告编号" width="140" />
|
|
|
+ <el-table-column label="日期">
|
|
|
+ <template slot-scope="{ row }">{{ formatDate(row.reportDate) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="personName" label="姓名" />
|
|
|
+ <el-table-column label="状态">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <StatusTag :options="reportStatusOptions" :value="row.status" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="overallScore" label="总分" width="70" />
|
|
|
+ <el-table-column prop="gutHealthScore" label="肠道分" width="80" />
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="方案" name="plans">
|
|
|
+ <el-table :data="overview.plans || []" border max-height="300">
|
|
|
+ <el-table-column label="类型">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <StatusTag :options="planTypeOptions" :value="row.planType" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="memberName" label="成员" />
|
|
|
+ <el-table-column prop="goal" label="目标" show-overflow-tooltip />
|
|
|
+ <el-table-column label="状态">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <StatusTag :options="planStatusOptions" :value="row.status" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="totalDays" label="天数" width="70" />
|
|
|
+ <el-table-column label="创建时间">
|
|
|
+ <template slot-scope="{ row }">{{ formatDate(row.createdAt) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="任务" name="tasks">
|
|
|
+ <el-table :data="tasks" border max-height="300">
|
|
|
+ <el-table-column prop="title" label="标题" show-overflow-tooltip />
|
|
|
+ <el-table-column label="状态">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <StatusTag :options="taskStatusOptions" :value="row.status" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="category" label="分类" width="110" />
|
|
|
+ <el-table-column prop="points" label="积分" width="70" />
|
|
|
+ <el-table-column label="截止">
|
|
|
+ <template slot-scope="{ row }">{{ formatDate(row.deadline) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <el-pagination
|
|
|
+ class="pagination-right"
|
|
|
+ @current-change="loadTasks"
|
|
|
+ :current-page="taskPage"
|
|
|
+ :page-size="taskSize"
|
|
|
+ :total="taskTotal"
|
|
|
+ layout="total, prev, pager, next"
|
|
|
+ />
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </template>
|
|
|
+ <el-empty v-else :description="loading ? '加载中...' : '暂无数据'" />
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getFamilyOverview, getFamilyTasks } from '@/api/admin'
|
|
|
+import { formatDate } from '@/utils/format'
|
|
|
+import StatCard from '@/components/StatCard.vue'
|
|
|
+import StatusTag from '@/components/StatusTag.vue'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'FamilyOverviewDialog',
|
|
|
+ props: {
|
|
|
+ /** 弹窗可见性,父组件通过 :visible.sync 控制 */
|
|
|
+ visible: { type: Boolean, default: false },
|
|
|
+ /** 家庭 ID,变化或弹窗打开时触发加载 */
|
|
|
+ familyId: { type: [Number, String], default: null }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ overview: null,
|
|
|
+ activeTab: 'reports',
|
|
|
+ tasks: [],
|
|
|
+ taskPage: 1,
|
|
|
+ taskSize: 20,
|
|
|
+ taskTotal: 0,
|
|
|
+ reportTypeOptions: [
|
|
|
+ { value: 'gut_flora', label: '菌群检测', type: 'success' },
|
|
|
+ { value: 'physical_exam', label: '体检报告', type: 'primary' },
|
|
|
+ { value: 'dan', label: 'DAN测评', type: 'warning' },
|
|
|
+ { value: 'brain_status', label: '脑状态', type: 'primary' },
|
|
|
+ { value: 'aptitude', label: '先天潜能', type: 'primary' },
|
|
|
+ { value: 'insurance', label: '保单', type: 'info' },
|
|
|
+ { value: 'fortune', label: '家庭周报', type: 'danger' }
|
|
|
+ ],
|
|
|
+ reportStatusOptions: [
|
|
|
+ { value: 'active', label: '生效', type: 'success' },
|
|
|
+ { value: 'archived', label: '已归档', type: 'warning' },
|
|
|
+ { value: 'deleted', label: '已删除', type: 'info' }
|
|
|
+ ],
|
|
|
+ planTypeOptions: [
|
|
|
+ { value: 'health_plan', label: '健康方案', type: 'primary' },
|
|
|
+ { value: 'family_attribution_plan', label: '家庭归因方案', type: 'warning' }
|
|
|
+ ],
|
|
|
+ planStatusOptions: [
|
|
|
+ { value: 'draft', label: '草稿', type: 'info' },
|
|
|
+ { value: 'pending_review', label: '待审核', type: 'warning' },
|
|
|
+ { value: 'published', label: '已发布', type: 'success' },
|
|
|
+ { value: 'rejected', label: '已驳回', type: 'danger' }
|
|
|
+ ],
|
|
|
+ taskStatusOptions: [
|
|
|
+ { value: 'pending', label: '进行中', type: 'warning' },
|
|
|
+ { value: 'pending_review', label: '待审核', type: 'warning' },
|
|
|
+ { value: 'completed', label: '已完成', type: 'success' },
|
|
|
+ { value: 'locked', label: '前置锁定', type: 'info' },
|
|
|
+ { value: 'assigned', label: '待接受', type: 'info' },
|
|
|
+ { value: 'in_progress', label: '进行中', type: 'warning' },
|
|
|
+ { value: 'rejected', label: '已拒绝', type: 'danger' },
|
|
|
+ { value: 'overdue', label: '已过期', type: 'danger' },
|
|
|
+ { value: 'cancelled', label: '已取消', type: 'info' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ /**
|
|
|
+ * 受控 dialog 可见性:写时派发 update:visible,保证父组件的 :visible.sync 同步。
|
|
|
+ * (el-dialog 关闭时 emit update:visible(false),若直接绑 visible prop 会导致
|
|
|
+ * 直接修改 prop 且无法回传父组件,故用计算属性桥接。)
|
|
|
+ */
|
|
|
+ dialogVisible: {
|
|
|
+ get() {
|
|
|
+ return this.visible
|
|
|
+ },
|
|
|
+ set(val) {
|
|
|
+ this.$emit('update:visible', val)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /** 计数卡片数据源(overview 未加载时兜底为空对象,避免 counts.xxx 报错) */
|
|
|
+ counts() {
|
|
|
+ return (this.overview && this.overview.counts) || {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ /** 家庭 ID 变化时(弹窗已打开)重新加载 */
|
|
|
+ familyId() {
|
|
|
+ if (this.visible) this.load()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ formatDate,
|
|
|
+ async load() {
|
|
|
+ if (!this.familyId) return
|
|
|
+ this.loading = true
|
|
|
+ this.overview = null
|
|
|
+ this.tasks = []
|
|
|
+ this.taskTotal = 0
|
|
|
+ this.taskPage = 1
|
|
|
+ try {
|
|
|
+ const res = await getFamilyOverview(this.familyId)
|
|
|
+ this.overview = res.data
|
|
|
+ this.loadTasks(1)
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('加载家庭信息失败')
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadTasks(page) {
|
|
|
+ this.taskPage = page
|
|
|
+ try {
|
|
|
+ const res = await getFamilyTasks(this.familyId, { page, size: this.taskSize })
|
|
|
+ this.tasks = res.data.records || []
|
|
|
+ this.taskTotal = res.data.total || 0
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('加载任务列表失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.count-cards {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ margin: 16px 0;
|
|
|
+}
|
|
|
+.count-cards >>> .adm-stat-card {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+.pagination-right {
|
|
|
+ margin-top: 12px;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+</style>
|