chore: initial commit
This commit is contained in:
25
src/schemas/system/api.ts
Normal file
25
src/schemas/system/api.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
import { HttpMethodSchema } from '@/enum'
|
||||
|
||||
export const SysApiSchema = z.object({
|
||||
id: z.number().int(),
|
||||
name: z.string(),
|
||||
group_name: z.string(),
|
||||
method: HttpMethodSchema,
|
||||
path: z.string(),
|
||||
sort: z.number(),
|
||||
})
|
||||
|
||||
export type SysApi = z.infer<typeof SysApiSchema>
|
||||
|
||||
export const SysApiFormSchema = z.object({
|
||||
id: z.number().optional(),
|
||||
name: z.string().min(2, '接口名称至少2个字符').max(16, '接口名称不能超过16个字符'),
|
||||
group_name: z.string().min(2, '分组名称至少2个字符').max(16, '分组名称不能超过16个字符'),
|
||||
path: z.string().min(2, '接口路径至少2个字符').max(200, '接口路径不能超过200个字符'),
|
||||
method: HttpMethodSchema,
|
||||
sort: z.coerce.number<number>().int().min(0, '排序值不能小于0'),
|
||||
})
|
||||
|
||||
export type SysApiForm = z.infer<typeof SysApiFormSchema>
|
||||
22
src/schemas/system/file.ts
Normal file
22
src/schemas/system/file.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const sysFileSchema = z.object({
|
||||
id: z.number().int(),
|
||||
file_name: z.string(),
|
||||
file_path: z.string(),
|
||||
original_name: z.string(),
|
||||
folder_name: z.string(),
|
||||
mime_type: z.string(),
|
||||
file_size: z.number().int(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
})
|
||||
|
||||
export type SysFile = z.infer<typeof sysFileSchema>
|
||||
|
||||
export const sysFileFormSchema = z.object({
|
||||
id: z.number().int(),
|
||||
file_path: z.string(),
|
||||
})
|
||||
|
||||
export type SysFileForm = z.infer<typeof sysFileFormSchema>
|
||||
66
src/schemas/system/menu.ts
Normal file
66
src/schemas/system/menu.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
import { MenuIconKey, MenuType, MenuTypeSchema, StatusSchema, type MenuIconKeyValues } from '@/enum'
|
||||
|
||||
const MenuIconKeySchema = z.custom<MenuIconKeyValues>(
|
||||
(value) => Object.values(MenuIconKey).includes(value as MenuIconKeyValues),
|
||||
{
|
||||
message: '请选择菜单图标',
|
||||
},
|
||||
)
|
||||
|
||||
export const sysMenuSchema = z.object({
|
||||
id: z.number().int(),
|
||||
name: z.string(),
|
||||
path: z.string(),
|
||||
component: z.string(),
|
||||
hidden: z.boolean(),
|
||||
sort: z.number(),
|
||||
type: MenuTypeSchema,
|
||||
status: StatusSchema,
|
||||
permission_code: z.string(),
|
||||
parent_id: z.number().int().nullable(),
|
||||
icon: MenuIconKeySchema.nullable(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
})
|
||||
|
||||
export type SysMenu = z.infer<typeof sysMenuSchema>
|
||||
|
||||
export interface SysMenuTree extends SysMenu {
|
||||
children?: SysMenuTree[]
|
||||
}
|
||||
|
||||
export const sysMenuFormSchema = z
|
||||
.object({
|
||||
id: z.number().optional(),
|
||||
name: z.string().min(1, '菜单名称不能为空').max(16, '菜单名称不能超过16个字符'),
|
||||
path: z.string().min(1, '路径不能为空'),
|
||||
component: z.string().optional(),
|
||||
hidden: z.boolean(),
|
||||
// https://www.reddit.com/r/reactjs/comments/1mmfnyt/typescript_error_when_using_zcoercenumberstring/
|
||||
sort: z.coerce.number<number>().int().min(0, '排序值不能小于0'),
|
||||
type: MenuTypeSchema.refine((val) => val !== undefined, {
|
||||
message: '请选择菜单类型',
|
||||
}),
|
||||
status: StatusSchema.refine((val) => val !== undefined, {
|
||||
message: '请选择状态',
|
||||
}),
|
||||
permission_code: z.string().min(1, '权限编码不能为空'),
|
||||
parent_id: z.number().int().nullable().optional(),
|
||||
icon: MenuIconKeySchema.nullable(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.type === MenuType.menu && !data.component) {
|
||||
ctx.addIssue({ code: 'custom', path: ['component'], message: '组件路径不能为空' })
|
||||
}
|
||||
})
|
||||
.transform((data) => {
|
||||
if (data.type !== MenuType.menu) {
|
||||
data.component = ''
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
export type SysMenuForm = z.infer<typeof sysMenuFormSchema>
|
||||
30
src/schemas/system/role.ts
Normal file
30
src/schemas/system/role.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const sysRoleSchema = z.object({
|
||||
id: z.number().int(),
|
||||
name: z.string(),
|
||||
code: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
})
|
||||
|
||||
export type SysRole = z.infer<typeof sysRoleSchema>
|
||||
|
||||
export const sysRoleFormSchema = z
|
||||
.object({
|
||||
id: z.number().optional(),
|
||||
name: z.string().min(2, '角色名称至少2个字符').max(16, '角色名称不能超过16个字符'),
|
||||
code: z
|
||||
.string()
|
||||
.min(4, '角色编码至少4个字符')
|
||||
.max(16, '角色编码不能超过16个字符')
|
||||
.regex(/^[a-zA-Z0-9_]+$/, '角色编码仅支持字母、数字和下划线')
|
||||
.optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (!data.id && !data.code) {
|
||||
ctx.addIssue({ code: 'custom', path: ['code'], message: '角色编码为必填项' })
|
||||
}
|
||||
})
|
||||
|
||||
export type SysRoleForm = z.infer<typeof sysRoleFormSchema>
|
||||
92
src/schemas/system/user.ts
Normal file
92
src/schemas/system/user.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
import { sysFileFormSchema } from './file'
|
||||
import { sysMenuSchema } from './menu'
|
||||
|
||||
export const sysUserSchema = z.object({
|
||||
id: z.number().int(),
|
||||
account: z.string(),
|
||||
username: z.string(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
avatar_url: z.string().optional(),
|
||||
avatar_id: z.number().int().optional(),
|
||||
})
|
||||
|
||||
export type SysUser = z.infer<typeof sysUserSchema>
|
||||
|
||||
export const sysUserInfoSchema = sysUserSchema
|
||||
.pick({
|
||||
id: true,
|
||||
account: true,
|
||||
username: true,
|
||||
created_at: true,
|
||||
avatar_url: true,
|
||||
})
|
||||
.extend({
|
||||
roles: z.array(z.string()),
|
||||
menus: z.array(sysMenuSchema),
|
||||
})
|
||||
|
||||
export type SysUserInfo = z.infer<typeof sysUserInfoSchema>
|
||||
|
||||
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{5,16}$/
|
||||
|
||||
export const changePasswordFormSchema = z
|
||||
.object({
|
||||
password: z
|
||||
.string()
|
||||
.min(1, '密码为必填项')
|
||||
.regex(passwordRegex, '密码需5-16位且包含字母和数字'),
|
||||
confirmPassword: z.string().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.password !== data.confirmPassword) {
|
||||
ctx.addIssue({ code: 'custom', path: ['confirmPassword'], message: '两次输入的密码不一致' })
|
||||
}
|
||||
})
|
||||
|
||||
export type ChangePasswordForm = z.infer<typeof changePasswordFormSchema>
|
||||
|
||||
export const sysUserFormSchema = z
|
||||
.object({
|
||||
id: z.number().optional(),
|
||||
account: z
|
||||
.string()
|
||||
.min(4, '账号至少4个字符')
|
||||
.regex(/^[a-zA-Z0-9_]+$/, '账号仅支持字母、数字和下划线'),
|
||||
username: z
|
||||
.string()
|
||||
.min(2, '用户名称至少2个字符')
|
||||
.max(16, '用户名称不能超过16个字符')
|
||||
.optional(),
|
||||
password: z.string().regex(passwordRegex, '密码需5-16位且包含字母和数字').optional(),
|
||||
confirmPassword: z.string().optional(),
|
||||
avatar: z.array(sysFileFormSchema).optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (!data.id) {
|
||||
if (!data.username) {
|
||||
ctx.addIssue({ code: 'custom', path: ['username'], message: '用户名称为必填项' })
|
||||
}
|
||||
|
||||
if (!data.password) {
|
||||
ctx.addIssue({ code: 'custom', path: ['password'], message: '密码为必填项' })
|
||||
}
|
||||
|
||||
if (data.password !== data.confirmPassword) {
|
||||
ctx.addIssue({ code: 'custom', path: ['confirmPassword'], message: '两次输入的密码不一致' })
|
||||
}
|
||||
}
|
||||
})
|
||||
.transform((data) => {
|
||||
const { avatar, ...rest } = data
|
||||
return {
|
||||
...rest,
|
||||
avatar_id: avatar && avatar.length > 0 ? avatar[0].id : undefined,
|
||||
}
|
||||
})
|
||||
|
||||
export type SysUserFormInput = z.input<typeof sysUserFormSchema>
|
||||
|
||||
export type SysUserFormOutput = z.output<typeof sysUserFormSchema>
|
||||
Reference in New Issue
Block a user