iter(g10): 新增 statistic 统计数值组件
This commit is contained in:
@@ -31,6 +31,7 @@ export { rate } from './rate.js';
|
||||
export { empty } from './empty.js';
|
||||
export { divider } from './divider.js';
|
||||
export { descriptions } from './descriptions.js';
|
||||
export { statistic } from './statistic.js';
|
||||
export { timeline } from './timeline.js';
|
||||
export { modal } from './modal.js';
|
||||
export { drawer } from './drawer.js';
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { effect, h } from '@ui-arch/frame';
|
||||
import type { StatisticProps } from './types.js';
|
||||
|
||||
/*** 统计数值:标题 + 大号数字 + 前后缀 + 环比趋势,数值变化时 rAF 滚动过渡(400ms,easeOutCubic) ***/
|
||||
export function statistic(props: StatisticProps): HTMLElement {
|
||||
// —— 状态集中声明 ——
|
||||
const precision = props.precision ?? 0; // 小数位
|
||||
const reduce = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches; // 减弱动效偏好
|
||||
const num = h('span', { class: 'ua-stat-num' }, '0'); // 数字节点:逐帧改文本,不重建 DOM
|
||||
let shown = 0; // 当前显示中的值(动画中间态)
|
||||
let raf = 0; // 进行中的动画帧句柄,重复调用时先取消
|
||||
|
||||
/*** 格式化:千分位 + 固定小数位 ***/
|
||||
const fmt = (v: number): string => v.toLocaleString('zh-CN', { minimumFractionDigits: precision, maximumFractionDigits: precision });
|
||||
|
||||
/*** 从当前显示值滚到目标值;偏好减弱动效时直接落终值 ***/
|
||||
const roll = (to: number): void => {
|
||||
cancelAnimationFrame(raf); // 新目标打断上一段动画,避免数值打架
|
||||
if (reduce) { shown = to; num.textContent = fmt(to); return; }
|
||||
const from = shown;
|
||||
const start = performance.now();
|
||||
const step = (t: number): void => {
|
||||
const k = Math.min((t - start) / 400, 1); // 归一化进度 0→1
|
||||
shown = from + (to - from) * (1 - (1 - k) ** 3); // easeOutCubic 缓出
|
||||
num.textContent = fmt(shown);
|
||||
if (k < 1) raf = requestAnimationFrame(step);
|
||||
else shown = to; // 收尾对齐目标值,避免浮点残差
|
||||
};
|
||||
raf = requestAnimationFrame(step);
|
||||
};
|
||||
|
||||
const row = h('div', { class: 'ua-stat-value' },
|
||||
props.prefix ? h('span', { class: 'ua-stat-affix' }, props.prefix) : null,
|
||||
num,
|
||||
props.suffix ? h('span', { class: 'ua-stat-affix' }, props.suffix) : null,
|
||||
props.trend ? h('span', { class: `ua-stat-trend is-${props.trend}` }, props.trend === 'up' ? '▲' : '▼') : null);
|
||||
const view = h('div', { class: 'ua-stat' }, props.title ? h('div', { class: 'ua-stat-title' }, props.title) : null, row);
|
||||
|
||||
effect(() => roll(props.value())); // 信号变化即滚动到新值(首跑做入场动画)
|
||||
return view;
|
||||
}
|
||||
@@ -388,4 +388,14 @@ export interface DescriptionsProps {
|
||||
title?: string; // 分组标题;缺省不渲染
|
||||
}
|
||||
|
||||
/*** 统计数值入参 ***/
|
||||
export interface StatisticProps {
|
||||
title?: string; // 指标名;缺省不渲染标题行
|
||||
value: Signal<number>; // 数值信号:变化时滚动过渡到新值(首跑即入场动画)
|
||||
precision?: number; // 小数位;缺省 0
|
||||
prefix?: string; // 数字前缀(如 ¥);缺省不渲染
|
||||
suffix?: string; // 数字后缀(如 元 / %);缺省不渲染
|
||||
trend?: 'up' | 'down'; // 环比方向:up 红 / down 绿(A 股口径);缺省不渲染
|
||||
}
|
||||
|
||||
export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方
|
||||
|
||||
Reference in New Issue
Block a user