Просмотр исходного кода

feat(family): 家庭信息查看弹窗组件

Xiaogang Liao 1 неделя назад
Родитель
Сommit
c88c27de46
1 измененных файлов с 230 добавлено и 0 удалено
  1. 230 0
      cfc-web/src/views/admin/components/FamilyOverviewDialog.vue

+ 230 - 0
cfc-web/src/views/admin/components/FamilyOverviewDialog.vue

@@ -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>