Admate
管理后台伴侣,用简洁而不失灵活的方式开发管理后台页面,可以集成进任意管理后台框架如 vue-vben-admin
, vue-element-admin 中。
🎉 在生产实践中,Admate 经受住了对接微信进件的考验。
案例对比 | 技术栈 | 业务代码量 (表单部分) | Bug 数量 (表单部分) |
---|
对接支付宝进件 | Vue 2 + ElementUI | 89293 字符 | 19 个 |
对接微信进件 | Vue 2 + ElementUI + Admate | 38718 字符 | 10 个 |
案例对比 | 技术栈 | 业务代码量 (整个页面) |
---|
常规增删查改页面 | Vue 2 + ElementUI | 13330 字符 |
常规增删查改页面 | Vue 2 + ElementUI + Admate | 不是特别复杂的不会过万,简单点的三千字符可以搞定 |

特性
- 🕶 Vue 2 和 Vue 3 通用 - 比如你在 Vue 2 中使用了 Admate,升级 Vue 3 时,你需要做的事情只是切换依赖源
- 🤝 不限制 UI 框架 - 只要技术栈是 Vue 和 axios 便可使用,并提供主流 UI 框架示例代码(Vuetify,Element,AntDesignVue,Quasar,PrimeVue)
- 🎨 组合式 API - 无侵入性
- 🪝 代理模式 + 控制反转 - 使用钩子函数的代理量身打造生命周期的行为
- 🌐 支持模块级的请求配置 - 同模块内请求配置相似,接口前缀通常是一致的
- 🍪 贴心而不武断的 CRUD 封装
- 列表筛选:支持监听筛选参数 + 节流控制接口调用频率的方式,也支持点击查询按钮触发的方式
- 表单展现形式:支持对话框的形式,也支持独立页面的形式
- 单条记录状态:支持分别调用启用/停用接口改变状态,也支持调用统一的更新状态接口指定新状态
- 加载状态:提供列表读取状态、表单读取状态、表单提交状态的响应式变量
- 🧹 周全的收尾工作,没有“后顾之忧”
- 关闭表单时,自动将表单绑定的数据恢复至初始状态(不是直接清空)
- 删除当前分页最后一条记录时,自动切换至上一页(如果当前不在第一页)
- 离开页面时,自动终止尚未完成的请求
过往版本的文档
https://www.npmjs.com/package/admate/v/0.5.4
将链接末尾替换为你想要查看的版本号即可
安装

Vue 3
npm add admate vue@3 axios
初始化示例
Vue 2
npm add admate vue@2 axios
npm add admate vue@2.6 axios @vue/composition-api
初始化示例
使用代码生成器
使用代码生成器生成页面模板
代码生成器不是必须的,它只是帮你省去从 YApi 复制字段名至代码中这一烦人步骤
目前仅支持 element-ui
安装
安装 Chrome / Edge 插件 YApi2Code,或使用离线版:
-
下载离线包后解压
-
打开浏览器扩展程序,并开启开发者模式
-
点击加载已解压的扩展程序,选择解压后的文件夹
使用
-
访问 YApi,选中相应模块的查询列表接口
-
点击浏览器右上角运行插件
-
点击生成代码,代码将被复制至剪贴板
-
创建页面文件 xxx.vue
,粘贴代码
搭配 Vuetify

Vuetify@3 仍处于 Alpha 阶段,只有少量组件可用,暂无示例
Vuetify@2 示例
搭配 ElementUI

示例
搭配 ElementPlus

示例
搭配 AntDesignVue

AntDesignVue@2 示例
AntDesignVue@1 示例
搭配 Quasar

Quasar@2 示例
Quasar@1(应该)不支持 Vite,暂无示例
搭配 PrimeVue

