1. 排行榜悬浮窗关闭后小按钮消失问题 - 修复面板居中模式关闭时按钮位置错误 - 保存并恢复按钮的居中状态 2. 首页tag按钮按素材数量排序 - 添加按素材数量降序排序逻辑 3. 上传弹窗输入框浅色模式文字不可见 - 添加 text-slate-900 确保浅色模式下文字可见 - 添加 placeholder 样式确保占位符正确显示
280 lines
7.1 KiB
TypeScript
280 lines
7.1 KiB
TypeScript
/**
|
||
* 字段验证规则配置和验证逻辑
|
||
*
|
||
* 支持的验证规则:
|
||
* - required: 必填验证
|
||
* - maxLength: 最大长度(text/textarea)
|
||
* - minLength: 最小长度(text/textarea)
|
||
* - min: 最小值(number)
|
||
* - max: 最大值(number)
|
||
* - pattern: 正则表达式验证
|
||
* - customMessage: 自定义错误提示
|
||
*/
|
||
|
||
export interface ValidationRule {
|
||
// 必填验证
|
||
required?: boolean
|
||
requiredMessage?: string
|
||
|
||
// 长度验证(text/textarea)
|
||
minLength?: number
|
||
minLengthMessage?: string
|
||
maxLength?: number
|
||
maxLengthMessage?: string
|
||
|
||
// 数值范围验证(number)
|
||
min?: number
|
||
minMessage?: string
|
||
max?: number
|
||
maxMessage?: string
|
||
|
||
// 正则表达式验证
|
||
pattern?: string
|
||
patternMessage?: string
|
||
|
||
// 通用自定义错误提示
|
||
customMessage?: string
|
||
}
|
||
|
||
export interface ValidationResult {
|
||
valid: boolean
|
||
errors: string[]
|
||
}
|
||
|
||
/**
|
||
* 解析验证规则JSON字符串
|
||
*/
|
||
export function parseValidationRules(validationsJson: string | null | undefined): ValidationRule | null {
|
||
if (!validationsJson || validationsJson.trim() === '') {
|
||
return null
|
||
}
|
||
|
||
try {
|
||
const parsed = JSON.parse(validationsJson)
|
||
if (typeof parsed !== 'object' || parsed === null) {
|
||
return null
|
||
}
|
||
return parsed as ValidationRule
|
||
} catch {
|
||
console.warn('[FieldValidation] Failed to parse validation rules:', validationsJson)
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 序列化验证规则为JSON字符串
|
||
*/
|
||
export function serializeValidationRules(rules: ValidationRule | null): string {
|
||
if (!rules) {
|
||
return ''
|
||
}
|
||
|
||
// 清理空值
|
||
const cleanedRules: ValidationRule = {}
|
||
|
||
if (rules.required) cleanedRules.required = true
|
||
if (rules.requiredMessage) cleanedRules.requiredMessage = rules.requiredMessage
|
||
|
||
if (typeof rules.minLength === 'number' && rules.minLength > 0) {
|
||
cleanedRules.minLength = rules.minLength
|
||
if (rules.minLengthMessage) cleanedRules.minLengthMessage = rules.minLengthMessage
|
||
}
|
||
|
||
if (typeof rules.maxLength === 'number' && rules.maxLength > 0) {
|
||
cleanedRules.maxLength = rules.maxLength
|
||
if (rules.maxLengthMessage) cleanedRules.maxLengthMessage = rules.maxLengthMessage
|
||
}
|
||
|
||
if (typeof rules.min === 'number') {
|
||
cleanedRules.min = rules.min
|
||
if (rules.minMessage) cleanedRules.minMessage = rules.minMessage
|
||
}
|
||
|
||
if (typeof rules.max === 'number') {
|
||
cleanedRules.max = rules.max
|
||
if (rules.maxMessage) cleanedRules.maxMessage = rules.maxMessage
|
||
}
|
||
|
||
if (rules.pattern) {
|
||
cleanedRules.pattern = rules.pattern
|
||
if (rules.patternMessage) cleanedRules.patternMessage = rules.patternMessage
|
||
}
|
||
|
||
if (rules.customMessage) cleanedRules.customMessage = rules.customMessage
|
||
|
||
// 如果没有任何规则,返回空字符串
|
||
if (Object.keys(cleanedRules).length === 0) {
|
||
return ''
|
||
}
|
||
|
||
return JSON.stringify(cleanedRules)
|
||
}
|
||
|
||
/**
|
||
* 验证单个字段值
|
||
*/
|
||
export function validateFieldValue(
|
||
value: unknown,
|
||
fieldType: string,
|
||
rules: ValidationRule | null,
|
||
fieldLabel?: string
|
||
): ValidationResult {
|
||
const errors: string[] = []
|
||
const label = fieldLabel || '此字段'
|
||
|
||
if (!rules) {
|
||
return { valid: true, errors: [] }
|
||
}
|
||
|
||
const stringValue = value === null || value === undefined ? '' : String(value).trim()
|
||
const isEmpty = stringValue === ''
|
||
|
||
// 必填验证
|
||
if (rules.required && isEmpty) {
|
||
errors.push(rules.requiredMessage || `${label}不能为空`)
|
||
// 如果必填且为空,不继续其他验证
|
||
return { valid: false, errors }
|
||
}
|
||
|
||
// 如果值为空且非必填,跳过其他验证
|
||
if (isEmpty) {
|
||
return { valid: true, errors: [] }
|
||
}
|
||
|
||
// 文本长度验证
|
||
if (fieldType === 'text' || fieldType === 'textarea') {
|
||
if (typeof rules.minLength === 'number' && stringValue.length < rules.minLength) {
|
||
errors.push(rules.minLengthMessage || `${label}长度不能少于${rules.minLength}个字符`)
|
||
}
|
||
|
||
if (typeof rules.maxLength === 'number' && stringValue.length > rules.maxLength) {
|
||
errors.push(rules.maxLengthMessage || `${label}长度不能超过${rules.maxLength}个字符`)
|
||
}
|
||
}
|
||
|
||
// 数值范围验证
|
||
if (fieldType === 'number') {
|
||
const numValue = parseFloat(stringValue)
|
||
|
||
if (isNaN(numValue)) {
|
||
errors.push(`${label}必须是有效的数字`)
|
||
} else {
|
||
if (typeof rules.min === 'number' && numValue < rules.min) {
|
||
errors.push(rules.minMessage || `${label}不能小于${rules.min}`)
|
||
}
|
||
|
||
if (typeof rules.max === 'number' && numValue > rules.max) {
|
||
errors.push(rules.maxMessage || `${label}不能大于${rules.max}`)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 正则表达式验证
|
||
if (rules.pattern) {
|
||
try {
|
||
const regex = new RegExp(rules.pattern)
|
||
if (!regex.test(stringValue)) {
|
||
errors.push(rules.patternMessage || `${label}格式不正确`)
|
||
}
|
||
} catch (e) {
|
||
console.warn('[FieldValidation] Invalid regex pattern:', rules.pattern)
|
||
}
|
||
}
|
||
|
||
return {
|
||
valid: errors.length === 0,
|
||
errors
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 验证多个字段
|
||
*/
|
||
export function validateFields(
|
||
fields: Array<{
|
||
key: string
|
||
label: string
|
||
type: string
|
||
validations?: string | null
|
||
value: unknown
|
||
}>
|
||
): Map<string, ValidationResult> {
|
||
const results = new Map<string, ValidationResult>()
|
||
|
||
for (const field of fields) {
|
||
const rules = parseValidationRules(field.validations)
|
||
const result = validateFieldValue(field.value, field.type, rules, field.label)
|
||
results.set(field.key, result)
|
||
}
|
||
|
||
return results
|
||
}
|
||
|
||
/**
|
||
* 检查验证规则是否有效(用于保存模板时)
|
||
*/
|
||
export function validateValidationRules(rules: ValidationRule): { valid: boolean; error?: string } {
|
||
// 检查长度规则冲突
|
||
if (typeof rules.minLength === 'number' && typeof rules.maxLength === 'number') {
|
||
if (rules.minLength > rules.maxLength) {
|
||
return { valid: false, error: '最小长度不能大于最大长度' }
|
||
}
|
||
}
|
||
|
||
// 检查数值范围规则冲突
|
||
if (typeof rules.min === 'number' && typeof rules.max === 'number') {
|
||
if (rules.min > rules.max) {
|
||
return { valid: false, error: '最小值不能大于最大值' }
|
||
}
|
||
}
|
||
|
||
// 检查正则表达式是否有效
|
||
if (rules.pattern) {
|
||
try {
|
||
new RegExp(rules.pattern)
|
||
} catch {
|
||
return { valid: false, error: '正则表达式格式无效' }
|
||
}
|
||
}
|
||
|
||
return { valid: true }
|
||
}
|
||
|
||
/**
|
||
* 获取字段类型支持的验证规则
|
||
*/
|
||
export function getSupportedValidations(fieldType: string): string[] {
|
||
const common = ['required']
|
||
|
||
switch (fieldType) {
|
||
case 'text':
|
||
case 'textarea':
|
||
return [...common, 'minLength', 'maxLength', 'pattern']
|
||
case 'number':
|
||
return [...common, 'min', 'max']
|
||
case 'select':
|
||
case 'checkbox':
|
||
case 'radio':
|
||
return common
|
||
case 'date':
|
||
return common
|
||
case 'file':
|
||
case 'image':
|
||
return common
|
||
default:
|
||
return common
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 默认验证规则
|
||
*/
|
||
export const DEFAULT_VALIDATION_RULES: ValidationRule = {
|
||
required: false,
|
||
minLength: undefined,
|
||
maxLength: undefined,
|
||
min: undefined,
|
||
max: undefined,
|
||
pattern: undefined,
|
||
}
|