feat: 构建系统重构 + VCHSM 架构迁移 + 右键菜单修复 + 尺寸参数统一
## 构建、打包与分发系统重构 (build/) - 构建系统收敛至 build/ 目录:build.mjs + electron-builder.js + 6 个脚本 - electron-builder 配置从 package.json 提取为 build/electron-builder.js - 新增 --edition (personal/enterprise) 和 --channel (stable/beta/alpha) 参数 - OSS 路径按渠道隔离 - 新增 pre-build-check.mjs:Git/CHANGELOG/版本/环境变量校验 - 新增 bump-version.mjs:版本号管理 + CHANGELOG 自动生成 - updater.ts 发送 channel/edition/deviceFingerprint - 新增 src/shared/utils/rollout.ts 灰度测试工具 - 新增 10 条 NPM 脚本 ## VCHSM 五层架构迁移 - 7 个业务模块 + ~500 处导入路径同步 ## 修复 - MediaCardContextMenu 改用共享 ContextMenu 组件 - ComboBox 尺寸参数显示统一 (size-format.ts) ## 文档 - UpdateA.md / build/README.md / README.md / .env.example 更新 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
162
src/modules/auth/settings/validators.ts
Normal file
162
src/modules/auth/settings/validators.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
// ============================================================
|
||||
// 校验函数 — 纯函数,与 UI 层解耦
|
||||
// 每个校验器返回 string(错误信息)或 null(通过)
|
||||
// ============================================================
|
||||
|
||||
type ValidateResult = string | null;
|
||||
|
||||
/** 用户名:3~64 位,仅允许字母、数字、-、_、. */
|
||||
export function validateUsername(value: string): ValidateResult {
|
||||
if (!value || !value.trim()) return '请输入用户名';
|
||||
if (value.length < 3) return '用户名至少 3 位';
|
||||
if (value.length > 64) return '用户名最多 64 位';
|
||||
if (!/^[a-zA-Z0-9\-_.]+$/.test(value)) return '仅允许字母、数字、-、_、.';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 密码:6~128 位 */
|
||||
export function validatePassword(value: string): ValidateResult {
|
||||
if (!value) return '请输入密码';
|
||||
if (value.length < 6) return '密码至少 6 位';
|
||||
if (value.length > 128) return '密码最多 128 位';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 确认密码:与密码一致 */
|
||||
export function validateConfirmPassword(value: string, password: string): ValidateResult {
|
||||
if (!value) return '请再次输入密码';
|
||||
if (value !== password) return '两次输入的密码不一致';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 手机号:5~32 位数字(可含 + 前缀) */
|
||||
export function validatePhone(value: string): ValidateResult {
|
||||
if (!value) return '请输入手机号';
|
||||
if (value.length < 5) return '手机号至少 5 位';
|
||||
if (value.length > 32) return '手机号最多 32 位';
|
||||
if (!/^\+?[0-9]+$/.test(value)) return '手机号格式不正确';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 短信验证码:4~8 位数字 */
|
||||
export function validateSmsCode(value: string): ValidateResult {
|
||||
if (!value) return '请输入验证码';
|
||||
if (value.length < 4) return '验证码至少 4 位';
|
||||
if (value.length > 8) return '验证码最多 8 位';
|
||||
if (!/^[0-9]+$/.test(value)) return '验证码仅允许数字';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 设备 ID:8~128 位 */
|
||||
export function validateDeviceId(value: string): ValidateResult {
|
||||
if (!value) return '请输入设备标识';
|
||||
if (value.length < 8) return '设备标识至少 8 位';
|
||||
if (value.length > 128) return '设备标识最多 128 位';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 可选字段:非空时不超过 maxLen */
|
||||
export function optionalMaxLen(maxLen: number, label: string) {
|
||||
return (value: string): ValidateResult => {
|
||||
if (!value) return null;
|
||||
if (value.length > maxLen) return `${label}最多 ${maxLen} 位`;
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/** 邀请码:可选,最多 32 位字母数字 */
|
||||
export function validateReferralCode(value: string): ValidateResult {
|
||||
if (!value) return null;
|
||||
if (value.length > 32) return '邀请码最多 32 位';
|
||||
if (!/^[a-zA-Z0-9]+$/.test(value)) return '仅允许字母和数字';
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对一条字段配置执行所有规则校验
|
||||
* @returns 错误信息数组(空数组表示通过)
|
||||
*/
|
||||
export function validateField(value: string, rules: import('../model/types').FieldRule[]): string[] {
|
||||
const errors: string[] = [];
|
||||
for (const rule of rules) {
|
||||
// required
|
||||
if (rule.required && (!value || !value.trim())) {
|
||||
errors.push(rule.message || '此字段为必填');
|
||||
continue;
|
||||
}
|
||||
if (!value) continue; // 非必填且为空 → 跳过后续校验
|
||||
|
||||
// minLen
|
||||
if (rule.minLen && value.length < rule.minLen) {
|
||||
errors.push(rule.message || `至少 ${rule.minLen} 位`);
|
||||
}
|
||||
// maxLen
|
||||
if (rule.maxLen && value.length > rule.maxLen) {
|
||||
errors.push(rule.message || `最多 ${rule.maxLen} 位`);
|
||||
}
|
||||
// pattern
|
||||
if (rule.pattern && !rule.pattern.test(value)) {
|
||||
errors.push(rule.message || '格式不正确');
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 逐字段运行校验器,有错误则设置并返回 true。
|
||||
*
|
||||
* 用法(在 handleSubmit 中):
|
||||
* if (runFieldChecks(setErrors,
|
||||
* { field: 'phone', error: validatePhone(phone) },
|
||||
* { field: 'pwd', error: validatePassword(pwd) },
|
||||
* { field: 'confirm', error: validateConfirmPassword(confirm, pwd) },
|
||||
* )) return;
|
||||
*/
|
||||
export function runFieldChecks(
|
||||
setErrors: (errors: Record<string, string>) => void,
|
||||
...checks: Array<{ field: string; error: string | null }>
|
||||
): boolean {
|
||||
const newErrors: Record<string, string> = {};
|
||||
for (const { field, error } of checks) {
|
||||
if (error) newErrors[field] = error;
|
||||
}
|
||||
if (Object.keys(newErrors).length > 0) {
|
||||
setErrors(newErrors);
|
||||
return true;
|
||||
}
|
||||
setErrors({});
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验整个表单
|
||||
* @returns 错误映射 { fieldName: errorMsg[] },空对象表示全部通过
|
||||
*/
|
||||
export function validateForm<T extends Record<string, string>>(
|
||||
values: T,
|
||||
fieldConfigs: import('../model/types').FieldConfig[],
|
||||
crossValidators?: Record<string, (value: string, allValues: T) => string | null>,
|
||||
): Record<string, string[]> {
|
||||
const errors: Record<string, string[]> = {};
|
||||
|
||||
for (const config of fieldConfigs) {
|
||||
const value = values[config.name] || '';
|
||||
const fieldErrors = validateField(value, config.rules);
|
||||
if (fieldErrors.length > 0) {
|
||||
errors[config.name] = fieldErrors;
|
||||
}
|
||||
}
|
||||
|
||||
// 跨字段校验(如确认密码)
|
||||
if (crossValidators) {
|
||||
for (const [name, fn] of Object.entries(crossValidators)) {
|
||||
const value = values[name] || '';
|
||||
const err = fn(value, values);
|
||||
if (err) {
|
||||
errors[name] = [...(errors[name] || []), err];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
Reference in New Issue
Block a user