
过去一年,我花了无数个夜晚,在一次次打磨与推翻中,完成了自己最满意的作品 —— Art Design Pro。
这不是一个普通的后台模板。
它是一场关于 「设计美学」与「工程化开发」 的融合实验——
希望让后台系统不再冰冷枯燥,而是像一件作品:优雅、流畅、有温度。
为什么要做这个项目?
在日常开发中,我几乎体验过所有主流后台模板。
它们的功能确实完整,但更多时候给人的感觉是——「工具」而非「产品」。
我希望做一个后台系统,
让人打开的第一眼就觉得舒服,
用起来像是在和它对话。
许多后台系统普遍存在这些问题 👇
视觉疲劳:灰白配色、生硬布局,难以长时间使用;
体验割裂:逻辑不统一、入口分散,操作效率低;
复用困难:组件风格不一致,二次开发成本高。
于是我决定从零开始,花一年的时间去打造一个真正属于自己的系统——
👉 一款 “既好看、又好用,还能直接用于商业项目” 的后台管理模板。
项目简介:Art Design Pro
Art Design Pro 是一款基于 Vue 3 + TypeScript + Vite + Element Plus 打造的现代化后台管理系统模板。
它的核心理念是:
让后台系统兼具设计美学与开发效率。
这个项目有什么特别的呢?
界面设计:现代化 UI 设计,流畅交互,以用户体验与视觉设计为核心
极速上手:简洁架构 + 完整文档,后端开发者也能轻松使用
丰富组件:内置数据展示、表单等多种高质量组件,满足不同业务场景的需求
丝滑交互:按钮点击、主题切换、页面过渡、图表动画,体验媲美商业产品
高效开发:内置 useTable、ArtForm 等实用 API,显著提升开发效率
精简脚本:内置一键清理脚本,可快速清理演示数据,立即得到可开发的基础项目
技术栈
开发框架:Vue3、TypeScript、Vite、Element-Plus
代码规范:Eslint、Prettier、Stylelint、Husky、Lint-staged、cz-git
预览
主页仪表盘

电子商务仪表盘

卡片


横幅

图表

系统图标库

富文本编辑器

礼花效果

全局搜索

系统设置

表格

暗黑模式





