/** * 财务/通用商品配置适配与容量文案解析:将数字类型码转换为前端可编辑模型。 * 数据不完整时明确失败,不能用空选项、零库存或默认价格掩盖后端配置错误。 */ import { n as product_fields_n } from "~/lib/product-fields.js"; import { t as bandwidth_display_t } from "~/lib/bandwidth-display.js"; // 类型码来自既有商品接口:n 为数量类,r 为离散选择类(含级联根)。 var n = new Set([4, 7, 9, 11, 14, 15, 16, 17, 18, 19]), r = new Set([1, 2, 6, 8, 10, 13, 20]); function i(e) { return typeof e == `object` && e && !Array.isArray(e) ? e : null; } function a(e) { return typeof e == `string` ? e : typeof e == `number` ? String(e) : ``; } function o(e) { return e === !0 || e === 1 || e === `1`; } function s(e) { throw Error(`${e},请刷新重试或联系客服`); } // 数值不能靠 Number("") 得到伪造的 0;同时校验有限值和业务下界。 function c(e, t, n = 0) { ((typeof e != `number` && typeof e != `string`) || String(e).trim() === ``) && s(t); let r = Number(e); return ((!Number.isFinite(r) || r < n) && s(t), r); } function l(e) { let t = c(e, `商品配置标识无效`, 1); return (Number.isSafeInteger(t) || s(`商品配置标识无效`), t); } function u(e, t = `商品配置数据不完整`) { return (Array.isArray(e) || s(t), e); } // 至少一个选项且 value 唯一,避免控件无法区分不同条目。 function d(e) { return ( (!e.length || new Set(e.map((e) => e.value)).size !== e.length) && s(`商品配置选项不完整`), e ); } // 清理区域文案中的“第一区/1区”等编号及冗余分隔,不改区域 id。 function f(e) { let t = `(?:第\\s*)?(?:[一二三四五六七八九十百零〇两]+|[0-90-9]+)\\s*区`; return e .replace(RegExp(`[((]\\s*${t}\\s*[))]`, `g`), ``) .replace(RegExp(t, `g`), ``) .replace(/\s{2,}/g, ` `) .replace(/^[\s\-—_/·]+|[\s\-—_/·]+$/g, ``) .trim(); } /** 展平系统版本、区域嵌套或普通子选项;返回统一 value/label,系统额外保留 group。 */ function p(e, n) { if (n === 5) { let t = i(e.sub); return ( t || s(`操作系统配置不完整`), d( Object.entries(t).flatMap(([e, t]) => u(i(t)?.child).map((t) => { let n = i(t); return ( n || s(`操作系统配置不完整`), { value: l(n.id), label: a(n.version || n.option_name).trim() || e, group: e, } ); }), ), ) ); } return d( n === 12 ? u(e.sub).flatMap((e) => { let t = i(e); return ( t || s(`区域配置不完整`), u(t.area).map((e) => { let n = i(e); n || s(`区域配置不完整`); let r = [f(a(t.option_name)), f(a(n.area_zh || n.area))].filter( Boolean, ); return { value: l(n.id), label: [...new Set(r)].join(` / `) || `默认区域`, }; }) ); }) : u(e.sub).map((e) => { let n = i(e); n || s(`商品配置选项不完整`); let r = l(n.id); return { value: r, label: bandwidth_display_t(a(n.option_name).trim()) || String(r), }; }), ); } /** * 转换一个配置项:布尔、系统、选择、级联或数量。 * 数量上下界与各子区间求交,保留不连续 ranges;最大值为 0 的禁用数量项不展示。 */ function m(e) { let t = i(e); t || s(`商品配置数据不完整`); let o = c(t.option_type, `商品配置类型无效`); !r.has(o) && !n.has(o) && ![3, 5, 12].includes(o) && s(`该商品配置类型暂不支持`); let d = { id: String(l(t.id)), label: a(t.option_name).trim() || `配置选项`, kind: o === 20 ? `cascade` : o === 3 ? `boolean` : o === 5 ? `os` : n.has(o) ? `quantity` : `choice`, choices: [], min: 0, max: 0, step: 1, unit: a(t.unit) || (o === 14 ? `GB` : ``), description: a(t.notes), }; if (d.kind === `boolean`) return ( (d.max = 1), (d.choices = [ { value: 0, label: `否`, }, { value: 1, label: `是`, }, ]), d ); if (d.kind !== `quantity`) return ((d.choices = p(t, o)), d); if ( ((d.min = c(t.qty_minimum, `商品数量配置无效`)), (d.max = c(t.qty_maximum, `商品数量配置无效`, d.min)), d.max === 0) ) return null; d.step = t.qty_stage === void 0 || t.qty_stage === null || t.qty_stage === `` ? 1 : c(t.qty_stage, `商品数量步长配置无效`, Number.MIN_VALUE); let f = u(t.sub) .map((e) => { let t = i(e); t || s(`商品数量范围配置无效`); let n = Math.max(d.min, c(t.qty_minimum, `商品数量范围配置无效`)), r = Math.min(d.max, c(t.qty_maximum, `商品数量范围配置无效`)); return r < n ? null : { min: n, max: r, step: d.step, }; }) .filter((e) => e !== null); return ( (!f.length || !f.some((e) => e.min === d.min)) && s(`商品数量范围配置不完整`), (d.ranges = f), d ); } // 主机名/密码分别带 show 和 modify,展示与是否允许修改是两个独立开关。 function h(e, t) { let n = i(e); return { show: o(n?.show), editable: o(n?.modify), value: a(n?.[t]), }; } /** 转换下单自定义字段,复用 PCRE 适配;不支持类型、空下拉选项或重复 id 均报错。 */ function g(t) { if (t == null) return []; let n = u(t, `自定义字段配置不完整`).map((t) => { let n = i(t); n || s(`自定义字段配置不完整`); let r = { text: `text`, password: `password`, textarea: `textarea`, dropdown: `select`, tickbox: `boolean`, checkbox: `boolean`, link: `link`, explain: `explain`, }[a(n.field_type)]; r || s(`该商品自定义字段类型暂不支持`); let c = product_fields_n(n.regexpr), u = []; if (r === `select`) { let e = typeof n.field_option == `string` ? n.field_option.split(`,`) : Array.isArray(n.field_option) && n.field_option.every((e) => typeof e == `string`) ? n.field_option : []; ((u = [...new Set(e.map((e) => e.trim()).filter(Boolean))].map((e) => ({ value: e, label: e, }))), u.length || s(`自定义字段选项不完整`)); } return { id: String(l(n.id)), label: a(n.field_name).trim() || `自定义字段`, kind: r, required: o(n.is_required), ...c, explain: a(n.explain_content), options: u, description: a(n.description), }; }); return ( new Set(n.map((e) => e.id)).size !== n.length && s(`自定义字段标识重复`), n ); } /** * 商品配置的总入口:兼容业务数据和 data 包装,生成选项、周期、库存及联动定义。 * stock 为 null 表示未启用库存控制,不能与真实的 0 库存混淆。 */ function _(e) { let t = i(e), n = i(t?.product) ? t : i(t?.data), r = i(n?.product); ((!n || !r || (n.status !== void 0 && Number(n.status) !== 200)) && s(`商品配置数据不完整`), n.links !== void 0 && n.links !== null && !Array.isArray(n.links) && s(`商品联动配置数据不完整`)); let l = u(n.option) .map(m) .filter((e) => e !== null); new Set(l.map((e) => e.id)).size !== l.length && s(`商品配置标识重复`); let d = u(r.cycle, `商品周期配置不完整`).map((e) => { let t = i(e), n = a(t?.billingcycle).trim(); return ( n || s(`商品周期配置不完整`), { value: n, label: a(t?.billingcycle_zh).trim() || n, } ); }); (!d.length || new Set(d.map((e) => e.value)).size !== d.length) && s(`商品周期配置不完整`); let f = o(r.stock_control) ? c(r.qty, `商品库存配置无效`) : null; return ( f !== null && !Number.isSafeInteger(f) && s(`商品库存配置无效`), { options: l, cycles: d, allowQty: o(n.allow_qty ?? r.allow_qty), stock: f, hostname: h(r.host, `host`), password: h(r.password, `password`), customFields: g(n.custom_fields), links: v(n.links ?? [], l), linkDefinitions: JSON.parse(JSON.stringify(n.links ?? [])), } ); } /** 将 seq/sneq 联动解析为本地模型;只保留当前配置项存在且选项有效的完整规则。 */ function v(e, t) { let n = new Map(t.map((e) => [e.id, e])); function r(e) { let t = i(e), r = String(l(t?.config_id)), o = n.get(r); (!o || ![`seq`, `sneq`].includes(a(t?.relation))) && s(`商品联动配置数据不完整`); let u = i(t?.sub_id); (!u || !Object.keys(u).length) && s(`商品联动选项不完整`); let d = o.kind === `quantity` ? Object.values(u).map((e) => { let t = i(e), n = c(t?.qty_minimum, `商品联动数量范围无效`); return { min: n, max: c(t?.qty_maximum, `商品联动数量范围无效`, n), }; }) : [], f = []; if (o.kind !== `quantity`) for (let e of Object.keys(u)) { if (!/^\d+$/.test(e)) return null; let t = Number(e); if ( !Number.isSafeInteger(t) || (o.kind === `cascade` && t <= 0) || !o.choices.some((e) => e.value === t) ) return null; f.push(t); } return { optionId: r, relation: t.relation, values: f, ranges: d, }; } return u(e).flatMap((e) => { let t = i(e); if ((t || s(`商品联动配置数据不完整`), !n.has(String(t.config_id)))) return []; let a = r(t); if (!a) return []; let o = u(t.result, `商品联动结果不完整`); o.length || s(`商品联动结果不完整`); let c = o.filter((e) => n.has(String(i(e)?.config_id))); if (!c.length) return []; let l = c.map(r); return l.some((e) => e === null) ? [] : [ { ...a, results: l, }, ]; }); } // seq 表示命中集合/闭区间,sneq 表示其补集;供条件判断和结果过滤共用。 function y(e, t) { let n = e.values.includes(t) || e.ranges.some((e) => t !== void 0 && t >= e.min && t <= e.max); return e.relation === `seq` ? n : !n; } /** 当前表单 n 激活规则 t 后,求出选项 e 的可用交集;不会直接修改输入选项。 */ function b(e, t, n) { let r = t.filter((e) => y(e, n[e.optionId])).flatMap((e) => e.results); return e.map((e) => { let t = r.filter((t) => t.optionId === e.id); if (!t.length) return e; if (e.kind !== `quantity`) return { ...e, choices: e.choices.filter((e) => t.every((t) => y(t, e.value))), }; let n = e.ranges ?? [ { min: e.min, max: e.max, step: e.step, }, ]; for (let e of t) { let t = []; // 在规则边界按原步长分段,而非逐容量枚举;多条结果规则顺序求交。 for (let r of n) { let n = [ ...new Set([ 0, Math.floor((r.max - r.min) / r.step + 1e-7) + 1, ...e.ranges.flatMap((e) => [ Math.max(0, Math.ceil((e.min - r.min) / r.step - 1e-7)), Math.floor((e.max - r.min) / r.step + 1e-7) + 1, ]), ]), ] .filter( (e) => e >= 0 && e <= Math.floor((r.max - r.min) / r.step + 1e-7) + 1, ) .sort((e, t) => e - t); for (let i = 0; i < n.length - 1; i++) { let a = Number((r.min + n[i] * r.step).toFixed(8)), o = Number((r.min + (n[i + 1] - 1) * r.step).toFixed(8)); o >= a && y(e, a) && t.push({ min: a, max: o, step: r.step, }); } } n = t; } return { ...e, ranges: n, min: n[0]?.min ?? 0, max: n.at(-1)?.max ?? -1, }; }); } /** 解析级联接口的子配置与默认选择,校验 id 不与根/其他子项重复,默认值必须可选。 */ function x(e, t) { let n = u( i(u(e, `商品级联配置数据不完整`)[0])?.son, `商品级联配置数据不完整`, ), r = {}; return { options: n.map((e) => { let n = i(e); n || s(`商品级联配置数据不完整`); let a = m({ ...n, option_type: 20, }); (a.id === t || r[a.id] !== void 0) && s(`商品级联配置标识重复`); let o = l(n.checkSubId); return ( a.choices.some((e) => e.value === o) || s(`商品级联默认配置无效`), (r[a.id] = o), { ...a, cascadeRoot: t, } ); }), values: r, }; } // 容量单位保留 B 与 iB 的文案区别,不在此处擅自做十进制/二进制容量换算。 function S(e) { let t = /^([kmgtpe])(?:i?b)?$|^(b)$/i.exec(e.trim()); return t ? t[2] ? `B` : `${t[1].toUpperCase()}${/ib$/i.test(e.trim()) ? `iB` : `B`}` : ``; } /** * 从单一磁盘规格文案提取 size/unit/type;多容量、乘数、价格或未知残留词均返回 null。 * t 仅在原文缺单位时作后备,NFKC 先统一全角字符,避免宽松匹配造成错误容量展示。 */ function C(e, t = ``) { let n = e.normalize(`NFKC`).trim(); if (!n || /[×*¥¥$€]|(?:^|[\s([{])[-+]\s*\d/.test(n)) return null; let r = [...n.matchAll(/\d+(?:\.\d+)?\s*(?:[kmgtpe](?:i?b)?|b)(?![a-z])/gi)], i = r[0], a = ``; if (r.length === 1) a = S(/[a-z]+$/i.exec(i[0])?.[0] ?? ``); else if (r.length === 0 && S(t)) { let e = [...n.matchAll(/\d+(?:\.\d+)?/g)]; if (e.length !== 1) return null; ((i = e[0]), (a = S(t))); } else return null; if (!i || !a) return null; let o = Number(/^\d+(?:\.\d+)?/.exec(i[0])?.[0]); if (!Number.isFinite(o)) return null; let s = n.slice(0, i.index) + n.slice(i.index + i[0].length), c = [...s.matchAll(/NVMe|SSD|HDD|SAS|SATA/gi)].map((e) => e[0].toUpperCase() === `NVME` ? `NVMe` : e[0].toUpperCase(), ); return s .replace( /NVMe|SSD|HDD|SAS|SATA|免费赠送|免费|赠送|系统盘|数据盘|云硬盘|云磁盘|云盘|硬盘|磁盘|存储|容量|高性能|高效|普通|标准|通用型|性能型|弹性|本地/gi, ``, ) .replace(/[\s()[\]{}::·]/g, ``) ? null : { size: o, unit: a, type: [...new Set(c)].join(` `), }; } export { _ as a, v as i, b as n, x as r, C as t };