Appearance
CrudAdapter
CrudAdapter 是数据层接口——你只需要实现它,所有 UI 层就能复用同一套 CRUD 能力。
类型签名
ts
interface CrudAdapter<Row = any, Query = Record<string, unknown>> {
list: (params: ListParams<Query>) => Promise<ListResult<Row>>
create?: (data: Partial<Row>) => Promise<Row>
update?: (id: string | number, data: Partial<Row>) => Promise<Row>
remove?: (id: string | number) => Promise<void>
export?: (params: ExportParams<Query>) => Promise<ExportResult>
getId?: (row: Row) => string | number
}方法说明
| 方法 | 必选 | 说明 |
|---|---|---|
list | 是 | 分页查询,返回 { items, total } |
create | 否 | 新增记录,返回创建后的完整行 |
update | 否 | 更新记录,接收 id + 变更数据,返回更新后的完整行 |
remove | 否 | 删除记录,接收 id |
export | 否 | 导出数据,返回 Blob / { blob } / { url } |
getId | 否 | 从行数据中提取唯一标识。默认回退到 row.id |
TIP
AutoCrud 会根据 adapter 是否具备 create / update / remove / export 方法来自动启用或隐藏对应的操作按钮。
ListParams
list 方法接收的参数对象:
| 属性 | 类型 | 说明 |
|---|---|---|
page | number | 当前页码(从 1 开始) |
pageSize | number | 每页条数 |
query | Query | 查询对象(结构由你的泛型定义) |
sort | CrudSort | null | 排序配置 { field, order } |
signal | AbortSignal | undefined | 支持 abort(useCrudList 在新请求前会 abort 上一个) |
ListResult
list 方法应返回:
| 属性 | 类型 | 说明 |
|---|---|---|
items | Row[] | 当前页的数据 |
total | number | 总条数(用于分页计算) |
ExportParams
export 方法接收的参数对象:
| 属性 | 类型 | 说明 |
|---|---|---|
query | Query | 当前查询条件 |
sort | CrudSort | null | 当前排序 |
signal | AbortSignal | undefined | abort 信号 |
ExportResult
export 方法可返回以下三种形式之一:
| 形式 | 类型 | 说明 |
|---|---|---|
| Blob | Blob | 直接返回文件 Blob |
| Blob + 文件名 | { blob: Blob, filename?: string } | 带文件名 |
| URL + 文件名 | { url: string, filename?: string } | 后端下载地址 |
示例
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
}
}