212 lines
6.4 KiB
JavaScript
212 lines
6.4 KiB
JavaScript
/** 商品自定义字段协议适配、默认值与提交前校验;服务端仍负责最终字段与规则校验。 */
|
|
function e(e) {
|
|
return e && typeof e == `object` && !Array.isArray(e) ? e : null;
|
|
}
|
|
function t(e) {
|
|
return typeof e == `string` || typeof e == `number` ? String(e) : ``;
|
|
}
|
|
function n(e) {
|
|
return e === !0 || e === 1 || e === `1`;
|
|
}
|
|
// 下拉选项兼容对象数组、纯值数组及逗号/换行分隔字符串,最终统一为 value/label。
|
|
function r(n) {
|
|
return (
|
|
Array.isArray(n)
|
|
? n.flatMap((n) => {
|
|
let r = e(n);
|
|
return r
|
|
? [
|
|
{
|
|
value: t(r.value ?? r.id ?? r.option_value),
|
|
label: t(r.label ?? r.name ?? r.option_name ?? r.value),
|
|
},
|
|
]
|
|
: [t(n)];
|
|
})
|
|
: t(n).split(/[,\n]/)
|
|
)
|
|
.map((e) =>
|
|
typeof e == `string`
|
|
? {
|
|
value: e.trim(),
|
|
label: e.trim(),
|
|
}
|
|
: e,
|
|
)
|
|
.filter((e) => e.value !== ``);
|
|
}
|
|
/**
|
|
* 只转换能确认等价的 ASCII PCRE 子集,不能把“JS 能编译”当作跨引擎语义相同。
|
|
* Unicode、换行模式、未知 escape、POSIX/补集及高级分组均交给后端最终校验。
|
|
*/
|
|
function i(e) {
|
|
let n = t(e).trim();
|
|
if (!n) return {};
|
|
let r = ``,
|
|
i = n[0],
|
|
a = {
|
|
"(": `)`,
|
|
"[": `]`,
|
|
"{": `}`,
|
|
"<": `>`,
|
|
};
|
|
// 识别 /.../i、{...} 等包围符,跳过转义字符并处理成对分隔符的嵌套。
|
|
if (/[^a-zA-Z0-9\\\s\0]/.test(i)) {
|
|
let e = a[i] ?? i,
|
|
t = 1,
|
|
o = -1;
|
|
for (let r = 1; r < n.length; r++) {
|
|
if (n[r] === `\\`) {
|
|
r++;
|
|
continue;
|
|
}
|
|
if ((a[i] && n[r] === i && t++, n[r] === e && --t === 0)) {
|
|
o = r;
|
|
break;
|
|
}
|
|
a[i] || (t = 1);
|
|
}
|
|
if (o > 0 && /^[a-zA-Z\s]*$/.test(n.slice(o + 1)))
|
|
((r = n.slice(o + 1).replace(/\s/g, ``)),
|
|
(n = n.slice(1, o)),
|
|
`\\^$.*+?()[]{}|/`.includes(e) || (n = n.split(`\\${e}`).join(e)));
|
|
else if (`/#~%!`.includes(i)) throw Error(`自定义字段验证规则无效`);
|
|
}
|
|
if (!/^[imsuxADSUXJnr]*$/.test(r))
|
|
throw Error(`自定义字段验证规则修饰符无效`);
|
|
const serverOnly = { pattern: n, patternServerOnly: !0 };
|
|
// 除 i 外的修饰符涉及字符/换行/量词等引擎差异;宁可多一次后端验证,也不误拒合法值。
|
|
if (/[^i]/.test(r)) return serverOnly;
|
|
let s = r ? `i` : ``,
|
|
c = ``,
|
|
l = !1;
|
|
for (let e = 0; e < n.length; e++) {
|
|
let t = n[e];
|
|
if (t.charCodeAt(0) > 127) return serverOnly;
|
|
if (t === `\\`) {
|
|
let t = n[++e];
|
|
if (t === `d`) c += l ? `0-9` : `[0-9]`;
|
|
else if (!l && t === `A`) c += `(?<![\\s\\S])`;
|
|
else if (!l && t === `z`) c += `(?![\\s\\S])`;
|
|
else if (!l && t === `Z`) c += `(?=\\n?(?![\\s\\S]))`;
|
|
else if (t && (`nrtf`.includes(t) || `\\^$.*+?()[]{}|/-`.includes(t)))
|
|
c += `\\${t}`;
|
|
else return serverOnly;
|
|
continue;
|
|
}
|
|
if (t === `[`) {
|
|
// 嵌套方括号可能是 POSIX 类;否定字符组还涉及非 ASCII 编码宽度,不作猜测。
|
|
if (l || n[e + 1] === `^`) return serverOnly;
|
|
l = !0;
|
|
} else if (t === `]`) {
|
|
if (!l) return serverOnly;
|
|
l = !1;
|
|
}
|
|
if (l) {
|
|
c += t;
|
|
continue;
|
|
}
|
|
if (t === `.` || (t === `(` && n[e + 1] === `*`)) return serverOnly;
|
|
if (t === `(` && n[e + 1] === `?`) {
|
|
if (n[e + 2] !== `:`) return serverOnly;
|
|
c += `(?:`;
|
|
e += 2;
|
|
continue;
|
|
}
|
|
if (t === `{`) {
|
|
const quantifier = /^\{\d+(?:,\d*)?\}/.exec(n.slice(e));
|
|
if (!quantifier || n[e + quantifier[0].length] === `+`) return serverOnly;
|
|
c += quantifier[0];
|
|
e += quantifier[0].length - 1;
|
|
continue;
|
|
}
|
|
if (t === `}` || (`*+?`.includes(t) && n[e + 1] === `+`)) return serverOnly;
|
|
// PCRE 默认 $ 只允许最终 LF 前;JS 原生 $ 还会放行 CR/Unicode 行分隔符。
|
|
c += t === `$` ? `(?=\\n?(?![\\s\\S]))` : t;
|
|
}
|
|
try {
|
|
new RegExp(c, s);
|
|
} catch {
|
|
throw Error(`自定义字段验证规则无效`);
|
|
}
|
|
return {
|
|
pattern: c,
|
|
patternFlags: s || void 0,
|
|
};
|
|
}
|
|
/** 只保留当前组件支持的字段类型,id 去重;未知类型不生成一个错误的可提交控件。 */
|
|
function a(a) {
|
|
let o = e(a),
|
|
s = Array.isArray(a) ? a : Array.isArray(o?.data) ? o.data : [],
|
|
c = [];
|
|
for (let a of s) {
|
|
let o = e(a);
|
|
if (!o || o.id === void 0) continue;
|
|
let s = t(o.field_type || o.type).toLowerCase();
|
|
if (
|
|
![
|
|
`text`,
|
|
`password`,
|
|
`textarea`,
|
|
`dropdown`,
|
|
`checkbox`,
|
|
`tickbox`,
|
|
`link`,
|
|
`explain`,
|
|
].includes(s)
|
|
)
|
|
continue;
|
|
let l =
|
|
s === `dropdown`
|
|
? `select`
|
|
: s === `checkbox` || s === `tickbox`
|
|
? `boolean`
|
|
: s;
|
|
c.push({
|
|
id: String(o.id),
|
|
label: t(o.field_name || o.name).trim() || `自定义字段`,
|
|
kind: l,
|
|
required: n(o.is_required ?? o.required),
|
|
...i(o.regexpr),
|
|
description: t(o.description).trim() || void 0,
|
|
explain: t(o.explain_content).trim() || void 0,
|
|
options: l === `select` ? r(o.field_option ?? o.options) : [],
|
|
});
|
|
}
|
|
return c.filter((e, t, n) => n.findIndex((t) => t.id === e.id) === t);
|
|
}
|
|
// explain 仅用于说明,不进入提交模型;其他类型初始为空,由控件和校验共同处理。
|
|
function o(e) {
|
|
return Object.fromEntries(
|
|
e.filter((e) => e.kind !== `explain`).map((e) => [e.id, (e.kind, ``)]),
|
|
);
|
|
}
|
|
/** 检查必填、选项和可本地执行的正则;校验时将布尔值视为 "1"/空串,不修改传入表单。 */
|
|
function s(e, t) {
|
|
for (let n of e) {
|
|
if (n.kind === `explain`) continue;
|
|
let e = t[n.id],
|
|
r =
|
|
n.kind === `boolean`
|
|
? e === !0 || e === 1 || e === `1`
|
|
? `1`
|
|
: ``
|
|
: String(e ?? ``).trim();
|
|
if (n.required && !r) throw Error(`请填写${n.label}`);
|
|
if (r) {
|
|
if (n.kind === `select` && !n.options.some((e) => e.value === r))
|
|
throw Error(`${n.label}选项无效`);
|
|
if (n.pattern && !n.patternServerOnly) {
|
|
let e;
|
|
try {
|
|
e = new RegExp(n.pattern, n.patternFlags);
|
|
} catch {
|
|
throw Error(`${n.label}验证规则无效`);
|
|
}
|
|
if (!e.test(r)) throw Error(`${n.label}格式不正确`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
export { s as i, i as n, a as r, o as t };
|