From 33979b23a0819ce5bc0cec79a694514ed5175a3a Mon Sep 17 00:00:00 2001 From: HIsheR Date: Fri, 23 Oct 2026 18:59:32 +0800 Subject: [PATCH] =?UTF-8?q?iter(g02):=20=E6=96=B0=E5=A2=9E=20image=20?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/index.json | 38 ++++++++++++++++++++++++--- AGENTS.md | 2 +- ARCHITECTURE.md | 2 +- README.md | 6 ++--- apps/demo/main.ts | 20 ++++++++++++++ llms-full.txt | 26 ++++++++++++++++++- llms.txt | 3 ++- packages/skin/ui/src/image.ts | 37 ++++++++++++++++++++++++++ packages/skin/ui/src/index.ts | 1 + packages/skin/ui/src/types.ts | 7 +++++ packages/skin/ui/ua.manifest.json | 36 ++++++++++++++++++++++++-- packages/skin/ui/ui.css | 43 +++++++++++++++++++++++++++++++ 12 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 packages/skin/ui/src/image.ts diff --git a/.ua/index.json b/.ua/index.json index 1523392..ade3b34 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T10:41:39.918Z", + "generatedAt": "2026-10-23T10:58:48.833Z", "repoRoot": ".", "packages": [ { @@ -130,7 +130,7 @@ { "name": "@ui-arch/ui", "type": "skin-dom", - "description": "DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。", + "description": "DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / image / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。", "tags": [ "components", "dom", @@ -175,7 +175,8 @@ "drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭", "form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit", "formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄", - "dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core" + "dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core", + "image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽" ], "keyTypes": [ "ButtonProps", @@ -232,6 +233,37 @@ "读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM" ], "components": [ + { + "id": "image", + "title": "图片 Image", + "demo": "sec-image", + "props": [ + { + "n": "src", + "t": "string", + "r": 1, + "d": "图片地址(缩略图与大图共用)" + }, + { + "n": "alt", + "t": "string", + "d": "替代文本;缺省空串", + "df": "''" + }, + { + "n": "width", + "t": "number", + "d": "缩略图宽度像素,高度自适应;缺省走样式默认" + } + ], + "example": "image({ src: '/a.png', alt: '架构图', width: 160 })", + "steps": [ + "基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜", + "点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图", + "宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width", + "alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它" + ] + }, { "id": "button", "title": "按钮 Button", diff --git a/AGENTS.md b/AGENTS.md index cb5f36a..402aefb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ - `packages/core/table-core`(@ui-arch/table-core):Headless 表格逻辑内核 - `packages/core/frame`(@ui-arch/frame):signal + hyperscript 微框架 -- `packages/skin/ui`(@ui-arch/ui):33 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 +- `packages/skin/ui`(@ui-arch/ui):34 个 DOM 组件(表单 / 反馈 / 浮层 / 导航 / 表格),样式只消费令牌 - `packages/design/tokens`(@ui-arch/tokens):设计令牌 - `packages/tools/cli`(@ui-arch/cli):`ua` 命令行 + MCP 注册表服务(private) - `apps/demo`(@ui-arch/demo):交互演示,产物 apps/demo/dist/demo.html diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 51517dd..95d2926 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -34,7 +34,7 @@ ui-arch-mvp/ │ │ ├── table-core/ # 表格状态内核 │ │ └── frame/ # 信号 + hyperscript 微框架 │ ├── skin/ # 皮肤层(DOM 组件,可发布) -│ │ └── ui/ # 33 个组件 + ui.css +│ │ └── ui/ # 34 个组件 + ui.css │ ├── design/ # 资产层(可发布) │ │ └── tokens/ # --ua-* 设计令牌 │ └── tools/ # 工程层(private,不发布) diff --git a/README.md b/README.md index b0c286e..f58e550 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ npm run demo # 打开演示:apps/demo/dist/demo.html npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt ``` -## 组件(共 33 个) +## 组件(共 34 个) -accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · divider · drawer · empty · form · inputNumber · menu · modal · pagination · popover · progress · radioGroup · rate · select · skeleton · slider · spinner · stepper · switchButton · tabs · tag · textField · textarea · toast · tooltip +accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · divider · drawer · empty · form · image · inputNumber · menu · modal · pagination · popover · progress · radioGroup · rate · select · skeleton · slider · spinner · stepper · switchButton · tabs · tag · textField · textarea · toast · tooltip 分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / rate / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge / empty)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion / stepper)、数据(dataTable / tag / avatar / card)、其他(divider)。 @@ -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/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) | -| `packages/skin/ui` | @ui-arch/ui:33 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | +| `packages/skin/ui` | @ui-arch/ui:34 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) | | `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) | | `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server | | `apps/demo` | 交互演示(esbuild 打包成单文件) | diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 623a8bb..b8e6f74 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -1,4 +1,12 @@ import { computed, h, mount, renderList, signal } from '@ui-arch/frame'; + +/*** 演示用内联 SVG 图片(data URI,离线可用) ***/ +const IMG_DEMO = 'data:image/svg+xml;utf8,' + encodeURIComponent( + '' + + '' + + '' + + 'IMAGE DEMO', +); import { accordion, alert, @@ -12,6 +20,7 @@ import { divider, drawer, empty, + image, form, formItem, inputNumber, @@ -384,6 +393,17 @@ const content = h( ), h('div', { class: 'row' }, rate({ value: score, disabled: true })), ), + section( + 'sec-image', + '图片', + 'image:缩略图点击 / 回车打开遮罩大图,Esc 或点击遮罩关闭,焦点归还缩略图。', + h( + 'div', + { class: 'row' }, + image({ src: IMG_DEMO, alt: '演示图一', width: 180 }), + image({ src: IMG_DEMO, alt: '演示图二', width: 120 }), + ), + ), section( 'sec-empty', '空状态', diff --git a/llms-full.txt b/llms-full.txt index 976212c..b11e5f4 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T10:14:36.972Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T10:58:48.833Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -50,6 +50,29 @@ 28 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。 +### image — 图片 Image + +参数: + +- src(必填):string —— 图片地址(缩略图与大图共用) +- alt(默认 ''):string —— 替代文本;缺省空串 +- width:number —— 缩略图宽度像素,高度自适应;缺省走样式默认 + +示例: + +``` +image({ src: '/a.png', alt: '架构图', width: 160 }) +``` + +手把手: + +1. 基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜 +2. 点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图 +3. 宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width +4. alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它 + +现场演示:apps/docs/demo.html#sec-image + ### button — 按钮 Button 参数: @@ -902,6 +925,7 @@ API 速览: - form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit - formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄 - dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core +- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽 一行用法: diff --git a/llms.txt b/llms.txt index 8def2f9..7818f1f 100644 --- a/llms.txt +++ b/llms.txt @@ -45,7 +45,7 @@ ## @ui-arch/ui (v0.1.0) - 类型: skin-dom · 目录: packages/skin/ui -- 描述: DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。 +- 描述: DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / image / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。 - API: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用 @@ -81,6 +81,7 @@ - form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit - formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄 - dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core + - image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽 - 用法: `const checked = signal(false); checkbox({ label: '同意', checked }); const open = signal(false); modal({ title: '确认', open }, '内容')` - 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号 - 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调 diff --git a/packages/skin/ui/src/image.ts b/packages/skin/ui/src/image.ts new file mode 100644 index 0000000..e8d41ce --- /dev/null +++ b/packages/skin/ui/src/image.ts @@ -0,0 +1,37 @@ +import { h } from '@ui-arch/frame'; +import type { ImageProps } from './types.js'; + +/*** 图片:缩略图展示,点击 / 回车打开遮罩大图预览(Esc 或点击遮罩关闭),样式只消费 --ua-* 令牌 ***/ +export function image(props: ImageProps): HTMLElement { + const style = props.width ? `width:${props.width}px` : ''; + return h('img', { + class: 'ua-image', + src: props.src, + alt: props.alt ?? '', + style, + tabindex: '0', // 可聚焦,键盘用户回车预览 + role: 'button', + 'aria-label': '预览图片', + onclick: () => openViewer(props.src, props.alt), + onkeydown: (e: KeyboardEvent) => e.key === 'Enter' && openViewer(props.src, props.alt), + }); +} + +/*** 打开全屏遮罩预览层;关闭时自清理、解绑键盘并归还焦点 ***/ +function openViewer(src: string, alt?: string): void { + const prev = document.activeElement as HTMLElement | null; // 焦点归还目标 + const mask = h( + 'div', + { class: 'ua-image-mask', role: 'dialog', 'aria-modal': 'true', 'aria-label': '图片预览' }, + h('img', { class: 'ua-image-full', src, alt: alt ?? '' }), + ); + const close = () => { + mask.remove(); // 卫语句式自清理:遮罩移除 + 键盘解绑 + 焦点归还 + document.removeEventListener('keydown', onKey); + prev?.focus?.(); + }; + const onKey = (e: KeyboardEvent) => e.key === 'Escape' && close(); + mask.addEventListener('click', close); + document.addEventListener('keydown', onKey); + document.body.appendChild(mask); +} diff --git a/packages/skin/ui/src/index.ts b/packages/skin/ui/src/index.ts index dd8d25b..ae400b6 100644 --- a/packages/skin/ui/src/index.ts +++ b/packages/skin/ui/src/index.ts @@ -15,6 +15,7 @@ export { card } from './card.js'; export { badge } from './badge.js'; export { tag } from './tag.js'; export { avatar } from './avatar.js'; +export { image } from './image.js'; export { alert } from './alert.js'; export { toast, showToast } from './toast.js'; export { tooltip } from './tooltip.js'; diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index c94821b..b250bf3 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -233,6 +233,13 @@ export interface TagProps { onClose?: () => void; // 关闭回调(调用方自行从数据源移除该项) } +/*** 图片入参 ***/ +export interface ImageProps { + src: string; // 图片地址(缩略图与大图共用) + alt?: string; // 替代文本;缺省空串 + width?: number; // 缩略图宽度像素,高度自适应;缺省走样式默认 +} + /*** 提示条入参 ***/ export interface AlertProps { message: string; // 提示内容 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index dd222e6..3cfad23 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1,7 +1,7 @@ { "name": "@ui-arch/ui", "type": "skin-dom", - "description": "DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。", + "description": "DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / image / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。", "tags": [ "components", "dom", @@ -46,7 +46,8 @@ "drawer(props, ...children):抽屉,open 信号驱动侧滑面板,placement 左右可配、width 可调、footer 操作区,遮罩 / × / Esc 关闭", "form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit", "formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄", - "dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core" + "dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core", + "image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽" ], "keyTypes": [ "ButtonProps", @@ -103,6 +104,37 @@ "读写状态:name() 读、name('张三') 写,界面自动同步,无需手动操作 DOM" ], "components": [ + { + "id": "image", + "title": "图片 Image", + "demo": "sec-image", + "props": [ + { + "n": "src", + "t": "string", + "r": 1, + "d": "图片地址(缩略图与大图共用)" + }, + { + "n": "alt", + "t": "string", + "d": "替代文本;缺省空串", + "df": "''" + }, + { + "n": "width", + "t": "number", + "d": "缩略图宽度像素,高度自适应;缺省走样式默认" + } + ], + "example": "image({ src: '/a.png', alt: '架构图', width: 160 })", + "steps": [ + "基础用法只传 src:渲染缩略图,hover 上浮提亮,光标变放大镜", + "点击缩略图(或聚焦后回车)打开全屏遮罩大图,Esc 或点击遮罩任意处关闭,焦点归还缩略图", + "宽度用 width(像素)控制,高度按比例自适应,容器内不撑破 max-width", + "alt 尽量传有意义的描述,读屏与图片加载失败时都依赖它" + ] + }, { "id": "button", "title": "按钮 Button", diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index bd3bf4d..2b95fdf 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -1565,3 +1565,46 @@ button.ua-step-dot:hover { transition: none !important; } } + +/* ---- image 图片(缩略图 + 遮罩大图预览) ---- */ +.ua-image { + display: inline-block; + max-width: 100%; + border-radius: var(--ua-radius-md); + cursor: zoom-in; + transition: transform 0.15s ease, box-shadow 0.15s ease; +} +.ua-image:hover, +.ua-image:focus-visible { + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14); + outline: none; +} +.ua-image-mask { + position: fixed; + inset: 0; + z-index: 1100; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.6); /* 遮罩透明度叠加,非实体色 */ + cursor: zoom-out; + animation: ua-image-in 0.18s ease; +} +.ua-image-full { + max-width: 90vw; + max-height: 90vh; + border-radius: var(--ua-radius-md); + box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16); +} +@keyframes ua-image-in { + from { opacity: 0; transform: scale(0.96); } + to { opacity: 1; transform: scale(1); } +} +@media (prefers-reduced-motion: reduce) { + .ua-image, + .ua-image-mask { + animation: none !important; + transition: none !important; + } +}