diff --git a/.ua/index.json b/.ua/index.json index 7574392..d8f6e60 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T08:18:29.931Z", + "generatedAt": "2026-10-26T08:20:12.255Z", "repoRoot": ".", "packages": [ { @@ -2432,7 +2432,8 @@ "把 upload 放进表单,accept 限定类型、multiple 允许多选", "需要拦体积就给 maxSize(字节),限制数量给 maxCount", "被拒的文件不会进清单,原因显示在下方提示行(拖拽与点选走同一套校验)", - "onChange 拿到的 File[] 是副本,自行发请求上传(组件不内置网络层)" + "onChange 拿到的 File[] 是副本,自行发请求上传(组件不内置网络层)", + "选中的图片会带 28px 缩略图(对象 URL,随文件离场即 revoke,不会泄漏);非图片只显示文件名与体积,点 × 移除" ] }, { diff --git a/llms-full.txt b/llms-full.txt index 6136d6b..a97cb0a 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T08:18:29.931Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T08:20:12.255Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -1247,6 +1247,7 @@ upload({ multiple: true, accept: 'image/*', hint: '拖拽图片到此处', onCha 2. 需要拦体积就给 maxSize(字节),限制数量给 maxCount 3. 被拒的文件不会进清单,原因显示在下方提示行(拖拽与点选走同一套校验) 4. onChange 拿到的 File[] 是副本,自行发请求上传(组件不内置网络层) +5. 选中的图片会带 28px 缩略图(对象 URL,随文件离场即 revoke,不会泄漏);非图片只显示文件名与体积,点 × 移除 现场演示:apps/docs/demo.html#sec-upload diff --git a/packages/skin/ui/src/upload.ts b/packages/skin/ui/src/upload.ts index db8cf9a..cf14efe 100644 --- a/packages/skin/ui/src/upload.ts +++ b/packages/skin/ui/src/upload.ts @@ -36,13 +36,24 @@ export function upload(props: UploadProps): HTMLElement { sync(); }; + // —— 图片缩略图:对象 URL 按 File 缓存,文件离场即释放(否则整页存活期都在泄漏) —— + const urls = new Map(); + const thumbOf = (file: File): HTMLElement | null => { + if (!file.type.startsWith('image/')) return null; // 卫语句:非图片不缩略,只给文件名 + let url = urls.get(file); + if (!url) { url = URL.createObjectURL(file); urls.set(file, url); } + return h('img', { class: 'ua-upload-thumb', src: url, alt: '', loading: 'lazy' }); + }; + /*** 重绘清单并回传(选择/拖拽/移除都走这里,保证单一出口) ***/ const sync = (tip = '') => { + for (const [file, url] of urls) if (!files.includes(file)) { URL.revokeObjectURL(url); urls.delete(file); } // 已离场的文件先释放 listEl.replaceChildren( ...files.map((file, i) => h( 'div', { class: 'ua-upload-item' }, + thumbOf(file), h('span', { class: 'ua-upload-name' }, file.name), h('span', { class: 'ua-upload-size' }, size(file.size)), h('button', { type: 'button', class: 'ua-upload-remove', 'aria-label': `移除 ${file.name}`, onclick: () => remove(i) }, '×'), diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 106c926..5a19876 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -2303,7 +2303,8 @@ "把 upload 放进表单,accept 限定类型、multiple 允许多选", "需要拦体积就给 maxSize(字节),限制数量给 maxCount", "被拒的文件不会进清单,原因显示在下方提示行(拖拽与点选走同一套校验)", - "onChange 拿到的 File[] 是副本,自行发请求上传(组件不内置网络层)" + "onChange 拿到的 File[] 是副本,自行发请求上传(组件不内置网络层)", + "选中的图片会带 28px 缩略图(对象 URL,随文件离场即 revoke,不会泄漏);非图片只显示文件名与体积,点 × 移除" ] }, { diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index ebb1975..f9cd8da 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -2700,6 +2700,15 @@ button.ua-step-dot:hover { border-radius: var(--ua-radius-sm); font-size: var(--ua-font-size-sm); } +.ua-upload-thumb { + width: 28px; + height: 28px; + object-fit: cover; + border: 1px solid var(--ua-color-border); + border-radius: var(--ua-radius-sm); + background: var(--ua-color-surface); + flex: 0 0 auto; +} .ua-upload-name { flex: 1; min-width: 0;