StatCard.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <template>
  2. <div
  3. class="adm-stat-card"
  4. :class="['adm-stat-card--' + resolvedColor, { 'is-clickable': clickable }]"
  5. @click="handleClick"
  6. >
  7. <div v-if="icon" class="stat-card__icon">
  8. <i :class="icon"></i>
  9. </div>
  10. <div class="stat-card__body">
  11. <div class="stat-card__value">
  12. <span v-if="prefix" class="stat-card__affix">{{ prefix }}</span>
  13. <span class="stat-card__num">{{ displayValue }}</span>
  14. <span v-if="suffix" class="stat-card__affix">{{ suffix }}</span>
  15. </div>
  16. <div class="stat-card__label">{{ label }}</div>
  17. </div>
  18. </div>
  19. </template>
  20. <script>
  21. /**
  22. * StatCard —— 企业级 KPI 统计卡
  23. *
  24. * 设计目的:统一后台 19 个页面各自手写的 .stat-card / .stat-icon-wrap / .stat-value / .stat-label,
  25. * 消除内联色值与圆角/阴影漂移,收敛到 Ant Design 5 语义色令牌。
  26. *
  27. * 兼容旧数据形态 { color, icon, value, label }:
  28. * 旧写法 <el-card shadow="hover" class="stat-card" :class="'stat-' + card.color"> ... </el-card>
  29. * 新写法 <StatCard :label="c.label" :value="c.value" :icon="c.icon" :color="c.color" />
  30. * 旧 color 名(blue/purple/green/orange/red/cyan 等)由 COLOR_MAP 自动映射,无需改数据。
  31. */
  32. const COLOR_MAP = {
  33. primary: 'primary',
  34. blue: 'primary',
  35. purple: 'primary', // 旧品牌紫统一收敛为品牌蓝
  36. success: 'success',
  37. green: 'success',
  38. warning: 'warning',
  39. orange: 'warning',
  40. amber: 'warning',
  41. yellow: 'warning',
  42. danger: 'danger',
  43. red: 'danger',
  44. cyan: 'cyan',
  45. teal: 'cyan',
  46. info: 'info',
  47. gray: 'info',
  48. grey: 'info',
  49. default: 'info'
  50. }
  51. export default {
  52. name: 'StatCard',
  53. props: {
  54. /** 指标名称 */
  55. label: { type: String, default: '' },
  56. /** 指标数值 */
  57. value: { type: [String, Number], default: '' },
  58. /** Element UI 图标 class,如 el-icon-user */
  59. icon: { type: String, default: '' },
  60. /** 语义色:primary/success/warning/danger/cyan/info(旧名自动映射) */
  61. color: { type: String, default: 'primary' },
  62. /** 数值前缀,如 ¥ */
  63. prefix: { type: String, default: '' },
  64. /** 数值后缀,如 元 / % */
  65. suffix: { type: String, default: '' },
  66. /** 数字是否按千分位格式化(仅对 number 生效) */
  67. thousand: { type: Boolean, default: false },
  68. /** 是否可点击(开启后带 hover 反馈并派发 click) */
  69. clickable: { type: Boolean, default: false }
  70. },
  71. computed: {
  72. resolvedColor() {
  73. return COLOR_MAP[String(this.color || 'primary').toLowerCase()] || 'primary'
  74. },
  75. displayValue() {
  76. const v = this.value
  77. if (v === null || v === undefined || v === '') return '-'
  78. if (this.thousand && typeof v === 'number' && isFinite(v)) {
  79. return v.toLocaleString('en-US')
  80. }
  81. return v
  82. }
  83. },
  84. methods: {
  85. handleClick() {
  86. if (this.clickable) this.$emit('click')
  87. }
  88. }
  89. }
  90. </script>
  91. <style scoped>
  92. .adm-stat-card {
  93. display: flex;
  94. align-items: center;
  95. gap: 12px;
  96. padding: 16px;
  97. background: #fff;
  98. border: 1px solid #f0f0f0;
  99. border-radius: 8px;
  100. height: 100%;
  101. box-sizing: border-box;
  102. transition: border-color 0.2s ease;
  103. }
  104. .adm-stat-card.is-clickable {
  105. cursor: pointer;
  106. }
  107. .adm-stat-card.is-clickable:hover {
  108. border-color: var(--adm-primary);
  109. }
  110. .adm-stat-card__icon {
  111. flex: 0 0 auto;
  112. width: 44px;
  113. height: 44px;
  114. border-radius: 8px;
  115. display: flex;
  116. align-items: center;
  117. justify-content: center;
  118. font-size: 20px;
  119. }
  120. .adm-stat-card__body {
  121. flex: 1 1 auto;
  122. min-width: 0;
  123. }
  124. .adm-stat-card__value {
  125. font-size: 24px;
  126. font-weight: 600;
  127. line-height: 1.2;
  128. color: #262626;
  129. font-variant-numeric: tabular-nums;
  130. font-feature-settings: 'tnum';
  131. white-space: nowrap;
  132. overflow: hidden;
  133. text-overflow: ellipsis;
  134. }
  135. .adm-stat-card__affix {
  136. font-size: 14px;
  137. font-weight: 400;
  138. color: #8c8c8c;
  139. margin: 0 2px;
  140. }
  141. .adm-stat-card__label {
  142. font-size: 12px;
  143. color: #8c8c8c;
  144. margin-top: 2px;
  145. white-space: nowrap;
  146. overflow: hidden;
  147. text-overflow: ellipsis;
  148. }
  149. /* ---- 语义色(Ant Design 5 浅底 + 深字) ---- */
  150. .adm-stat-card--primary .adm-stat-card__icon {
  151. background: #e6f4ff;
  152. color: var(--adm-primary);
  153. }
  154. .adm-stat-card--success .adm-stat-card__icon {
  155. background: #f6ffed;
  156. color: #389e0d;
  157. }
  158. .adm-stat-card--warning .adm-stat-card__icon {
  159. background: #fffbe6;
  160. color: #d48806;
  161. }
  162. .adm-stat-card--danger .adm-stat-card__icon {
  163. background: #fff1f0;
  164. color: #cf1322;
  165. }
  166. .adm-stat-card--cyan .adm-stat-card__icon {
  167. background: #e6fffb;
  168. color: #08979c;
  169. }
  170. .adm-stat-card--info .adm-stat-card__icon {
  171. background: #f5f5f5;
  172. color: #595959;
  173. }
  174. </style>