开发预览 更新于 2026-05-10

MetricCard 指标卡

数值 + 单位 + 涨跌 + 内嵌 sparkline 趋势,支持展开 series 明细。

基础用法

数据通过 props 传入,纯 SVG 渲染,无第三方图表库依赖。 配色取自 --viz-1..8 token,色盲友好。

背景 视口
今日 PV+8.4%
12,420
活跃用户-2.1%
3,180
付费转化+0.4%
6.2%
src/App.vue
<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(...)) 自适应密度。

背景 视口
今日 PV+8.4%
12,420
活跃用户-2.1%
3,180
付费转化+0.4%
6.2%
平均停留0.0%
3:42
错误率-0.1%
0.18%
src/App.vue
<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”的联动。

背景 视口
DAU+3.2%
318K

无 series 时不渲染 chevron

点击渠道 尚未点击

src/App.vue
<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

属性类型默认值说明
labelstring顶部小标签
valuestring | number主数值
prefixstring数值前缀(如货币符号 $
suffixstring数值后缀(与 unit 同义,dashboard 风格)
unitstring单位(数值后)
hintstring数值下方的辅助说明小字
deltanumber涨跌百分比,正负染色
trendnumber[] | 'up' | 'down' | 'flat'内嵌 Sparkline 数据;或传 'up'/'down'/'flat' 显示对应趋势图标
deltaFn(delta: number) => string+N%自定义 delta 文案格式化(如绝对值、保留小数位等)
seriesMetricSeriesItem[]展开后展示的明细行
expandablebooleantrue有 series 时是否显示 chevron
defaultExpandedbooleanfalse非受控初始态
expandedboolean受控展开标志,配合 @update:expanded (Vue) / onExpandedChange (React)
ariaLabelstring透传给根元素的 aria-label

事件

Vue 事件React 回调payload说明
update:expandedonExpandedChangeboolean受控开关
expand{ expanded }展开 / 收起触发
series-selectonSeriesSelect{ index, item }明细行点击 / 键盘 Enter / Space

类型

interface MetricSeriesItem {
  label: string;
  value: string | number;
  prefix?: string;
  suffix?: string;
  delta?: number;
  /** 内嵌迷你 sparkline。 */
  trend?: number[];
  /** 行首色板点的颜色覆盖。 */
  color?: string;
}

反馈与讨论

MetricCard 指标卡 的讨论

0
0 / 600
正在加载评论...