feat(validation): 创建前端验证规则常量文件
以后端 @Valid 注解为唯一真相源,建立 VALIDATION 常量映射, 统一前后端验证规则,消除 roleSort 类不一致问题。
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { VALIDATION, getRules, getInitialValue, type ValidationField } from '@/constants/validation-rules'
|
||||||
|
|
||||||
|
describe('validation-rules', () => {
|
||||||
|
describe('VALIDATION', () => {
|
||||||
|
const requiredFields: ValidationField[] = [
|
||||||
|
'username', 'password', 'email', 'phone',
|
||||||
|
'roleName', 'roleKey', 'roleSort',
|
||||||
|
'menuName', 'dictName', 'dictType', 'dictLabel', 'dictValue',
|
||||||
|
'configName', 'configKey', 'configValue',
|
||||||
|
'noticeTitle', 'noticeContent',
|
||||||
|
'deptName',
|
||||||
|
]
|
||||||
|
|
||||||
|
it.each(requiredFields)('%s should have at least one rule', (field) => {
|
||||||
|
expect(VALIDATION[field].rules.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('username should require 3-50 alphanumeric/underscore/dash', () => {
|
||||||
|
const rules = VALIDATION.username.rules
|
||||||
|
expect(rules.some((r) => 'required' in r && r.required)).toBe(true)
|
||||||
|
expect(rules.some((r) => 'min' in r && r.min === 3)).toBe(true)
|
||||||
|
expect(rules.some((r) => 'max' in r && r.max === 50)).toBe(true)
|
||||||
|
expect(rules.some((r) => 'pattern' in r)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('password should require 8-20 with uppercase, lowercase, digit', () => {
|
||||||
|
const rules = VALIDATION.password.rules
|
||||||
|
expect(rules.some((r) => 'required' in r && r.required)).toBe(true)
|
||||||
|
expect(rules.some((r) => 'min' in r && r.min === 8)).toBe(true)
|
||||||
|
expect(rules.some((r) => 'max' in r && r.max === 20)).toBe(true)
|
||||||
|
expect(rules.some((r) => 'pattern' in r)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('roleSort should have initialValue 1 and min 1', () => {
|
||||||
|
expect(VALIDATION.roleSort.initialValue).toBe(1)
|
||||||
|
const rules = VALIDATION.roleSort.rules
|
||||||
|
expect(rules.some((r) => 'type' in r && r.type === 'number' && 'min' in r && r.min === 1)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('menuSort should have initialValue 0 and min 0', () => {
|
||||||
|
expect(VALIDATION.menuSort.initialValue).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deptName should require 1-100 chars', () => {
|
||||||
|
const rules = VALIDATION.deptName.rules
|
||||||
|
expect(rules.some((r) => 'required' in r && r.required)).toBe(true)
|
||||||
|
expect(rules.some((r) => 'min' in r && r.min === 1)).toBe(true)
|
||||||
|
expect(rules.some((r) => 'max' in r && r.max === 100)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deptSort should have initialValue 0', () => {
|
||||||
|
expect(VALIDATION.deptSort.initialValue).toBe(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getRules', () => {
|
||||||
|
it('should return a copy of rules array', () => {
|
||||||
|
const rules1 = getRules('username')
|
||||||
|
const rules2 = getRules('username')
|
||||||
|
expect(rules1).not.toBe(rules2)
|
||||||
|
expect(rules1).toEqual(rules2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getInitialValue', () => {
|
||||||
|
it('should return initialValue for fields that have it', () => {
|
||||||
|
expect(getInitialValue('roleSort')).toBe(1)
|
||||||
|
expect(getInitialValue('menuSort')).toBe(0)
|
||||||
|
expect(getInitialValue('deptSort')).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return undefined for fields without initialValue', () => {
|
||||||
|
expect(getInitialValue('username')).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import type { Rule } from 'antd/es/form'
|
||||||
|
|
||||||
|
interface FieldValidation {
|
||||||
|
rules: Rule[]
|
||||||
|
initialValue?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredRule(message: string): Rule {
|
||||||
|
return { required: true, message }
|
||||||
|
}
|
||||||
|
|
||||||
|
function lengthRule(min: number, max: number, message: string): Rule {
|
||||||
|
return { min, max, message }
|
||||||
|
}
|
||||||
|
|
||||||
|
function patternRule(pattern: RegExp, message: string): Rule {
|
||||||
|
return { pattern, message }
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeNumberMinRule(min: number, message: string): Rule {
|
||||||
|
return { type: 'number' as const, min, message }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VALIDATION = {
|
||||||
|
username: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入用户名'),
|
||||||
|
lengthRule(3, 50, '用户名长度必须在3-50之间'),
|
||||||
|
patternRule(/^[a-zA-Z0-9_-]+$/, '用户名只能包含字母、数字、下划线和横线'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入密码'),
|
||||||
|
lengthRule(8, 20, '密码长度必须在8-20之间'),
|
||||||
|
patternRule(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/, '密码必须包含大小写字母和数字'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
nickname: {
|
||||||
|
rules: [
|
||||||
|
lengthRule(0, 100, '昵称长度不能超过100'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入邮箱'),
|
||||||
|
{ type: 'email' as const, message: '邮箱格式不正确' },
|
||||||
|
lengthRule(0, 100, '邮箱长度不能超过100'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入手机号'),
|
||||||
|
patternRule(/^1[3-9]\d{9}$/, '手机号格式不正确'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
roleName: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入角色名称'),
|
||||||
|
lengthRule(2, 50, '角色名称长度必须在2-50之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
roleKey: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入角色标识'),
|
||||||
|
lengthRule(2, 50, '角色标识长度必须在2-50之间'),
|
||||||
|
patternRule(/^[a-zA-Z0-9_-]+$/, '角色标识只能包含字母、数字、下划线和横线'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
roleSort: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入排序'),
|
||||||
|
typeNumberMinRule(1, '排序必须大于0'),
|
||||||
|
],
|
||||||
|
initialValue: 1,
|
||||||
|
},
|
||||||
|
menuName: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入菜单名称'),
|
||||||
|
lengthRule(1, 50, '菜单名称长度必须在1-50之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
menuSort: {
|
||||||
|
rules: [
|
||||||
|
typeNumberMinRule(0, '排序不能为负数'),
|
||||||
|
],
|
||||||
|
initialValue: 0,
|
||||||
|
},
|
||||||
|
dictName: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入字典名称'),
|
||||||
|
lengthRule(1, 100, '字典名称长度必须在1-100之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
dictType: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入字典类型'),
|
||||||
|
lengthRule(1, 100, '字典类型长度必须在1-100之间'),
|
||||||
|
patternRule(/^[a-zA-Z0-9_]+$/, '字典类型只能包含字母、数字和下划线'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
dictLabel: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入字典标签'),
|
||||||
|
lengthRule(1, 100, '字典标签长度必须在1-100之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
dictValue: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入字典值'),
|
||||||
|
lengthRule(1, 100, '字典值长度必须在1-100之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
configName: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入配置名称'),
|
||||||
|
lengthRule(1, 100, '配置名称长度必须在1-100之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
configKey: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入配置键'),
|
||||||
|
lengthRule(1, 100, '配置键长度必须在1-100之间'),
|
||||||
|
patternRule(/^[a-zA-Z0-9_.-]+$/, '配置键只能包含字母、数字、下划线、点和横线'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
configValue: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入配置值'),
|
||||||
|
lengthRule(1, 500, '配置值长度必须在1-500之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
noticeTitle: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入标题'),
|
||||||
|
lengthRule(1, 50, '标题长度必须在1-50之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
noticeContent: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入内容'),
|
||||||
|
lengthRule(1, 65535, '内容长度不能超过65535'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
deptName: {
|
||||||
|
rules: [
|
||||||
|
requiredRule('请输入部门名称'),
|
||||||
|
lengthRule(1, 100, '部门名称长度必须在1-100之间'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
deptSort: {
|
||||||
|
rules: [
|
||||||
|
typeNumberMinRule(0, '排序不能为负数'),
|
||||||
|
],
|
||||||
|
initialValue: 0,
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ValidationField = keyof typeof VALIDATION
|
||||||
|
|
||||||
|
export function getRules(field: ValidationField): Rule[] {
|
||||||
|
return [...VALIDATION[field].rules]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInitialValue(field: ValidationField): unknown {
|
||||||
|
return VALIDATION[field].initialValue
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user