report-detail.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <template>
  2. <view class="container">
  3. <!-- 顶部导航 -->
  4. <!-- 加载状态 -->
  5. <view class="loading" v-if="loading">加载中...</view>
  6. <!-- 报告内容 -->
  7. <template v-else-if="report">
  8. <!-- 报告头部 -->
  9. <view class="report-header">
  10. <view class="report-type-badge" :class="'badge-' + report.reportType">
  11. <text>{{ typeLabel }}</text>
  12. </view>
  13. <text class="report-date">{{ formatDate(report.reportDate) }}</text>
  14. </view>
  15. <!-- 综合评分 -->
  16. <view class="score-section" v-if="report.overallScore">
  17. <view class="score-circle">
  18. <text class="score-value">{{ report.overallScore }}</text>
  19. <text class="score-unit">分</text>
  20. </view>
  21. <view class="score-level">
  22. <text class="level-text">{{ scoreLevel }}</text>
  23. </view>
  24. </view>
  25. <!-- 报告内容 -->
  26. <view class="report-content">
  27. <mp-html :content="report.content || ''" />
  28. </view>
  29. <!-- 报告附件 -->
  30. <view class="report-attachment" v-if="report.fileUrl">
  31. <view class="attachment-label">
  32. <text>原始报告</text>
  33. <text class="attachment-size">{{ report.fileSize || '' }}</text>
  34. </view>
  35. <button class="attachment-btn" @click="downloadFile">查看原文件</button>
  36. </view>
  37. </template>
  38. <!-- 空状态 -->
  39. <view v-else class="empty-state">
  40. <text class="empty-icon">📋</text>
  41. <text class="empty-title">报告不存在</text>
  42. <text class="empty-desc">该报告可能已被删除</text>
  43. <view class="empty-btn" @click="goBack">返回</view>
  44. </view>
  45. </view>
  46. </template>
  47. <script>
  48. import MpHtml from '@/components/mp-html/mp-html.vue'
  49. import { getReportDetail } from '@/utils/api'
  50. import { parseDate } from '@/utils/format'
  51. export default {
  52. components: { MpHtml },
  53. data() {
  54. return {
  55. reportId: null,
  56. report: null,
  57. loading: true
  58. }
  59. },
  60. computed: {
  61. typeLabel() {
  62. const labels = {
  63. 'physical_exam': '体检报告',
  64. 'gut_flora': '肠道检测',
  65. 'tongue': '舌诊报告'
  66. }
  67. return labels[this.report.reportType] || '健康报告'
  68. },
  69. scoreLevel() {
  70. const score = this.report.overallScore
  71. if (!score) return '--'
  72. if (score >= 90) return '优秀'
  73. if (score >= 80) return '良好'
  74. if (score >= 70) return '一般'
  75. return '需关注'
  76. }
  77. },
  78. onLoad(options) {
  79. this.reportId = options.reportId
  80. if (this.reportId) {
  81. this.loadReport()
  82. } else {
  83. this.loading = false
  84. }
  85. },
  86. methods: {
  87. loadReport() {
  88. this.loading = true
  89. getReportDetail(this.reportId).then(res => {
  90. if (res.code === 200 && res.data) {
  91. this.report = res.data
  92. }
  93. }).catch(() => {
  94. uni.showToast({ title: '加载失败', icon: 'none' })
  95. }).finally(() => {
  96. this.loading = false
  97. })
  98. },
  99. formatDate(dateStr) {
  100. if (!dateStr) return '--'
  101. const d = parseDate(dateStr)
  102. if (!d) return dateStr
  103. const y = d.getFullYear()
  104. const m = ('' + (d.getMonth() + 1)).padStart(2, '0')
  105. const day = ('' + d.getDate()).padStart(2, '0')
  106. return y + '-' + m + '-' + day
  107. },
  108. downloadFile() {
  109. if (!this.report.fileUrl) return
  110. uni.downloadFile({
  111. url: this.report.fileUrl,
  112. success: (res) => {
  113. if (res.statusCode === 200) {
  114. uni.openDocument({
  115. filePath: res.tempFilePath,
  116. fileType: this.getFileType(),
  117. success: () => {},
  118. fail: () => {
  119. uni.showToast({ title: '打开失败', icon: 'none' })
  120. }
  121. })
  122. }
  123. },
  124. fail: () => {
  125. uni.showToast({ title: '下载失败', icon: 'none' })
  126. }
  127. })
  128. },
  129. getFileType() {
  130. const url = this.report.fileUrl || ''
  131. if (url.includes('.pdf')) return 'pdf'
  132. if (url.includes('.jpg') || url.includes('.jpeg')) return 'jpg'
  133. if (url.includes('.png')) return 'png'
  134. return 'pdf'
  135. },
  136. goBack() {
  137. uni.navigateBack()
  138. }
  139. }
  140. }
  141. </script>
  142. <style scoped>
  143. .container {
  144. min-height: 100vh;
  145. background: #f5f7fa;
  146. }
  147. .nav-placeholder {
  148. width: 60rpx;
  149. }
  150. .loading {
  151. text-align: center;
  152. padding: 100rpx;
  153. color: #999;
  154. }
  155. .report-header {
  156. background: #fff;
  157. padding: 30rpx;
  158. margin-bottom: 20rpx;
  159. display: flex;
  160. justify-content: space-between;
  161. align-items: center;
  162. }
  163. .report-type-badge {
  164. padding: 8rpx 20rpx;
  165. border-radius: 20rpx;
  166. font-size: 24rpx;
  167. font-weight: 600;
  168. }
  169. .badge-physical_exam {
  170. background: #e6f7ff;
  171. color: #1890ff;
  172. }
  173. .badge-gut_flora {
  174. background: #f6ffed;
  175. color: #52c41a;
  176. }
  177. .badge-tongue {
  178. background: #fff7e6;
  179. color: #fa8c16;
  180. }
  181. .report-date {
  182. font-size: 26rpx;
  183. color: #999;
  184. }
  185. .score-section {
  186. background: #fff;
  187. padding: 40rpx;
  188. margin-bottom: 20rpx;
  189. display: flex;
  190. flex-direction: column;
  191. align-items: center;
  192. }
  193. .score-circle {
  194. position: relative;
  195. width: 160rpx;
  196. height: 160rpx;
  197. display: flex;
  198. align-items: center;
  199. justify-content: center;
  200. }
  201. .score-value {
  202. font-size: 48rpx;
  203. font-weight: 700;
  204. color: #333;
  205. }
  206. .score-unit {
  207. position: absolute;
  208. bottom: 20rpx;
  209. right: 30rpx;
  210. font-size: 24rpx;
  211. color: #999;
  212. }
  213. .score-level {
  214. margin-top: 16rpx;
  215. }
  216. .level-text {
  217. font-size: 28rpx;
  218. color: #666;
  219. }
  220. .report-content {
  221. background: #fff;
  222. padding: 30rpx;
  223. margin-bottom: 20rpx;
  224. min-height: 200rpx;
  225. }
  226. .report-attachment {
  227. background: #fff;
  228. padding: 30rpx;
  229. margin-bottom: 40rpx;
  230. display: flex;
  231. justify-content: space-between;
  232. align-items: center;
  233. }
  234. .attachment-label {
  235. display: flex;
  236. flex-direction: column;
  237. }
  238. .attachment-label text:first-child {
  239. font-size: 28rpx;
  240. color: #333;
  241. font-weight: 500;
  242. }
  243. .attachment-size {
  244. font-size: 22rpx;
  245. color: #999;
  246. margin-top: 4rpx;
  247. }
  248. .attachment-btn {
  249. background: #f97316;
  250. color: #fff;
  251. font-size: 24rpx;
  252. padding: 12rpx 30rpx;
  253. border-radius: 20rpx;
  254. border: none;
  255. }
  256. .empty-state {
  257. text-align: center;
  258. padding: 120rpx 40rpx;
  259. }
  260. .empty-icon {
  261. font-size: 80rpx;
  262. display: block;
  263. margin-bottom: 20rpx;
  264. }
  265. .empty-title {
  266. font-size: 32rpx;
  267. font-weight: 600;
  268. color: #333;
  269. display: block;
  270. margin-bottom: 12rpx;
  271. }
  272. .empty-desc {
  273. font-size: 26rpx;
  274. color: #999;
  275. display: block;
  276. margin-bottom: 40rpx;
  277. }
  278. .empty-btn {
  279. display: inline-block;
  280. background: #f97316;
  281. color: #fff;
  282. font-size: 28rpx;
  283. padding: 16rpx 60rpx;
  284. border-radius: 40rpx;
  285. }
  286. </style>