Appearance
useCrudSelection
行选择管理:选中集合、跨页选择、批量操作支持。
基本用法
ts
import { useCrudList, useCrudSelection } from '@uozi/vito-core'
const list = useCrudList({ adapter })
const selection = useCrudSelection({
rows: list.rows,
getId: row => row.id,
})
selection.toggle(1) // 切换选中
selection.select(2) // 选中
selection.deselect(1) // 取消选中
selection.selectAll() // 全选当前页
selection.clear() // 清空
selection.isSelected(2) // true
selection.selectedCount.value // 1交互示例
loading
Options
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
rows | Ref<Row[]> | — | 当前页数据(必填),通常来自 useCrudList().rows |
getId | (row: Row) => string | number | row => row.id | 从行数据中提取唯一 ID |
返回值
State
| 属性 | 类型 | 说明 |
|---|---|---|
selectedIds | Ref<Set<string | number>> | 选中的 ID 集合 |
selectedRows | ComputedRef<Row[]> | 已选行的缓存快照(跨页保留;在翻页/刷新时会用当前页数据更新对应条目) |
selectedCount | ComputedRef<number> | 选中行数 |
Actions
| 方法 | 签名 | 说明 |
|---|---|---|
setSelectedIds | (ids: (string | number)[]) => void | 替换整个选中集合(UI 适配层使用) |
select | (id: string | number) => void | 选中一行 |
deselect | (id: string | number) => void | 取消选中 |
toggle | (id: string | number) => void | 切换选中状态 |
selectAll | () => void | 选中当前页所有行 |
clear | () => void | 清空所有选中 |
isSelected | (id: string | number) => boolean | 判断是否选中 |
行为说明
跨页选择
selectedIds 不会随翻页/刷新自动裁剪,因此可以自然支持“跨页勾选 → 批量操作”。
selectedRows 缓存
selectedRows 维护 id -> row 的缓存:当你在某一页选中行时会缓存该行;后续翻页/刷新时,如果当前页包含已选中的 ID,会用最新的行对象覆盖缓存。\n\n注意:对于不在当前页的数据,selectedRows 可能是较早的快照;如果你需要绝对最新的数据,请以 selectedIds 重新向后端查询。
完整示例
vue
<script setup lang="ts">
import { AutoCrud } from '@uozi/vito-naive-ui'
import { NAlert, NText } from 'naive-ui'
import { ref } from 'vue'
import { createBasicAdapter } from './basic-adapter'
import { demoColumns, demoFields } from './basic-schema'
const { adapter } = createBasicAdapter()
const crudRef = ref<InstanceType<typeof AutoCrud> | null>(null)
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<NAlert
:bordered="false"
type="info"
>
<div>
这是一个最小的 <NText code>
AutoCrud
</NText> 示例:搜索(query.search)、分页、排序、表单(Modal)、新增/编辑/删除。
</div>
</NAlert>
<AutoCrud
ref="crudRef"
:adapter="adapter"
:fields="demoFields"
:columns="demoColumns"
search-query-key="search"
form-mode="modal"
show-selection
:show-actions-column="true"
/>
</div>
</template>ts
import type { CrudAdapter, CrudSort, ListResult } from '@uozi/vito-core'
import type { DemoQuery, DemoRow } from './basic-types'
function compare(a: unknown, b: unknown): number {
if (a === b)
return 0
if (a === null || a === undefined)
return -1
if (b === null || b === undefined)
return 1
if (typeof a === 'number' && typeof b === 'number')
return a - b
return String(a).localeCompare(String(b))
}
function sortRows(rows: DemoRow[], sort?: CrudSort | null): DemoRow[] {
if (!sort)
return rows
const dir = sort.order === 'descend' ? -1 : 1
return rows.slice().sort((ra, rb) => dir * compare((ra as any)[sort.field], (rb as any)[sort.field]))
}
function filterRows(rows: DemoRow[], query: DemoQuery): DemoRow[] {
const s = query.search ?? {}
const name = s.name?.trim() ?? null
const status = s.status ?? null
return rows.filter((r) => {
if (name && !r.name.toLowerCase().includes(name.toLowerCase()))
return false
if (status && r.status !== status)
return false
return true
})
}
function createSeed(): DemoRow[] {
const now = Date.now()
return [
{ id: 1, name: '示例 1', status: 'enabled', amount: 12.3, createdAt: now - 3600_000 },
{ id: 2, name: '示例 2', status: 'draft', amount: 45.6, createdAt: now - 7200_000 },
{ id: 3, name: '示例 3', status: 'disabled', amount: 78.9, createdAt: now - 10800_000 },
]
}
export function createBasicAdapter(initial?: DemoRow[]): {
adapter: CrudAdapter<DemoRow, DemoQuery>
reset: (next?: DemoRow[]) => void
getAll: () => DemoRow[]
} {
let db = (initial ?? createSeed()).slice()
let idSeq = db.reduce((m, r) => Math.max(m, r.id), 0) + 1
function getAll() {
return db.slice()
}
function reset(next?: DemoRow[]) {
db = (next ?? createSeed()).slice()
idSeq = db.reduce((m, r) => Math.max(m, r.id), 0) + 1
}
const adapter: CrudAdapter<DemoRow, DemoQuery> = {
getId(row) {
return row.id
},
async list(params): Promise<ListResult<DemoRow>> {
const filtered = filterRows(db, params.query)
const sorted = sortRows(filtered, params.sort)
const page = Math.max(1, params.page)
const pageSize = Math.max(1, params.pageSize)
const start = (page - 1) * pageSize
return {
items: sorted.slice(start, start + pageSize),
total: sorted.length,
}
},
async create(data): Promise<DemoRow> {
const row: DemoRow = {
id: idSeq++,
name: String((data as any)?.name ?? ''),
status: ((data as any)?.status ?? 'draft') as DemoRow['status'],
amount: Number((data as any)?.amount ?? 0),
createdAt: Date.now(),
}
db = [row, ...db]
return row
},
async update(id, data): Promise<DemoRow> {
const idx = db.findIndex(r => r.id === id)
if (idx < 0)
throw new Error(`记录不存在:${id}`)
const next: DemoRow = { ...db[idx], ...(data as any), id: db[idx].id }
db = db.slice()
db[idx] = next
return next
},
async remove(id): Promise<void> {
db = db.filter(r => r.id !== id)
},
}
return { adapter, reset, getAll }
}ts
export interface DemoRow {
id: number
name: string
status: 'draft' | 'enabled' | 'disabled'
amount: number
createdAt: number
}
export interface DemoQuery {
search?: {
name?: string | null
status?: DemoRow['status'] | null
}
}