|
@@ -0,0 +1,182 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="adm-stat-card"
|
|
|
|
|
+ :class="['adm-stat-card--' + resolvedColor, { 'is-clickable': clickable }]"
|
|
|
|
|
+ @click="handleClick"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div v-if="icon" class="stat-card__icon">
|
|
|
|
|
+ <i :class="icon"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-card__body">
|
|
|
|
|
+ <div class="stat-card__value">
|
|
|
|
|
+ <span v-if="prefix" class="stat-card__affix">{{ prefix }}</span>
|
|
|
|
|
+ <span class="stat-card__num">{{ displayValue }}</span>
|
|
|
|
|
+ <span v-if="suffix" class="stat-card__affix">{{ suffix }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-card__label">{{ label }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+/**
|
|
|
|
|
+ * StatCard —— 企业级 KPI 统计卡
|
|
|
|
|
+ *
|
|
|
|
|
+ * 设计目的:统一后台 19 个页面各自手写的 .stat-card / .stat-icon-wrap / .stat-value / .stat-label,
|
|
|
|
|
+ * 消除内联色值与圆角/阴影漂移,收敛到 Ant Design 5 语义色令牌。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 兼容旧数据形态 { color, icon, value, label }:
|
|
|
|
|
+ * 旧写法 <el-card shadow="hover" class="stat-card" :class="'stat-' + card.color"> ... </el-card>
|
|
|
|
|
+ * 新写法 <StatCard :label="c.label" :value="c.value" :icon="c.icon" :color="c.color" />
|
|
|
|
|
+ * 旧 color 名(blue/purple/green/orange/red/cyan 等)由 COLOR_MAP 自动映射,无需改数据。
|
|
|
|
|
+ */
|
|
|
|
|
+const COLOR_MAP = {
|
|
|
|
|
+ primary: 'primary',
|
|
|
|
|
+ blue: 'primary',
|
|
|
|
|
+ purple: 'primary', // 旧品牌紫统一收敛为品牌蓝
|
|
|
|
|
+ success: 'success',
|
|
|
|
|
+ green: 'success',
|
|
|
|
|
+ warning: 'warning',
|
|
|
|
|
+ orange: 'warning',
|
|
|
|
|
+ amber: 'warning',
|
|
|
|
|
+ yellow: 'warning',
|
|
|
|
|
+ danger: 'danger',
|
|
|
|
|
+ red: 'danger',
|
|
|
|
|
+ cyan: 'cyan',
|
|
|
|
|
+ teal: 'cyan',
|
|
|
|
|
+ info: 'info',
|
|
|
|
|
+ gray: 'info',
|
|
|
|
|
+ grey: 'info',
|
|
|
|
|
+ default: 'info'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'StatCard',
|
|
|
|
|
+ props: {
|
|
|
|
|
+ /** 指标名称 */
|
|
|
|
|
+ label: { type: String, default: '' },
|
|
|
|
|
+ /** 指标数值 */
|
|
|
|
|
+ value: { type: [String, Number], default: '' },
|
|
|
|
|
+ /** Element UI 图标 class,如 el-icon-user */
|
|
|
|
|
+ icon: { type: String, default: '' },
|
|
|
|
|
+ /** 语义色:primary/success/warning/danger/cyan/info(旧名自动映射) */
|
|
|
|
|
+ color: { type: String, default: 'primary' },
|
|
|
|
|
+ /** 数值前缀,如 ¥ */
|
|
|
|
|
+ prefix: { type: String, default: '' },
|
|
|
|
|
+ /** 数值后缀,如 元 / % */
|
|
|
|
|
+ suffix: { type: String, default: '' },
|
|
|
|
|
+ /** 数字是否按千分位格式化(仅对 number 生效) */
|
|
|
|
|
+ thousand: { type: Boolean, default: false },
|
|
|
|
|
+ /** 是否可点击(开启后带 hover 反馈并派发 click) */
|
|
|
|
|
+ clickable: { type: Boolean, default: false }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ resolvedColor() {
|
|
|
|
|
+ return COLOR_MAP[String(this.color || 'primary').toLowerCase()] || 'primary'
|
|
|
|
|
+ },
|
|
|
|
|
+ displayValue() {
|
|
|
|
|
+ const v = this.value
|
|
|
|
|
+ if (v === null || v === undefined || v === '') return '-'
|
|
|
|
|
+ if (this.thousand && typeof v === 'number' && isFinite(v)) {
|
|
|
|
|
+ return v.toLocaleString('en-US')
|
|
|
|
|
+ }
|
|
|
|
|
+ return v
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ handleClick() {
|
|
|
|
|
+ if (this.clickable) this.$emit('click')
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.adm-stat-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ transition: border-color 0.2s ease;
|
|
|
|
|
+}
|
|
|
|
|
+.adm-stat-card.is-clickable {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+.adm-stat-card.is-clickable:hover {
|
|
|
|
|
+ border-color: #1677ff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.adm-stat-card__icon {
|
|
|
|
|
+ flex: 0 0 auto;
|
|
|
|
|
+ width: 44px;
|
|
|
|
|
+ height: 44px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.adm-stat-card__body {
|
|
|
|
|
+ flex: 1 1 auto;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.adm-stat-card__value {
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+ color: #262626;
|
|
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
|
|
+ font-feature-settings: 'tnum';
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+}
|
|
|
|
|
+.adm-stat-card__affix {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ color: #8c8c8c;
|
|
|
|
|
+ margin: 0 2px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.adm-stat-card__label {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #8c8c8c;
|
|
|
|
|
+ margin-top: 2px;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- 语义色(Ant Design 5 浅底 + 深字) ---- */
|
|
|
|
|
+.adm-stat-card--primary .adm-stat-card__icon {
|
|
|
|
|
+ background: #e6f4ff;
|
|
|
|
|
+ color: #1677ff;
|
|
|
|
|
+}
|
|
|
|
|
+.adm-stat-card--success .adm-stat-card__icon {
|
|
|
|
|
+ background: #f6ffed;
|
|
|
|
|
+ color: #389e0d;
|
|
|
|
|
+}
|
|
|
|
|
+.adm-stat-card--warning .adm-stat-card__icon {
|
|
|
|
|
+ background: #fffbe6;
|
|
|
|
|
+ color: #d48806;
|
|
|
|
|
+}
|
|
|
|
|
+.adm-stat-card--danger .adm-stat-card__icon {
|
|
|
|
|
+ background: #fff1f0;
|
|
|
|
|
+ color: #cf1322;
|
|
|
|
|
+}
|
|
|
|
|
+.adm-stat-card--cyan .adm-stat-card__icon {
|
|
|
|
|
+ background: #e6fffb;
|
|
|
|
|
+ color: #08979c;
|
|
|
|
|
+}
|
|
|
|
|
+.adm-stat-card--info .adm-stat-card__icon {
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ color: #595959;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|