|
@@ -0,0 +1,333 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="proxy-report admin-page">
|
|
|
|
|
+ <el-card>
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span class="card-title">📋 代上传健康报告</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 上传区域 -->
|
|
|
|
|
+ <el-form ref="uploadForm" :model="uploadForm" label-width="100px" class="upload-form">
|
|
|
|
|
+ <el-form-item label="选择家庭" prop="familyId" required>
|
|
|
|
|
+ <el-select v-model="uploadForm.familyId" placeholder="请选择家庭" filterable style="width:100%" @change="onFamilyChange">
|
|
|
|
|
+ <el-option v-for="f in familyList" :key="f.id" :label="f.name" :value="f.id" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="报告对象" prop="subjectId" required>
|
|
|
|
|
+ <el-select v-model="uploadForm.subjectId" placeholder="请选择家庭成员" filterable style="width:100%">
|
|
|
|
|
+ <el-option v-for="m in memberList" :key="m.id" :label="m.nickname || m.realName" :value="m.id" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="报告文件" prop="file" required>
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ ref="upload"
|
|
|
|
|
+ action=""
|
|
|
|
|
+ :auto-upload="false"
|
|
|
|
|
+ :limit="1"
|
|
|
|
|
+ accept=".pdf"
|
|
|
|
|
+ :on-change="handleFileChange"
|
|
|
|
|
+ :on-exceed="handleExceed"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button size="small" type="primary">选择PDF文件</el-button>
|
|
|
|
|
+ <div slot="tip" class="el-upload__tip">仅支持PDF格式,大小不超过50MB</div>
|
|
|
|
|
+ </el-upload>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="代上传备注">
|
|
|
|
|
+ <el-input v-model="uploadForm.note" type="textarea" :rows="2" placeholder="选填,备注上传原因" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" :loading="uploading" @click="handleUpload">上传并解析</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 解析结果预览 -->
|
|
|
|
|
+ <el-card v-if="previewResult" style="margin-top:20px">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span class="card-title">📊 解析结果预览</span>
|
|
|
|
|
+ <el-tag :type="previewResult.needBind ? 'warning' : 'success'" size="small">
|
|
|
|
|
+ {{ previewResult.needBind ? '待绑定成员' : '已匹配成员' }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-descriptions :column="2" border>
|
|
|
|
|
+ <el-descriptions-item label="被检测人">{{ previewResult.extractedName || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="报告编号">{{ previewResult.extractedReportNumber || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="报告日期">{{ previewResult.extractedReportDate || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="健康总分">{{ previewResult.payload && previewResult.payload.overallScore ? previewResult.payload.overallScore : '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="菌群健康分">{{ previewResult.payload && previewResult.payload.gutHealthScore ? previewResult.payload.gutHealthScore : '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="肠型">{{ previewResult.payload && previewResult.payload.gutType ? previewResult.payload.gutType : '-' }}</el-descriptions-item>
|
|
|
|
|
+ </el-descriptions>
|
|
|
|
|
+
|
|
|
|
|
+ <div style="margin-top:20px;text-align:right">
|
|
|
|
|
+ <el-button @click="previewResult=null">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="confirming" @click="handleConfirm">确认入库</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- AI解读结果 -->
|
|
|
|
|
+ <el-card v-if="analysisResult" style="margin-top:20px">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span class="card-title">🤖 AI解读结果</span>
|
|
|
|
|
+ <el-tag type="success" size="small">已完成</el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-html="analysisResult" class="analysis-content"></div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 历史报告列表 -->
|
|
|
|
|
+ <el-card style="margin-top:20px">
|
|
|
|
|
+ <div slot="header" class="card-header">
|
|
|
|
|
+ <span class="card-title">📁 代上传历史记录</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-table :data="reportList" border stripe v-loading="listLoading" :max-height="tableHeight">
|
|
|
|
|
+ <el-table-column prop="id" label="ID" width="60" />
|
|
|
|
|
+ <el-table-column label="报告对象" width="120">
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row.subjectName || row.userName || '-' }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="reportType" label="类型" width="100">
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row.reportType === 'gut_flora' ? '菌群检测' : row.reportType === 'physical_exam' ? '体检报告' : row.reportType }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="overallScore" label="健康分" width="80">
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row.overallScore || '-' }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="代上传人" width="100">
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row.proxyUploaderName || '-' }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="remark" label="备注" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="createdAt" label="上传时间" width="160">
|
|
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="120" fixed="right">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <el-button size="mini" type="text" @click="viewReport(row)">查看</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ @size-change="onSizeChange"
|
|
|
|
|
+ @current-change="onPageChange"
|
|
|
|
|
+ :current-page="page"
|
|
|
|
|
+ :page-size="size"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ layout="total, prev, pager, next"
|
|
|
|
|
+ class="pagination-wrap"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 报告详情弹窗 -->
|
|
|
|
|
+ <el-dialog title="报告详情" :visible.sync="detailDialogVisible" width="700px" top="5vh">
|
|
|
|
|
+ <el-descriptions v-if="detailReport" :column="2" border>
|
|
|
|
|
+ <el-descriptions-item label="报告ID">{{ detailReport.id }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="报告类型">{{ detailReport.reportType }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="报告编号">{{ detailReport.reportNumber || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="报告日期">{{ detailReport.reportDate || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="健康总分">{{ detailReport.overallScore || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="菌群健康分">{{ detailReport.gutHealthScore || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="肠型">{{ detailReport.gutType || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="肠道年龄">{{ detailReport.gutAge || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="健康评级">{{ detailReport.healthRating || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="代上传人">{{ detailReport.proxyUploaderName || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="备注" :span="2">{{ detailReport.remark || '-' }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="AI解读" :span="2">
|
|
|
|
|
+ <div v-html="detailReport.aiAnalysis" class="analysis-content" style="max-height:300px;overflow:auto"></div>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ </el-descriptions>
|
|
|
|
|
+ <span slot="footer">
|
|
|
|
|
+ <el-button @click="detailDialogVisible = false">关闭</el-button>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { proxyParseReport, proxyConfirmReport, listHealthReports, getHealthReportDetail } from '@/api/admin'
|
|
|
|
|
+import { getFamilyList } from '@/api/admin'
|
|
|
|
|
+import { getFamilyMemberList } from '@/api/admin'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'ProxyReport',
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ uploading: false,
|
|
|
|
|
+ confirming: false,
|
|
|
|
|
+ listLoading: false,
|
|
|
|
|
+ familyList: [],
|
|
|
|
|
+ memberList: [],
|
|
|
|
|
+ reportList: [],
|
|
|
|
|
+ previewResult: null,
|
|
|
|
|
+ analysisResult: null,
|
|
|
|
|
+ detailReport: null,
|
|
|
|
|
+ detailDialogVisible: false,
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ size: 20,
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ uploadForm: {
|
|
|
|
|
+ familyId: null,
|
|
|
|
|
+ subjectId: null,
|
|
|
|
|
+ file: null,
|
|
|
|
|
+ note: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ tableHeight() {
|
|
|
|
|
+ return window.innerHeight - 420
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ this.loadFamilies()
|
|
|
|
|
+ this.loadReports()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ async loadFamilies() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getFamilyList({ page: 1, size: 200 })
|
|
|
|
|
+ this.familyList = res.data && res.data.records ? res.data.records : (res.data || [])
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error(e)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ async onFamilyChange(familyId) {
|
|
|
|
|
+ if (!familyId) { this.memberList = []; return }
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getFamilyMemberList({ familyId })
|
|
|
|
|
+ this.memberList = res.data || []
|
|
|
|
|
+ this.uploadForm.subjectId = null
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.memberList = []
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ handleFileChange(file) {
|
|
|
|
|
+ this.uploadForm.file = file.raw
|
|
|
|
|
+ },
|
|
|
|
|
+ handleExceed() {
|
|
|
|
|
+ this.$message.warning('只能上传一个文件')
|
|
|
|
|
+ },
|
|
|
|
|
+ async handleUpload() {
|
|
|
|
|
+ if (!this.uploadForm.familyId) {
|
|
|
|
|
+ this.$message.warning('请选择家庭')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.uploadForm.subjectId) {
|
|
|
|
|
+ this.$message.warning('请选择报告对象')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.uploadForm.file) {
|
|
|
|
|
+ this.$message.warning('请选择PDF文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.uploading = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await proxyParseReport({
|
|
|
|
|
+ familyId: this.uploadForm.familyId,
|
|
|
|
|
+ subjectId: this.uploadForm.subjectId,
|
|
|
|
|
+ file: this.uploadForm.file,
|
|
|
|
|
+ note: this.uploadForm.note
|
|
|
|
|
+ })
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ this.previewResult = res.data
|
|
|
|
|
+ this.analysisResult = null
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$message.error(res.message || '解析失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.$message.error('上传失败: ' + (e.message || ''))
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.uploading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ async handleConfirm() {
|
|
|
|
|
+ if (!this.previewResult || !this.previewResult.draftId) {
|
|
|
|
|
+ this.$message.warning('没有可确认的草稿')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.confirming = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await proxyConfirmReport({
|
|
|
|
|
+ draftId: this.previewResult.draftId,
|
|
|
|
|
+ subjectId: this.uploadForm.subjectId
|
|
|
|
|
+ })
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ this.$message.success('报告入库成功')
|
|
|
|
|
+ this.previewResult = null
|
|
|
|
|
+ this.analysisResult = res.data && res.data.aiAnalysis ? res.data.aiAnalysis : null
|
|
|
|
|
+ this.loadReports()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$message.error(res.message || '确认失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.$message.error('确认失败: ' + (e.message || ''))
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.confirming = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ async loadReports() {
|
|
|
|
|
+ this.listLoading = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await listHealthReports({ page: this.page, size: this.size })
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ this.reportList = res.data && res.data.records ? res.data.records : (res.data || [])
|
|
|
|
|
+ this.total = res.data && res.data.total ? res.data.total : this.reportList.length
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error(e)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.listLoading = false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ async viewReport(row) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getHealthReportDetail(row.id)
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ this.detailReport = res.data
|
|
|
|
|
+ this.detailDialogVisible = true
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.$message.error('加载详情失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onSizeChange(size) {
|
|
|
|
|
+ this.size = size
|
|
|
|
|
+ this.loadReports()
|
|
|
|
|
+ },
|
|
|
|
|
+ onPageChange(page) {
|
|
|
|
|
+ this.page = page
|
|
|
|
|
+ this.loadReports()
|
|
|
|
|
+ },
|
|
|
|
|
+ formatTime(t) {
|
|
|
|
|
+ if (!t) return '-'
|
|
|
|
|
+ return t.replace('T', ' ').substring(0, 19)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+.card-title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+}
|
|
|
|
|
+.upload-form {
|
|
|
|
|
+ max-width: 600px;
|
|
|
|
|
+}
|
|
|
|
|
+.analysis-content {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1.8;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+}
|
|
|
|
|
+.analysis-content :deep(p) {
|
|
|
|
|
+ margin: 8px 0;
|
|
|
|
|
+}
|
|
|
|
|
+.pagination-wrap {
|
|
|
|
|
+ margin-top: 20px;
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|