一个基于 Zod 封装实现的快速后台管理系统
- zod admin 分为
core
和views
以及配置文件三部分,其中src\core
是 zod admin 的核心部分,它实现了如下的功能:- 涵盖了整体项目的框架
- 实现了项目的账号密码登录,登录重定向,错误页面重定向,路由守卫
- 实现了项目的用户信息模块
- 实现了文件路由系统,菜单路由系统
- 实现了依据 zod 的表单,表格,弹窗生成
- 实现了主题色切换
- 实现了实时环境切换,系统配置
- 实现了基于 Apex Charts 的图表
zod admin 增加了自动识别路由功能,在 src\views\Module\Admin\pages
下,创建文件夹视为文件目录,在文件目录中创建文件视为视图路由;
- pages
- Announcement
- AnnounceMsg.vue
- User
- UserMsg.vue
注意
- 文件识别仅支持.vue 文件
- 仅支持一级目录
自动路由目前是根据路由的名称来创建视图上的名称,但也提供了额外的选项支持用户自定义路由名称;
在 zod-admin.config.ts
中可以配置
参考:
routers: {
User: {
title: '用户管理',
icon: 'tabler:user',
},
UserMsg: {
title: '用户信息',
},
Announcement: {
title: '公告管理',
icon: 'tabler:article-filled-filled',
},
AnnounceMsg: {
title: '公告信息',
},
},
注意
- key 值必须和原始路由名称相同
- icon 参考:https://icones.js.org/collection/tabler
zod admin 支持环境配置,可以配置
- git 仓库地址
- 环境存储的 key(用于防止在本地启动同个继承自 zod admin 环境冲突)
- 环境配置
在 zod-admin.config.ts
中可以配置
参考:
env: {
git: '',
storageKey: 'Your Keys Here',
option: [
{
i18n: {
cn: '开发环境',
en: 'DEV',
},
key: import.meta.env.VITE_APP_ADMIN_DEV,
},
{
i18n: {
cn: '海外开发环境',
en: 'DEV_OVERSEA',
},
key: import.meta.env.VITE_APP_ADMIN_DEV_OVERSEA,
},
{
i18n: {
cn: '测试环境',
en: 'TEST',
},
key: import.meta.env.VITE_APP_ADMIN_TEST,
},
{
i18n: {
cn: '海外测试环境',
en: 'TEST_OVERSEA',
},
key: import.meta.env.VITE_APP_ADMIN_TEST_OVERSEA,
},
{
i18n: {
cn: '生产环境',
en: 'PROD',
},
key: import.meta.env.VITE_APP_ADMIN_PROD,
},
{
i18n: {
cn: '海外生产环境',
en: 'PROD_OVERSEA',
},
key: import.meta.env.VITE_APP_ADMIN_PROD_OVERSEA,
},
{
i18n: {
cn: '本地Mock环境',
en: 'Mock',
},
key: import.meta.env.VITE_APP_ADMIN_MOCK,
},
],
},
- 当 key 在环境变量中不存在时会自动过滤
zod admin 同时支持无代码的本地项目环境配置,用户点击系统设置后可以进行本地环境 URL 的替换,用于后端快速调试且不修改前端代码
zod admin 支持登录模块配置,目前仅支持账号密码登录;
在 zod-admin.config.ts
中可以配置
参考:
login: {
default: {
url: 'http://47.113.106.234:8001/api/admin/admin_user/login',
method: 'GET',
schema: z.object({
email: z.string().min(1),
password: z.string().min(1),
}),
tokenKey: 'token',
},
},
zod admin 支持配置请求相关,可以配置 token 前缀,mock token
在 zod-admin.config.ts
中可以配置
参考:
request: {
token: {
prefix: 'Bearer ',
mock: '',
},
},
zod admin 支持配置用户登录信息,可以配置用户名称,用户邮箱,用户头像
在 zod-admin.config.ts
中可以配置
参考:
user: {
default: {
url: '/mock/admin/user/info',
method: 'GET',
apiKeyAdapter: {
nickName: 'nick_name',
email: 'email',
},
},
},
- 当不存在用户头像的时候,用户头像会以用户的第一个字符自动生成
zod admin 支持配置网站相关主题信息
在 zod-admin.config.ts
中可以配置
参考:
site: {
title: 'lmc cms',
logoName: 'LMC CMS',
theme: {
primaryColor: 'rgba(35, 92, 222, 1)',
},
},
zod admin 支持进行一些无代码的系统配置,目前可以配置的行为有
- 本地环境 URL
- 点击 Table 的默认行为
- 见:/examples/schema/schemas
- 数据来源于外部的组件
- 常规组件
- 组件配置
- 枚举组件
- 对象嵌套组件
- 数组对象嵌套组件
- 文件上传组件
- 见:/examples/dialog/autoformdialog
- AutoFormDialog
- MultipleDialog
- 见:/examples/config/config
- Config
- 见:/examples/table/crudtable
- CRUDTable
- 所有的样例数据均已完成 mock
- 所有的样例不会出现在非开发环境
完整示例参考:
src\core\examples
-
Api 参考
src\core\examples\views\Table\api
为 api 相关定义application.api.ts
- 集成了 axios 相关 httpClient,在此处定义接口 api
applicationTableQuery.ts~~~~(已废弃,合并更新到 useRushTableV2 中)集成了 tanstackquery 相关 query 库,在此处定义自动请求 api(对于表格为查询用 api)
application.zod.ts
- 集成了 zod 相关,在此处定义 api 相关 zod schema
application.type.ts- 集成了 ts 相关,在此处定义 api 相关类型
-
columns 参考
src\core\examples\views\Table\columns
- 表格字段相关定义
- 特殊操作(编辑,删除,查看)相关定义
-
CRUDTable.vue 参考
src\core\examples\views\Table\CRUDTable.vue
- 查询,重置,新增相关定义
- 表格数据,表格分页相关定义
- 说明:获取表单的查询函数,重置函数,表单绑定值,分页 Props,AutoFormProps
- 类型定义:
interface Transform<T extends GenericObject, K extends unknown> {
transform: (v: T) => K
}
interface UseRushTable<
T extends GenericObject,
K extends GenericObject,
U extends Record<keyof T, z.ZodTypeAny>,
> {
/**
* schema
*/
schema: z.ZodObject<U>
/**
* api
* @param args
* @returns
*/
api: (...args: T[]) => Promise<K>
/**
* api适配器
*/
apiKeyAdapter: {
params: {
page: keyof T
size: keyof T
}
data: {
total: keyof K
}
}
/**
* form field配置
*/
fieldConfig?: (
values: UnwrapZod<T>
) => Partial<Record<keyof T, FieldConfigItem>>
}
type UseRushTableWithTransform<
T extends GenericObject,
K extends GenericObject,
U extends Record<keyof T, z.ZodTypeAny>,
E extends unknown,
> = UseRushTable<T, K, U> & Transform<K, E>
type UseRushTableWithPartialTransform<
T extends GenericObject,
K extends GenericObject,
U extends Record<keyof T, z.ZodTypeAny>,
E extends unknown,
> = UseRushTable<T, K, U> & Partial<Transform<K, E>>
type RushTableReturnType<
T extends GenericObject,
U extends Record<keyof T, z.ZodTypeAny>,
> = {
/**
* 搜索函数
* @param e
* @returns
*/
onSearch: (e?: Event | undefined) => Promise<void | undefined>
/**
* 重置函数
* @param state
* @param opts
* @returns
*/
onReset: (
state?: Partial<FormState<GenericObject>> | undefined,
opts?: Partial<ResetFormOpts> | undefined
) => void
/**
* 分页Props
*/
paginationProps: globalThis.ComputedRef<PaginationProps>
/**
* schema values
*/
values: UnwrapZod<T>
/**
* AutoForm props
*/
formProps: MaybeRefOrGetter<{
schema: z.ZodObject<U>
fieldConfig?: Partial<Record<keyof T, FieldConfigItem>>
}>
}
- 说明:获取表单的查询函数,重置函数,表单绑定值
- 类型定义:
export const useRushTable = <T extends GenericObject>(option: {
schema: z.ZodObject<Record<keyof T, z.ZodTypeAny>>
}) => {
//
return {
onSearch,
onReset,
values: values as T,
}
}
- 说明:获取自动查询接口所需配置项
- 类型定义:
export const createFormSearchQueryOption = () => {
//
return {
placeholderData: keepPreviousData,
enabled: () =>
!isFormDirty.value || (isFormValid.value && isSubmitting.value),
}
}
- 说明:获取分页参数
- 类型定义:
export const useRushTablePagination = <T extends GenericObject>(option: {
/**
* schema中定义的page的键名称
*/
page?: keyof T
/**
* schema中定义的pageSize的键名称
*/
pageSize?: keyof T
/**
* 接口返回的分页总数
*/
total: MaybeRefOrGetter<number>
/**
* 自动请求所需的refetch函数
* @returns
*/
onRefetch: () => void
}) => {
//
return {
/**
* 分页所需props
*/
paginationProps,
}
}
-
说明:获取表格 columns
-
类型定义:
- fieldConfig
- render,可以通过 Helper 自定义
- fieldConfig
type UnwrapZod<T extends Record<string, z.ZodTypeAny>> = z.infer<z.ZodObject<T>>
export const useRushColumns = <T extends Record<string, z.ZodTypeAny>>({
/**
* schema
*/
schema,
/**
* autoform的fieldConfig
*/
fieldConfig,
/**
* 自定义操作栏
*/
suffix,
}: {
schema: z.ZodObject<T>
fieldConfig?: Partial<
Record<
keyof T,
{
render?: Helper<UnwrapZod<T>>
colName?: string
ignore?: boolean
}
>
>
suffix?: Helper<UnwrapZod<T>>
}) => {
return columns as ColumnDef<UnwrapZod<T>, any>[]
}
- 说明:Column 的一系列渲染函数
- 类型定义:
export type Helper<T extends any> = (
props: CellContext<T, any | undefined>
) => any
- 说明:带省略号的渲染函数
- 类型定义:
export const EllipsisHelper = <T extends any>(
props: CellContext<T, any | undefined>
) => <NEllipsis>{props.getValue()}</NEllipsis>
- 说明:格式化日期的渲染函数
- 类型定义:
export const DateHelper = <T extends any>(
props: CellContext<T, Date | undefined>
) => dayjs(props.getValue()).format('YYYY-MM-DD HH:mm:ss')
- 说明:用户头像的渲染函数
- 类型定义:
export const AvatarHelper = ({
avatar,
name,
description,
}: {
/**
* 用户头像url
*/
avatar?: string
/**
* 用户名称
*/
name?: string
/**
* 用户描述
*/
description?: string
}) => (
<NFlex align="center" wrap={false}>
<NAvatar src={avatar} round class={'size-9 min-w-9'} />
<NFlex vertical class={'min-w-0'}>
{name && <NEllipsis>{name}</NEllipsis>}
{description && <NEllipsis>{description}</NEllipsis>}
</NFlex>
</NFlex>
)
- 说明:根据 object 创建 optionHelper
- 类型定义:
export const createOptionHelper =
(options: Record<string, VNode | string>) =>
<T extends any>(props: CellContext<T, any | undefined>) => (
<NEllipsis>
{{
default: () => options[props.getValue() + ''],
}}
</NEllipsis>
)
- 说明:布尔值渲染函数
- 类型定义:
export const BooleanHelper = createOptionHelper({
true: <IconTool icon={'tabler:check'} />,
false: <IconTool icon={'tabler:x'} />,
})
- 说明:获取表格 dialog 函数
- 类型定义:
export type UnwrapZod<T extends Record<string, z.ZodTypeAny>> = z.infer<
z.ZodObject<T>
>
interface AutoFormDialogOption<T extends Record<string, z.ZodTypeAny>> {
/**
* 接口返回data
*/
data?: MaybeRefOrGetter<UnwrapZod<T>>
/**
* 标题
*/
title: string
/**
* 提交按钮文本
*/
submitText?: MaybeRefOrGetter<string>
/**
* 提交回调函数
* @param option 返回原始值和提交函数
* @returns
*/
onSubmit?: (option: {
originValues: UnwrapZod<T>
form: FormContext<
{
[k in keyof z.objectUtil.addQuestionMarks<
z.baseObjectOutputType<T>,
any
>]: z.objectUtil.addQuestionMarks<z.baseObjectOutputType<T>, any>[k]
},
{
[k in keyof z.objectUtil.addQuestionMarks<
z.baseObjectOutputType<T>,
any
>]: z.objectUtil.addQuestionMarks<z.baseObjectOutputType<T>, any>[k]
}
>
/**
* 返回带表单值的回调函数
*/
handleSubmit: HandleSubmit<UnwrapZod<T>>
/**
* 结束回调函数
*/
onFinish: () => void
}) => Promise<void>
fieldConfig?: (
values: UnwrapZod<T>
) => Partial<Record<keyof T, FieldConfigItem>>
schema: z.ZodObject<T>
autoOpen?: boolean
}
type HandleSubmit<T extends GenericObject> = (
cb: SubmissionHandler<T, T, any>
) => Promise<any>
export const useAutoFormDialog = () => {
const openAutoFormDialog = <T extends Record<string, z.ZodTypeAny>>(
dialogOption: AutoFormDialogOption<T>
) => {
//
return {
component: <AutoFormModal />,
}
}
return {
openAutoFormDialog,
}
}
- 说明:继承自
useAutoFormDialog
,打开表格 dialog,并返回 component - 类型定义:
export const useAutoFormDialog = () => {
const openAutoFormDialog = <T extends Record<string, z.ZodTypeAny>>(
dialogOption: AutoFormDialogOption<T>
) => {
//
return {
component: <AutoFormModal />,
}
}
return {
openAutoFormDialog,
}
}
-
说明:内容卡片组件
-
类型定义:
- 卡片标题
- 插槽:右侧栏
const props = withDefaults(
defineProps<{
title?: string
}>(),
{}
)
defineSlots<{
suffix(): any
default(): any
}>()
- 说明:表格容器组件
- 类型定义:无
- 说明:表单组件
- 类型定义:
export type FieldConfigItem = {
/**
* 是否忽略显示
*/
ignore?: boolean
inputProps?: any & {
/**
* 是否展示label
*/
showLabel?: boolean
/**
* placeholder
*/
placeholder?: string
/**
* 是否disabled
*/
disabled?: boolean
}
/**
* 字段渲染的组件类型
*/
fieldType?: keyof typeof INPUT_COMPONENTS
}
export type FieldConfig<SchemaType extends z.infer<z.ZodObject<any, any>>> = {
[Key in keyof SchemaType]?: SchemaType[Key] extends object ?
FieldConfig<z.infer<SchemaType[Key]>>
: FieldConfigItem
}
export type AutoFormInputComponentProps = {
fieldConfigItem?: FieldConfigItem
fieldProps: any
zodItem: z.ZodAny
className?: string
}
const AutoForm = defineComponent({
emits: {
submit: () => true,
},
//
props: {
schema: {
type: Object as PropType<ZodObjectOrWrapped>,
},
fieldConfig: {
type: Object as PropType<
MaybeRefOrGetter<FieldConfig<z.infer<ZodObjectOrWrapped>>>
>,
},
// 表单class
formClass: {
type: String,
},
},
})
-
说明:表格组件
-
类型定义:
- 表格所需要的接口值
- 表格 columns
- 表格分页所需参数
const props = defineProps<{
value: T[]
columns: ColumnDef<T, any>[]
paginationProps: PaginationProps
}>()
-
说明:表格编辑,详情,删除,更多相关组件
-
类型定义:
- buttons 种类(编辑,详情,删除, 下载,重试)
- 点击对应 button 触发的函数
export enum OperationKeys {
detail = 'detail',
update = 'update',
delete = 'delete',
download = 'download',
retry = 'retry',
}
//
emits: {
select: (key: OperationKeys) => true,
},
props: {
buttons: {
type: Array as PropType<Array<keyof typeof OperationKeys>>,
required: false,
default: () => [
OperationKeys.detail,
OperationKeys.update,
OperationKeys.delete,
],
},
},
- 本部分和使用无关
- 本部分记录了 zod 定义如何转义类型组件的实现
这部分内容可以不看,是历史遗留产物
zod admin 有如下几种 auto 组件;
export const INPUT_COMPONENTS = {
checkbox: AutoFormCheckbox,
date: AutoFormDate,
select: AutoFormEnum,
radio: AutoFormRadioGroup,
switch: AutoFormSwitch,
textarea: AutoFormTextarea,
number: AutoFormNumber,
fallback: AutoFormInput,
}
其中,当 zod 定义为如下条件的时候,会生成对应的组件;
number 类型,且可选;默认对应数字选择器组件
number: z.coerce.number().optional().describe('数字输入')
string 类型,且可选;默认对应文本输入组件,可以被修改为文本区(textarea)输入组件
fallback: z.string().optional().describe('文本输入')
//修改为文本区(textarea)输入组件
<AutoForm
inline
label-placement={'left'}
schema={schema}
fieldConfig={{
fallback: {
fieldType: 'textarea',
}
}}>
</AutoForm>
boolean 类型,且可选;默认对应复选框组件,可以被修改为开关组件
checkbox: z.boolean().optional().describe('复选框')
//修改为开关组件
<AutoForm
inline
label-placement={'left'}
schema={schema}
fieldConfig={{
checkbox: {
fieldType: 'switch',
}
}}>
</AutoForm>
date 类型,且可选;默认对应日期选择器
date: z.coerce.date().optional().describe('日期选择器')
enum 类型
const options = ['option1', 'option2', 'option3'] as const
select: z.enum(options).optional().describe('选择器')
由于 enum 类型常用于选择器组件使用,但是特殊情况下,也需要将其作为单选组件使用;
radio: z.enum(options).optional().describe('单选'),
//修改为单选组件
<AutoForm
inline
label-placement={'left'}
schema={schema}
fieldConfig={{
radio: {
fieldType: 'radio',
}
}}>
</AutoForm>
有时候需要传递一些嵌套字段,auto-form 同时也支持解析生成
address: z.object({
street: z.string(),
city: z.string(),
zip: z.string(),
}),
auto-form 在默认情况下,选择器的键值对是 enum 定义时候的键值
const options = ['option1', 'option2', 'option3'] as const
select: z.enum(options).optional().describe('选择器')
但有时候需要中文的 key,那么就可以使用 nativeEnum 进行定义
enum Options {
选项1 = 'option1',
选项2 = 'option2',
选项3 = 'option3',
}
select: z.nativeEnum(Options).optional().describe('选择器'),