@xwink/ui

CardList

卡片列表:布局、加载与多选手势一处收齐,每项由 #item 插槽给——写 Card 或整张自定义。插槽参数是 { item, index, itemKey, selected, active, selecting, toggle }(叫 itemKey 不叫 key:key 是 Vue 保留字,传进去会变成 vnode key)。

状态归列表、渲染归项:列表持有选中集合、高亮项与选择模式,经 provideListItemContext 逐项传下去,项组件用 useListItemContext 读本项状态、自己决定怎么画。XCard 是第一个接它的项组件,插槽里写 <XCard> 不必逐项绑定;hover 给列表统一的悬停效果,单张卡自己给了以卡片为准。容器 role=list,class / style 落在根上。

数据侧见 usePagedList,多选手势见 useCollectionSelect。

无限加载

has-more 时底部哨兵进视口发 load-more。没铺满且还有更多就接着要:列表变了、拉完一页、容器尺寸变了都主动量一次(IntersectionObserver 只在进出视口时回调,一页拉完哨兵仍在视口里不会再报);要过一页而列表原样没变就停,防空页死循环;容器在 display: none 里不拉。数据可以是同步的本地切片,不必有 loading。

首次加载(loading 且没有项)出骨架(skeleton 块数,或 #skeleton 插槽);error 独立于「没有更多」,显示重试、发 retry。

usePagedList 接无限加载,出错与重试
已加载 0 / 46

布局

  • grid 纯 CSS 宫格:按 min-item-width 自动排列数,或 columns 固定列数。
  • masonry 瀑布流:按 item-size(多是图的像素尺寸)的宽高比估高、放进最矮的一列,DOM 按列排,追加下一页时前面的不换列;卡片里固定高度的部分给 item-extra。
  • columns CSS 多栏纵向流:先排满一栏再排下一栏。

gap 行列同一个数;min-columns 给列数下限,按实测的内容宽度(已扣滚动条)排,宿主不必按边线、内边距反推卡片宽度。

瀑布流 / 宫格 / 多栏
灵感 14:3
灵感 23:4
灵感 31:1
灵感 416:9
灵感 52:3
灵感 64:3
灵感 73:4
灵感 81:1
灵感 916:9
灵感 102:3
灵感 114:3
灵感 123:4
灵感 131:1
灵感 1416:9
灵感 152:3
灵感 164:3
灵感 173:4
灵感 181:1

没有更多了

多选

selectable 打开,选中集合与选择模式是 v-model:selected(Set<string>)与 v-model:selecting。手势同 Finder 与 iOS 相册:点勾选圈或 ⌘ / Ctrl / Shift 点进入选择模式;模式里点一项加减、Shift 连选、按住一项划过去连成一段、从空白处拖框;⌘A 全选、Esc 退出。选择模式里卡片内的点击在捕获阶段拦下,宿主的「打开」不会触发。

批量操作栏由 #selection-bar 给({ count, total, selectAll, invert, exit }),组件不认识业务动作。无限加载时给 selectable-keys(全部可选的键,含没加载的):只按已加载的算,「全选」只选了一页,换筛选重新分页时后面几页的选中会被悄悄清掉。

选择模式、批量操作栏与高亮项

点卡片「打开」:无

素材 1
素材 2
素材 3
素材 4
素材 5
素材 6
素材 7
素材 8
素材 9
素材 10
素材 11
素材 12
素材 13
素材 14
素材 15
素材 16
素材 17
素材 18
素材 19
素材 20

没有更多了

分页

paginate 换成底部分页器(v-model:page、page-size、total),不再无限加载。行视图也是卡片:plain + hover="tint",换视图时选中、高亮与加载状态不用换一套。scroll 关掉时列表随外层滚。

翻页 + 行视图
0-0,共 0 条
1 / 1

文案与边界

空态、到底、出错、重试、加载中与勾选圈名取 useUiLocale().cardList,props 可覆盖;空态与到底传空串即不显示。当前不做虚拟化,项全量渲染。布局判定的纯逻辑(columnCount / masonry / estimateHeight / wantsMore 等)已导出,宿主自写列表时复用同一套。

API

Props

名称类型默认值说明
items 必填 T[]——
itemKey 必填 ItemKey<T>——
layout"columns" | "grid" | "masonry" | undefined"grid"`grid` 宫格、`masonry` 瀑布流(按 `itemSize` 估高放进最矮一列)、`columns` 多栏纵向流
minItemWidthnumber | undefined160卡片最小宽度(px):没给 `columns` 时按它排列数
columnsnumber | undefinedundefined固定列数
minColumnsnumber | undefined1至少几列(没给 `columns` 时):按实测的内容宽度排,窄到放不下也不少于它,不靠宿主反推卡片宽度
gapnumber | undefined8卡片间距(px),行列同值
itemSize((item: T) => readonly [number, number] | undefined) | undefinedundefined瀑布流估高用的内容宽高(多是图的像素尺寸);缺省按正方形
itemExtranumber | undefined0瀑布流估高时卡片里固定高度的部分(标题栏、信息栏,px)
minItemHeightnumber | undefined0瀑布流估高时内容的最小高度(px):卡片给画面设了最小高度(扁图也要装下叠层)时与之一致
hover"lift" | "none" | "ring" | "tint" | undefined"ring"卡片统一的悬停效果(见 `XCard` 的 `hover`;缺省只描边,不上浮);单张卡自己给了以卡片为准
activestring | readonly string[] | undefinedundefined高亮的项(当前打开的、菜单开着的):描边淡一档
scrollboolean | undefinedtrue根节点里的列表区就是滚动容器(缺省);关掉时随外层滚,无限加载的哨兵按视口算
loadingboolean | undefinedfalse—
hasMoreboolean | undefinedfalse—
errorunknownundefined出错了(有值即是):显示 `errorText` 与重试,不当成「没有更多」
skeletonnumber | undefined8首次加载的骨架块数
paginateboolean | undefinedfalse换成底部分页器(`v-model:page`),不再无限加载
totalnumber | undefinedundefined—
pageSizenumber | undefined20—
selectableboolean | undefinedfalse—
selectableKeysreadonly string[] | undefinedundefined全部可选的键(含分页还没加载出来的):全选、反选、选择栏的总数、清理筛掉的选中按它算;缺省是已加载的项。 无限加载时要给,否则「全选」只选了一页
emptyTextstring | undefinedundefined空态文案,缺省取 locale;传空串不显示
doneTextstring | undefinedundefined到底文案,缺省取 locale;传空串不显示
errorTextstring | undefinedundefined—
retryTextstring | undefinedundefined—
selectLabelstring | undefinedundefined勾选圈的读屏名,缺省取 locale
pagenumber | undefined1—
selectedSet<string> | undefinednew Set()—
selectingboolean | undefinedfalse—

Emits

事件载荷说明
load-more[]—
retry[]—
update:page[value: number]—
update:selected[value: Set<string>]—
update:selecting[value: boolean]—

Slots

插槽说明
item—
empty—
skeleton—
header—
selection-bar—