iter(g10): 新增 statistic 统计数值组件
This commit is contained in:
+53
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T02:22:42.729Z",
|
"generatedAt": "2026-10-26T02:24:35.622Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -179,7 +179,8 @@
|
|||||||
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽",
|
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽",
|
||||||
"timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点",
|
"timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点",
|
||||||
"segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用",
|
"segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用",
|
||||||
"descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列"
|
"descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列",
|
||||||
|
"statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值"
|
||||||
],
|
],
|
||||||
"keyTypes": [
|
"keyTypes": [
|
||||||
"ButtonProps",
|
"ButtonProps",
|
||||||
@@ -221,7 +222,8 @@
|
|||||||
"TimelineProps",
|
"TimelineProps",
|
||||||
"SegmentedProps",
|
"SegmentedProps",
|
||||||
"DescriptionItem",
|
"DescriptionItem",
|
||||||
"DescriptionsProps"
|
"DescriptionsProps",
|
||||||
|
"StatisticProps"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')",
|
"usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')",
|
||||||
@@ -1711,6 +1713,54 @@
|
|||||||
"窄屏自动降为单列,无需在调用方写媒体查询",
|
"窄屏自动降为单列,无需在调用方写媒体查询",
|
||||||
"纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value"
|
"纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "statistic",
|
||||||
|
"title": "统计数值 Statistic",
|
||||||
|
"demo": "sec-statistic",
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"n": "title",
|
||||||
|
"t": "string",
|
||||||
|
"d": "指标名(小字灰标题);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "value",
|
||||||
|
"t": "Signal<number>",
|
||||||
|
"r": 1,
|
||||||
|
"d": "数值信号:变化时从当前显示值滚动到新值,首跑即入场动画"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "precision",
|
||||||
|
"t": "number",
|
||||||
|
"d": "小数位,千分位分隔自动带;缺省 0",
|
||||||
|
"df": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "prefix",
|
||||||
|
"t": "string",
|
||||||
|
"d": "数字前缀(如 ¥);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "suffix",
|
||||||
|
"t": "string",
|
||||||
|
"d": "数字后缀(如 元 / %);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "trend",
|
||||||
|
"t": "'up' | 'down'",
|
||||||
|
"d": "环比方向:up 红 / down 绿(A 股涨红跌绿口径);缺省不渲染"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "const gmv = signal(128000); statistic({ title: '本月 GMV', value: gmv, prefix: '¥', precision: 2, trend: 'up' })",
|
||||||
|
"steps": [
|
||||||
|
"value 必须传信号:组件用 effect 监听,值变了自动滚动过渡,不要在外部手动改文本",
|
||||||
|
"首跑也会从 0 滚到目标值(入场动画);不想要就初始化信号为终值后在挂载后再改",
|
||||||
|
"precision 控制小数位,千分位由 toLocaleString 自动加,不要自己拼格式化",
|
||||||
|
"trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织",
|
||||||
|
"多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格",
|
||||||
|
"用户开启「减弱动效」时组件直接落终值,无需调用方判断"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
- `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核
|
- `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核
|
||||||
- `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架
|
- `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架
|
||||||
- `packages/skin/ui`(@ui-arch/ui):37 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌
|
- `packages/skin/ui`(@ui-arch/ui):38 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌
|
||||||
- `packages/design/tokens`(@ui-arch/tokens):设计令牌
|
- `packages/design/tokens`(@ui-arch/tokens):设计令牌
|
||||||
- `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private)
|
- `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private)
|
||||||
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
|
- `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ ui-arch-mvp/
|
|||||||
│ │ ├── table-core/ # 表格状态内核
|
│ │ ├── table-core/ # 表格状态内核
|
||||||
│ │ └── frame/ # 信号 + hyperscript 微框架
|
│ │ └── frame/ # 信号 + hyperscript 微框架
|
||||||
│ ├── skin/ # 皮肤层(DOM 组件,可发布)
|
│ ├── skin/ # 皮肤层(DOM 组件,可发布)
|
||||||
│ │ └── ui/ # 37 个组件 + ui.css
|
│ │ └── ui/ # 38 个组件 + ui.css
|
||||||
│ ├── design/ # 资产层(可发布)
|
│ ├── design/ # 资产层(可发布)
|
||||||
│ │ └── tokens/ # --ua-* 设计令牌
|
│ │ └── tokens/ # --ua-* 设计令牌
|
||||||
│ └── tools/ # 工程层(private,不发布)
|
│ └── tools/ # 工程层(private,不发布)
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ npm run demo # 打开演示:apps/demo/dist/demo.html
|
|||||||
npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt
|
npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## 组件(共 37 个)
|
## 组件(共 38 个)
|
||||||
|
|
||||||
<!-- ua:components -->
|
<!-- ua:components -->
|
||||||
accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · descriptions · divider · drawer · empty · form · image · inputNumber · menu · modal · pagination · popover · progress · radioGroup · rate · segmented · select · skeleton · slider · spinner · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip
|
accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · descriptions · divider · drawer · empty · form · image · inputNumber · menu · modal · pagination · popover · progress · radioGroup · rate · segmented · select · skeleton · slider · spinner · statistic · stepper · switchButton · tabs · tag · textField · textarea · timeline · toast · tooltip
|
||||||
<!-- /ua:components -->
|
<!-- /ua:components -->
|
||||||
|
|
||||||
分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / segmented / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge / empty)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / descriptions / tag / avatar / card)、其他(divider / timeline)。
|
分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / segmented / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge / empty / statistic)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / descriptions / tag / avatar / card)、其他(divider / timeline)。
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ accordion · alert · avatar · badge · breadcrumb · button · card · checkbo
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) |
|
| `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) |
|
||||||
| `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) |
|
| `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) |
|
||||||
| `packages/skin/ui` | @ui-arch/ui:37 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
|
| `packages/skin/ui` | @ui-arch/ui:38 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
|
||||||
| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) |
|
| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) |
|
||||||
| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server |
|
| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server |
|
||||||
| `apps/demo` | 交互演示(esbuild 打包成单文件) |
|
| `apps/demo` | 交互演示(esbuild 打包成单文件) |
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
slider,
|
slider,
|
||||||
spinner,
|
spinner,
|
||||||
stepper,
|
stepper,
|
||||||
|
statistic,
|
||||||
switchButton,
|
switchButton,
|
||||||
tabs,
|
tabs,
|
||||||
tag,
|
tag,
|
||||||
@@ -154,6 +155,10 @@ const menuPick = signal('');
|
|||||||
// —— 禁用态演示:开关信号同时绑定 switch 的 checked 与 checkbox 的 disabled ——
|
// —— 禁用态演示:开关信号同时绑定 switch 的 checked 与 checkbox 的 disabled ——
|
||||||
const locked = signal(true);
|
const locked = signal(true);
|
||||||
|
|
||||||
|
// —— 统计数值:两个指标共用同一组按钮刷新,演示滚动过渡 ——
|
||||||
|
const gmv = signal(128000);
|
||||||
|
const orders = signal(2048);
|
||||||
|
|
||||||
// —— 分段控制器:两个实例共用同一信号,演示信号驱动同步与整组禁用 ——
|
// —— 分段控制器:两个实例共用同一信号,演示信号驱动同步与整组禁用 ——
|
||||||
const range = signal('week');
|
const range = signal('week');
|
||||||
|
|
||||||
@@ -449,6 +454,25 @@ const content = h(
|
|||||||
button({ label: '导出', variant: 'ghost' }),
|
button({ label: '导出', variant: 'ghost' }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
section(
|
||||||
|
'sec-statistic',
|
||||||
|
'统计数值',
|
||||||
|
'statistic:大号等宽数字 + 前后缀 + 环比趋势;value 变化时 400ms 缓出滚动(减弱动效偏好下直落终值)。',
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ class: 'row' },
|
||||||
|
statistic({ title: '本月 GMV', value: gmv, prefix: '¥', precision: 2, trend: 'up' }),
|
||||||
|
statistic({ title: '订单数', value: orders, suffix: '单', trend: 'down' }),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ class: 'row' },
|
||||||
|
button({ label: '刷新数据', variant: 'ghost', onClick: () => {
|
||||||
|
gmv(gmv() + Math.round(Math.random() * 20000 - 8000)); // 随机涨跌,演示双向滚动
|
||||||
|
orders(orders() + Math.round(Math.random() * 200 - 80));
|
||||||
|
} }),
|
||||||
|
),
|
||||||
|
),
|
||||||
section(
|
section(
|
||||||
'sec-descriptions',
|
'sec-descriptions',
|
||||||
'描述列表',
|
'描述列表',
|
||||||
|
|||||||
+30
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-26T02:22:42.729Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-26T02:24:35.622Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -977,6 +977,34 @@ descriptions({ title: '订单信息', column: 2, bordered: true, items: [{ label
|
|||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-descriptions
|
现场演示:apps/docs/demo.html#sec-descriptions
|
||||||
|
|
||||||
|
### statistic — 统计数值 Statistic
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
- title:string —— 指标名(小字灰标题);缺省不渲染
|
||||||
|
- value(必填):Signal<number> —— 数值信号:变化时从当前显示值滚动到新值,首跑即入场动画
|
||||||
|
- precision(默认 0):number —— 小数位,千分位分隔自动带;缺省 0
|
||||||
|
- prefix:string —— 数字前缀(如 ¥);缺省不渲染
|
||||||
|
- suffix:string —— 数字后缀(如 元 / %);缺省不渲染
|
||||||
|
- trend:'up' | 'down' —— 环比方向:up 红 / down 绿(A 股涨红跌绿口径);缺省不渲染
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
const gmv = signal(128000); statistic({ title: '本月 GMV', value: gmv, prefix: '¥', precision: 2, trend: 'up' })
|
||||||
|
```
|
||||||
|
|
||||||
|
手把手:
|
||||||
|
|
||||||
|
1. value 必须传信号:组件用 effect 监听,值变了自动滚动过渡,不要在外部手动改文本
|
||||||
|
2. 首跑也会从 0 滚到目标值(入场动画);不想要就初始化信号为终值后在挂载后再改
|
||||||
|
3. precision 控制小数位,千分位由 toLocaleString 自动加,不要自己拼格式化
|
||||||
|
4. trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织
|
||||||
|
5. 多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格
|
||||||
|
6. 用户开启「减弱动效」时组件直接落终值,无需调用方判断
|
||||||
|
|
||||||
|
现场演示:apps/docs/demo.html#sec-statistic
|
||||||
|
|
||||||
API 速览:
|
API 速览:
|
||||||
|
|
||||||
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
||||||
@@ -1017,6 +1045,7 @@ API 速览:
|
|||||||
- timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点
|
- timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点
|
||||||
- segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用
|
- segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用
|
||||||
- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列
|
- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列
|
||||||
|
- statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值
|
||||||
|
|
||||||
一行用法:
|
一行用法:
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
- timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点
|
- timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点
|
||||||
- segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用
|
- segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用
|
||||||
- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列
|
- descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列
|
||||||
|
- statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值
|
||||||
- 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')`
|
- 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')`
|
||||||
- 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
- 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
||||||
- 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
- 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export { rate } from './rate.js';
|
|||||||
export { empty } from './empty.js';
|
export { empty } from './empty.js';
|
||||||
export { divider } from './divider.js';
|
export { divider } from './divider.js';
|
||||||
export { descriptions } from './descriptions.js';
|
export { descriptions } from './descriptions.js';
|
||||||
|
export { statistic } from './statistic.js';
|
||||||
export { timeline } from './timeline.js';
|
export { timeline } from './timeline.js';
|
||||||
export { modal } from './modal.js';
|
export { modal } from './modal.js';
|
||||||
export { drawer } from './drawer.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; // 分组标题;缺省不渲染
|
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 }; // 再导出常用内核类型,方便调用方
|
export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方
|
||||||
|
|||||||
@@ -50,7 +50,8 @@
|
|||||||
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽",
|
"image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽",
|
||||||
"timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点",
|
"timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点",
|
||||||
"segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用",
|
"segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用",
|
||||||
"descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列"
|
"descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列",
|
||||||
|
"statistic(props):统计数值,标题 + 大号数字 + 前后缀 + 环比趋势,value 变化时 rAF 滚动过渡(400ms easeOutCubic),减弱动效偏好下直落终值"
|
||||||
],
|
],
|
||||||
"keyTypes": [
|
"keyTypes": [
|
||||||
"ButtonProps",
|
"ButtonProps",
|
||||||
@@ -92,7 +93,8 @@
|
|||||||
"TimelineProps",
|
"TimelineProps",
|
||||||
"SegmentedProps",
|
"SegmentedProps",
|
||||||
"DescriptionItem",
|
"DescriptionItem",
|
||||||
"DescriptionsProps"
|
"DescriptionsProps",
|
||||||
|
"StatisticProps"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')",
|
"usage": "const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')",
|
||||||
@@ -1582,6 +1584,54 @@
|
|||||||
"窄屏自动降为单列,无需在调用方写媒体查询",
|
"窄屏自动降为单列,无需在调用方写媒体查询",
|
||||||
"纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value"
|
"纯展示组件,无状态无回调:值变了由调用方重新渲染或传函数式 value"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "statistic",
|
||||||
|
"title": "统计数值 Statistic",
|
||||||
|
"demo": "sec-statistic",
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"n": "title",
|
||||||
|
"t": "string",
|
||||||
|
"d": "指标名(小字灰标题);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "value",
|
||||||
|
"t": "Signal<number>",
|
||||||
|
"r": 1,
|
||||||
|
"d": "数值信号:变化时从当前显示值滚动到新值,首跑即入场动画"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "precision",
|
||||||
|
"t": "number",
|
||||||
|
"d": "小数位,千分位分隔自动带;缺省 0",
|
||||||
|
"df": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "prefix",
|
||||||
|
"t": "string",
|
||||||
|
"d": "数字前缀(如 ¥);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "suffix",
|
||||||
|
"t": "string",
|
||||||
|
"d": "数字后缀(如 元 / %);缺省不渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "trend",
|
||||||
|
"t": "'up' | 'down'",
|
||||||
|
"d": "环比方向:up 红 / down 绿(A 股涨红跌绿口径);缺省不渲染"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "const gmv = signal(128000); statistic({ title: '本月 GMV', value: gmv, prefix: '¥', precision: 2, trend: 'up' })",
|
||||||
|
"steps": [
|
||||||
|
"value 必须传信号:组件用 effect 监听,值变了自动滚动过渡,不要在外部手动改文本",
|
||||||
|
"首跑也会从 0 滚到目标值(入场动画);不想要就初始化信号为终值后在挂载后再改",
|
||||||
|
"precision 控制小数位,千分位由 toLocaleString 自动加,不要自己拼格式化",
|
||||||
|
"trend 只表达方向与颜色(涨红跌绿),数值本身与环比幅度仍由调用方组织",
|
||||||
|
"多条指标并排时用 descriptions 或 flex 容器排列,本组件不自带网格",
|
||||||
|
"用户开启「减弱动效」时组件直接落终值,无需调用方判断"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1880,3 +1880,45 @@ button.ua-step-dot:hover {
|
|||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* —— 统计数值:大号等宽数字 + 前后缀 + 环比趋势,涨红跌绿 —— */
|
||||||
|
.ua-stat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.ua-stat-title {
|
||||||
|
color: var(--ua-color-text-muted);
|
||||||
|
font-size: var(--ua-font-size-sm);
|
||||||
|
}
|
||||||
|
.ua-stat-value {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
.ua-stat-num {
|
||||||
|
color: var(--ua-color-text);
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.ua-stat-affix {
|
||||||
|
color: var(--ua-color-text-muted);
|
||||||
|
font-size: var(--ua-font-size-sm);
|
||||||
|
}
|
||||||
|
.ua-stat-trend {
|
||||||
|
font-size: var(--ua-font-size-sm);
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
.ua-stat-trend.is-up {
|
||||||
|
color: var(--ua-color-danger);
|
||||||
|
}
|
||||||
|
.ua-stat-trend.is-down {
|
||||||
|
color: var(--ua-color-success);
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ua-stat-num {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user