- BasicLayout:侧边栏/顶栏/面包屑/移动端抽屉;菜单从路由表派生; 路由守卫新增 requiresAdmin 层(菜单隐藏 + 直连 URL 弹回) - 用户/角色/日志三个页面:分页请求序号防竞态、确认动作流抽 useConfirmAction、 时间范围配 default-time、日志详情按需懒加载并缓存 - 空 catch 统一改 handledError 显式声明;词表/选项常量集中(与后端对齐), 「停用」文案全站统一为「禁用」 - 品牌色提升为全局 token,Element Plus 主色指向赭金 - e2e:公共工具抽 common.py;系统管理 13 项(含详情懒加载脱敏断言), 管理员密码改 E2E_ADMIN_PASSWORD 环境变量;login_flow 8 项回归通过 Co-Authored-By: Claude Code <noreply@anthropic.com>
39 lines
1.2 KiB
TypeScript
39 lines
1.2 KiB
TypeScript
/** 表单校验工具。 */
|
||
|
||
/**
|
||
* 密码校验:按 **UTF-8 字节数** 而非字符数。
|
||
*
|
||
* 后端 bcrypt 对超过 72 字节的密码直接拒绝;中文一个字符占 3 字节,
|
||
* 24 个汉字即到顶 —— 按字符数校验(如 maxlength=20)根本拦不住,
|
||
* 会出现"前端放行、后端 400"的错位体验。与后端 `PASSWORD_*_BYTES` 对齐。
|
||
*/
|
||
export const PASSWORD_MIN_BYTES = 8
|
||
export const PASSWORD_MAX_BYTES = 72
|
||
|
||
const BYTE_HINT = '(中文一个字符约 3 字节)'
|
||
|
||
/** 计算 UTF-8 字节数 */
|
||
export function passwordByteLength(value: string): number {
|
||
return new TextEncoder().encode(value).length
|
||
}
|
||
|
||
/** Element Plus 表单校验器(用于 el-form 的 rules.validator) */
|
||
export function passwordRule(
|
||
_rule: unknown,
|
||
value: string,
|
||
callback: (error?: Error) => void,
|
||
): void {
|
||
if (!value) {
|
||
callback(new Error('请输入密码'))
|
||
return
|
||
}
|
||
const length = passwordByteLength(value)
|
||
if (length < PASSWORD_MIN_BYTES) {
|
||
callback(new Error(`密码不足 ${PASSWORD_MIN_BYTES} 字节${BYTE_HINT}`))
|
||
} else if (length > PASSWORD_MAX_BYTES) {
|
||
callback(new Error(`密码超过 ${PASSWORD_MAX_BYTES} 字节上限${BYTE_HINT}`))
|
||
} else {
|
||
callback()
|
||
}
|
||
}
|