Appearance
路由同步
路由同步让搜索条件、分页等状态写入 URL query,实现"可分享链接"和"刷新不丢条件"。
方式一:AutoCrud 一行开启
在 AutoCrud 上加两个 prop 即可:
vue
<AutoCrud
:adapter="adapter"
:fields="fields"
:columns="columns"
search-query-key="search"
route-sync
route-query-key="q"
/>| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
routeSync | boolean | false | 开启路由同步 |
routeQueryKey | string | 'q' | URL query 参数名 |
开启后,URL 会变为 ?q={"search":{"name":"xxx"}} 这样的形式。刷新页面后条件自动还原。
WARNING
routeSync 需要组件运行在 vue-router 上下文里(能拿到 useRoute / useRouter)。
方式二:手动使用 useCrudRouteSync
当你使用 hooks 自由组合时,可以直接调用 useCrudRouteSync:
vue
<script setup lang="ts">
import type { DemoQuery, DemoRow } from './basic-types'
import { useCrudList, useCrudRouteSync } from '@uozi/vito-core'
import { useRoute, useRouter } from 'vue-router'
import { createBasicAdapter } from './basic-adapter'
const route = useRoute()
const router = useRouter()
const { adapter } = createBasicAdapter()
const list = useCrudList<DemoRow, DemoQuery>({
adapter,
})
useCrudRouteSync<DemoQuery>({
query: list.query,
setQuery: list.setQuery,
router,
route,
queryKey: 'q',
})
</script>
<template>
<div>
<!-- 这里只演示 route-sync 的核心用法,渲染由你自行决定 -->
</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
}
}配置项
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
query | Ref<Query> | — | 来自 useCrudList 的 query ref |
setQuery | (partial: Partial<Query>, options?: SetQueryOptions) => void | — | 来自 useCrudList 的 setQuery |
router | Router | — | vue-router 的 router 实例 |
route | RouteLocationNormalized | — | vue-router 的 route 实例 |
queryKey | string | 'q' | URL query 参数名 |
serialize | (query: Query) => string | JSON.stringify | 自定义序列化 |
deserialize | (str: string) => Partial<Query> | JSON.parse | 自定义反序列化 |
debounceMs | number | 300 | 写入 URL 的防抖延迟(ms) |
syncFromRouteMode | 'replace' | 'merge' | 'replace' | 从 URL 还原时的写回策略(默认以 URL 为准) |
行为说明
- 初始化:组件挂载时从
route.query[queryKey]读取并反序列化,写回setQuery - 写入 URL:监听
query变化,防抖后调用router.replace({ query }) - 防循环:从路由读取时会设置标记,避免写回时触发 watch 循环
- 空值清理:深度裁剪空值后若 query 为空,自动删除 URL 中的 queryKey 参数(保留
0/false)
自定义序列化
默认使用 JSON.stringify / JSON.parse。如果你想用更紧凑的格式(如 qs 库),可以传入自定义函数:
ts
import qs from 'qs'
useCrudRouteSync({
query: list.query,
setQuery: list.setQuery,
router,
route,
serialize: q => qs.stringify(q),
deserialize: s => qs.parse(s),
})