| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- <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 :max-height="tableHeight">
- <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="tableHeight">
- <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="tableHeight">
- <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="tableHeight">
- <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: {
- tableHeight() {
- return window.innerHeight - 280
- },
- 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>
|