128 lines
4.2 KiB
JavaScript
128 lines
4.2 KiB
JavaScript
/** 购物车编辑的数据快照、并发校验与选中项恢复;所有比较以规范化的可提交字段为准。 */
|
|
// 合并原商品和编辑值,剔除展示字段,统一数量和可选配置的默认值。
|
|
function e(e, t = {}) {
|
|
let n = {
|
|
...e,
|
|
...t,
|
|
};
|
|
return {
|
|
product_id: Number(n.product_id),
|
|
config_options: n.config_options ?? {},
|
|
qty: Math.max(1, Number(n.qty ?? 1)),
|
|
customfield: n.customfield ?? {},
|
|
self_defined_field: n.self_defined_field ?? {},
|
|
};
|
|
}
|
|
// 隔离编辑页和列表页的可变对象;只用于接口提供的 JSON 数据。
|
|
function t(e) {
|
|
return JSON.parse(JSON.stringify(e));
|
|
}
|
|
// 版本只接受服务端对规范购物车生成的 SHA-256,缺失时停止位置写入。
|
|
function requireCartRevision(cart) {
|
|
if (
|
|
typeof cart?.revision !== `string` ||
|
|
!/^[a-f0-9]{64}$/.test(cart.revision)
|
|
)
|
|
throw Error(`购物车版本缺失,请返回购物车刷新后重试`);
|
|
return cart.revision;
|
|
}
|
|
// 递归排序对象键并省略 undefined,使相同内容不受属性插入顺序影响。
|
|
function r(e) {
|
|
return Array.isArray(e)
|
|
? e.map(r)
|
|
: e && typeof e == `object`
|
|
? Object.fromEntries(
|
|
Object.entries(e)
|
|
.filter(([, e]) => e !== void 0)
|
|
.sort(([e], [t]) => e.localeCompare(t))
|
|
.map(([e, t]) => [e, r(t)]),
|
|
)
|
|
: e;
|
|
}
|
|
function i(t) {
|
|
return JSON.stringify(r(e(t)));
|
|
}
|
|
// 编辑期间后端若删除了已保存的自定义字段,不能静默丢字段继续保存。
|
|
function a(e, t) {
|
|
if (!e) return;
|
|
let n = e.item.self_defined_field ?? {};
|
|
if (Object.keys(n).some((e) => !t.includes(e)))
|
|
throw Error(`商品自定义字段已调整,请返回购物车删除该项后重新配置`);
|
|
}
|
|
// 选中态只在内存中跨购物车/编辑页传递,不把商品配置长期写入浏览器存储。
|
|
var o = null;
|
|
async function s(e) {
|
|
let t = new TextEncoder().encode(i(e));
|
|
return Array.from(
|
|
new Uint8Array(await crypto.subtle.digest(`SHA-256`, t)),
|
|
(e) => e.toString(16).padStart(2, `0`),
|
|
).join(``);
|
|
}
|
|
/** 记录离开购物车前各项指纹与选中位置;SHA-256 用于内容匹配,不承担身份认证。 */
|
|
async function c(e, t) {
|
|
let n = [...t];
|
|
o = {
|
|
fingerprints: await Promise.all(e.map(s)),
|
|
indices: n,
|
|
};
|
|
}
|
|
/** 一次性消费选中态;相同商品按其重复出现次序匹配,避免全部映射到同一位置。 */
|
|
async function l(e) {
|
|
let t = o;
|
|
if (!t) return null;
|
|
o = null;
|
|
let n = await Promise.all(e.map(s)),
|
|
r = new Set();
|
|
return t.indices.flatMap((e) => {
|
|
let i = t.fingerprints
|
|
.slice(0, e)
|
|
.filter((n) => n === t.fingerprints[e]).length,
|
|
a = n.flatMap((n, r) => (n === t.fingerprints[e] ? [r] : []))[i];
|
|
return a === void 0 || r.has(a) ? [] : (r.add(a), [a]);
|
|
});
|
|
}
|
|
/**
|
|
* n 为 API 客户端,r 包含首次读取时绑定的 position/item/revision;第二参 u 检查报价是否仍有效。
|
|
* 保存始终提交原始版本;冲突必须返回购物车重新确认,不能用新版本授权旧位置。
|
|
*/
|
|
function u(n, r) {
|
|
let a = r ? t(r.item) : null,
|
|
revision = r ? requireCartRevision(r) : null,
|
|
c = !1;
|
|
return async (l, u) => {
|
|
if (!r || !a) throw Error(`购物车编辑上下文缺失`);
|
|
if (c) throw Error(`正在保存,请勿重复操作`);
|
|
c = !0;
|
|
let d = t(e(a, l));
|
|
try {
|
|
if (!u()) throw Error(`配置已改变,请重新确认报价后保存`);
|
|
let l = await n.put(`/cart/${r.position}`, {
|
|
...d,
|
|
expected_revision: revision,
|
|
}),
|
|
f = a;
|
|
a = t({
|
|
...a,
|
|
...d,
|
|
});
|
|
// 成功后同步指纹,返回购物车时仍能识别刚编辑过的选中商品。
|
|
let p = o;
|
|
return (
|
|
p &&
|
|
p.fingerprints[r.position] === (await s(f)) &&
|
|
(p.fingerprints[r.position] = await s(a)),
|
|
l
|
|
);
|
|
} catch (error) {
|
|
if ([409, 428].includes(Number(error?.status)))
|
|
throw Error(`购物车状态已变化,请返回购物车刷新并重新确认配置`, {
|
|
cause: error,
|
|
});
|
|
throw error;
|
|
} finally {
|
|
c = !1;
|
|
}
|
|
};
|
|
}
|
|
export { l as a, c as i, t as n, a as o, u as r, requireCartRevision };
|