|
|
@@ -0,0 +1,249 @@
|
|
|
+<template>
|
|
|
+ <div class="health-report-audit admin-page">
|
|
|
+ <div class="header admin-page-header">
|
|
|
+ <h2 class="admin-page-title">报告草稿审核</h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="list" v-loading="loading" border stripe>
|
|
|
+ <el-table-column prop="id" label="草稿ID" width="80" />
|
|
|
+ <el-table-column label="上传用户" width="120">
|
|
|
+ <template slot-scope="{ row }">{{ row.userId }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="reportType" label="报告类型" width="120" />
|
|
|
+ <el-table-column prop="originalFilename" label="文件名" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column label="状态" width="100">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag type="warning">待审核</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="上传时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="过期时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.expireAt) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="240">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button size="mini" type="primary" @click="viewDetail(row)">查看详情</el-button>
|
|
|
+ <el-button size="mini" type="success" @click="handleConfirm(row)">确认</el-button>
|
|
|
+ <el-button size="mini" type="danger" @click="handleDiscard(row)">丢弃</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <el-pagination
|
|
|
+ v-if="total > size"
|
|
|
+ @current-change="onPageChange"
|
|
|
+ :current-page="page"
|
|
|
+ :page-size="size"
|
|
|
+ :total="total"
|
|
|
+ layout="total, prev, pager, next"
|
|
|
+ class="pagination-wrap"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 详情弹窗 -->
|
|
|
+ <el-dialog title="草稿详情" :visible.sync="detailVisible" width="800px">
|
|
|
+ <div v-if="detail">
|
|
|
+ <el-descriptions :column="2" border>
|
|
|
+ <el-descriptions-item label="草稿ID">{{ detail.id }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="上传用户ID">{{ detail.userId }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="报告类型">{{ detail.reportType }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="文件名">{{ detail.originalFilename }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="上传时间">{{ formatTime(detail.createdAt) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="过期时间">{{ formatTime(detail.expireAt) }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <el-divider content-position="left">解析数据</el-divider>
|
|
|
+ <div v-if="parsedPayload">
|
|
|
+ <el-row :gutter="20" style="margin-bottom: 16px">
|
|
|
+ <el-col :span="6" v-for="(val, key) in scoreFields" :key="key">
|
|
|
+ <div class="score-card" :style="{ borderColor: scoreColor(val.value) }">
|
|
|
+ <div class="score-label">{{ val.label }}</div>
|
|
|
+ <div class="score-value" :style="{ color: scoreColor(val.value) }">
|
|
|
+ {{ val.value ?? '-' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-tabs v-model="activeTab">
|
|
|
+ <el-tab-pane label="指标" name="indicators">
|
|
|
+ <el-table :data="parsedPayload.indicators || []" border size="small" max-height="300">
|
|
|
+ <el-table-column prop="category" label="类别" width="100" />
|
|
|
+ <el-table-column prop="indicatorName" label="指标名称" width="160" />
|
|
|
+ <el-table-column prop="indicatorValue" label="检测值" width="100" />
|
|
|
+ <el-table-column prop="unit" label="单位" width="80" />
|
|
|
+ <el-table-column prop="refRange" label="参考范围" width="140" />
|
|
|
+ <el-table-column prop="status" label="状态" width="80">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="row.status === '异常' ? 'danger' : 'success'" size="small">
|
|
|
+ {{ row.status || '正常' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="菌群" name="gutFlora">
|
|
|
+ <el-table :data="parsedPayload.gutFlora || []" border size="small" max-height="300">
|
|
|
+ <el-table-column prop="bacteriaName" label="菌种" width="160" />
|
|
|
+ <el-table-column prop="bacteriaValue" label="检测值" width="100" />
|
|
|
+ <el-table-column prop="normalRange" label="正常范围" width="140" />
|
|
|
+ <el-table-column prop="populationLevel" label="水平" width="80" />
|
|
|
+ <el-table-column prop="level" label="级别" width="80" />
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="疾病风险" name="diseaseRisks">
|
|
|
+ <el-table :data="parsedPayload.diseaseRisks || []" border size="small" max-height="300">
|
|
|
+ <el-table-column prop="diseaseName" label="疾病" width="200" />
|
|
|
+ <el-table-column prop="riskValue" label="风险值" width="100" />
|
|
|
+ <el-table-column prop="riskLevel" label="风险等级" width="100" />
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <el-alert title="无法解析负载数据" type="warning" :closable="false" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'HealthReportAudit',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ list: [],
|
|
|
+ loading: false,
|
|
|
+ page: 1,
|
|
|
+ size: 20,
|
|
|
+ total: 0,
|
|
|
+ detailVisible: false,
|
|
|
+ detail: null,
|
|
|
+ activeTab: 'indicators'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ parsedPayload() {
|
|
|
+ if (!this.detail || !this.detail.payloadJson) return null
|
|
|
+ try {
|
|
|
+ const p = JSON.parse(this.detail.payloadJson)
|
|
|
+ return typeof p === 'string' ? JSON.parse(p) : p
|
|
|
+ } catch { return null }
|
|
|
+ },
|
|
|
+ scoreFields() {
|
|
|
+ if (!this.parsedPayload) return []
|
|
|
+ const p = this.parsedPayload
|
|
|
+ return [
|
|
|
+ { label: '综合评分', value: p.overallScore, key: 'overallScore' },
|
|
|
+ { label: '肠道健康', value: p.gutHealthScore, key: 'gutHealthScore' },
|
|
|
+ { label: '慢病风险', value: p.chronicDiseaseScore, key: 'chronicDiseaseScore' },
|
|
|
+ { label: '营养评分', value: p.nutritionScore, key: 'nutritionScore' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() { this.loadList() },
|
|
|
+ methods: {
|
|
|
+ async loadList() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ const baseURL = process.env.VUE_APP_BASE_API || ''
|
|
|
+ const token = localStorage.getItem('token')
|
|
|
+ const axios = this.$axios || (await import('axios')).default
|
|
|
+ const res = await axios.post(baseURL + '/api/health/report/audit/pending', {
|
|
|
+ page: this.page, size: this.size
|
|
|
+ }, {
|
|
|
+ headers: { Authorization: 'Bearer ' + token }
|
|
|
+ })
|
|
|
+ const records = res.data?.data || []
|
|
|
+ this.list = records
|
|
|
+ this.total = records.length
|
|
|
+ } catch (e) {
|
|
|
+ this.$message?.error?.('加载草稿列表失败')
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onPageChange(val) {
|
|
|
+ this.page = val
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ viewDetail(row) {
|
|
|
+ this.detail = row
|
|
|
+ this.activeTab = 'indicators'
|
|
|
+ this.detailVisible = true
|
|
|
+ },
|
|
|
+ async handleConfirm(row) {
|
|
|
+ try {
|
|
|
+ await this.$confirm(`确认草稿 #${row.id}?确认后将生成正式报告。`, '提示', { type: 'warning' })
|
|
|
+ const baseURL = process.env.VUE_APP_BASE_API || ''
|
|
|
+ const token = localStorage.getItem('token')
|
|
|
+ const axios = this.$axios || (await import('axios')).default
|
|
|
+ await axios.post(baseURL + '/api/health/report/audit/confirm', {
|
|
|
+ draftId: row.id
|
|
|
+ }, {
|
|
|
+ headers: { Authorization: 'Bearer ' + token }
|
|
|
+ })
|
|
|
+ this.$message?.success?.('审核确认完成')
|
|
|
+ this.loadList()
|
|
|
+ } catch (e) {
|
|
|
+ if (e !== 'cancel') this.$message?.error?.('确认失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async handleDiscard(row) {
|
|
|
+ try {
|
|
|
+ await this.$confirm(`确认丢弃草稿 #${row.id}?丢弃后不可恢复。`, '警告', { type: 'warning' })
|
|
|
+ const baseURL = process.env.VUE_APP_BASE_API || ''
|
|
|
+ const token = localStorage.getItem('token')
|
|
|
+ const axios = this.$axios || (await import('axios')).default
|
|
|
+ await axios.post(baseURL + '/api/health/report/audit/discard', {
|
|
|
+ draftId: row.id
|
|
|
+ }, {
|
|
|
+ headers: { Authorization: 'Bearer ' + token }
|
|
|
+ })
|
|
|
+ this.$message?.success?.('已丢弃')
|
|
|
+ this.loadList()
|
|
|
+ } catch (e) {
|
|
|
+ if (e !== 'cancel') this.$message?.error?.('操作失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatTime(t) {
|
|
|
+ if (!t) return '-'
|
|
|
+ return t.replace ? t.replace('T', ' ').substring(0, 19) : t
|
|
|
+ },
|
|
|
+ scoreColor(score) {
|
|
|
+ if (score == null) return '#909399'
|
|
|
+ if (score >= 80) return '#67c23a'
|
|
|
+ if (score >= 60) return '#e6a23c'
|
|
|
+ return '#f56c6c'
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.health-report-audit {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+.score-card {
|
|
|
+ border: 2px solid #ebeef5;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 12px;
|
|
|
+ text-align: center;
|
|
|
+ background: #fafafa;
|
|
|
+}
|
|
|
+.score-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+.score-value {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.pagination-wrap {
|
|
|
+ margin-top: 16px;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+</style>
|