PatientPhysicalSignRecordsWidget.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. <script setup lang="ts">
  2. import { ArrowDownOutlined, ArrowUpOutlined, CaretRightOutlined } from '@ant-design/icons-vue';
  3. import { h, ref } from 'vue';
  4. import { useRequest } from 'alova/client';
  5. import { getPatientHealthIndicatorRecordsMethod } from '@/request/api/report.api';
  6. import type { HealthIndicatorItemVO, HealthIndicatorVO } from '@/model/health-report.model';
  7. import dayjs from 'dayjs';
  8. const props = defineProps<{ patient: { id: string } }>();
  9. const indicators = shallowRef<HealthIndicatorVO[]>([]);
  10. const activeKey = ref<string[]>([]);
  11. const { loading } = useRequest(() => getPatientHealthIndicatorRecordsMethod(props.patient.id)).onSuccess(({ data }) => {
  12. indicators.value = conversionGrouping(data);
  13. activeKey.value = indicators.value.map((item) => item.name);
  14. });
  15. function conversionGrouping(data: HealthIndicatorVO[]): HealthIndicatorVO[] {
  16. const gather = new Map<number, HealthIndicatorVO>();
  17. const indicators = data.flatMap((indicator) => indicator.items);
  18. for (const indicator of indicators) {
  19. const key = dayjs(indicator.date).toDate().valueOf();
  20. const value = gather.has(key)
  21. ? gather.get(key)!
  22. : (gather.set(key, {
  23. id: indicator.date,
  24. name: indicator.date,
  25. items: [],
  26. }),
  27. gather.get(key)!);
  28. value.items.push(indicator);
  29. }
  30. return Array.from(gather.keys())
  31. .sort((a, b) => b - a)
  32. .map((title) => ({ title, ...gather.get(title)! }));
  33. }
  34. </script>
  35. <template>
  36. <a-spin :spinning="loading">
  37. <a-collapse v-if="indicators.length" class="physical-sign-records-wrapper" v-model:activeKey="activeKey" :bordered="false">
  38. <a-collapse-panel v-for="indicator in indicators" :key="indicator.name" :header="indicator.name">
  39. <div class="flex flex-wrap">
  40. <div class="text-center w-260px row" v-for="item in indicator.items" :key="item.name">
  41. <div class="flex justify-center">
  42. <span><label>{{ item.name }}</label>{{ item.value }}{{ item.unit }}</span>
  43. <div class="inline-block ml-2 size-24px">
  44. <a-button v-if="item.trend > 0" :icon="h(ArrowUpOutlined)" shape="circle" size="small" class="trend-up" />
  45. <a-button v-else-if="item.trend < 0" :icon="h(ArrowDownOutlined)" shape="circle" size="small" class="trend-down" />
  46. </div>
  47. </div>
  48. </div>
  49. </div>
  50. </a-collapse-panel>
  51. </a-collapse>
  52. <a-empty v-else description="暂无数据"></a-empty>
  53. </a-spin>
  54. </template>
  55. <style scoped lang="scss">
  56. .physical-sign-records-wrapper {
  57. background-color: transparent;
  58. :deep(.ant-collapse-item) {
  59. margin-bottom: 12px;
  60. border-bottom: none;
  61. }
  62. .row {
  63. padding: 12px 0;
  64. span > label {
  65. color: rgba(0, 0, 0, 0.45);
  66. }
  67. label::after {
  68. margin-left: 2px;
  69. margin-right: 8px;
  70. content: ':';
  71. }
  72. > header::before {
  73. $size: 10px;
  74. content: '';
  75. display: inline-block;
  76. margin-right: 12px;
  77. width: $size;
  78. height: $size;
  79. border: 2px solid #1d6ff6;
  80. border-radius: 50%;
  81. }
  82. > main {
  83. margin-left: 18px * 2;
  84. }
  85. }
  86. }
  87. .trend-up {
  88. color: #ff4d4f;
  89. border-color: #ff4d4f;
  90. }
  91. .trend-down {
  92. color: #87d068;
  93. border-color: #87d068;
  94. }
  95. </style>