Files
console-dev/app/lib/cart-edit.js
T
2026-11-07 08:27:36 +08:00

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 };