report-blocks-renderer.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <template>
  2. <view class="blocks-renderer">
  3. <view v-for="(block, bi) in blocks" :key="getBlockKey(bi)" class="block-wrap">
  4. <!-- score: 评分圆环组 -->
  5. <view v-if="block.type === 'score'" class="block score-block">
  6. <view class="block-title">{{ block.title }}</view>
  7. <view class="score-grid">
  8. <view class="score-item" v-for="(item, si) in block.items" :key="getScoreKey(si)">
  9. <view class="score-circle" :style="'border-color:' + (item.color || '#4A9BD7')">
  10. <text class="score-value">{{ item.value }}</text>
  11. </view>
  12. <text class="score-label">{{ item.label }}</text>
  13. <view class="sub-items" v-if="item.subItems && item.subItems.length" style="margin-top:8rpx; display:flex; flex-direction:column; align-items:center;">
  14. <view class="sub-item" v-for="(sub, ssi) in item.subItems" :key="getSubKey(ssi)" style="display:flex; flex-direction:row; align-items:center;">
  15. <text class="sub-label" style="font-size:20rpx; color:#666; margin-right:4rpx;">{{ sub.label }}:</text>
  16. <text class="sub-value" style="font-size:20rpx; color:#333;">{{ sub.value }}</text>
  17. </view>
  18. </view>
  19. </view>
  20. </view>
  21. </view>
  22. <!-- risk_group: 风险分组(按等级徽章) -->
  23. <view v-else-if="block.type === 'risk_group'" class="block risk-block">
  24. <view class="block-title">{{ block.title }}</view>
  25. <view class="risk-card" v-for="(item, ri) in block.items" :key="getRiskKey(ri)"
  26. :class="'risk-level-' + riskCss(item.level)">
  27. <text class="risk-name">{{ item.name }}</text>
  28. <text class="risk-value">风险值: {{ item.value || '--' }}</text>
  29. <text class="risk-badge" :class="'badge-' + riskCss(item.level)">{{ riskText(item.level) }}</text>
  30. <text class="ind-help-btn" @tap.stop="onHelp(item, 'indicator')">?</text>
  31. </view>
  32. </view>
  33. <!-- indicator: 指标明细 -->
  34. <view v-else-if="block.type === 'indicator'" class="block indicator-block">
  35. <view class="block-title">
  36. <text>{{ block.title }}</text>
  37. <text class="title-abnormal" v-if="block.abnormalCount && block.abnormalCount > 0">,{{ block.abnormalCount }} 项异常</text>
  38. </view>
  39. <view class="indicator-item" v-for="(item, ii) in block.items" :key="getIndicatorKey(ii)">
  40. <text class="ind-name">{{ item.name }}</text>
  41. <text class="ind-value">{{ item.value }} {{ item.unit }}</text>
  42. <text class="ind-status" :class="'status-' + statusCss(item.status)">{{ item.status }}</text>
  43. <text class="ind-help-btn" @tap.stop="onHelp(item, 'indicator')">?</text>
  44. </view>
  45. </view>
  46. <!-- list: 通用列表(columns 驱动,含 ? 按钮) -->
  47. <view v-else-if="block.type === 'list'" class="block list-block">
  48. <view class="block-title">{{ block.title }}</view>
  49. <view class="list-head" v-if="block.columns">
  50. <text class="list-cell head-cell" v-for="(colItem, ci) in block.columns" :key="getColumnKey(ci)"
  51. :style="'flex:' + (ci === 0 ? 2 : 1)">{{ colItem.label }}</text>
  52. <text class="list-cell head-cell" style="flex:0 0 60rpx;">帮助</text>
  53. </view>
  54. <view class="list-row" v-for="(item, li) in block.items" :key="getListItemKey(li)">
  55. <text class="list-cell" :style="'flex:' + (ci === 0 ? 2 : 1)"
  56. v-for="(colItem, ci) in block.columns" :key="getListColumnKey(ci)">{{ item[colItem.key] || '--' }}</text>
  57. <text class="list-cell ind-help-btn" style="flex:0 0 60rpx; text-align:center;" @tap.stop="onHelp(item, 'bacteria')">?</text>
  58. </view>
  59. </view>
  60. <!-- text: 文本段落 -->
  61. <view v-else-if="block.type === 'text'" class="block text-block">
  62. <view class="block-title">{{ block.title }}</view>
  63. <text class="text-content">{{ block.content }}</text>
  64. </view>
  65. <!-- chart: 预留 -->
  66. <view v-else class="block chart-block">
  67. <view class="block-title">{{ block.title }}</view>
  68. <text class="chart-placeholder">图表组件开发中</text>
  69. </view>
  70. </view>
  71. </view>
  72. </template>
  73. <script>
  74. export default {
  75. name: 'ReportBlocksRenderer',
  76. props: {
  77. blocks: { type: Array, default: function() { return [] } }
  78. },
  79. methods: {
  80. getBlockKey: function(bi) { return 'b' + bi },
  81. getSubKey: function(ssi) { return 'sub' + ssi },
  82. getScoreKey: function(si) { return 's' + si },
  83. getRiskKey: function(ri) { return 'r' + ri },
  84. getIndicatorKey: function(ii) { return 'i' + ii },
  85. getColumnKey: function(ci) { return 'c' + ci },
  86. getListItemKey: function(li) { return 'l' + li },
  87. getListColumnKey: function(ci) { return 'lc' + ci },
  88. riskCss: function(level) {
  89. var important = ['需注意', '注意', '高风险', '异常']
  90. return important.indexOf(level) >= 0 ? 'warning' : 'low'
  91. },
  92. riskText: function(level) {
  93. var map = { '低风险': '✓ 低', '需注意': '⚠ 注意', '注意': '⚠ 注意', '高风险': '⚠ 高', '异常': '⚠ 异常' }
  94. return map[level] || level || ''
  95. },
  96. statusCss: function(status) {
  97. var map = { '偏高': 'high', '偏低': 'low', '缺乏': 'low', '不足': 'low', '过多': 'high', '异常': 'high' }
  98. return map[status] || 'normal'
  99. },
  100. onHelp: function(item, blockType) {
  101. this.$emit('help', item, blockType)
  102. }
  103. }
  104. }
  105. </script>
  106. <style scoped>
  107. .blocks-renderer { width: 100%; }
  108. .block { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; }
  109. .block-title { font-size: 30rpx; font-weight: 600; color: #333; margin-bottom: 20rpx; display: flex; align-items: baseline; }
  110. .title-abnormal { font-size: 24rpx; color: #C62828; font-weight: 400; }
  111. /* score */
  112. .score-grid { display: flex; flex-wrap: wrap; }
  113. .score-item { width: 33.33%; display: flex; flex-direction: column; align-items: center; margin-bottom: 24rpx; }
  114. .score-circle { width: 96rpx; height: 96rpx; border-radius: 50%; border: 8rpx solid; display: flex; align-items: center; justify-content: center; }
  115. .score-value { font-size: 30rpx; font-weight: 600; color: #333; }
  116. .score-label { font-size: 24rpx; color: #666; margin-top: 8rpx; }
  117. /* risk */
  118. .risk-card { display: flex; align-items: center; padding: 16rpx 0; border-bottom: 1rpx solid #f0f0f0; }
  119. .risk-card:last-child { border-bottom: none; }
  120. .risk-name { flex: 1; font-size: 28rpx; color: #333; }
  121. .risk-value { font-size: 24rpx; color: #999; margin-right: 16rpx; }
  122. .risk-badge { padding: 4rpx 14rpx; border-radius: 20rpx; font-size: 22rpx; }
  123. .badge-warning { background: #FFF3E0; color: #E65100; }
  124. .badge-low { background: #E8F5E9; color: #2E7D32; }
  125. /* indicator */
  126. .indicator-item { display: flex; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f0f0f0; }
  127. .indicator-item:last-child { border-bottom: none; }
  128. .ind-name { flex: 1; font-size: 26rpx; color: #333; }
  129. .ind-value { font-size: 26rpx; color: #666; margin-right: 16rpx; }
  130. .ind-status { font-size: 22rpx; padding: 2rpx 12rpx; border-radius: 16rpx; }
  131. .status-high { background: #FFEBEE; color: #C62828; }
  132. .status-low { background: #FFF3E0; color: #E65100; }
  133. .status-normal { background: #E8F5E9; color: #2E7D32; }
  134. /* list */
  135. .list-head { display: flex; padding: 12rpx 0; border-bottom: 2rpx solid #eee; }
  136. .list-row { display: flex; padding: 12rpx 0; border-bottom: 1rpx solid #f5f5f5; }
  137. .list-cell { font-size: 24rpx; color: #333; }
  138. .head-cell { font-size: 24rpx; color: #999; font-weight: 600; }
  139. /* text */
  140. .text-content { font-size: 26rpx; color: #555; line-height: 1.7; white-space: pre-wrap; }
  141. .chart-placeholder { font-size: 24rpx; color: #ccc; }
  142. /* help button */
  143. .ind-help-btn { display: inline-flex; align-items: center; justify-content: center; width: 36rpx; height: 36rpx; border-radius: 50%; background: #E3F2FD; color: #1976D2; font-size: 22rpx; font-weight: 600; margin-left: 8rpx; flex-shrink: 0; }
  144. </style>