HealthIndicators.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <template>
  2. <div class="health-indicators page-container">
  3. <el-card>
  4. <div slot="header"><span>健康指标</span></div>
  5. <el-table :data="indicators" v-loading="loading" border stripe :max-height="tableHeight" :cell-style="cellStyle">
  6. <el-table-column prop="id" label="ID" width="80">
  7. <template slot="header">
  8. <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
  9. </template>
  10. </el-table-column>
  11. <el-table-column prop="childName" label="孩子" width="120">
  12. <template slot="header">
  13. <span class="sort-header" @click.stop="onSortClick('childName', $event)">孩子<span v-if="sortIndex('childName') !== null" class="sort-index" :class="sortClass('childName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childName') }}</span>{{ sortIcon('childName') }}</span>
  14. </template>
  15. </el-table-column>
  16. <el-table-column prop="indicatorName" label="指标名称" width="120">
  17. <template slot="header">
  18. <span class="sort-header" @click.stop="onSortClick('indicatorName', $event)">指标名称<span v-if="sortIndex('indicatorName') !== null" class="sort-index" :class="sortClass('indicatorName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('indicatorName') }}</span>{{ sortIcon('indicatorName') }}</span>
  19. </template>
  20. <template slot-scope="{ row }">
  21. <span :style="row.status && row.status !== '正常' ? 'color: #DC2626; font-weight: bold' : ''">
  22. {{ row.indicatorName }}
  23. </span>
  24. </template>
  25. </el-table-column>
  26. <el-table-column prop="value" label="数值" width="100">
  27. <template slot="header">
  28. <span class="sort-header" @click.stop="onSortClick('value', $event)">数值<span v-if="sortIndex('value') !== null" class="sort-index" :class="sortClass('value') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('value') }}</span>{{ sortIcon('value') }}</span>
  29. </template>
  30. </el-table-column>
  31. <el-table-column prop="status" label="状态" width="80">
  32. <template slot="header">
  33. <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
  34. </template>
  35. <template slot-scope="{ row }">
  36. <el-tag v-if="row.status && row.status !== '正常'" :type="statusTagType(row.status)" size="small">
  37. {{ row.status }}
  38. </el-tag>
  39. <span v-else>{{ row.status || '正常' }}</span>
  40. </template>
  41. </el-table-column>
  42. <el-table-column label="关联营养素" width="120">
  43. <template slot-scope="{ row }">
  44. <template v-if="row.linkedNutrients && row.linkedNutrients.length > 0">
  45. <el-tag v-for="nutrient in row.linkedNutrients" :key="nutrient" size="mini" style="margin-right: 4px">
  46. {{ nutrient }}
  47. </el-tag>
  48. </template>
  49. <span v-else style="color: #999; font-size: 12px">—</span>
  50. </template>
  51. </el-table-column>
  52. <el-table-column prop="recordedAt" label="记录时间" width="180">
  53. <template slot="header">
  54. <span class="sort-header" @click.stop="onSortClick('recordedAt', $event)">记录时间<span v-if="sortIndex('recordedAt') !== null" class="sort-index" :class="sortClass('recordedAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('recordedAt') }}</span>{{ sortIcon('recordedAt') }}</span>
  55. </template>
  56. </el-table-column>
  57. </el-table>
  58. </el-card>
  59. </div>
  60. </template>
  61. <script>
  62. import SortMixin from '@/mixins/SortMixin'
  63. export default {
  64. mixins: [SortMixin],
  65. name: 'HealthIndicators',
  66. computed: {
  67. tableHeight() {
  68. return window.innerHeight - 280
  69. }
  70. },
  71. data() { return { loading: false, indicators: [], sortableColumns: { id: 'ID', recordedAt: '记录时间', childName: '孩子', indicatorName: '指标名称', value: '数值', status: '状态' } } },
  72. created() { this.loadIndicators() },
  73. methods: {
  74. loadList() { this.loadIndicators() },
  75. async loadIndicators() {
  76. this.loading = true
  77. try {
  78. const baseURL = process.env.VUE_APP_BASE_API || ''
  79. const token = localStorage.getItem('token')
  80. const axios = this.$axios || (await import('axios')).default
  81. const params = {}
  82. if (this.sortState.length > 0) params.sort = this.sortState
  83. const res = await axios.post(baseURL + '/api/health/indicator/list', params, {
  84. headers: { Authorization: 'Bearer ' + token }
  85. })
  86. this.indicators = res.data?.data || []
  87. } catch (e) {
  88. this.$message?.error?.('加载健康指标失败')
  89. } finally {
  90. this.loading = false
  91. }
  92. },
  93. cellStyle({ row, columnIndex }) {
  94. if (columnIndex === 1 && row.status && row.status !== '正常') {
  95. return 'background: #FEF2F2'
  96. }
  97. return ''
  98. },
  99. statusTagType(status) {
  100. if (!status) return 'info'
  101. var s = status.toLowerCase()
  102. if (s.indexOf('低') >= 0 || s.indexOf('缺乏') >= 0 || s.indexOf('不足') >= 0 || s === 'low') return 'danger'
  103. if (s.indexOf('高') >= 0 || s.indexOf('过量') >= 0 || s === 'high') return 'warning'
  104. if (s.indexOf('临界') >= 0 || s.indexOf('边缘') >= 0 || s === 'borderline') return 'warning'
  105. return 'info'
  106. }
  107. }
  108. }
  109. </script>
  110. <style scoped>
  111. .sort-header {
  112. cursor: pointer;
  113. user-select: none;
  114. font-weight: 500;
  115. font-size: 12px;
  116. }
  117. .sort-header:hover {
  118. color: #409eff;
  119. }
  120. </style>