- 金额形态正则收进 utils/validate.ts 单点导出(此前四页各写,精度 调整漏改一页即「A 页拦得住 B 页放行后端 400」或历史行无法编辑) - params 版本/历史两抽屉的手写分页五件套换 usePagedList(白得请求 序号防竞态——快速翻页旧响应覆盖新页的手写坑不再复现) - 业务抽屉三份 id 状态收为 drawerRowId computed(单一状态源,消除 「标题 A 业务、留痕表 B 业务」的失同步面) - useEditingRow(三页逐字相同的 computed)与 useDropdownOptions (取数失败不阻断列表的骨架)收口,映射函数由调用方给出 - e2e 冒烟:基础数据 13 步 + 业务全链路 16 步全过 Co-Authored-By: Claude Code <noreply@anthropic.com>
54 lines
2.1 KiB
TypeScript
54 lines
2.1 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 字节)'
|
||
|
||
/**
|
||
* 金额形态正则(与后端 `schemas/business._check_amount` 同口径单点维护)。
|
||
*
|
||
* 「DECIMAL 列 ↔ 前端放行位数」规则此前在业务/发货/回款/人员四页各写一份
|
||
* (businesses 与 shipments 逐字重复,receipts/personnel 又各自内联两位小数
|
||
* 正则),后端精度调整时漏改任何一页,就会出现「A 页面拦得住、B 页面放行
|
||
* 后端 400」或「带精度历史行永远无法编辑」(fba8cfc 的教训)。
|
||
*/
|
||
/** 非负数字、最多两位小数(对齐 DECIMAL(14,2):金额/数量/成本/运费/工资) */
|
||
export const AMOUNT_PATTERN = /^(\d+)(\.\d{1,2})?$/
|
||
/** 非负数字、最多四位小数(对齐 DECIMAL(14,4):单价/指导价) */
|
||
export const PRICE_PATTERN = /^(\d+)(\.\d{1,4})?$/
|
||
/** 全零形态(数量「0 或 0.00」须拦:数量 >0 是明细行的硬约束) */
|
||
export const ZERO_PATTERN = /^0+(\.0+)?$/
|
||
|
||
/** 计算 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()
|
||
}
|
||
}
|