初始化项目
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 数据校验与冲突提示(1.7.11)。
|
||||
*
|
||||
* 两层:
|
||||
* - RowValidator:按列规则(必填/长度/正则)校验单元格与整行;
|
||||
* - ConflictDetector:乐观并发——提交前比对「读取时版本」与「当前库中版本」,
|
||||
* 不一致即判定冲突,UI 提示用户重新加载或覆盖。
|
||||
*/
|
||||
export interface ColumnRule {
|
||||
column: string;
|
||||
required: boolean;
|
||||
minLength: number;
|
||||
maxLength: number;
|
||||
/** 正则;为空表示不校验。 */
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
export interface ValidationIssue {
|
||||
column: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export class RowValidator {
|
||||
/** 校验单元格,返回错误消息(空数组通过)。 */
|
||||
static validateCell(rule: ColumnRule, value: string): string[] {
|
||||
const errors: string[] = [];
|
||||
const text: string = value === undefined || value === null ? '' : value;
|
||||
if (rule.required && text.trim().length === 0) {
|
||||
errors.push(rule.column + ' is required');
|
||||
return errors;
|
||||
}
|
||||
if (text.length > 0 && rule.maxLength > 0 && text.length > rule.maxLength) {
|
||||
errors.push(rule.column + ' exceeds max length ' + rule.maxLength);
|
||||
}
|
||||
if (text.length > 0 && rule.minLength > 0 && text.length < rule.minLength) {
|
||||
errors.push(rule.column + ' is shorter than min length ' + rule.minLength);
|
||||
}
|
||||
if (text.length > 0 && rule.pattern.length > 0 && !new RegExp(rule.pattern).test(text)) {
|
||||
errors.push(rule.column + ' does not match pattern ' + rule.pattern);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/** 校验整行(values: 列 → 值)。 */
|
||||
static validateRow(values: Map<string, string>, rules: ColumnRule[]): ValidationIssue[] {
|
||||
const issues: ValidationIssue[] = [];
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const rule: ColumnRule = rules[i];
|
||||
const value: string | undefined = values.get(rule.column);
|
||||
const errors: string[] = RowValidator.validateCell(rule, value === undefined ? '' : value);
|
||||
for (let j = 0; j < errors.length; j++) {
|
||||
issues.push({ column: rule.column, message: errors[j] });
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
/** 便捷:无错误为 true。 */
|
||||
static isValid(values: Map<string, string>, rules: ColumnRule[]): boolean {
|
||||
return RowValidator.validateRow(values, rules).length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ConflictInfo {
|
||||
conflict: boolean;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export class ConflictDetector {
|
||||
/**
|
||||
* 乐观并发检测:读取时版本与提交时版本不同即冲突。
|
||||
* 版本可以是时间戳、行版本号或 updated_at 字符串。
|
||||
*/
|
||||
static detect(fetchedVersion: string, currentVersion: string): ConflictInfo {
|
||||
if (fetchedVersion === currentVersion) {
|
||||
return { conflict: false, reason: '' };
|
||||
}
|
||||
return {
|
||||
conflict: true,
|
||||
reason: 'row changed since load: fetched=' + fetchedVersion + ', current=' + currentVersion
|
||||
};
|
||||
}
|
||||
|
||||
/** 批量检测(行号 → {fetched, current})。 */
|
||||
static detectBatch(pairs: Map<number, string[]>): Map<number, ConflictInfo> {
|
||||
const out: Map<number, ConflictInfo> = new Map<number, ConflictInfo>();
|
||||
pairs.forEach((versions: string[], row: number) => {
|
||||
if (versions.length >= 2) {
|
||||
out.set(row, ConflictDetector.detect(versions[0], versions[1]));
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user