Files
ele-HeiXiu/src/modules/auth/settings/validators.ts
YoungestSongMo 337d6c1205 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>
2026-06-29 18:41:07 +08:00

163 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// 校验函数 — 纯函数,与 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;
}
/** 设备 ID8~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;
}