MetricCard 指标卡
数值 + 单位 + 涨跌 + 内嵌 sparkline 趋势,支持展开 series 明细。
基础用法
数据通过 props 传入,纯 SVG 渲染,无第三方图表库依赖。
配色取自 --viz-1..8 token,色盲友好。
背景 视口
12,420
3,180
6.2%
<script setup lang="ts">
import { CfMetricCard } from '@chufix-design/vue';
</script>
<template>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; width: 100%;">
<CfMetricCard label="今日 PV" value="12,420" :delta="8.4" :trend="[40, 45, 50, 52, 60, 65, 72]" />
<CfMetricCard label="活跃用户" value="3,180" :delta="-2.1" :trend="[80, 78, 82, 85, 79, 76, 73]" />
<CfMetricCard label="付费转化" value="6.2" unit="%" :delta="0.4" :trend="[50, 52, 50, 55, 56, 58, 58]" />
</div>
</template> <script setup>
import { CfMetricCard } from '@chufix-design/vue';
</script>
<template>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; width: 100%;">
<CfMetricCard label="今日 PV" value="12,420" :delta="8.4" :trend="[40, 45, 50, 52, 60, 65, 72]" />
<CfMetricCard label="活跃用户" value="3,180" :delta="-2.1" :trend="[80, 78, 82, 85, 79, 76, 73]" />
<CfMetricCard label="付费转化" value="6.2" unit="%" :delta="0.4" :trend="[50, 52, 50, 55, 56, 58, 58]" />
</div>
</template> import { CfMetricCard } from '@chufix-design/react';
export default function Demo() {
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 12, width: "100%" }}>
<CfMetricCard label="今日 PV" value="12,420" delta={8.4} trend={[40, 45, 50, 52, 60, 65, 72]} />
<CfMetricCard label="活跃用户" value="3,180" delta={-2.1} trend={[80, 78, 82, 85, 79, 76, 73]} />
<CfMetricCard label="付费转化" value="6.2" unit="%" delta={0.4} trend={[50, 52, 50, 55, 56, 58, 58]} />
</div>
</>
);
} import { CfMetricCard } from '@chufix-design/react';
export default function Demo() {
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 12, width: "100%" }}>
<CfMetricCard label="今日 PV" value="12,420" delta={8.4} trend={[40, 45, 50, 52, 60, 65, 72]} />
<CfMetricCard label="活跃用户" value="3,180" delta={-2.1} trend={[80, 78, 82, 85, 79, 76, 73]} />
<CfMetricCard label="付费转化" value="6.2" unit="%" delta={0.4} trend={[50, 52, 50, 55, 56, 58, 58]} />
</div>
</>
);
} 网格布局
配 grid-template-columns: repeat(auto-fit, minmax(...)) 自适应密度。
背景 视口
12,420
3,180
6.2%
3:42
0.18%
<script setup lang="ts">
import { CfMetricCard } from '@chufix-design/vue';
function trend(n: number, base: number, jitter: number, phase: number) {
return Array.from({ length: n }, (_, i) => Number((
base
+ Math.sin((i + phase) / 2.4) * jitter * 0.36
+ Math.cos((i + phase) / 5) * jitter * 0.18
).toFixed(3)));
}
</script>
<template>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; width: 100%;">
<CfMetricCard label="今日 PV" value="12,420" unit="" :delta="8.4" :trend="trend(20, 65, 30, 0)" />
<CfMetricCard label="活跃用户" value="3,180" :delta="-2.1" :trend="trend(20, 80, 20, 4)" />
<CfMetricCard label="付费转化" value="6.2" unit="%" :delta="0.4" :trend="trend(20, 55, 15, 8)" />
<CfMetricCard label="平均停留" value="3:42" unit="" :delta="0" :trend="trend(20, 50, 8, 12)" />
<CfMetricCard label="错误率" value="0.18" unit="%" :delta="-0.06" :trend="trend(20, 40, 25, 16)" />
</div>
</template> <script setup>
import { CfMetricCard } from '@chufix-design/vue';
function trend(n, base, jitter, phase) {
return Array.from({ length: n }, (_, i) => Number((
base
+ Math.sin((i + phase) / 2.4) * jitter * 0.36
+ Math.cos((i + phase) / 5) * jitter * 0.18
).toFixed(3)));
}
</script>
<template>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; width: 100%;">
<CfMetricCard label="今日 PV" value="12,420" unit="" :delta="8.4" :trend="trend(20, 65, 30, 0)" />
<CfMetricCard label="活跃用户" value="3,180" :delta="-2.1" :trend="trend(20, 80, 20, 4)" />
<CfMetricCard label="付费转化" value="6.2" unit="%" :delta="0.4" :trend="trend(20, 55, 15, 8)" />
<CfMetricCard label="平均停留" value="3:42" unit="" :delta="0" :trend="trend(20, 50, 8, 12)" />
<CfMetricCard label="错误率" value="0.18" unit="%" :delta="-0.06" :trend="trend(20, 40, 25, 16)" />
</div>
</template> import { CfMetricCard } from '@chufix-design/react';
export default function Demo() {
function trend(n: number, base: number, jitter: number, phase: number) {
return Array.from({ length: n }, (_, i) => Number((
base
+ Math.sin((i + phase) / 2.4) * jitter * 0.36
+ Math.cos((i + phase) / 5) * jitter * 0.18
).toFixed(3)));
}
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 12, width: "100%" }}>
<CfMetricCard label="今日 PV" value="12,420" unit="" delta={8.4} trend={trend(20, 65, 30, 0)} />
<CfMetricCard label="活跃用户" value="3,180" delta={-2.1} trend={trend(20, 80, 20, 4)} />
<CfMetricCard label="付费转化" value="6.2" unit="%" delta={0.4} trend={trend(20, 55, 15, 8)} />
<CfMetricCard label="平均停留" value="3:42" unit="" delta={0} trend={trend(20, 50, 8, 12)} />
<CfMetricCard label="错误率" value="0.18" unit="%" delta={-0.06} trend={trend(20, 40, 25, 16)} />
</div>
</>
);
} import { CfMetricCard } from '@chufix-design/react';
export default function Demo() {
function trend(n, base, jitter, phase) {
return Array.from({ length: n }, (_, i) => Number((
base
+ Math.sin((i + phase) / 2.4) * jitter * 0.36
+ Math.cos((i + phase) / 5) * jitter * 0.18
).toFixed(3)));
}
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 12, width: "100%" }}>
<CfMetricCard label="今日 PV" value="12,420" unit="" delta={8.4} trend={trend(20, 65, 30, 0)} />
<CfMetricCard label="活跃用户" value="3,180" delta={-2.1} trend={trend(20, 80, 20, 4)} />
<CfMetricCard label="付费转化" value="6.2" unit="%" delta={0.4} trend={trend(20, 55, 15, 8)} />
<CfMetricCard label="平均停留" value="3:42" unit="" delta={0} trend={trend(20, 50, 8, 12)} />
<CfMetricCard label="错误率" value="0.18" unit="%" delta={-0.06} trend={trend(20, 40, 25, 16)} />
</div>
</>
);
} series 展开明细 + 行点击下钻
传入 series 数组,卡片右上角出现 chevron 按钮;点击后向下展开渠道 / 组件 / 来源等明细行,每行可带自己的 sparkline、delta 和色板点。可用 defaultExpanded 控制初始态,或 expanded + @update:expanded 做受控开关。
每一行都是 role="button" + tabindex=0,点击或键盘 Enter / Space 触发 @series-select (payload { index, item }),最适合”点 KPI 行 → 跳到下钻页面 / 打开 Drawer”的联动。
背景 视口
12,060 万
点 chevron 展开,点行下钻
318K
无 series 时不渲染 chevron
点击渠道 尚未点击
<script setup lang="ts">
import { ref } from 'vue';
import { CfMetricCard, CfTag, toast } from '@chufix-design/vue';
const channels = [
{ label: '官网', value: '4,820', suffix: ' 万', delta: 8.2, trend: [120, 140, 158, 162, 180, 200, 220], color: 'var(--viz-1, oklch(64% 0.16 263))' },
{ label: '门店', value: '3,140', suffix: ' 万', delta: 2.4, trend: [80, 86, 92, 96, 100, 108, 112], color: 'var(--viz-2, oklch(70% 0.13 175))' },
{ label: 'App', value: '2,680', suffix: ' 万', delta: 14.6, trend: [60, 70, 88, 102, 118, 130, 142], color: 'var(--viz-3, oklch(74% 0.16 80))' },
{ label: '分销', value: '1,420', suffix: ' 万', delta: -4.1, trend: [80, 76, 72, 68, 64, 60, 58], color: 'var(--viz-4, oklch(64% 0.18 30))' },
];
const lastSelected = ref<string>('');
function onSelect(p: { index: number; item: { label: string; value: string | number } }) {
lastSelected.value = `${p.item.label} (#${p.index + 1}) → ${p.item.value}`;
toast({ type: 'info', message: `点击 ${p.item.label}` });
}
</script>
<template>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; max-width: 640px;">
<CfMetricCard
label="本月营收"
value="12,060"
suffix=" 万"
:delta="6.4"
:trend="[820, 880, 920, 1020, 1140, 1180, 1206]"
hint="点 chevron 展开,点行下钻"
:series="channels"
default-expanded
@series-select="onSelect"
/>
<CfMetricCard
label="DAU"
value="318"
suffix="K"
:delta="3.2"
:trend="[260, 268, 275, 282, 296, 308, 318]"
hint="无 series 时不渲染 chevron"
/>
</div>
<p style="margin-top: 8px; font-size: 12px;">
<CfTag tone="info" size="sm">点击渠道</CfTag>
{{ lastSelected || '尚未点击' }}
</p>
</template> <script setup>
import { ref } from 'vue';
import { CfMetricCard, CfTag, toast } from '@chufix-design/vue';
const channels = [
{ label: '官网', value: '4,820', suffix: ' 万', delta: 8.2, trend: [120, 140, 158, 162, 180, 200, 220], color: 'var(--viz-1, oklch(64% 0.16 263))' },
{ label: '门店', value: '3,140', suffix: ' 万', delta: 2.4, trend: [80, 86, 92, 96, 100, 108, 112], color: 'var(--viz-2, oklch(70% 0.13 175))' },
{ label: 'App', value: '2,680', suffix: ' 万', delta: 14.6, trend: [60, 70, 88, 102, 118, 130, 142], color: 'var(--viz-3, oklch(74% 0.16 80))' },
{ label: '分销', value: '1,420', suffix: ' 万', delta: -4.1, trend: [80, 76, 72, 68, 64, 60, 58], color: 'var(--viz-4, oklch(64% 0.18 30))' },
];
const lastSelected = ref<string>('');
function onSelect(p: { index: number; item: { label: string; value: string | number } }) {
lastSelected.value = `${p.item.label} (#${p.index + 1}) → ${p.item.value}`;
toast({ type: 'info', message: `点击 ${p.item.label}` });
}
</script>
<template>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; max-width: 640px;">
<CfMetricCard
label="本月营收"
value="12,060"
suffix=" 万"
:delta="6.4"
:trend="[820, 880, 920, 1020, 1140, 1180, 1206]"
hint="点 chevron 展开,点行下钻"
:series="channels"
default-expanded
@series-select="onSelect"
/>
<CfMetricCard
label="DAU"
value="318"
suffix="K"
:delta="3.2"
:trend="[260, 268, 275, 282, 296, 308, 318]"
hint="无 series 时不渲染 chevron"
/>
</div>
<p style="margin-top: 8px; font-size: 12px;">
<CfTag tone="info" size="sm">点击渠道</CfTag>
{{ lastSelected || '尚未点击' }}
</p>
</template> import { CfMetricCard } from '@chufix-design/react';
export default function Demo() {
const channels = [
{ label: '官网', value: '4,820', suffix: ' 万', delta: 8.2, trend: [120, 140, 158, 162, 180, 200, 220], color: 'var(--viz-1, oklch(64% 0.16 263))' },
{ label: '门店', value: '3,140', suffix: ' 万', delta: 2.4, trend: [80, 86, 92, 96, 100, 108, 112], color: 'var(--viz-2, oklch(70% 0.13 175))' },
{ label: 'App', value: '2,680', suffix: ' 万', delta: 14.6, trend: [60, 70, 88, 102, 118, 130, 142], color: 'var(--viz-3, oklch(74% 0.16 80))' },
{ label: '分销', value: '1,420', suffix: ' 万', delta: -4.1, trend: [80, 76, 72, 68, 64, 60, 58], color: 'var(--viz-4, oklch(64% 0.18 30))' },
];
return (
<>
<CfMetricCard label="本月营收" value="12,060" delta={6.4} series={channels} />
</>
);
} import { CfMetricCard } from '@chufix-design/react';
export default function Demo() {
const channels = [
{ label: '官网', value: '4,820', suffix: ' 万', delta: 8.2, trend: [120, 140, 158, 162, 180, 200, 220], color: 'var(--viz-1, oklch(64% 0.16 263))' },
{ label: '门店', value: '3,140', suffix: ' 万', delta: 2.4, trend: [80, 86, 92, 96, 100, 108, 112], color: 'var(--viz-2, oklch(70% 0.13 175))' },
{ label: 'App', value: '2,680', suffix: ' 万', delta: 14.6, trend: [60, 70, 88, 102, 118, 130, 142], color: 'var(--viz-3, oklch(74% 0.16 80))' },
{ label: '分销', value: '1,420', suffix: ' 万', delta: -4.1, trend: [80, 76, 72, 68, 64, 60, 58], color: 'var(--viz-4, oklch(64% 0.18 30))' },
];
return (
<>
<CfMetricCard label="本月营收" value="12,060" delta={6.4} series={channels} />
</>
);
} API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | — | 顶部小标签 |
value | string | number | — | 主数值 |
prefix | string | — | 数值前缀(如货币符号 $) |
suffix | string | — | 数值后缀(与 unit 同义,dashboard 风格) |
unit | string | — | 单位(数值后) |
hint | string | — | 数值下方的辅助说明小字 |
delta | number | — | 涨跌百分比,正负染色 |
trend | number[] | 'up' | 'down' | 'flat' | — | 内嵌 Sparkline 数据;或传 'up'/'down'/'flat' 显示对应趋势图标 |
deltaFn | (delta: number) => string | +N% | 自定义 delta 文案格式化(如绝对值、保留小数位等) |
series | MetricSeriesItem[] | — | 展开后展示的明细行 |
expandable | boolean | true | 有 series 时是否显示 chevron |
defaultExpanded | boolean | false | 非受控初始态 |
expanded | boolean | — | 受控展开标志,配合 @update:expanded (Vue) / onExpandedChange (React) |
ariaLabel | string | — | 透传给根元素的 aria-label |
事件
| Vue 事件 | React 回调 | payload | 说明 |
|---|---|---|---|
update:expanded | onExpandedChange | boolean | 受控开关 |
expand | — | { expanded } | 展开 / 收起触发 |
series-select | onSeriesSelect | { index, item } | 明细行点击 / 键盘 Enter / Space |
类型
interface MetricSeriesItem {
label: string;
value: string | number;
prefix?: string;
suffix?: string;
delta?: number;
/** 内嵌迷你 sparkline。 */
trend?: number[];
/** 行首色板点的颜色覆盖。 */
color?: string;
}
反馈与讨论
MetricCard 指标卡 的讨论