iter(g95): 统计数值支持倒计时模式

This commit is contained in:
HIsheR
2026-10-26 13:14:05 +08:00
parent 5e81cfe42b
commit 7e0612ef47
7 changed files with 57 additions and 12 deletions
+8
View File
@@ -1116,6 +1116,14 @@
"acceptance": "recipes.md 新增「配方 5:数据表 + 独立分页器 + 导出」,覆盖同一 page 信号双绑、exportable 导全量行与当页的区别、拖拽只对数字列宽生效、rowKey 记账;npm run docs 后 llms-full.txt 出现该配方;npm run check 全绿",
"notes": "自补:文档完善;只写用法不碰实现,配方随 docs 注入 llms-full",
"status": "done"
},
{
"id": "g95",
"title": "统计数值支持倒计时模式",
"motivation": "活动剩余时间这类需求只能调用方自己 setInterval 改信号再塞进 statistic,还得自己管停表;组件本身只认一个数值信号,秒级刷新没有落脚点",
"acceptance": "StatisticProps 新增 countdown(目标时间戳毫秒),传了即每秒显示 DD 天 HH:MM:SS(不足一天省天数),value 转为可选;到点或组件被移除后自动 clearInterval,不留常驻定时器;倒计时不走滚动动画;文档与演示同步;npm run check 全绿",
"notes": "自补:功能补齐;停表条件放在 tick 内自检 isConnected,无需额外卸载钩子",
"status": "open"
}
]
}
+9 -3
View File
@@ -1,6 +1,6 @@
{
"$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-26T05:12:11.001Z",
"generatedAt": "2026-10-26T05:13:47.565Z",
"repoRoot": ".",
"packages": [
{
@@ -1953,7 +1953,12 @@
"n": "value",
"t": "Signal<number>",
"r": 1,
"d": "数值信号:变化时从当前显示值滚动到新值,首跑即入场动画"
"d": "数值信号:变化时滚动过渡到新值;倒计时模式可不传"
},
{
"n": "countdown",
"t": "number",
"d": "目标时间戳(毫秒):传了即倒计时模式,每秒显示 DD 天 HH:MM:SS,忽略 value"
},
{
"n": "precision",
@@ -1984,7 +1989,8 @@
"precision 控制小数位,千分位由 toLocaleString 自动加,不要自己拼格式化",
"trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织",
"多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格",
"用户开启「减弱动效」时组件直接落终值,无需调用方判断"
"用户开启「减弱动效」时组件直接落终值,无需调用方判断",
"做倒计时(活动剩余时间等)传 countdown: 目标时间戳,组件每秒刷新并在到点/卸载时自动停表"
]
},
{
+2 -1
View File
@@ -552,12 +552,13 @@ const content = h(
section(
'sec-statistic',
'统计数值',
'statistic:大号等宽数字 + 前后缀 + 环比趋势;value 变化时 400ms 缓出滚动(减弱动效偏好下直落终值)。',
'statistic:大号等宽数字 + 前后缀 + 环比趋势;value 变化时 400ms 缓出滚动(减弱动效偏好下直落终值);传 countdown 目标时间戳即变倒计时,到点自动停表。',
h(
'div',
{ class: 'row' },
statistic({ title: '本月 GMV', value: gmv, prefix: '¥', precision: 2, trend: 'up' }),
statistic({ title: '订单数', value: orders, suffix: '单', trend: 'down' }),
statistic({ title: '活动倒计时', countdown: Date.now() + 95 * 60 * 1000 }),
),
h(
'div',
+4 -2
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-26T05:12:11.001Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 自动生成于 2026-10-26T05:13:47.565Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
@@ -1052,7 +1052,8 @@ descriptions({ title: '订单信息', column: 2, bordered: true, items: [{ label
参数:
- title:string —— 指标名(小字灰标题);缺省不渲染
- value(必填):Signal<number> —— 数值信号:变化时从当前显示值滚动到新值,首跑即入场动画
- value(必填):Signal<number> —— 数值信号:变化时滚动过渡到新值;倒计时模式可不传
- countdown:number —— 目标时间戳(毫秒):传了即倒计时模式,每秒显示 DD 天 HH:MM:SS,忽略 value
- precision(默认 0):number —— 小数位,千分位分隔自动带;缺省 0
- prefix:string —— 数字前缀(如 ¥);缺省不渲染
- suffix:string —— 数字后缀(如 元 / %);缺省不渲染
@@ -1072,6 +1073,7 @@ const gmv = signal(128000); statistic({ title: '本月 GMV', value: gmv, prefix:
4. trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织
5. 多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格
6. 用户开启「减弱动效」时组件直接落终值,无需调用方判断
7. 做倒计时(活动剩余时间等)传 countdown: 目标时间戳,组件每秒刷新并在到点/卸载时自动停表
现场演示:apps/docs/demo.html#sec-statistic
+24 -3
View File
@@ -1,7 +1,17 @@
import { effect, h } from '@ui-arch/frame';
import { effect, h, signal } from '@ui-arch/frame';
import type { StatisticProps } from './types.js';
/*** 统计数值:标题 + 大号数字 + 前后缀 + 环比趋势,数值变化时 rAF 滚动过渡(400ms,easeOutCubic) ***/
/*** 剩余毫秒 → DD 天 HH:MM:SS(不足一天省略天数,保持等宽跳动不抖动) ***/
function fmtLeft(ms: number): string {
const total = Math.floor(ms / 1000);
const d = Math.floor(total / 86400);
const hh = String(Math.floor((total % 86400) / 3600)).padStart(2, '0');
const mm = String(Math.floor((total % 3600) / 60)).padStart(2, '0');
const ss = String(total % 60).padStart(2, '0');
return d > 0 ? `${d} 天 ${hh}:${mm}:${ss}` : `${hh}:${mm}:${ss}`;
}
/*** 统计数值:标题 + 大号数字 + 前后缀 + 环比趋势;数值滚动过渡,传 countdown 则改为每秒倒计时 ***/
export function statistic(props: StatisticProps): HTMLElement {
// —— 状态集中声明 ——
const precision = props.precision ?? 0; // 小数位
@@ -36,6 +46,17 @@ export function statistic(props: StatisticProps): HTMLElement {
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())); // 信号变化即滚动到新值(首跑做入场动画)
const target = props.countdown;
if (target) {
const left = signal(Math.max(0, target - Date.now())); // 剩余毫秒
const timer = window.setInterval(() => {
const ms = Math.max(0, target - Date.now());
left(ms);
if (!ms || !view.isConnected) window.clearInterval(timer); // 到点或已卸载即停表,不留常驻定时器
}, 1000);
effect(() => { num.textContent = fmtLeft(left()); }); // 倒计时模式不走滚动动画,每秒直落
} else if (props.value) {
effect(() => roll(props.value!())); // 信号变化即滚动到新值(首跑做入场动画)
}
return view;
}
+2 -1
View File
@@ -419,7 +419,8 @@ export interface DescriptionsProps {
/*** 统计数值入参 ***/
export interface StatisticProps {
title?: string; // 指标名;缺省不渲染标题行
value: Signal<number>; // 数值信号:变化时滚动过渡到新值(首跑即入场动画)
value?: Signal<number>; // 数值信号:变化时滚动过渡到新值(首跑即入场动画);倒计时模式可不传
countdown?: number; // 目标时间戳(毫秒):传了即倒计时模式,每秒显示 DD 天 HH:MM:SS,忽略 value
precision?: number; // 小数位;缺省 0
prefix?: string; // 数字前缀(如 ¥);缺省不渲染
suffix?: string; // 数字后缀(如 元 / %);缺省不渲染
+8 -2
View File
@@ -1824,7 +1824,12 @@
"n": "value",
"t": "Signal<number>",
"r": 1,
"d": "数值信号:变化时从当前显示值滚动到新值,首跑即入场动画"
"d": "数值信号:变化时滚动过渡到新值;倒计时模式可不传"
},
{
"n": "countdown",
"t": "number",
"d": "目标时间戳(毫秒):传了即倒计时模式,每秒显示 DD 天 HH:MM:SS,忽略 value"
},
{
"n": "precision",
@@ -1855,7 +1860,8 @@
"precision 控制小数位,千分位由 toLocaleString 自动加,不要自己拼格式化",
"trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织",
"多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格",
"用户开启「减弱动效」时组件直接落终值,无需调用方判断"
"用户开启「减弱动效」时组件直接落终值,无需调用方判断",
"做倒计时(活动剩余时间等)传 countdown: 目标时间戳,组件每秒刷新并在到点/卸载时自动停表"
]
},
{