iter(g146): 进度条支持环形形态

This commit is contained in:
HIsheR
2026-10-26 15:43:31 +08:00
parent adc69fa880
commit 2adae6e29e
8 changed files with 93 additions and 5 deletions
+33 -1
View File
@@ -1,7 +1,8 @@
import { effect, h } from '@ui-arch/frame';
import type { Signal } from '@ui-arch/frame';
import type { ProgressProps } from './types.js';
/*** 进度条:value 信号驱动 0-100 定量态,缺省 value 则退化为不确定态(无限往复);满格切 success 色并停条纹 ***/
/*** 进度条:value 信号驱动 0-100 定量态(缺省则不确定态),type='circle' 且传 value 时出环形;满格切 success 色 ***/
export function progress(props: ProgressProps = {}): HTMLElement {
// —— 状态集中声明 ——
const value = props.value; // 可选:不传即不确定态
@@ -17,6 +18,7 @@ export function progress(props: ProgressProps = {}): HTMLElement {
fill.classList.add('ua-indeterminate'); // 宽度与往复动画交给 CSS
return h('div', { class: cls, role: 'progressbar', 'aria-label': label ?? '进行中' }, track);
}
if (props.type === 'circle') return ring(value, label, variant, withText); // 环形:SVG 版
effect(() => {
const v = clamp(value()); // 依赖收集:信号变化自动写回
fill.style.width = `${v}%`;
@@ -43,3 +45,33 @@ function clamp(v: number): number {
if (Number.isNaN(v)) return 0; // 卫语句:非法值归零
return Math.min(100, Math.max(0, v));
}
/*** 环形进度:SVG 双圆用 dashoffset 表达弧长(必须 createElementNS,h() 造不出可渲染的 SVG) ***/
function ring(value: Signal<number>, label: string | undefined, variant: string, withText: boolean): HTMLElement {
const NS = 'http://www.w3.org/2000/svg'; // SVG 命名空间
const R = 26; // 半径:viewBox 64 两侧留出线宽
const C = 2 * Math.PI * R; // 周长:dasharray 的基准长度
const svg = document.createElementNS(NS, 'svg');
svg.setAttribute('viewBox', '0 0 64 64');
svg.setAttribute('class', `ua-progress-ring${variant === 'default' ? '' : ` is-${variant}`}`);
svg.setAttribute('role', 'img');
const base = { cx: '32', cy: '32', r: String(R), fill: 'none', 'stroke-width': '6' }; // 两圆共用几何
const track = document.createElementNS(NS, 'circle');
const arc = document.createElementNS(NS, 'circle');
for (const [k, v] of Object.entries({ ...base, class: 'ua-progress-ring-track' })) track.setAttribute(k, v);
for (const [k, v] of Object.entries({ ...base, class: 'ua-progress-ring-arc', 'stroke-dasharray': String(C), transform: 'rotate(-90 32 32)' })) arc.setAttribute(k, v); // 从 12 点起画
svg.append(track, arc);
const txt = document.createElementNS(NS, 'text');
txt.setAttribute('x', '32');
txt.setAttribute('y', '36');
txt.setAttribute('text-anchor', 'middle');
txt.setAttribute('class', 'ua-progress-ring-text');
if (withText) svg.append(txt);
effect(() => {
const v = clamp(value());
arc.setAttribute('stroke-dashoffset', String(C * (1 - v / 100))); // 弧长按比例收缩
txt.textContent = `${Math.round(v)}%`;
svg.setAttribute('aria-label', `${label ?? '进度'} ${Math.round(v)}%`); // 环形没有原生 progressbar,名称自己拼
});
return h('div', { class: 'ua-progress ua-progress-circle' }, svg as unknown as HTMLElement) as HTMLElement;
}
+1
View File
@@ -332,6 +332,7 @@ export interface SpinnerProps {
/*** 进度条入参 ***/
export interface ProgressProps {
value?: Signal<number>; // 当前进度 0-100 的信号;不传即不确定态(未知时长的加载)
type?: 'line' | 'circle'; // 形态:线性条或环形(环形需同时传 value);缺省 line
showText?: boolean; // 是否右侧显示百分比;缺省 true(不确定态忽略)
label?: string; // 可访问名,如「上传进度」;缺省不确定态念「进行中」,定量态无名称
variant?: 'default' | 'success' | 'warning' | 'danger'; // 填充语义色;缺省 default(满格自动切 success)
+8 -1
View File
@@ -1212,6 +1212,12 @@
"t": "Signal<number>",
"d": "当前进度 0-100 的信号;不传即不确定态(未知时长的加载)"
},
{
"n": "type",
"t": "'line' | 'circle'",
"df": "line",
"d": "形态:线性条或环形;环形需同时传 value(不确定态仍走线性条)"
},
{
"n": "showText",
"t": "boolean",
@@ -1232,7 +1238,8 @@
"steps": [
"传 value 信号给定量进度,不传则退化为往复的不确定态",
"label 写清这是什么进度,读屏会把名称与百分比一起念出来",
"失败或告警用 variant: 'danger' / 'warning',该颜色在满格时也不被 success 覆盖"
"失败或告警用 variant: 'danger' / 'warning',该颜色在满格时也不被 success 覆盖",
"看板 / 上传卡片要环形进度就传 type: 'circle'(同时必须传 value):SVG 双圆按 dashoffset 出弧,中间自动显示百分比,语义色同 variant"
]
},
{
+30
View File
@@ -4482,3 +4482,33 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); }
line-height: 14px;
white-space: nowrap;
}
/* —— 环形进度:SVG 双圆,弧长由 dashoffset 控制 —— */
.ua-progress-circle {
display: inline-flex;
}
.ua-progress-ring {
width: 64px;
height: 64px;
}
.ua-progress-ring-track {
stroke: color-mix(in srgb, var(--ua-color-border) 80%, transparent);
}
.ua-progress-ring-arc {
stroke: var(--ua-color-accent);
stroke-linecap: round;
transition: stroke-dashoffset 0.3s ease;
}
.ua-progress-ring.is-success .ua-progress-ring-arc { stroke: var(--ua-color-success); }
.ua-progress-ring.is-warning .ua-progress-ring-arc { stroke: var(--ua-color-warning); }
.ua-progress-ring.is-danger .ua-progress-ring-arc { stroke: var(--ua-color-danger); }
.ua-progress-ring-text {
fill: var(--ua-color-text);
font-size: 14px;
font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
.ua-progress-ring-arc {
transition: none;
}
}