快速访问
GitHub:
github.com/Daymychen/a…
演示地址:
www.artd.pro
官方文档:
www.artd.pro/docs
高效开发
在后台管理系统开发中,表格页面占据了 80% 的工作量。每次都要写分页、搜索、刷新、列配置...这些重复的代码让人头疼。今天分享一套我们团队正在使用的表格开发方案,让你的开发效率提升 10 倍!
痛点分析
在开发后台管理系统时,你是否遇到过这些问题:
- 每个表格页面都要写一遍分页逻辑
- 搜索、重置、刷新等功能重复实现
- 表格列配置、显示隐藏、拖拽排序需要手动处理
- 数据请求、loading 状态、错误处理代码冗余
- 缓存策略难以统一管理
- 移动端适配需要额外处理
如果你的答案是"是",那这篇文章就是为你准备的。
解决方案概览
我们的方案包含以下核心部分:
-
useTable - 强大的表格数据管理 Hook
-
ArtTable - 增强的表格组件
-
ArtTableHeader - 表格工具栏组件
-
ArtSearchBar - 智能搜索栏组件
-
ArtForm - 通用表单组件
一、useTable:表格数据管理的核心
1.1 基础用法
先看一个最简单的例子,只需要几行代码就能实现一个完整的表格:
const {
data,
columns,
columnChecks,
loading,
pagination,
refreshData,
handleSizeChange,
handleCurrentChange
} = useTable({
core: {
apiFn: fetchGetUserList,
apiParams: {
current: 1,
size: 20
},
columnsFactory: () => [
{ prop: 'id', label: 'ID' },
{ prop: 'userName', label: '用户名' },
{ prop: 'userPhone', label: '手机号' }
]
}
})
就这么简单!你已经拥有了:
- ✅ 自动的数据请求
- ✅ 分页功能
- ✅ Loading 状态
- ✅ 列配置管理
1.2 核心特性
🚀 智能缓存机制
useTable({
core: {
/* ... */
},
performance: {
enableCache: true, // 启用缓存
cacheTime: 5 * 60 * 1000, // 缓存 5 分钟
debounceTime: 300, // 防抖 300ms
maxCacheSize: 50 // 最多缓存 50 条
}
})
缓存带来的好处:
- 相同参数的请求直接从缓存读取,秒开
- 减少服务器压力
- 提升用户体验
🎯 多种刷新策略
不同的业务场景需要不同的刷新策略:
// 新增数据后:回到第一页,清空分页缓存
await refreshCreate()
// 编辑数据后:保持当前页,只清空当前搜索缓存
await refreshUpdate()
// 删除数据后:智能处理页码,避免空页面
await refreshRemove()
// 手动刷新:清空所有缓存
await refreshData()
// 定时刷新:轻量刷新,保持分页状态
await refreshSoft()
这些方法让你的代码更语义化,不用再纠结什么时候该清缓存。
🔄 数据转换器
有时候接口返回的数据需要处理一下才能用:
useTable({
core: {
/* ... */
},
transform: {
dataTransformer: (records) => {
return records.map((item, index) => ({
...item,
// 替换头像
avatar: localAvatars[index % localAvatars.length].avatar,
// 格式化日期
createTime: dayjs(item.createTime).format('YYYY-MM-DD')
}))
}
}
})
📊 生命周期钩子
useTable({
core: {
/* ... */
},
hooks: {
onSuccess: (data, response) => {
console.log('数据加载成功', data)
},
onError: (error) => {
ElMessage.error('加载失败:' + error.message)
},
onCacheHit: (data) => {
console.log('从缓存读取', data)
}
}
})
1.3 搜索功能
搜索是表格的核心功能,useTable 提供了完善的搜索支持:
// 定义搜索参数
const searchParams = reactive({
userName: '',
userPhone: '',
status: '1'
})
const { getData, resetSearchParams } = useTable({
core: {
apiFn: fetchGetUserList,
apiParams: searchParams
}
})
// 搜索
const handleSearch = (params) => {
Object.assign(searchParams, params)
getData() // 自动回到第一页
}
// 重置
const handleReset = () => {
resetSearchParams() // 清空搜索条件并重新加载
}
二、ArtTable:增强的表格组件
2.1 核心特性
ArtTable 基于 Element Plus 的 ElTable 封装,完全兼容原有 API,同时提供了更多增强功能:
<ArtTable
:loading="loading"
:data="data"
:columns="columns"
:pagination="pagination"
@selection-change="handleSelectionChange"
@pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange"
/>
✨ 自动高度计算
不用再手动计算表格高度了!ArtTable 会自动计算剩余空间:
<div class="art-full-height">
<UserSearch />
<ElCard class="art-table-card">
<ArtTableHeader />
<ArtTable /> <!-- 自动占满剩余高度 -->
</ElCard>
</div>
🎨 列配置灵活
支持多种列类型和自定义渲染:
columnsFactory: () => [
{ type: 'selection' }, // 勾选列
{ type: 'index', width: 60, label: '序号' }, // 序号列
{ type: 'globalIndex' }, // 全局序号(跨页)
// 自定义渲染
{
prop: 'avatar',
label: '用户',
formatter: (row) => {
return h('div', { class: 'user-info' }, [
h(ElImage, { src: row.avatar }),
h('span', row.userName)
])
}
},
// 使用插槽
{
prop: 'status',
label: '状态',
useSlot: true // 在模板中使用 #status 插槽
},
// 操作列
{
prop: 'operation',
label: '操作',
fixed: 'right',
formatter: (row) =>
h('div', [
h(ArtButtonTable, {
type: 'edit',
onClick: () => handleEdit(row)
}),
h(ArtButtonTable, {
type: 'delete',
onClick: () => handleDelete(row)
})
])
}
]
📱 响应式分页
自动适配移动端、平板、桌面端:
// 移动端:prev, pager, next, sizes, jumper, total
// 平板:prev, pager, next, jumper, total
// 桌面端:total, prev, pager, next, sizes, jumper
三、ArtTableHeader:强大的工具栏
3.1 开箱即用的功能
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
<ElButton @click="handleAdd">新增用户</ElButton>
</template>
</ArtTableHeader>
一行代码,你就拥有了:
- 🔍 搜索栏切换
- 🔄 刷新按钮(带加载动画)
- 📏 表格尺寸切换(大、中、小)
- 🖥️ 全屏模式
- 📋 列显示/隐藏配置
- 🎨 斑马纹、边框、表头背景切换
3.2 列配置功能
用户可以:
- ✅ 勾选显示/隐藏列
- ✅ 拖拽调整列顺序
- ✅ 固定列不可拖动
这些配置会自动同步到表格显示。
3.3 自定义布局
<ArtTableHeader layout="refresh,size,fullscreen,columns" :show-zebra="false" :show-border="false" />
通过 layout
属性控制显示哪些功能按钮。
四、ArtSearchBar:智能搜索栏
4.1 配置化搜索表单
不用再手写一堆 ElFormItem 了,用配置就能搞定:
<template>
<ArtSearchBar
ref="searchBarRef"
v-model="formData"
:items="formItems"
:rules="rules"
@reset="handleReset"
@search="handleSearch"
/>
</template>
<script setup lang="ts">
const formData = ref({
userName: undefined,
userPhone: undefined,
status: '1'
})
const formItems = computed(() => [
{
label: '用户名',
key: 'userName',
type: 'input',
placeholder: '请输入用户名',
clearable: true
},
{
label: '手机号',
key: 'userPhone',
type: 'input',
props: { placeholder: '请输入手机号', maxlength: '11' }
},
{
label: '状态',
key: 'status',
type: 'select',
props: {
placeholder: '请选择状态',
options: [
{ label: '在线', value: '1' },
{ label: '离线', value: '2' }
]
}
},
{
label: '性别',
key: 'userGender',
type: 'radiogroup',
props: {
options: [
{ label: '男', value: '1' },
{ label: '女', value: '2' }
]
}
}
])
const handleSearch = async () => {
await searchBarRef.value.validate()
emit('search', formData.value)
}
const handleReset = () => {
emit('reset')
}
</script>
4.2 核心特性
🎯 支持多种表单组件
开箱即用的组件类型:
-
input
- 输入框
-
select
- 下拉选择
-
date
/ datetime
/ daterange
- 日期选择
-
radiogroup
/ checkboxgroup
- 单选/多选
-
cascader
- 级联选择
-
treeselect
- 树选择
- 自定义组件 - 支持任意 Vue 组件
📦 自动展开/收起
当搜索项过多时,自动显示展开/收起按钮:
<ArtSearchBar :items="formItems" :show-expand="true" :default-expanded="false" :span="6" />
- 默认只显示一行
- 超出部分自动隐藏
- 点击"展开"查看全部搜索项
🔄 动态选项加载
支持异步加载选项数据:
const statusOptions = ref([])
onMounted(async () => {
// 模拟接口请求
statusOptions.value = await fetchStatusOptions()
})
const formItems = computed(() => [
{
label: '状态',
key: 'status',
type: 'select',
props: {
options: statusOptions.value // 动态选项
}
}
])
🎨 响应式布局
自动适配不同屏幕尺寸:
// 通过 span 控制每行显示的表单项数量
// span=6: 一行显示 4 个(24/6=4)
// span=8: 一行显示 3 个(24/8=3)
// span=12: 一行显示 2 个(24/12=2)
移动端自动调整为单列布局。
4.3 表单校验
支持完整的表单校验:
const rules = {
userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
userPhone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
]
}
const handleSearch = async () => {
// 校验通过才执行搜索
await searchBarRef.value.validate()
emit('search', formData.value)
}
五、ArtForm:通用表单组件
5.1 配置化表单
ArtForm 和 ArtSearchBar 使用相同的配置方式,但更适合弹窗、详情页等场景:
<template>
<ArtForm
ref="formRef"
v-model="formData"
:items="formItems"
:rules="formRules"
:label-width="100"
:span="12"
@reset="handleReset"
@submit="handleSubmit"
/>
</template>
<script setup lang="ts">
const formData = ref({
userName: '',
userPhone: '',
userEmail: '',
userGender: '1',
status: true
})
const formItems = [
{
label: '用户名',
key: 'userName',
type: 'input',
placeholder: '请输入用户名'
},
{
label: '手机号',
key: 'userPhone',
type: 'input',
props: { maxlength: 11 }
},
{
label: '邮箱',
key: 'userEmail',
type: 'input',
placeholder: '请输入邮箱'
},
{
label: '性别',
key: 'userGender',
type: 'radiogroup',
props: {
options: [
{ label: '男', value: '1' },
{ label: '女', value: '2' }
]
}
},
{
label: '是否启用',
key: 'status',
type: 'switch'
},
{
label: '备注',
key: 'remark',
type: 'input',
span: 24, // 占满整行
props: {
type: 'textarea',
rows: 4
}
}
]
const formRules = {
userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
userEmail: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }]
}
const handleSubmit = async () => {
await formRef.value.validate()
// 提交表单
console.log('表单数据:', formData.value)
}
</script>
5.2 高级特性
🎨 自定义组件渲染
支持使用 h 函数渲染任意组件:
import ArtIconSelector from '@/components/core/base/art-icon-selector/index.vue'
const formItems = [
{
label: '图标选择',
key: 'icon',
type: () =>
h(ArtIconSelector, {
iconType: IconTypeEnum.UNICODE,
width: '100%'
})
},
{
label: '文件上传',
key: 'files',
type: () =>
h(
ElUpload,
{
action: '#',
multiple: true,
limit: 5,
onChange: (file, fileList) => {
formData.value.files = fileList
}
},
{
default: () => h(ElButton, { type: 'primary' }, () => '点击上传')
}
)
}
]
🔌 插槽支持
支持为表单项添加插槽:
<ArtForm v-model="formData" :items="formItems">
<template #customField>
<div class="custom-content">
<!-- 自定义内容 -->
</div>
</template>
</ArtForm>
或者在配置中使用插槽:
const formItems = [
{
label: '网站',
key: 'website',
type: 'input',
slots: {
prepend: () => h('span', 'https://'),
append: () => h('span', '.com')
}
}
]
🎯 条件显示
根据条件动态显示/隐藏表单项:
const formItems = computed(() => [
{
label: '用户类型',
key: 'userType',
type: 'select',
props: {
options: [
{ label: '个人', value: 'personal' },
{ label: '企业', value: 'enterprise' }
]
}
},
{
label: '企业名称',
key: 'companyName',
type: 'input',
// 只有选择企业类型时才显示
hidden: formData.value.userType !== 'enterprise'
}
])
📏 灵活布局
通过 span
控制表单项宽度:
const formItems = [
{
label: '用户名',
key: 'userName',
type: 'input',
span: 12 // 占半行
},
{
label: '手机号',
key: 'userPhone',
type: 'input',
span: 12 // 占半行
},
{
label: '地址',
key: 'address',
type: 'input',
span: 24 // 占整行
}
]
5.3 与 ArtSearchBar 的区别
特性 |
ArtSearchBar |
ArtForm |
使用场景 |
表格搜索 |
表单提交、弹窗 |
展开/收起 |
✅ 支持 |
❌ 不支持 |
按钮文案 |
搜索/重置 |
提交/重置 |
样式 |
卡片样式 |
无背景 |
默认布局 |
横向排列 |
横向排列 |
六、完整示例
让我们看一个完整的用户管理页面:
<template>
<div class="user-page art-full-height">
<!-- 搜索栏 -->
<ArtSearchBar
v-model="searchForm"
:items="searchItems"
@search="handleSearch"
@reset="resetSearchParams"
/>
<ElCard class="art-table-card" shadow="never">
<!-- 工具栏 -->
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<template #left>
<ElButton @click="handleAdd">新增用户</ElButton>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
:loading="loading"
:data="data"
:columns="columns"
:pagination="pagination"
@selection-change="handleSelectionChange"
@pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange"
/>
</ElCard>
<!-- 弹窗 -->
<UserDialog
v-model:visible="dialogVisible"
:type="dialogType"
:user-data="currentUserData"
@submit="handleDialogSubmit"
/>
</div>
</template>
<script setup lang="ts">
import { useTable } from '@/composables/useTable'
import { fetchGetUserList } from '@/api/system-manage'
import UserDialog from './modules/user-dialog.vue'
// 搜索表单
const searchForm = ref({
userName: undefined,
userPhone: undefined,
status: '1'
})
// 搜索项配置
const searchItems = [
{
label: '用户名',
key: 'userName',
type: 'input',
placeholder: '请输入用户名',
clearable: true
},
{
label: '手机号',
key: 'userPhone',
type: 'input',
props: { placeholder: '请输入手机号', maxlength: '11' }
},
{
label: '状态',
key: 'status',
type: 'select',
props: {
placeholder: '请选择状态',
options: [
{ label: '在线', value: '1' },
{ label: '离线', value: '2' },
{ label: '异常', value: '3' }
]
}
}
]
// 表格配置
const {
data,
columns,
columnChecks,
loading,
pagination,
getData,
searchParams,
resetSearchParams,
handleSizeChange,
handleCurrentChange,
refreshData
} = useTable({
core: {
apiFn: fetchGetUserList,
apiParams: {
current: 1,
size: 20,
...searchForm.value
},
columnsFactory: () => [
{ type: 'selection' },
{ type: 'index', width: 60, label: '序号' },
{
prop: 'avatar',
label: '用户名',
width: 280,
formatter: (row) => {
return h('div', { class: 'user-info' }, [
h(ElImage, { src: row.avatar }),
h('div', [h('p', row.userName), h('p', { class: 'email' }, row.userEmail)])
])
}
},
{ prop: 'userGender', label: '性别' },
{ prop: 'userPhone', label: '手机号' },
{
prop: 'status',
label: '状态',
formatter: (row) => {
const config = getStatusConfig(row.status)
return h(ElTag, { type: config.type }, () => config.text)
}
},
{
prop: 'operation',
label: '操作',
width: 120,
fixed: 'right',
formatter: (row) =>
h('div', [
h(ArtButtonTable, {
type: 'edit',
onClick: () => handleEdit(row)
}),
h(ArtButtonTable, {
type: 'delete',
onClick: () => handleDelete(row)
})
])
}
]
}
})
// 搜索处理
const handleSearch = (params) => {
Object.assign(searchParams, params)
getData()
}
// 新增
const handleAdd = () => {
dialogType.value = 'add'
dialogVisible.value = true
}
// 编辑
const handleEdit = (row) => {
dialogType.value = 'edit'
currentUserData.value = row
dialogVisible.value = true
}
// 删除
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除该用户吗?', '提示', {
type: 'warning'
}).then(() => {
// 调用删除接口
// await deleteUser(row.id)
ElMessage.success('删除成功')
refreshData()
})
}
</script>
七、性能优化
7.1 智能防抖
搜索时自动防抖,避免频繁请求:
const { getDataDebounced } = useTable({
performance: {
debounceTime: 300 // 300ms 防抖
}
})
// 使用防抖搜索
const handleSearch = () => {
getDataDebounced()
}
7.2 请求取消
切换页面或快速切换搜索条件时,自动取消上一个请求:
// useTable 内部实现
let abortController = new AbortController()
const fetchData = async () => {
// 取消上一个请求
if (abortController) {
abortController.abort()
}
// 创建新的控制器
abortController = new AbortController()
// 发起请求
await apiFn(params)
}
7.3 缓存统计
实时查看缓存状态:
const { cacheInfo } = useTable({
performance: { enableCache: true }
})
console.log(cacheInfo.value)
// { total: 10, size: '45KB', hitRate: '8 avg hits' }
八、最佳实践
8.1 目录结构
views/
system/
user/
index.vue # 主页面
modules/
user-search.vue # 搜索组件
user-dialog.vue # 弹窗组件
8.2 搜索组件封装
使用 ArtSearchBar 封装搜索组件:
<!-- user-search.vue -->
<template>
<ArtSearchBar
ref="searchBarRef"
v-model="formData"
:items="formItems"
@reset="handleReset"
@search="handleSearch"
/>
</template>
<script setup lang="ts">
const formData = defineModel({ required: true })
const emit = defineEmits(['search', 'reset'])
const formItems = [
{
label: '用户名',
key: 'userName',
type: 'input',
placeholder: '请输入用户名',
clearable: true
},
{
label: '手机号',
key: 'userPhone',
type: 'input',
props: { placeholder: '请输入手机号', maxlength: '11' }
}
]
const handleSearch = () => {
emit('search', formData.value)
}
const handleReset = () => {
emit('reset')
}
</script>
8.3 类型安全
充分利用 TypeScript 的类型推导:
// API 类型定义
declare namespace Api.SystemManage {
interface UserListItem {
id: number
userName: string
userPhone: string
userEmail: string
status: string
avatar: string
}
interface UserSearchParams {
userName?: string
userPhone?: string
status?: string
}
}
// useTable 会自动推导类型
const { data } = useTable({
core: {
apiFn: fetchGetUserList // 返回 Promise<UserListItem[]>
// data 的类型自动推导为 UserListItem[]
}
})
九、对比传统方案
传统方案(约 200 行代码)
// 需要手动管理的状态
const loading = ref(false)
const data = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(20)
const searchForm = ref({})
// 需要手动实现的方法
const fetchData = async () => {
loading.value = true
try {
const res = await api.getUserList({
current: currentPage.value,
size: pageSize.value,
...searchForm.value
})
data.value = res.records
total.value = res.total
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
const handleSizeChange = (val) => {
pageSize.value = val
currentPage.value = 1
fetchData()
}
const handleCurrentChange = (val) => {
currentPage.value = val
fetchData()
}
const handleSearch = () => {
currentPage.value = 1
fetchData()
}
const handleReset = () => {
searchForm.value = {}
currentPage.value = 1
fetchData()
}
// ... 还有更多代码
使用 useTable(约 30 行代码)
const {
data,
columns,
loading,
pagination,
searchParams,
getData,
resetSearchParams,
handleSizeChange,
handleCurrentChange
} = useTable({
core: {
apiFn: fetchGetUserList,
apiParams: { current: 1, size: 20 },
columnsFactory: () => [
/* 列配置 */
]
}
})
const handleSearch = (params) => {
Object.assign(searchParams, params)
getData()
}
代码量减少 85%,功能更强大!
十、总结
这套表格开发方案的核心优势:
-
开发效率提升 10 倍 - 从 200 行代码减少到 30 行
-
功能更强大 - 缓存、防抖、多种刷新策略、列配置等
-
类型安全 - 完整的 TypeScript 支持
-
易于维护 - 统一的代码风格和最佳实践
-
用户体验好 - 响应式设计、智能缓存、流畅交互
如果你也在开发后台管理系统,强烈建议尝试这套方案。它不仅能让你的代码更简洁,还能让你有更多时间专注于业务逻辑,而不是重复造轮子。
如果这篇文章对你有帮助,欢迎点赞收藏!有任何问题也欢迎在评论区讨论 💬