表格与列表体系
列表页通常是中后台项目数量最多、维护时间最长的一类页面。Art Design Pro X 把请求、分页、搜索、列配置和展示组件拆开,目的是让业务页面专注字段和操作,而不是重复编写加载状态与分页逻辑。
组件关系
业务页面 index.vue
├── *-search.vue
│ └── ArtSearchBar
├── ArtTableHeader
├── ArtTable
└── useTable
├── apiFn: src/mock 或 src/api
├── 搜索参数
├── 分页状态
├── 响应适配
├── 列配置 useTableColumns
└── 刷新与可选缓存主要入口:
| 能力 | 文件 |
|---|---|
| 列表状态 | src/hooks/core/useTable.ts |
| 列配置 | src/hooks/core/useTableColumns.ts |
| 搜索栏 | src/components/core/forms/art-search-bar |
| 表格主体 | src/components/core/tables/art-table |
| 表格工具栏 | src/components/core/tables/art-table-header |
| 全局表格映射 | src/utils/table |
| 列偏好状态 | src/store/modules/table.ts |
| 标准参考页面 | src/views/system/user |
标准页面结构
<template>
<div class="art-full-height">
<UserSearch
v-model="searchForm"
:role-list="roleList"
@search="handleSearch"
@reset="handleReset"
/>
<ElCard class="art-table-card" shadow="never">
<ArtTableHeader
v-model:columns="columnChecks"
:loading="loading"
@refresh="refreshData"
>
<template #left>
<ElButton v-auth="'add'" type="primary" @click="showDialog('add')">
新增用户
</ElButton>
</template>
</ArtTableHeader>
<ArtTable
:loading="loading"
:data="data"
:columns="columns"
:pagination="pagination"
@pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange"
/>
</ElCard>
</div>
</template>art-full-height 和 art-table-card 负责让搜索区与表格区形成稳定的全高布局。不要再给表格外层嵌套多层卡片,否则容易出现高度计算和滚动条问题。
配置 useTable
const {
columns,
columnChecks,
data,
loading,
pagination,
replaceSearchParams,
resetSearchParams,
handleSizeChange,
handleCurrentChange,
refreshData
} = useTable({
core: {
apiFn: getDemoUserList,
apiParams: {
current: 1,
size: 20,
...searchForm.value
},
columnsFactory: () => [
{ type: 'selection' },
{ type: 'index', width: 60, label: '序号' },
{ prop: 'username', label: '用户名' },
{ prop: 'createdAt', label: '创建时间' },
{
prop: 'operation',
label: '操作',
width: 120,
fixed: 'right'
}
]
}
})核心配置:
apiFn:返回 Promise 的列表函数,可以来自src/mock或src/api。apiParams:初始分页和搜索参数。columnsFactory:返回列配置,适合依赖权限、语言或响应式状态的列。paginationKey:单页覆盖请求分页字段。responseAdapter:单页覆盖响应结构解析。- 数据转换与缓存等高级能力按页面需要开启,不建议默认堆叠配置。
搜索参数
搜索组件通过 v-model 维护表单值,并向页面发出 search 与 reset:
const handleSearch = async (params: UserSearchFormParams) => {
const { daterange, ...filters } = params
const [startTime, endTime] = Array.isArray(daterange)
? daterange
: [undefined, undefined]
await replaceSearchParams({
...filters,
startTime,
endTime
})
}使用 replaceSearchParams 而不是直接修改内部参数,可以保证搜索后页码回到正确位置并触发统一查询。
重置时同步清空页面表单:
const handleReset = () => {
Object.assign(searchForm.value, {
username: undefined,
role: undefined,
daterange: undefined
})
resetSearchParams()
}分页字段
项目默认请求字段为:
{
"current": 1,
"size": 20
}常见响应字段可以映射为:
| 语义 | 常见字段 |
|---|---|
| 列表 | records、list、data、items、rows |
| 总数 | total、count |
| 当前页 | current、page、pageNum |
| 每页数量 | size、pageSize、limit |
只有一个接口格式特殊时,在页面使用 paginationKey 或 responseAdapter。当客户全部列表都遵循同一格式时,再修改 src/utils/table 中的全局配置。
列配置
列可以定义:
- 普通字段与标题。
- 选择列、序号列。
- 固定宽度和左右固定。
- 排序。
formatter自定义内容。- 表头插槽和表头搜索。
- 操作列中的权限按钮。
{
prop: 'roles',
label: '关联角色',
formatter: (row) =>
row.roles?.length
? row.roles.map((item) => item.name).join(' / ')
: '-'
}formatter 应保持无副作用,不要在格式化过程中修改行数据或发起请求。
表头搜索与历史
表头搜索适合高频、单字段过滤,但不要与顶部搜索栏重复堆叠所有条件。搜索历史默认关闭,只有满足以下条件时才开启:
- 字段不包含手机号、证件号、邮箱、账号、订单号等敏感或可识别信息。
- 配置稳定的
historyKey。 - 明确使用标准历史策略。
- 已验证账号切换和登出时不会串数据。
从预置数据迁移到 API
迁移前:
import { getDemoUserList } from '@/mock/system/organization'迁移后:
import { fetchUserList } from '@/api/customer/user'然后只修改:
apiFn: fetchUserList为了做到这一点,API 应尽量保持:
- 相同的查询参数类型。
- 相同的分页语义。
- 相同的列表项类型。
- 错误通过 Promise 抛出。
写操作与刷新
接口接入后,新增或编辑成功通常执行:
await saveUser(payload)
dialogVisible.value = false
await refreshData()注意:
- 提交期间禁用确认按钮,避免重复请求。
- 接口失败时不要关闭弹窗。
- 删除最后一条数据时处理当前页回退。
- 批量操作完成后清理选择状态。
- 本地数据阶段继续使用边界提示,不要伪造刷新后的持久化结果。
常见问题
接口成功但表格为空
检查统一请求层是否已经返回 data,以及列表字段能否被当前响应适配器识别。不要把完整 Axios Response 直接交给 useTable。
搜索后仍停留在后面的页码
使用 replaceSearchParams,不要只修改查询对象后调用普通刷新。
表格出现双滚动条
检查页面是否使用 art-full-height,表格卡片是否处于正确的弹性布局,以及外层是否额外设置固定高度或多层卡片。
列设置在不同用户之间串用
检查表格偏好键和用户隔离逻辑。包含敏感字段的列和搜索历史不要默认持久化。
useTable、列配置和搜索历史的组合边界见Hooks 与组合式能力,表格相关公共视图见通用组件。
