HomePage.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <template>
  2. <PageHeader />
  3. <div class="main">
  4. <QueryFilter />
  5. <div class="top-wrapper">
  6. <MetricCard
  7. v-for="item in metricCardList"
  8. :key="item.id"
  9. :title="item.title"
  10. :text="item.text"
  11. :showTrendIcon="item.showTrendIcon"
  12. :trend="item.trend"
  13. :percent="item.percent"
  14. :value="item.value"
  15. :showUnit="item.showUnit"
  16. />
  17. </div>
  18. <ChartSpecial />
  19. <MonthlyReport />
  20. </div>
  21. </template>
  22. <script setup>
  23. import PageHeader from '@/components/layout/PageHeader.vue';
  24. import QueryFilter from '@/components/stats/QueryFilter.vue';
  25. import ChartSpecial from '@/components/stats/ChartSpecial.vue';
  26. import MonthlyReport from '@/components/stats/MonthlyReport.vue';
  27. import MetricCard from '@/components/stats/MetricCard.vue';
  28. import { computed, onMounted } from 'vue';
  29. import { useEnterpriseStore } from '@/store/enterprise';
  30. import { useFilterStore } from '@/store/filter';
  31. import { sumFieldToWan, sumFieldsToWan } from '@/utils/areaMath';
  32. import { formatPercent2 } from '@/utils/calculatePercentage';
  33. import {
  34. buildHistoricalSnapshots,
  35. computeCertifiedRateFromSnapshots,
  36. computeIdleRateFromSnapshots,
  37. emptyComparison,
  38. formatRatePointChange,
  39. formatRelativeChange,
  40. getPreviousMonthPeriod,
  41. getYearAgoPeriod,
  42. sumSnapshotsToWan,
  43. } from '@/utils/statsComparison';
  44. const filterStore = useFilterStore();
  45. const enterpriseStore = useEnterpriseStore();
  46. const filteredList = computed(() =>
  47. enterpriseStore.filteredByGroup(filterStore.selectGroup, filterStore.selectDate)
  48. );
  49. const previousMonth = computed(() => getPreviousMonthPeriod(filterStore.selectDate));
  50. const previousYear = computed(() => getYearAgoPeriod(filterStore.selectDate));
  51. const momSnapshots = computed(() =>
  52. buildHistoricalSnapshots(filteredList.value, previousMonth.value)
  53. );
  54. const yoySnapshots = computed(() =>
  55. buildHistoricalSnapshots(filteredList.value, previousYear.value)
  56. );
  57. const totalBuildingArea = computed(() => sumFieldToWan(filteredList.value, 'c_sjjzmj'));
  58. const totalRentedArea = computed(() =>
  59. sumFieldsToWan(filteredList.value, ['c_zymj', 'c_cjmj', 'c_czmj'])
  60. );
  61. const rentPercent = computed(() =>
  62. formatPercent2(totalRentedArea.value, totalBuildingArea.value)
  63. );
  64. const idleArea = computed(() => sumFieldToWan(filteredList.value, 'c_xzmj'));
  65. const idleRate = computed(() => formatPercent2(idleArea.value, totalBuildingArea.value));
  66. const idleRateNum = computed(() => {
  67. const { rateNum } = computeIdleRateFromSnapshots(filteredList.value);
  68. return rateNum;
  69. });
  70. const certifiedAssetPercent = computed(() => {
  71. const sum = filteredList.value.filter((item) => item.c_sfblcz === '是').length;
  72. return formatPercent2(sum, filteredList.value.length);
  73. });
  74. const certifiedRateNum = computed(() => {
  75. const { rateNum } = computeCertifiedRateFromSnapshots(filteredList.value);
  76. return rateNum;
  77. });
  78. const buildingAreaYoY = computed(() => {
  79. if (!filterStore.selectDate) return emptyComparison();
  80. const previous = sumSnapshotsToWan(yoySnapshots.value, 'c_sjjzmj');
  81. return formatRelativeChange(totalBuildingArea.value, previous);
  82. });
  83. const idleRateMoM = computed(() => {
  84. if (!filterStore.selectDate) return emptyComparison();
  85. const { rateNum: previous } = computeIdleRateFromSnapshots(momSnapshots.value);
  86. return formatRatePointChange(idleRateNum.value, previous);
  87. });
  88. const certifiedRateMoM = computed(() => {
  89. if (!filterStore.selectDate) return emptyComparison();
  90. const { rateNum: previous } = computeCertifiedRateFromSnapshots(momSnapshots.value);
  91. return formatRatePointChange(certifiedRateNum.value, previous);
  92. });
  93. const metricCardList = computed(() => [
  94. {
  95. id: 1,
  96. title: '建筑总面积',
  97. text: '同比去年',
  98. percent: buildingAreaYoY.value.text,
  99. trend: buildingAreaYoY.value.trend,
  100. value: totalBuildingArea.value,
  101. showTrendIcon: true,
  102. showUnit: true,
  103. },
  104. {
  105. id: 2,
  106. title: '已出租/在用建筑面积',
  107. text: '占总建筑面积',
  108. showTrendIcon: false,
  109. percent: rentPercent.value,
  110. value: totalRentedArea.value,
  111. showUnit: true,
  112. },
  113. {
  114. id: 3,
  115. title: '闲置率',
  116. text: '环比上月',
  117. trend: idleRateMoM.value.trend,
  118. percent: idleRateMoM.value.text,
  119. value: idleRate.value,
  120. showTrendIcon: true,
  121. showUnit: false,
  122. },
  123. {
  124. id: 4,
  125. title: '已办证资产占比',
  126. text: '环比上月',
  127. trend: certifiedRateMoM.value.trend,
  128. percent: certifiedRateMoM.value.text,
  129. value: certifiedAssetPercent.value,
  130. showTrendIcon: true,
  131. showUnit: false,
  132. },
  133. ]);
  134. onMounted(async () => {
  135. await enterpriseStore.fetchStatisticsList();
  136. });
  137. </script>
  138. <style scoped>
  139. .main {
  140. width: 100%;
  141. max-width: var(--page-max-width);
  142. min-height: 1080px;
  143. height: auto;
  144. padding: 24px;
  145. display: flex;
  146. flex-direction: column;
  147. gap: 12px;
  148. position: relative;
  149. }
  150. .top-wrapper {
  151. width: 100%;
  152. max-width: var(--content-max-width);
  153. min-height: 146px;
  154. display: grid;
  155. grid-template-columns: repeat(4, minmax(0, 1fr));
  156. gap: 16px;
  157. }
  158. .top-wrapper :deep(.stat-card) {
  159. width: 100%;
  160. max-width: none;
  161. height: 100%;
  162. min-height: 146px;
  163. }
  164. .top-wrapper :deep(.stat-card-header),
  165. .top-wrapper :deep(.stat-card-main) {
  166. width: 100%;
  167. max-width: none;
  168. }
  169. .top-wrapper :deep(.stat-card-trend) {
  170. width: auto;
  171. max-width: 100%;
  172. flex-shrink: 1;
  173. }
  174. </style>