chore: initial commit

This commit is contained in:
2026-07-22 17:50:30 +08:00
parent d46cdd676e
commit 3abf272e33
190 changed files with 18073 additions and 0 deletions

25
src/schemas/system/api.ts Normal file
View 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>

View 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>

View 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>

View 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>

View 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>