diff --git a/.ua/backlog.json b/.ua/backlog.json index 4a6849b..d0bef82 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -484,6 +484,14 @@ "acceptance": "showToast 悬停时暂停倒计时、移出后按剩余时长继续;duration 语义不变;manifest 与 demo 说明同步;npm run check 全绿", "status": "done", "notes": "自补:既有组件可用性细节" + }, + { + "id": "g16", + "title": "image 加载失败占位", + "motivation": "图片地址失效时显示破图图标,观感差且无提示;需要统一的失败占位兜底", + "acceptance": "img 加载失败自动替换为虚线占位块,fallback 可自定义文案;样式只消费 --ua-* 令牌;manifest/demo 同步;npm run check 全绿", + "status": "open", + "notes": "自补:既有组件可用性细节" } ] } diff --git a/.ua/index.json b/.ua/index.json index b400123..4d5ebe9 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T02:54:41.057Z", + "generatedAt": "2026-10-26T02:57:22.328Z", "repoRoot": ".", "packages": [ { @@ -177,7 +177,7 @@ "form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit", "formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄", "dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core", - "image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽", + "image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽,加载失败自动落占位(fallback 自定义文案)", "timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点", "segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用", "descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列", diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 5f5e3de..e67b4a6 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -435,12 +435,13 @@ const content = h( section( 'sec-image', '图片', - 'image:缩略图点击 / 回车打开遮罩大图,Esc 或点击遮罩关闭,焦点归还缩略图。', + 'image:缩略图点击 / 回车打开遮罩大图,Esc 或点击遮罩关闭,焦点归还缩略图;加载失败自动落虚线占位(第三张故意给错地址)。', h( 'div', { class: 'row' }, image({ src: IMG_DEMO, alt: '演示图一', width: 180 }), image({ src: IMG_DEMO, alt: '演示图二', width: 120 }), + image({ src: './no-such-image.png', alt: '演示图三(地址无效)', width: 120, fallback: '图片走丢了' }), ), ), section( diff --git a/llms-full.txt b/llms-full.txt index 240e0e8..ef55279 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T02:54:41.057Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T02:57:22.328Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -1042,7 +1042,7 @@ API 速览: - form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit - formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄 - dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core -- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽 +- image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽,加载失败自动落占位(fallback 自定义文案) - timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点 - segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用 - descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列 diff --git a/llms.txt b/llms.txt index 1a3b483..7353d49 100644 --- a/llms.txt +++ b/llms.txt @@ -82,7 +82,7 @@ - form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit - formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄 - dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core - - image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽 + - image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽,加载失败自动落占位(fallback 自定义文案) - timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点 - segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用 - descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列 diff --git a/packages/skin/ui/src/image.ts b/packages/skin/ui/src/image.ts index e8d41ce..f40fdf1 100644 --- a/packages/skin/ui/src/image.ts +++ b/packages/skin/ui/src/image.ts @@ -1,10 +1,10 @@ import { h } from '@ui-arch/frame'; import type { ImageProps } from './types.js'; -/*** 图片:缩略图展示,点击 / 回车打开遮罩大图预览(Esc 或点击遮罩关闭),样式只消费 --ua-* 令牌 ***/ +/*** 图片:缩略图展示,点击 / 回车打开遮罩大图预览(Esc 或点击遮罩关闭),加载失败自动落占位;样式只消费 --ua-* 令牌 ***/ export function image(props: ImageProps): HTMLElement { const style = props.width ? `width:${props.width}px` : ''; - return h('img', { + const img = h('img', { class: 'ua-image', src: props.src, alt: props.alt ?? '', @@ -15,6 +15,11 @@ export function image(props: ImageProps): HTMLElement { onclick: () => openViewer(props.src, props.alt), onkeydown: (e: KeyboardEvent) => e.key === 'Enter' && openViewer(props.src, props.alt), }); + img.addEventListener('error', () => { + const box = h('span', { class: 'ua-image-fallback', style, role: 'img', 'aria-label': props.fallback ?? '图片加载失败' }, props.fallback ?? '图片加载失败'); + img.replaceWith(box); // 失败兜底:整块替换,避免残留破图图标 + }); + return img; } /*** 打开全屏遮罩预览层;关闭时自清理、解绑键盘并归还焦点 ***/ diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index e6f0a08..66f21e4 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -250,9 +250,10 @@ export interface TagProps { /*** 图片入参 ***/ export interface ImageProps { - src: string; // 图片地址(缩略图与大图共用) - alt?: string; // 替代文本;缺省空串 - width?: number; // 缩略图宽度像素,高度自适应;缺省走样式默认 + src: string; // 图片地址(缩略图与大图共用) + alt?: string; // 替代文本;缺省空串 + width?: number; // 缩略图宽度像素,高度自适应;缺省走样式默认 + fallback?: string; // 加载失败占位文案;缺省「图片加载失败」 } /*** 提示条入参 ***/ diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 4c5f982..5f9a3ca 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -48,7 +48,7 @@ "form(props, items):表单容器,布局 + 聚合校验,全部通过才触发 onSubmit", "formItem(props, control):表单字段,label + 必填红星 + 规则校验 + 错误区,返回 { el, validate } 句柄", "dataTable(props):数据表格,粘性表头 / 行选择(全选三态)/ 排序 / 筛选 / 分页 / 空状态;loading 为真时数据行换骨架行且筛选/翻页禁用,内核为 table-core", - "image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽", + "image(props):图片,缩略图点击 / 回车开遮罩大图预览(Esc 或点击遮罩关闭),width 控制缩略宽,加载失败自动落占位(fallback 自定义文案)", "timeline(props):时间线,items 按序渲染纵向事件流(标题 / 时间 / 正文 + 语义色节点),mode 控制轴线左右,pending 追加进行中节点", "segmented(props):分段控制器,胶囊分段 + 滑动滑块,value 信号双向绑定,←→ 环形导航,整组或单项可禁用", "descriptions(props):描述列表,items(label/value/span) 铺进 column 列网格,bordered 切表格形态,窄屏自动单列", diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 69ed123..5ed6e8f 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -1590,6 +1590,18 @@ button.ua-step-dot:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14); outline: none; } +.ua-image-fallback { + display: inline-flex; /* 加载失败占位:虚线框 + 弱化文案 */ + align-items: center; + justify-content: center; + min-height: 48px; + padding: var(--ua-space-2) var(--ua-space-3); + border: 1px dashed var(--ua-color-border); + border-radius: var(--ua-radius-sm); + background: var(--ua-color-surface); + color: var(--ua-color-text-muted); + font-size: var(--ua-font-size-sm); +} .ua-image-mask { position: fixed; inset: 0;