PrimeVue@3 示例
PrimeVue@2(应该)不支持 Vite,暂无示例
请求配置
axios
useAdmate({
axios,
})
axiosConfig
useAdmate({
axiosConfig: {
getList: {
method: 'GET',
},
c: {
method: 'POST',
},
r: {
method: 'GET',
},
u: {
method: 'PUT',
},
d: {
method: 'DELETE',
},
enable: {
method: 'PUT',
},
disable: {
method: 'PUT',
},
updateStatus: {
method: 'PUT',
},
},
})
urlPrefix
useAdmate({
urlPrefix: '',
})
useAdmate({
urlPrefix: 'somepage',
axiosConfig: {
r: {
url: '/anotherpage/selectOne',
},
}
})
RESTful
如果接口地址需要进行动态拼接
<!-- 示例 -->
const { openForm, d, enable, disable, updateStatus } = useAdmate({
axiosConfig: {
r: payload => ({
method: 'GET',
url: 'module/' + payload.id
}),
u: payload => ({
method: 'PUT',
url: 'module/' + payload.id
}),
d: payload => ({
method: 'DELETE',
url: 'module/' + payload.id
}),
enable: payload => ({
method: 'PUT',
url: 'module/' + payload.id
}),
disable: payload => ({
method: 'PUT',
url: 'module/' + payload.id
}),
updateStatus: payload => ({
method: 'PUT',
url: 'module/' + payload.id
}),
}
})
openForm({ id: 1 }, 'config')
d({ id: 1 }, 'config')
enable({ id: 1 }, 'config')
disable({ id: 1 }, 'config')
updateStatus({ id: 1 }, 'config')
FormData
axios 的 data 默认以 application/json
作为 MIME type,如果你需要使用 multipart/form-data
:
给你的 axios 配置 transformRequest
、headers['Content-Type']
getList
、openForm
、d
、updateStatus
、enable
、disable
、submitForm
的参数 1 均支持 FormData 类型
<!-- 示例:局部配置 -->
<template>
<el-table>
<el-table-column label="操作">
<template #default="{ row: { id } }">
<el-button @click="r(FormData.from({ id }))">查看</el-button>
<el-button @click="u(FormData.from({ id }))">编辑</el-button>
<el-button @click="d(FormData.from({ id }))">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog>
<template #footer>
<el-button @click="submitForm(FormData.from( form.data ))">
确 定
</el-button>
</template>
</el-dialog>
</template>
<script>
import useAdmate from 'admate'
import { jsonToFormData, pickDeepBy } from 'kayran'
export default {
setup: () => {
// 过滤参数并转换为 FormData
// 此处示例为将过滤方法绑定到 window.FormData,方便其他地方使用
FormData.from = data => jsonToFormData(pickDeepBy(data, (v, k) => ![NaN, null, undefined].includes(v)))
// 直接转换为 FormData
//FormData.from = jsonToFormData
const admate = useAdmate({
getListProxy (getList, trigger) {
getList(FormData.from(list.filter))
},
})
return {
...admate,
r: (...args) => {
admate.form.status = 'r'
admate.openForm(...args)
},
u: (...args) => {
admate.form.status = 'u'
admate.openForm(...args)
},
FormData
}
}
}
</script>
列表
筛选参数
list.filter
useAdmate({
list: {
filter: {
[list.pageNumberKey]: 1,
},
pageNumberKey: undefined,
},
})
如果你的参数筛选项中包含 el-checkbox
组件,则必须在 data 中声明其初始值,否则将导致无法正确重置(element-ui 的 Bug)
<!-- 示例 -->
<template>
<el-form ref="listFilterRef" :model="list.filter" inline>
<el-form-item prop="effect">
<el-checkbox
v-model="list.filter.effect"
label="生效"
border
/>
</el-form-item>
<el-button @click="reset">重置</el-button>
</el-form>
</template>
<script>
import useAdmate from 'admate'
export default {
setup: () => useAdmate({
urlPrefix: '',
list: {
filter: {
effect: null
}
}
}),
}
</script>
触发查询
- 点击专用的
查询
按钮触发
- :x: 操作相对繁琐。
- :x: 列表数据与筛选条件可能是无关的。可能产生“当前的列表数据是否基于筛选项?”的顾虑,导致徒增点击查询按钮的次数。
- :heavy_check_mark: 想同时设置多个筛选条件时,只调用一次接口,不会造成资源浪费。
useAdmate({
list: {
watchFilter: false,
}
})
- 改变筛选条件后即时触发
- :heavy_check_mark: 操作相对简便。
- :heavy_check_mark: 列表数据与筛选条件即时绑定。
- :heavy_check_mark:
想同时设置多个筛选条件时,接口会被多次调用,造成资源浪费(Admate 已优化)。
useAdmate({
list: {
watchFilter: true,
throttleInterval: 500,
}
})
列表数据
list.data
useAdmate({
list: {
data: [],
dataAt: undefined,
totalAt: undefined,
}
})
读取状态
list.loading
axiosConfig.getList
被调用时值为 true
,否则为 false
<!-- 示例 -->
<template>
<el-table v-loading="list.loading"/>
</template>
<script>
import useAdmate from 'admate'
export default {
setup: () => {
const { list } = useAdmate()
return { list }
},
methods: {
handleTable () {
this.list.loading = true
this.$POST('').finally(() => {
this.list.loading = false
})
}
}
}
</script>
Hook: 查询列表时
getList
:获取列表,在首次进入页面、列表筛选参数改变、单条记录增删查改后会被调用
const {
getList
} = useAdmate()
getList()
getListProxy
:你可以使用 getListProxy
来代理 getList
,以便在 getList 前后做一些操作,或改变 getList 的行为
useAdmate({
getListProxy (getList, trigger) {},
})
useAdmate({
getListProxy (getList, trigger) {
if (trigger === 'filterChange') {
listFilterRef.value.validate().then(() => {
getList()
})
} else {
getList()
}
},
})
useAdmate({
getListProxy (getList, trigger) {
getList()
if (['c', 'u', 'd', 'updateStatus', 'enable', 'disable'].includes(caller)) {
currentInstance.value.$message.success('操作成功')
}
},
})
const { list } = useAdmate({
getListProxy (getList, trigger) {
getList().then(response => {
list.data = response.data?.filter(v => !v.disabled)
})
},
})
表单
新增
打开表单,提交时会调用 axiosConfig.c
const { form, openForm } = useAdmate()
form.status = 'c'
openForm()
查看
打开表单,并调用 axiosConfig.r
回显表单内容
const { form, openForm } = useAdmate()
form.status = 'r'
openForm()
编辑
打开表单,并调用 axiosConfig.r
回显表单内容,提交时会调用 axiosConfig.u
const { form, openForm } = useAdmate()
form.status = 'u'
openForm()
删除
const {
d
} = useAdmate()
状态变更
状态变更有两种方式:
- 后端只提供一个接口,传参指定新的状态
- 后端提供启用和停用两个接口
<!-- 方式一:使用 updateStatus -->
<template>
<el-table>
<el-table-column label="操作" align="center">
<template slot-scope="{ row: { id, status } }">
<el-switch @change="updateStatus({ id, status: status^1 })"/>
</template>
</el-table-column>
</el-table>
</template>
<script>
import useAdmate from 'admate'
export default {
setup: () => {
const {
/**
* @param {any} [payload]
* @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
* 'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默认)
* 'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
* 'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
* @returns {Promise<any>} axiosConfig.updateStatus 的返回值
*/
updateStatus,
} = useAdmate()
return { updateStatus }
}
}
</script>
<!-- 方式二:使用 enable 和 disable -->
<template>
<el-table>
<el-table-column label="操作" align="center">
<template slot-scope="{ row: { id, status } }">
<el-switch @change="[enable,disable][status]({id})"/>
</template>
</el-table-column>
</el-table>
</template>
<script>
import useAdmate from 'admate'
export default {
setup: () => {
const {
/**
* @param {any} [payload]
* @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
* 'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默认)
* 'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
* 'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
* @returns {Promise<any>} axiosConfig.enable 的返回值
*/
enable,
/**
* @param {any} [payload]
* @param {'data'|'params'|'config'} [payloadAs] 指定 payload 的用途
* 'data': 将 payload 用作请求配置的 `data` 参数(请求方式为 POST / PATCH / PUT / DELETE 时默认)
* 'params': 将 payload 用作请求配置的 `params` 参数(请求方式为 GET / HEAD 时默认)
* 'config': 将 payload 仅用于构建请求配置(详见 RESTful 章节)
* @returns {Promise<any>} axiosConfig.disable 的返回值
*/
disable
} = useAdmate()
return { enable, disable }
}
}
</script>
表单数据
form.data
useAdmate({
form: {
data: {},
dataAt: undefined,
mergeData: 'deep',
},
})
mergeData:
'deep'
: 深合并(默认)'shallow'
: 浅合并(newFormData: any) => any
: 自定义合并方式false
: 不合并,直接替换
为什么默认是深合并?
在 Vue 2 中,template 不支持 ?.
语法,要在 template 中判空,代码写起来会非常冗余,通常的做法是在 data 中声明空对象
比如给 form.data 提供默认值:
<template>
{{ form.data.a.b.c }}
</template>
<script>
import useAdmate from 'admate'
export default {
setup: () => {
const { form } = useAdmate({
form: {
data: {
a: {
b: {}
}
}
}
})
return { form }
}
}
</script>
如果 axiosConfig.r 的返回值为:
{ a: {} }
如果与默认值浅合并后将得到:
{ a: {} }
—— 默认值中的对象b丢失了,引发空指针异常。
如果与默认值深合并后将得到:
{ a: { b: {} } }
—— 代码正常工作。
import { mergeWith } from 'lodash'
const defaultFormData = () => ({
a: {
b: {}
}
})
const { form } = useAdmate({
form: {
data: defaultFormData(),
mergeData (
newFormData
) {
form.data = mergeWith(
defaultFormData(),
newFormData,
(oldObj, newObj) => [undefined, null].includes(newObj) ? oldObj : undefined
)
},
},
})
表单形态
form.show
: 表单是否打开
可能的值:
form.status
: 表单的形态
可能的值:
form.show
为 false
时,form.status
为 ''
表单默认是对话框的形式,但也支持表单是独立页面 的情况
两种表单形式对比:
- 对话框:体验好,割裂感低,表单的开闭不影响父页面状态
- 独立页面:体验较差,从表单返回父页面时,父页面的状态会丢失,比如列表筛选状态
读取状态
form.loading
axiosConfig.r
被调用时值为 true
,否则为 false
不能将该值当作表单回显结束的标志,因为复用列表数据时不会调用 axiosConfig.r
<!-- 示例 -->
<template>
<el-dialog>
<el-form v-loading="form.loading"/>
</el-dialog>
</template>
<script>
import useAdmate from 'admate'
export default {
setup: () => {
const { form } = useAdmate()
return { form }
}
}
</script>
提交状态
form.submitting
axiosConfig.c
或 axiosConfig.u
被调用时值为 true
,否则为 false
<!-- 示例 -->
<template>
<el-dialog>
<template #footer>
<el-button :loading="form.submitting">
确 定
</el-button>
</template>
</el-dialog>
</template>
<script>
import useAdmate from 'admate'
export default {
setup: () => {
const { form } = useAdmate()
return { form }
}
}
</script>
Hook: 打开表单时
openForm
:打开表单,函数签名要分情况:
openFormProxy
:你可以使用 openFormProxy
来代理 openForm
,以便在 openForm 前后做一些操作,或改变 openForm 的行为
useAdmate({
openFormProxy (openForm) {},
})
const { form } = useAdmate({
openFormProxy (openForm) {
return new Promise((resolve, reject) => {
openForm()?.then(response => {
form.data.status = 1
resolve()
}).catch(() => {
reject()
})
})
},
})
useAdmate({
openFormProxy (openForm) {
return new Promise((resolve, reject) => {
openForm()?.finally(() => {
formRef.value.clearValidate()
}).then(() => {
resolve()
}).catch(() => {
reject()
})
})
},
})
useAdmate({
openFormProxy (openForm) {
return new Promise((resolve, reject) => {
openForm().then(() => {
resolve({
loading: false,
})
}).catch(() => {
reject({
show: false,
loading: false,
})
})
})
}
})
useAdmate({
openFormProxy (openForm) {
return {
loading: false
}
}
})
Hook: 提交表单时
submitForm
:提交表单,新增时调用 axiosConfig.c
,编辑时调用 axiosConfig.u
const {
submitForm
} = useAdmate()
submitFormProxy
:你可以使用 submitFormProxy
来代理 submitForm
,以便在 submitForm 前后做一些操作,或改变submitForm的行为
useAdmate({
submitFormProxy (submitForm) {}
})
submitForm({
...form.data,
status: 1,
})
useAdmate({
submitFormProxy (submitForm) {
return new Promise((resolve, reject) => {
submitForm({
...form.data,
status: 1,
}).then(() => {
resolve()
}).catch(() => {
reject()
})
})
}
})
useAdmate({
submitFormProxy (submitForm) {
return new Promise((resolve, reject) => {
formRef.value.validate().then(() => {
submitForm().then(() => {
resolve()
}).catch(() => {
reject()
})
})
})
}
})
useAdmate({
submitFormProxy (submitForm) {
return new Promise((resolve, reject) => {
formRef.value.validate().then(() => {
submitForm().then(() => {
resolve({
show: false,
submitting: false,
})
}).catch(() => {
reject({
show: true,
submitting: false,
})
})
})
})
}
})
useAdmate({
submitFormProxy (submitForm) {
return {
show: false,
submitting: false,
}
}
})
示例:表单是子组件
将表单抽离为子组件
示例
示例:表单是独立页面
操作单条记录时,跳转到专用的表单页面,操作完毕后返回
示例
示例:只有表单没有列表
表单默认打开,且无法关闭,通常用于列表中只有一条数据,故列表被省略的场景
示例
示例:嵌套使用
嵌套其它也使用 Admate 的页面
示例