physical-exam-detail.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <template>
  2. <view class="detail-container">
  3. <!-- 导航栏 -->
  4. <view class="nav-bar">
  5. <text class="nav-back" @click="goBack">‹ 返回</text>
  6. <text class="nav-title">体检报告详情</text>
  7. <view class="nav-placeholder"></view>
  8. </view>
  9. <!-- 加载状态 -->
  10. <view class="loading-wrap" v-if="loading">
  11. <text class="loading-text">加载中...</text>
  12. </view>
  13. <template v-else>
  14. <!-- 报告摘要卡片 -->
  15. <view class="summary-card">
  16. <view class="summary-header">
  17. <text class="summary-icon">🏥</text>
  18. <text class="summary-title">体检报告</text>
  19. </view>
  20. <view class="summary-body">
  21. <view class="summary-row">
  22. <text class="summary-label">报告日期</text>
  23. <text class="summary-value">{{ report.reportDate || '--' }}</text>
  24. </view>
  25. <view class="summary-row">
  26. <text class="summary-label">综合评分</text>
  27. <text class="summary-score" :class="scoreClass">{{ report.overallScore || '--' }}<text class="score-unit">分</text></text>
  28. </view>
  29. <view class="summary-row">
  30. <text class="summary-label">报告编号</text>
  31. <text class="summary-value">{{ report.reportNumber || '--' }}</text>
  32. </view>
  33. <view class="summary-row">
  34. <text class="summary-label">备注</text>
  35. <text class="summary-value">{{ report.remark || '无' }}</text>
  36. </view>
  37. </view>
  38. </view>
  39. <!-- 指标分类列表 -->
  40. <view class="indicator-section" v-for="(group, groupName) in groupedIndicators" :key="groupName">
  41. <view class="group-header">
  42. <text class="group-icon">{{ getCategoryIcon(groupName) }}</text>
  43. <text class="group-title">{{ groupName }}</text>
  44. <text class="group-count">{{ group.length }}项</text>
  45. </view>
  46. <view class="indicator-list">
  47. <view class="indicator-item" v-for="(ind, idx) in group" :key="idx">
  48. <view class="indicator-left">
  49. <text class="indicator-name">{{ ind.indicatorName }}</text>
  50. <text class="indicator-range" v-if="ind.refRange">参考值: {{ ind.refRange }}</text>
  51. </view>
  52. <view class="indicator-right">
  53. <text class="indicator-value" :class="ind.statusClass">{{ ind.indicatorValue }}</text>
  54. <text class="indicator-unit" v-if="ind.unit">{{ ind.unit }}</text>
  55. <text class="indicator-status" :class="ind.statusClass">{{ ind.status || '--' }}</text>
  56. </view>
  57. </view>
  58. </view>
  59. </view>
  60. <!-- 空状态 -->
  61. <view class="empty-wrap" v-if="!loading && indicators.length === 0">
  62. <text class="empty-icon">📋</text>
  63. <text class="empty-text">暂无指标数据</text>
  64. </view>
  65. </template>
  66. <view class="bottom-spacer"></view>
  67. <!-- AI 解读入口(已屏蔽)
  68. <view class="ai-fab" @click="goAIChat" v-if="report && reportId">
  69. <text class="fab-icon">🤖</text>
  70. <text class="fab-text">AI 解读报告</text>
  71. </view> -->
  72. </view>
  73. </template>
  74. <script>
  75. import { getReportDetail } from '../../utils/api.js'
  76. export default {
  77. data() {
  78. return {
  79. reportId: null,
  80. report: {},
  81. indicators: [],
  82. loading: false
  83. }
  84. },
  85. computed: {
  86. groupedIndicators: function() {
  87. var groups = {}
  88. var self = this
  89. self.indicators.forEach(function(ind) {
  90. var cat = ind.category || '其他指标'
  91. if (!groups[cat]) {
  92. groups[cat] = []
  93. }
  94. groups[cat].push(ind)
  95. })
  96. // 按分类排序,常用分类在前
  97. var priority = ['血常规', '肝功能', '肾功能', '血脂', '血糖', '尿常规', '便常规',
  98. '甲状腺功能', '肿瘤标志物', '维生素', '微量元素', '营养状况', '炎症指标',
  99. '免疫指标', '其他指标']
  100. var sorted = {}
  101. priority.forEach(function(p) {
  102. if (groups[p]) {
  103. sorted[p] = groups[p]
  104. delete groups[p]
  105. }
  106. })
  107. // 剩余分类保持原序
  108. Object.keys(groups).forEach(function(k) {
  109. sorted[k] = groups[k]
  110. })
  111. return sorted
  112. },
  113. scoreClass: function() {
  114. var s = parseInt(this.report.overallScore)
  115. if (isNaN(s)) return ''
  116. if (s >= 80) return 'score-good'
  117. if (s >= 60) return 'score-warn'
  118. return 'score-bad'
  119. }
  120. },
  121. onLoad: function(options) {
  122. this.reportId = options.reportId ? parseInt(options.reportId) : null
  123. this.loadData()
  124. },
  125. methods: {
  126. loadData: function() {
  127. var self = this
  128. if (!self.reportId) {
  129. uni.showToast({ title: '参数错误', icon: 'none' })
  130. return
  131. }
  132. self.loading = true
  133. getReportDetail(self.reportId).then(function(res) {
  134. if (res.code === 200 && res.data) {
  135. var data = res.data
  136. self.report = data.report || data
  137. // 指标可能来自 indicators 字段或直接是 report 的一部分
  138. var list = []
  139. if (data.indicators) {
  140. list = data.indicators
  141. } else if (data.indicatorList) {
  142. list = data.indicatorList
  143. }
  144. // 预计算状态样式类(小程序 :class 不支持方法调用语法)
  145. list.forEach(function(ind) {
  146. ind.statusClass = self.getStatusClass(ind)
  147. })
  148. self.indicators = list
  149. } else {
  150. uni.showToast({ title: res.message || '加载失败', icon: 'none' })
  151. }
  152. }).catch(function(e) {
  153. console.log('获取报告详情失败', e)
  154. uni.showToast({ title: '加载失败', icon: 'none' })
  155. }).finally(function() {
  156. self.loading = false
  157. })
  158. },
  159. getCategoryIcon: function(cat) {
  160. var icons = {
  161. '血常规': '🩸',
  162. '肝功能': '🫁',
  163. '肾功能': '🫘',
  164. '血脂': '🫧',
  165. '血糖': '🍬',
  166. '尿常规': '💧',
  167. '便常规': '💩',
  168. '甲状腺功能': '🦋',
  169. '肿瘤标志物': '🔬',
  170. '维生素': '🍊',
  171. '微量元素': '⚗️',
  172. '营养状况': '🥗',
  173. '炎症指标': '🔥',
  174. '免疫指标': '🛡️',
  175. }
  176. return icons[cat] || '📊'
  177. },
  178. getStatusClass: function(ind) {
  179. var status = (ind.status || '').toLowerCase()
  180. if (status.indexOf('偏高') !== -1 || status.indexOf('高') !== -1 || status.indexOf('过多') !== -1) {
  181. return 'status-high'
  182. }
  183. if (status.indexOf('偏低') !== -1 || status.indexOf('低') !== -1 || status.indexOf('缺乏') !== -1) {
  184. return 'status-low'
  185. }
  186. if (status.indexOf('注意') !== -1 || status.indexOf('风险') !== -1) {
  187. return 'status-warn'
  188. }
  189. if (status.indexOf('正常') !== -1 || status.indexOf('未检出') !== -1) {
  190. return 'status-normal'
  191. }
  192. return ''
  193. },
  194. // AI问答功能已屏蔽
  195. goAIChat: function() {},
  196. goBack: function() {
  197. uni.navigateBack()
  198. }
  199. }
  200. }
  201. </script>
  202. <style scoped>
  203. .detail-container {
  204. min-height: 100vh;
  205. background: #F5F7FA;
  206. }
  207. .nav-bar {
  208. position: sticky;
  209. top: 0;
  210. background: #fff;
  211. display: flex;
  212. flex-direction: row;
  213. align-items: center;
  214. justify-content: space-between;
  215. padding: 20rpx 30rpx;
  216. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
  217. z-index: 100;
  218. }
  219. .nav-back { font-size: 32rpx; color: #5B9BD5; }
  220. .nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
  221. .nav-placeholder { width: 80rpx; }
  222. .loading-wrap {
  223. display: flex;
  224. justify-content: center;
  225. padding: 80rpx 0;
  226. }
  227. .loading-text { font-size: 28rpx; color: #999; }
  228. /* 摘要卡片 */
  229. .summary-card {
  230. background: linear-gradient(135deg, #E3F2FD, #BBDEFB);
  231. border-radius: 20rpx;
  232. margin: 20rpx 30rpx;
  233. padding: 28rpx 24rpx;
  234. }
  235. .summary-header {
  236. display: flex;
  237. flex-direction: row;
  238. align-items: center;
  239. margin-bottom: 20rpx;
  240. }
  241. .summary-icon { font-size: 40rpx; margin-right: 12rpx; }
  242. .summary-title { font-size: 32rpx; font-weight: bold; color: #1565C0; }
  243. .summary-body { display: flex; flex-direction: column; gap: 14rpx; }
  244. .summary-row {
  245. display: flex;
  246. flex-direction: row;
  247. align-items: center;
  248. justify-content: space-between;
  249. }
  250. .summary-label { font-size: 26rpx; color: #555; }
  251. .summary-value { font-size: 26rpx; color: #333; font-weight: 500; }
  252. .summary-score { font-size: 40rpx; font-weight: bold; }
  253. .score-unit { font-size: 22rpx; font-weight: normal; }
  254. .score-good { color: #2E7D32; }
  255. .score-warn { color: #E65100; }
  256. .score-bad { color: #C62828; }
  257. /* 指标分组 */
  258. .indicator-section {
  259. margin: 0 30rpx 20rpx;
  260. }
  261. .group-header {
  262. display: flex;
  263. flex-direction: row;
  264. align-items: center;
  265. padding: 16rpx 20rpx;
  266. background: #fff;
  267. border-radius: 16rpx 16rpx 0 0;
  268. border-bottom: 2rpx solid #E2E8F0;
  269. }
  270. .group-icon { font-size: 28rpx; margin-right: 8rpx; }
  271. .group-title { font-size: 28rpx; font-weight: bold; color: #333; flex: 1; }
  272. .group-count { font-size: 22rpx; color: #999; }
  273. .indicator-list {
  274. background: #fff;
  275. border-radius: 0 0 16rpx 16rpx;
  276. overflow: hidden;
  277. }
  278. .indicator-item {
  279. display: flex;
  280. flex-direction: row;
  281. align-items: center;
  282. justify-content: space-between;
  283. padding: 18rpx 20rpx;
  284. border-bottom: 1rpx solid #F0F0F0;
  285. }
  286. .indicator-item:last-child { border-bottom: none; }
  287. .indicator-left {
  288. flex: 1;
  289. display: flex;
  290. flex-direction: column;
  291. gap: 4rpx;
  292. }
  293. .indicator-name { font-size: 28rpx; color: #333; font-weight: 500; }
  294. .indicator-range { font-size: 22rpx; color: #999; }
  295. .indicator-right {
  296. display: flex;
  297. flex-direction: column;
  298. align-items: flex-end;
  299. gap: 4rpx;
  300. margin-left: 20rpx;
  301. min-width: 120rpx;
  302. }
  303. .indicator-value { font-size: 32rpx; font-weight: bold; }
  304. .indicator-unit { font-size: 22rpx; color: #999; }
  305. .indicator-status { font-size: 20rpx; padding: 2rpx 10rpx; border-radius: 10rpx; }
  306. /* 状态颜色 */
  307. .status-high { color: #C62828; }
  308. .status-low { color: #E65100; }
  309. .status-warn { color: #F57F17; }
  310. .status-normal { color: #2E7D32; }
  311. /* 空状态 */
  312. .empty-wrap {
  313. display: flex;
  314. flex-direction: column;
  315. align-items: center;
  316. padding: 100rpx 0;
  317. }
  318. .empty-icon { font-size: 80rpx; margin-bottom: 20rpx; }
  319. .empty-text { font-size: 28rpx; color: #999; }
  320. .bottom-spacer { height: 60rpx; }
  321. .ai-fab {
  322. position: fixed;
  323. right: 30rpx;
  324. bottom: 100rpx;
  325. display: flex;
  326. flex-direction: row;
  327. align-items: center;
  328. background: linear-gradient(135deg, #6366F1, #8B5CF6);
  329. border-radius: 40rpx;
  330. padding: 16rpx 28rpx;
  331. box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.4);
  332. z-index: 100;
  333. }
  334. .ai-fab .fab-icon {
  335. font-size: 32rpx;
  336. margin-right: 12rpx;
  337. }
  338. .ai-fab .fab-text {
  339. font-size: 26rpx;
  340. color: #fff;
  341. font-weight: 500;
  342. }
  343. </style>