1400 lines
53 KiB
Vue
1400 lines
53 KiB
Vue
<script>
|
||
/**
|
||
* 购物车:读取服务端购物车、逐项重新报价、保留选择并结算。修改使用 revision 或 expected_cart 进行并发校验,不能把列表下标当作永久商品标识。
|
||
*/
|
||
import "~/assets/styles/PaymentDialog.BNFKQF1U.css";
|
||
import "~/assets/styles/cart.CSS2vTmS.css";
|
||
import { toDisplayString } from "vue";
|
||
import { renderList } from "vue";
|
||
import { onMounted } from "vue";
|
||
import { onBeforeUnmount } from "vue";
|
||
import { watch } from "vue";
|
||
import { createBlock } from "vue";
|
||
import { ref } from "vue";
|
||
import { withCtx } from "vue";
|
||
import { createCommentVNode } from "vue";
|
||
import { computed } from "vue";
|
||
import { isRef } from "vue";
|
||
import { createElementBlock } from "vue";
|
||
import { Fragment } from "vue";
|
||
import { createElementVNode } from "vue";
|
||
import { createTextVNode } from "vue";
|
||
import { unref } from "vue";
|
||
import { defineComponent } from "vue";
|
||
import { createVNode } from "vue";
|
||
import { openBlock } from "vue";
|
||
import { useRouter } from "#app";
|
||
import { useApi } from "~/lib/auth.js";
|
||
import { useAuth } from "~/lib/auth.js";
|
||
import dialog from "primevue/dialog";
|
||
import button from "primevue/button";
|
||
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
|
||
import message from "primevue/message";
|
||
import inputtext from "primevue/inputtext";
|
||
import { n as product_name_n } from "~/lib/product-name.js";
|
||
import checkbox from "primevue/checkbox";
|
||
import ListEmpty from "~/components/ListEmpty.vue";
|
||
import PaymentDialog from "~/components/PaymentDialog.vue";
|
||
import skeleton from "primevue/skeleton";
|
||
import { t as os_name_t } from "~/lib/os-name.js";
|
||
import { r as configuration_display_r } from "~/lib/configuration-display.js";
|
||
import { t as configuration_display_t } from "~/lib/configuration-display.js";
|
||
import { t as purchase_limit_state_t } from "~/lib/purchase-limit-state.js";
|
||
import { a as cart_edit_a } from "~/lib/cart-edit.js";
|
||
import { i as cart_edit_i } from "~/lib/cart-edit.js";
|
||
import { t as cart_edit_t } from "~/lib/cart-edit.js";
|
||
// 只接受普通对象形状的数据,供配置摘要解析使用。
|
||
function E(e) {
|
||
return typeof e == `object` && e && !Array.isArray(e) ? e : void 0;
|
||
}
|
||
// 将可展示的配置值限制为文本;疑似 HTML 或编码标签不作为摘要内容。
|
||
function D(e) {
|
||
if (typeof e != `string` && (typeof e != `number` || !Number.isFinite(e)))
|
||
return ``;
|
||
let t = String(e).trim();
|
||
return /<\/?[a-z!]|&(?:amp;)*(?:lt|gt|quot|#0*60|#x0*3c);/i.test(t) ? `` : t;
|
||
}
|
||
// 仅在数值格式与单位明确时生成 CPU/内存/带宽等展示值。
|
||
function O(e, t) {
|
||
let n = D(e);
|
||
return n
|
||
? /^\d+(?:\.\d+)?$/.test(n)
|
||
? `${n} ${t}`
|
||
: RegExp(`^\\d+(?:\\.\\d+)?\\s*${t}$`, `i`).test(n)
|
||
? n
|
||
: ``
|
||
: ``;
|
||
}
|
||
var k = {
|
||
monthly: `月`,
|
||
quarterly: `季`,
|
||
semiannually: `半年`,
|
||
annually: `年`,
|
||
biennially: `两年`,
|
||
triennially: `三年`,
|
||
onetime: `一次性`,
|
||
free: `免费`,
|
||
hour: `小时`,
|
||
hourly: `小时`,
|
||
day: `天`,
|
||
daily: `天`,
|
||
};
|
||
// 将计费周期转换为可读名称,未识别的机器枚举不直接展示。
|
||
function A(e) {
|
||
let t = D(e);
|
||
return k[t.toLowerCase()] ?? (/^(?:[a-z_]+|\d+)$/i.test(t) ? `` : t);
|
||
}
|
||
// 归一化不同模块的配置标签,用于摘要去重和识别 CPU、内存、周期、镜像等字段。
|
||
function j(e, t = ``) {
|
||
return /^(?:cpu|cpu_num)$/i.test(t) ||
|
||
/^(?:cpu|CPU核数|处理器|核数)$/i.test(e)
|
||
? `cpu`
|
||
: t === `memory` || /^(?:内存|内存容量|memory)$/i.test(e)
|
||
? `memory`
|
||
: /^(?:时长|购买时长|周期|计费周期)$/.test(e) ||
|
||
/^(?:duration|billing_cycle)(?:_name)?$/.test(t)
|
||
? `duration`
|
||
: e === `套餐` || t === `package`
|
||
? `package`
|
||
: /^(?:镜像|系统|系统镜像|操作系统|系统版本|镜像版本)$/.test(e) ||
|
||
/^(?:image|image_name|os|os_name|version_name)$/.test(t)
|
||
? `os`
|
||
: e;
|
||
}
|
||
// 摘要排除密码、令牌、私钥及内部 ID 等字段;不能直接遍历配置对象展示所有内容。
|
||
function M(e, t = ``) {
|
||
return (
|
||
/(?:password|passwd|secret|token|private.?key|ssh.?key|credential|description|密码|口令|密钥|令牌|产品介绍|商品介绍|产品描述|商品描述)/i.test(
|
||
`${e} ${t}`,
|
||
) ||
|
||
/(?:^|[_\s])id$/i.test(t) ||
|
||
/(?:^|\s)id$/i.test(e)
|
||
);
|
||
}
|
||
// 当报价接口未提供 preview 时,从已知配置字段整理摘要。
|
||
function N(e, t) {
|
||
let n = [],
|
||
r = (e, t) => {
|
||
t &&
|
||
n.push({
|
||
label: e,
|
||
value: t,
|
||
});
|
||
};
|
||
(t || r(`区域`, D(e.region_name) || D(e.data_center_name)),
|
||
r(t ? `区域` : `线路`, D(e.line_name)),
|
||
t ? r(`线路`, D(e.network_name)) : r(`CPU 型号`, D(e.cpu_model_name)),
|
||
r(`CPU`, O(e.cpu_num, `核`) || O(e.cpu, `核`)),
|
||
r(`内存`, O(e.memory, `GB`)));
|
||
let i = (e) => {
|
||
let t = E(e);
|
||
if (!t) return ``;
|
||
let n = O(t.size, `GB`),
|
||
r = D(t.type ?? t.disk_type);
|
||
return n ? `${n}${r && !/^\d+$/.test(r) ? ` ${r}` : ``}` : ``;
|
||
};
|
||
return (
|
||
r(`系统盘`, i(e.system_disk)),
|
||
Array.isArray(e.data_disk) &&
|
||
e.data_disk.forEach((e, t) => r(`数据盘 ${t + 1}`, i(e))),
|
||
r(`网络类型`, t ? `经典网络` : D(e.network_type) || D(e.network_name)),
|
||
r(`带宽计费方式`, D(e.bill_method_name)),
|
||
r(`带宽`, O(e.bandwidth ?? e.bw, `Mbps`)),
|
||
r(`操作系统`, os_name_t(D(e.version_name))),
|
||
n
|
||
);
|
||
}
|
||
// 优先使用报价 preview,过滤敏感项并合并 CPU/内存/套餐等重复信息;缺失时才使用已知配置字段。
|
||
function pe(e, t) {
|
||
let n = E(e.config_options) ?? {},
|
||
r =
|
||
`package_config_id` in n &&
|
||
(`cpu_model_id` in n || `cpu_model_name` in n),
|
||
i = E(t),
|
||
a = [];
|
||
if (Array.isArray(i?.preview))
|
||
for (let e of i.preview) {
|
||
let t = E(e);
|
||
if (!t) continue;
|
||
let n = D(t.name),
|
||
i = r ? n : configuration_display_t(n, t.option_type),
|
||
o = D(t.value),
|
||
s = D(t.key);
|
||
if (!n || !i || !o || M(n, s)) continue;
|
||
let c = j(i, s);
|
||
(c === `os` && (o = os_name_t(o)),
|
||
!a.some((e) => e.kind === c && e.value === o) &&
|
||
a.push({
|
||
label: i,
|
||
value: o,
|
||
kind: c,
|
||
}));
|
||
}
|
||
if (r && a.length) {
|
||
let e = a.findIndex((e) => /^(?:机型|CPU\s*型号)$/i.test(e.label)),
|
||
t = a.findIndex((e) => /^(?:网络|网络类型)$/.test(e.label)),
|
||
n = !a.some((e) => e.label === `区域`);
|
||
for (let r = 0; r < a.length; r++) {
|
||
let i = a[r];
|
||
((i.label === `区域线路` || (n && i.label === `线路`)) &&
|
||
(i.label = `区域`),
|
||
r === e
|
||
? ((i.label = `网络类型`), (i.value = `经典网络`))
|
||
: r === t && e >= 0
|
||
? (i.label = `线路`)
|
||
: i.label === `网络类型` && (i.value = `经典网络`));
|
||
}
|
||
e >= 0 && t >= 0 && ([a[e], a[t]] = [a[t], a[e]]);
|
||
let r = a.findIndex((e) => e.label === `网络类型`),
|
||
i = a.findIndex((e) => e.label === `线路`);
|
||
r >= 0 && i > r && ([a[r], a[i]] = [a[i], a[r]]);
|
||
}
|
||
let o = a.length
|
||
? a.map(({ label: e, value: t }) => ({
|
||
label: e,
|
||
value: t,
|
||
}))
|
||
: N(n, r);
|
||
if (a.length) {
|
||
let e = [],
|
||
t = a
|
||
.find((e) => e.kind === `package`)
|
||
?.value.match(/^(\d+)\s*核\s*(\d+)\s*G(?:B)?$/i);
|
||
if (!a.some((e) => e.kind === `cpu`)) {
|
||
let r = O(t?.[1] ?? n.cpu_num, `核`) || O(n.cpu, `核`);
|
||
r &&
|
||
e.push({
|
||
label: `CPU`,
|
||
value: r,
|
||
});
|
||
}
|
||
if (!a.some((e) => e.kind === `memory`)) {
|
||
let r = O(t?.[2] ?? n.memory, `GB`);
|
||
r &&
|
||
e.push({
|
||
label: `内存`,
|
||
value: r,
|
||
});
|
||
}
|
||
o.unshift(...e);
|
||
let r =
|
||
o.some((e) => j(e.label) === `cpu`) || a.some((e) => e.kind === `cpu`),
|
||
i =
|
||
o.some((e) => j(e.label) === `memory`) ||
|
||
a.some((e) => e.kind === `memory`);
|
||
if (r && i)
|
||
for (let e = o.length - 1; e >= 0; e--) {
|
||
let t = o[e],
|
||
n = t.value.match(/^(\d+)\s*核\s*(\d+)\s*G(?:B)?$/i),
|
||
r = o.find((e) => j(e.label) === `cpu`)?.value.match(/^\d+/)?.[0],
|
||
i = o.find((e) => j(e.label) === `memory`)?.value.match(/^\d+/)?.[0];
|
||
j(t.label) === `package` &&
|
||
n &&
|
||
n[1] === r &&
|
||
n[2] === i &&
|
||
o.splice(e, 1);
|
||
}
|
||
}
|
||
if (!a.some((e) => e.kind === `duration`)) {
|
||
let e =
|
||
A(i?.billing_cycle_name) ||
|
||
A(i?.billing_cycle) ||
|
||
A(n.duration_name) ||
|
||
A(n.billing_cycle) ||
|
||
k[D(n.cycle).toLowerCase()];
|
||
e &&
|
||
o.push({
|
||
label: `购买时长`,
|
||
value: e,
|
||
});
|
||
}
|
||
return o.map((e) => ({
|
||
label: configuration_display_t(e.label),
|
||
value:
|
||
configuration_display_t(e.label) === `网络类型`
|
||
? configuration_display_r(e.value)
|
||
: e.value,
|
||
}));
|
||
}
|
||
var me = {
|
||
class: `page`,
|
||
},
|
||
he = {
|
||
class: `page-head`,
|
||
},
|
||
ge = {
|
||
key: 3,
|
||
class: `panel`,
|
||
},
|
||
_e = {
|
||
key: 4,
|
||
class: `cart-layout`,
|
||
},
|
||
ve = {
|
||
class: `panel cart-products`,
|
||
},
|
||
ye = {
|
||
class: `cart-head`,
|
||
},
|
||
be = {
|
||
class: `head-text`,
|
||
},
|
||
xe = {
|
||
class: `item-count`,
|
||
},
|
||
Se = [`disabled`],
|
||
Ce = [`disabled`],
|
||
we = {
|
||
class: `item-main`,
|
||
},
|
||
Te = {
|
||
class: `item-name`,
|
||
},
|
||
Ee = {
|
||
key: 0,
|
||
class: `item-configuration`,
|
||
},
|
||
De = {
|
||
key: 1,
|
||
class: `item-configuration-empty`,
|
||
},
|
||
Oe = {
|
||
class: `item-qty`,
|
||
},
|
||
ke = {
|
||
class: `qty-num`,
|
||
},
|
||
Ae = {
|
||
class: `item-price`,
|
||
"aria-live": `polite`,
|
||
},
|
||
je = {
|
||
key: 0,
|
||
class: `quote-status`,
|
||
},
|
||
Me = {
|
||
class: `quote-error`,
|
||
},
|
||
Ne = {
|
||
class: `panel cart-foot`,
|
||
"aria-label": `订单确认`,
|
||
},
|
||
Pe = {
|
||
class: `summary-line`,
|
||
},
|
||
Fe = {
|
||
class: `foot-promo`,
|
||
},
|
||
Ie = {
|
||
key: 0,
|
||
class: `promo-count`,
|
||
},
|
||
Le = {
|
||
class: `foot-total`,
|
||
},
|
||
Re = {
|
||
class: `total`,
|
||
},
|
||
ze = {
|
||
class: `checkout-note`,
|
||
},
|
||
Be = {
|
||
key: 5,
|
||
class: `panel empty-panel`,
|
||
},
|
||
Ve = {
|
||
key: 6,
|
||
class: `panel empty-panel`,
|
||
},
|
||
P = _plugin_vue_export_helper_t(
|
||
defineComponent({
|
||
__name: `index`,
|
||
setup(h) {
|
||
let x = useApi(),
|
||
C = purchase_limit_state_t((e) =>
|
||
x.post(`/purchase_limit/check`, e, !1, {
|
||
timeoutMs: 15e3,
|
||
}),
|
||
),
|
||
w = ref(!1),
|
||
T,
|
||
E = useRouter(),
|
||
D = ref([]),
|
||
O = ref(``),
|
||
k = ref(!0),
|
||
A = ref([]),
|
||
j = ref(!1),
|
||
M = ref(``),
|
||
N = ref(!1),
|
||
P = ref(!1),
|
||
F = ref(null),
|
||
He = ref(``),
|
||
I = ref(``),
|
||
L = computed(() => [
|
||
...new Set(I.value.split(/[\s,]+/).filter(Boolean)),
|
||
]),
|
||
Ue = computed(
|
||
() => D.value.length > 0 && A.value.length === D.value.length,
|
||
),
|
||
R = ref({}),
|
||
z = ref({}),
|
||
B = computed(() => D.value.map((e, t) => pe(e, z.value[t]))),
|
||
V = ref({}),
|
||
H = ref({});
|
||
computed(() => Object.values(H.value).some(Boolean));
|
||
let U = 0,
|
||
W = 0,
|
||
G = !1,
|
||
K = new Map(),
|
||
q = () => JSON.stringify(D.value),
|
||
J = () =>
|
||
O.value
|
||
? {
|
||
expected_revision: O.value,
|
||
}
|
||
: {
|
||
expected_cart: cart_edit_t(D.value),
|
||
},
|
||
Y = (e) =>
|
||
JSON.stringify([
|
||
e.product_id,
|
||
e.config_options,
|
||
e.customfield,
|
||
e.self_defined_field,
|
||
]);
|
||
// 按商品配置签名及同签名出现次数记录选择,列表重排时不误选另一件同款商品。
|
||
function We() {
|
||
let e = new Map();
|
||
return D.value
|
||
.map((t, n) => {
|
||
let r = Y(t),
|
||
i = e.get(r) ?? 0;
|
||
return (e.set(r, i + 1), A.value.includes(n) ? `${r}:${i}` : ``);
|
||
})
|
||
.filter(Boolean);
|
||
}
|
||
// 根据重新加载后的列表重建选择下标,避免沿用过期的位置。
|
||
function Ge(e) {
|
||
let t = new Map();
|
||
A.value = D.value.flatMap((n, r) => {
|
||
let i = Y(n),
|
||
a = t.get(i) ?? 0;
|
||
return (t.set(i, a + 1), e.includes(`${i}:${a}`) ? [r] : []);
|
||
});
|
||
}
|
||
// 金额必须是非空且有限的非负数字;未返回报价不能当作零元。
|
||
function Ke(e) {
|
||
return (
|
||
e != null &&
|
||
String(e).trim() !== `` &&
|
||
Number.isFinite(Number(e)) &&
|
||
Number(e) >= 0
|
||
);
|
||
}
|
||
// 逐项请求最新报价;同时核对购物车代次、列表快照和单项请求序号,旧响应不覆盖新配置。
|
||
async function qe(e = D.value.map((e, t) => t)) {
|
||
let t = U,
|
||
n = q();
|
||
await Promise.all(
|
||
e.map(async (e) => {
|
||
let r = D.value[e];
|
||
if (!r) return;
|
||
let i = (K.get(e) ?? 0) + 1;
|
||
K.set(e, i);
|
||
let a = () => !G && t === U && n === q() && K.get(e) === i;
|
||
((H.value[e] = !0), delete R.value[e], delete V.value[e]);
|
||
try {
|
||
let t = await x.post(`/product/${r.product_id}/config_option`, {
|
||
id: r.product_id,
|
||
config_options: {
|
||
...(r.config_options ?? {}),
|
||
promo_code: ``,
|
||
event_promotion: ``,
|
||
},
|
||
qty: r.qty ?? 1,
|
||
});
|
||
if (!a()) return;
|
||
let n = t?.price_total ?? t?.price;
|
||
if (!Ke(n)) throw Error(`未获得有效报价`);
|
||
((R.value[e] = String(n)),
|
||
(z.value[e] = {
|
||
preview: t.preview,
|
||
billing_cycle_name: t.billing_cycle_name,
|
||
billing_cycle: t.billing_cycle,
|
||
}));
|
||
} catch (t) {
|
||
a() &&
|
||
(delete z.value[e],
|
||
(V.value[e] =
|
||
t instanceof Error ? t.message : `报价加载失败`));
|
||
} finally {
|
||
a() && (H.value[e] = !1);
|
||
}
|
||
}),
|
||
);
|
||
}
|
||
function Je(e) {
|
||
return R.value[e] ?? ``;
|
||
}
|
||
let X = computed(() => {
|
||
let e = 0;
|
||
for (let t of A.value) {
|
||
if (!D.value[t] || H.value[t] || !Ke(R.value[t])) return null;
|
||
e += Number(R.value[t]);
|
||
}
|
||
return e.toFixed(2);
|
||
}),
|
||
Ye = computed(() =>
|
||
A.value.reduce((e, t) => e + (D.value[t]?.qty ?? 1), 0),
|
||
),
|
||
Xe = computed(
|
||
() =>
|
||
!k.value &&
|
||
!j.value &&
|
||
!P.value &&
|
||
A.value.length > 0 &&
|
||
X.value !== null &&
|
||
(!w.value || (C.checked.value && C.allowed.value)),
|
||
);
|
||
// 结算限购预检针对当前选择的位置;没有选择或仍在加载时清除旧预检。
|
||
function Ze() {
|
||
if (!w.value || k.value || !A.value.length) {
|
||
C.reset();
|
||
return;
|
||
}
|
||
return C.check({
|
||
mode: `cart_settle`,
|
||
positions: [...A.value],
|
||
});
|
||
}
|
||
// 刷新购物车时先废弃旧报价,再按配置签名恢复摘要与选择,最后重新报价。
|
||
async function Z(e = !1) {
|
||
let t = ++W,
|
||
n = e ? We() : [],
|
||
r = new Map(D.value.map((e, t) => [Y(e), z.value[t]]));
|
||
((U += 1),
|
||
(H.value = {}),
|
||
(R.value = {}),
|
||
(V.value = {}),
|
||
(k.value = !0),
|
||
(N.value = !1),
|
||
(M.value = ``));
|
||
try {
|
||
let i = await x.get(`/cart`);
|
||
if (G || t !== W) return;
|
||
if (
|
||
((D.value = i.list ?? []),
|
||
(O.value =
|
||
typeof i.revision == `string` &&
|
||
/^[a-f0-9]{64}$/.test(i.revision)
|
||
? i.revision
|
||
: ``),
|
||
(z.value = Object.fromEntries(
|
||
D.value.flatMap((e, t) => {
|
||
let n = r.get(Y(e));
|
||
return n ? [[t, n]] : [];
|
||
}),
|
||
)),
|
||
e)
|
||
)
|
||
Ge(n);
|
||
else {
|
||
let e = await cart_edit_a(D.value);
|
||
if (G || t !== W) return;
|
||
A.value = e ?? D.value.map((e, t) => t);
|
||
}
|
||
} catch (e) {
|
||
if (G || t !== W) return;
|
||
((N.value = !0),
|
||
(D.value = []),
|
||
(O.value = ``),
|
||
(A.value = []),
|
||
(M.value = e instanceof Error ? e.message : `购物车加载失败`));
|
||
} finally {
|
||
!G && t === W && (k.value = !1);
|
||
}
|
||
!G && t === W && !N.value && (await qe());
|
||
}
|
||
function Qe() {
|
||
j.value || (A.value = Ue.value ? [] : D.value.map((e, t) => t));
|
||
}
|
||
function $e(e) {
|
||
if (j.value) return;
|
||
let t = A.value.indexOf(e);
|
||
t === -1 ? A.value.push(e) : A.value.splice(t, 1);
|
||
}
|
||
// 遇到 409 并发冲突时重载购物车并清空选择,要求用户重新确认,避免误改已移动的下标。
|
||
async function Q(e, t) {
|
||
let n = e instanceof Error ? e.message : t;
|
||
e && typeof e == `object` && `status` in e && e.status === 409
|
||
? (($.value = null),
|
||
(A.value = []),
|
||
await Z(),
|
||
(A.value = []),
|
||
(M.value = N.value ? `${n};${M.value}` : n))
|
||
: (M.value = n);
|
||
}
|
||
// 先保存购物车编辑上下文,再进入对应商品页;商品页会再次确认该位置仍匹配商品。
|
||
async function et(e) {
|
||
if (!j.value && D.value[e]) {
|
||
j.value = !0;
|
||
try {
|
||
(await cart_edit_i(D.value, A.value),
|
||
await E.push({
|
||
path: `/products/${D.value[e].product_id}`,
|
||
query: {
|
||
edit_position: String(e),
|
||
},
|
||
}));
|
||
} catch (e) {
|
||
M.value = e instanceof Error ? e.message : `打开配置失败,请重试`;
|
||
} finally {
|
||
j.value = !1;
|
||
}
|
||
}
|
||
}
|
||
// 修改数量必须为正整数,并携带当前购物车并发校验信息。
|
||
async function tt(e, t) {
|
||
if (!(j.value || !Number.isInteger(t) || t < 1 || !D.value[e])) {
|
||
((j.value = !0), (M.value = ``));
|
||
try {
|
||
(await x.put(`/cart/${e}/qty`, {
|
||
qty: t,
|
||
...J(),
|
||
}),
|
||
await Z(!0));
|
||
} catch (e) {
|
||
await Q(e, `修改数量失败`);
|
||
} finally {
|
||
j.value = !1;
|
||
}
|
||
}
|
||
}
|
||
let $ = ref(null);
|
||
// 确认删除前记录位置与整个购物车签名,等待确认期间列表变化则不得继续使用旧位置。
|
||
function nt(e) {
|
||
let t = D.value[e];
|
||
!j.value &&
|
||
t &&
|
||
($.value = {
|
||
kind: `item`,
|
||
positions: [e],
|
||
signature: q(),
|
||
label: `删除“${product_name_n(t.name)}”`,
|
||
});
|
||
}
|
||
// 清空操作记录当时的全部位置与签名,最终仍由统一确认函数校验。
|
||
function rt() {
|
||
!j.value &&
|
||
D.value.length &&
|
||
($.value = {
|
||
kind: `all`,
|
||
positions: D.value.map((e, t) => t),
|
||
signature: q(),
|
||
label: `清空购物车`,
|
||
});
|
||
}
|
||
// 批量删除仅暂存当前选中位置,不立即发送删除请求。
|
||
function it() {
|
||
!j.value &&
|
||
A.value.length &&
|
||
($.value = {
|
||
kind: `selected`,
|
||
positions: [...A.value],
|
||
signature: q(),
|
||
label: `删除选中的 ${A.value.length} 项商品`,
|
||
});
|
||
}
|
||
// 确认时再核对购物车签名;单项、批量、清空分别调用对应接口并携带并发校验。
|
||
async function at() {
|
||
let e = $.value;
|
||
if (e && !j.value) {
|
||
if (e.signature !== q()) {
|
||
(($.value = null),
|
||
(M.value = `购物车已变化,请重新选择要删除的商品`));
|
||
return;
|
||
}
|
||
((j.value = !0), (M.value = ``));
|
||
try {
|
||
(e.kind === `all`
|
||
? await x.del(`/cart`, void 0, J())
|
||
: e.kind === `item`
|
||
? await x.del(`/cart/${e.positions[0]}`, void 0, J())
|
||
: await x.del(`/cart/batch`, void 0, {
|
||
positions: e.positions,
|
||
...J(),
|
||
}),
|
||
($.value = null),
|
||
(A.value = []),
|
||
await Z(!0));
|
||
} catch (e) {
|
||
await Q(e, `删除失败`);
|
||
} finally {
|
||
j.value = !1;
|
||
}
|
||
}
|
||
}
|
||
// 结算要求选择项报价均已确认;服务端返回订单后打开支付并刷新购物车。
|
||
async function ot() {
|
||
if (!(j.value || P.value)) {
|
||
if (((M.value = ``), !A.value.length)) {
|
||
M.value = `请选择要结算的商品`;
|
||
return;
|
||
}
|
||
if (k.value || X.value === null) {
|
||
M.value = `所选商品报价尚未确认,请等待或重试报价`;
|
||
return;
|
||
}
|
||
j.value = !0;
|
||
try {
|
||
let e = await x.post(`/cart/settle`, {
|
||
positions: [...A.value],
|
||
...J(),
|
||
...(L.value.length
|
||
? {
|
||
customfield: {
|
||
promo_code: L.value,
|
||
},
|
||
}
|
||
: {}),
|
||
});
|
||
((F.value = e.order_id),
|
||
(He.value = e.amount),
|
||
(P.value = !0),
|
||
(A.value = []),
|
||
await Z(!0));
|
||
} catch (e) {
|
||
await Q(e, `结算失败`);
|
||
} finally {
|
||
j.value = !1;
|
||
}
|
||
}
|
||
}
|
||
// 支付完成后进入订单列表,订单与支付状态仍以服务端为准。
|
||
function st() {
|
||
E.push(`/orders`);
|
||
}
|
||
return (
|
||
onBeforeUnmount(() => {
|
||
((G = !0), (U += 1), (W += 1), T?.(), C.reset());
|
||
}),
|
||
onMounted(() => {
|
||
let e = useAuth();
|
||
return (
|
||
(T = watch(
|
||
() => [
|
||
e.token,
|
||
e.isLoggedIn,
|
||
k.value,
|
||
q(),
|
||
JSON.stringify(A.value),
|
||
],
|
||
() => ((w.value = e.isLoggedIn), Ze()),
|
||
{
|
||
immediate: !0,
|
||
flush: `sync`,
|
||
},
|
||
)),
|
||
Z()
|
||
);
|
||
}),
|
||
// 渲染当前购物车行与逐项报价,合计不可用时禁用结算;删除确认和订单支付各自保留独立目标。
|
||
(n, r) => {
|
||
let i = button,
|
||
o = message,
|
||
l = skeleton,
|
||
h = checkbox,
|
||
v = inputtext,
|
||
y = ListEmpty,
|
||
b = dialog,
|
||
x = PaymentDialog;
|
||
return (
|
||
openBlock(),
|
||
createElementBlock(`div`, me, [
|
||
createElementVNode(`div`, he, [
|
||
(r[7] ||= createElementVNode(
|
||
`div`,
|
||
{
|
||
class: `page-heading`,
|
||
},
|
||
[
|
||
createElementVNode(`h2`, null, `购物车`),
|
||
createElementVNode(
|
||
`p`,
|
||
null,
|
||
`确认产品配置与数量,统一提交订单`,
|
||
),
|
||
],
|
||
-1,
|
||
)),
|
||
createVNode(i, {
|
||
label: `订购产品`,
|
||
icon: `pi pi-plus`,
|
||
severity: `secondary`,
|
||
outlined: ``,
|
||
onClick: (r[0] ||= (e) => unref(E).push(`/products`)),
|
||
}),
|
||
]),
|
||
unref(M)
|
||
? (openBlock(),
|
||
createBlock(
|
||
o,
|
||
{
|
||
key: 0,
|
||
severity: `error`,
|
||
closable: !1,
|
||
},
|
||
{
|
||
default: withCtx(() => [
|
||
createTextVNode(toDisplayString(unref(M)), 1),
|
||
]),
|
||
_: 1,
|
||
},
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
unref(C).message.value
|
||
? (openBlock(),
|
||
createBlock(
|
||
o,
|
||
{
|
||
key: 1,
|
||
severity: `warn`,
|
||
closable: !1,
|
||
role: `alert`,
|
||
},
|
||
{
|
||
default: withCtx(() => [
|
||
createTextVNode(
|
||
toDisplayString(unref(C).message.value),
|
||
1,
|
||
),
|
||
]),
|
||
_: 1,
|
||
},
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
unref(C).message.value
|
||
? (openBlock(),
|
||
createBlock(
|
||
i,
|
||
{
|
||
key: 2,
|
||
label: `重新检查购买限制`,
|
||
icon: `pi pi-refresh`,
|
||
severity: `secondary`,
|
||
size: `small`,
|
||
outlined: ``,
|
||
disabled:
|
||
unref(k) || unref(j) || unref(C).pending.value,
|
||
onClick: Ze,
|
||
},
|
||
null,
|
||
8,
|
||
[`disabled`],
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
unref(k) && !unref(D).length
|
||
? (openBlock(),
|
||
createElementBlock(`div`, ge, [
|
||
createVNode(l, {
|
||
height: `180px`,
|
||
}),
|
||
]))
|
||
: unref(D).length
|
||
? (openBlock(),
|
||
createElementBlock(`div`, _e, [
|
||
createElementVNode(`section`, ve, [
|
||
createElementVNode(`div`, ye, [
|
||
createVNode(
|
||
h,
|
||
{
|
||
modelValue: unref(Ue),
|
||
binary: ``,
|
||
"aria-label": `选择全部商品`,
|
||
disabled: unref(j),
|
||
"onUpdate:modelValue": Qe,
|
||
},
|
||
null,
|
||
8,
|
||
[`modelValue`, `disabled`],
|
||
),
|
||
createElementVNode(`span`, be, [
|
||
(r[8] ||= createTextVNode(`全选 `, -1)),
|
||
createElementVNode(
|
||
`span`,
|
||
xe,
|
||
`共 ` +
|
||
toDisplayString(unref(D).length) +
|
||
` 项商品`,
|
||
1,
|
||
),
|
||
]),
|
||
unref(A).length
|
||
? (openBlock(),
|
||
createElementBlock(
|
||
`button`,
|
||
{
|
||
key: 0,
|
||
type: `button`,
|
||
class: `clear-btn`,
|
||
disabled: unref(j),
|
||
onClick: it,
|
||
},
|
||
` 删除选中(` +
|
||
toDisplayString(unref(A).length) +
|
||
`) `,
|
||
9,
|
||
Se,
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
createElementVNode(
|
||
`button`,
|
||
{
|
||
type: `button`,
|
||
class: `clear-btn`,
|
||
disabled: unref(j),
|
||
onClick: rt,
|
||
},
|
||
`清空购物车`,
|
||
8,
|
||
Ce,
|
||
),
|
||
]),
|
||
(openBlock(!0),
|
||
createElementBlock(
|
||
Fragment,
|
||
null,
|
||
renderList(
|
||
unref(D),
|
||
(n, a) => (
|
||
openBlock(),
|
||
createElementBlock(
|
||
`div`,
|
||
{
|
||
key: a,
|
||
class: `cart-item`,
|
||
},
|
||
[
|
||
createVNode(
|
||
h,
|
||
{
|
||
modelValue: unref(A).includes(a),
|
||
"aria-label": `选择 ${unref(product_name_n)(n.name)}`,
|
||
disabled: unref(j),
|
||
binary: ``,
|
||
"onUpdate:modelValue": (e) => $e(a),
|
||
},
|
||
null,
|
||
8,
|
||
[
|
||
`modelValue`,
|
||
`aria-label`,
|
||
`disabled`,
|
||
`onUpdate:modelValue`,
|
||
],
|
||
),
|
||
createElementVNode(`div`, we, [
|
||
createElementVNode(
|
||
`div`,
|
||
Te,
|
||
toDisplayString(
|
||
unref(product_name_n)(n.name),
|
||
),
|
||
1,
|
||
),
|
||
unref(B)[a]?.length
|
||
? (openBlock(),
|
||
createElementBlock(`dl`, Ee, [
|
||
(openBlock(!0),
|
||
createElementBlock(
|
||
Fragment,
|
||
null,
|
||
renderList(
|
||
unref(B)[a],
|
||
(t, n) => (
|
||
openBlock(),
|
||
createElementBlock(
|
||
`div`,
|
||
{
|
||
key: `${t.label}-${n}`,
|
||
class: `item-configuration-row`,
|
||
},
|
||
[
|
||
createElementVNode(
|
||
`dt`,
|
||
null,
|
||
toDisplayString(
|
||
t.label,
|
||
),
|
||
1,
|
||
),
|
||
createElementVNode(
|
||
`dd`,
|
||
null,
|
||
toDisplayString(
|
||
t.value,
|
||
),
|
||
1,
|
||
),
|
||
],
|
||
)
|
||
),
|
||
),
|
||
128,
|
||
)),
|
||
]))
|
||
: (openBlock(),
|
||
createElementBlock(
|
||
`p`,
|
||
De,
|
||
toDisplayString(
|
||
unref(H)[a]
|
||
? `正在加载所选配置…`
|
||
: `暂无配置明细`,
|
||
),
|
||
1,
|
||
)),
|
||
]),
|
||
createElementVNode(`div`, Oe, [
|
||
createVNode(
|
||
i,
|
||
{
|
||
icon: `pi pi-minus`,
|
||
"aria-label": `减少 ${unref(product_name_n)(n.name)} 的数量`,
|
||
size: `small`,
|
||
severity: `secondary`,
|
||
outlined: ``,
|
||
disabled:
|
||
unref(j) || (n.qty || 1) <= 1,
|
||
onClick: (e) =>
|
||
tt(a, (n.qty || 1) - 1),
|
||
},
|
||
null,
|
||
8,
|
||
[`aria-label`, `disabled`, `onClick`],
|
||
),
|
||
createElementVNode(
|
||
`span`,
|
||
ke,
|
||
toDisplayString(n.qty || 1),
|
||
1,
|
||
),
|
||
createVNode(
|
||
i,
|
||
{
|
||
icon: `pi pi-plus`,
|
||
"aria-label": `增加 ${unref(product_name_n)(n.name)} 的数量`,
|
||
size: `small`,
|
||
severity: `secondary`,
|
||
outlined: ``,
|
||
disabled: unref(j),
|
||
onClick: (e) =>
|
||
tt(a, (n.qty || 1) + 1),
|
||
},
|
||
null,
|
||
8,
|
||
[`aria-label`, `disabled`, `onClick`],
|
||
),
|
||
]),
|
||
createElementVNode(`div`, Ae, [
|
||
unref(H)[a]
|
||
? (openBlock(),
|
||
createElementBlock(
|
||
`span`,
|
||
je,
|
||
`正在报价…`,
|
||
))
|
||
: unref(V)[a]
|
||
? (openBlock(),
|
||
createElementBlock(
|
||
Fragment,
|
||
{
|
||
key: 1,
|
||
},
|
||
[
|
||
(r[9] ||= createElementVNode(
|
||
`span`,
|
||
{
|
||
class: `quote-status`,
|
||
},
|
||
`报价失败`,
|
||
-1,
|
||
)),
|
||
createVNode(
|
||
i,
|
||
{
|
||
label: `重试报价`,
|
||
size: `small`,
|
||
text: ``,
|
||
"aria-label": `重新获取 ${unref(product_name_n)(n.name)} 的报价`,
|
||
disabled: unref(j),
|
||
onClick: (e) => qe([a]),
|
||
},
|
||
null,
|
||
8,
|
||
[
|
||
`aria-label`,
|
||
`disabled`,
|
||
`onClick`,
|
||
],
|
||
),
|
||
createElementVNode(
|
||
`small`,
|
||
Me,
|
||
toDisplayString(unref(V)[a]),
|
||
1,
|
||
),
|
||
],
|
||
64,
|
||
))
|
||
: (openBlock(),
|
||
createElementBlock(
|
||
Fragment,
|
||
{
|
||
key: 2,
|
||
},
|
||
[
|
||
(r[10] ||= createElementVNode(
|
||
`small`,
|
||
{
|
||
class: `quote-status`,
|
||
},
|
||
`小计`,
|
||
-1,
|
||
)),
|
||
createTextVNode(
|
||
` ¥` +
|
||
toDisplayString(
|
||
Je(a) || `—`,
|
||
),
|
||
1,
|
||
),
|
||
],
|
||
64,
|
||
)),
|
||
]),
|
||
createVNode(
|
||
i,
|
||
{
|
||
label: `编辑配置`,
|
||
icon: `pi pi-pencil`,
|
||
size: `small`,
|
||
severity: `secondary`,
|
||
text: ``,
|
||
disabled: unref(j),
|
||
onClick: (e) => et(a),
|
||
},
|
||
null,
|
||
8,
|
||
[`disabled`, `onClick`],
|
||
),
|
||
createVNode(
|
||
i,
|
||
{
|
||
icon: `pi pi-trash`,
|
||
"aria-label": `删除 ${unref(product_name_n)(n.name)}`,
|
||
size: `small`,
|
||
severity: `danger`,
|
||
text: ``,
|
||
disabled: unref(j),
|
||
onClick: (e) => nt(a),
|
||
},
|
||
null,
|
||
8,
|
||
[`aria-label`, `disabled`, `onClick`],
|
||
),
|
||
],
|
||
)
|
||
),
|
||
),
|
||
128,
|
||
)),
|
||
]),
|
||
createElementVNode(`aside`, Ne, [
|
||
(r[14] ||= createElementVNode(
|
||
`h3`,
|
||
null,
|
||
`订单确认`,
|
||
-1,
|
||
)),
|
||
createElementVNode(`div`, Pe, [
|
||
(r[11] ||= createElementVNode(
|
||
`span`,
|
||
null,
|
||
`已选商品`,
|
||
-1,
|
||
)),
|
||
createElementVNode(
|
||
`span`,
|
||
null,
|
||
toDisplayString(unref(A).length) +
|
||
` 项 / ` +
|
||
toDisplayString(unref(Ye)) +
|
||
` 件`,
|
||
1,
|
||
),
|
||
]),
|
||
createElementVNode(`div`, Fe, [
|
||
(r[12] ||= createElementVNode(
|
||
`label`,
|
||
{
|
||
for: `cart-promo`,
|
||
},
|
||
`优惠码`,
|
||
-1,
|
||
)),
|
||
createVNode(
|
||
v,
|
||
{
|
||
id: `cart-promo`,
|
||
modelValue: unref(I),
|
||
"onUpdate:modelValue": (r[1] ||= (e) =>
|
||
isRef(I) ? (I.value = e) : null),
|
||
placeholder: `选填,多个用空格或逗号分隔`,
|
||
disabled: unref(j),
|
||
class: `promo-input`,
|
||
},
|
||
null,
|
||
8,
|
||
[`modelValue`, `disabled`],
|
||
),
|
||
unref(L).length
|
||
? (openBlock(),
|
||
createElementBlock(
|
||
`span`,
|
||
Ie,
|
||
toDisplayString(unref(L).length) +
|
||
` 个待使用`,
|
||
1,
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
]),
|
||
createElementVNode(`div`, Le, [
|
||
(r[13] ||= createElementVNode(
|
||
`span`,
|
||
null,
|
||
`合计费用`,
|
||
-1,
|
||
)),
|
||
createElementVNode(
|
||
`span`,
|
||
Re,
|
||
toDisplayString(
|
||
unref(X) === null ? `待确认` : `¥${unref(X)}`,
|
||
),
|
||
1,
|
||
),
|
||
]),
|
||
createElementVNode(
|
||
`p`,
|
||
ze,
|
||
toDisplayString(
|
||
unref(A).length
|
||
? unref(X) === null
|
||
? `所选商品的报价尚未确认,请等待报价或重试。`
|
||
: `优惠码折扣以订单确认后的应付金额为准。`
|
||
: `选择商品后即可提交订单。`,
|
||
),
|
||
1,
|
||
),
|
||
createVNode(
|
||
i,
|
||
{
|
||
label: `提交订单`,
|
||
loading: unref(j),
|
||
disabled: !unref(Xe),
|
||
onClick: ot,
|
||
},
|
||
null,
|
||
8,
|
||
[`loading`, `disabled`],
|
||
),
|
||
]),
|
||
]))
|
||
: unref(N)
|
||
? (openBlock(),
|
||
createElementBlock(`div`, Be, [
|
||
createVNode(
|
||
y,
|
||
{
|
||
icon: `pi pi-exclamation-circle`,
|
||
title: `暂时无法加载购物车`,
|
||
description: `请重新加载,已有商品会继续保留。`,
|
||
},
|
||
{
|
||
default: withCtx(() => [
|
||
createVNode(i, {
|
||
label: `重新加载`,
|
||
icon: `pi pi-refresh`,
|
||
severity: `secondary`,
|
||
outlined: ``,
|
||
onClick: (r[2] ||= (e) => Z()),
|
||
}),
|
||
]),
|
||
_: 1,
|
||
},
|
||
),
|
||
]))
|
||
: (openBlock(),
|
||
createElementBlock(`div`, Ve, [
|
||
createVNode(
|
||
y,
|
||
{
|
||
icon: `pi pi-shopping-cart`,
|
||
title: `购物车是空的`,
|
||
description: `选好产品并加入购物车后,可在这里统一结算。`,
|
||
},
|
||
{
|
||
default: withCtx(() => [
|
||
createVNode(i, {
|
||
label: `订购产品`,
|
||
icon: `pi pi-plus`,
|
||
onClick: (r[3] ||= (e) =>
|
||
unref(E).push(`/products`)),
|
||
}),
|
||
]),
|
||
_: 1,
|
||
},
|
||
),
|
||
])),
|
||
createVNode(
|
||
b,
|
||
{
|
||
visible: !!unref($),
|
||
header: unref($)?.label,
|
||
modal: ``,
|
||
draggable: !1,
|
||
closable: !unref(j),
|
||
closeOnEscape: !unref(j),
|
||
style: {
|
||
width: `440px`,
|
||
},
|
||
"onUpdate:visible": (r[5] ||= (e) =>
|
||
!unref(j) && ($.value = null)),
|
||
},
|
||
{
|
||
footer: withCtx(() => [
|
||
createVNode(
|
||
i,
|
||
{
|
||
label: `取消`,
|
||
severity: `secondary`,
|
||
text: ``,
|
||
disabled: unref(j),
|
||
onClick: (r[4] ||= (e) => ($.value = null)),
|
||
},
|
||
null,
|
||
8,
|
||
[`disabled`],
|
||
),
|
||
createVNode(
|
||
i,
|
||
{
|
||
label: `确认删除`,
|
||
severity: `danger`,
|
||
loading: unref(j),
|
||
onClick: at,
|
||
},
|
||
null,
|
||
8,
|
||
[`loading`],
|
||
),
|
||
]),
|
||
default: withCtx(() => [
|
||
createElementVNode(
|
||
`p`,
|
||
null,
|
||
`将移除 ` +
|
||
toDisplayString(unref($)?.positions.length) +
|
||
` 项商品,需要时可重新选购。`,
|
||
1,
|
||
),
|
||
unref(M)
|
||
? (openBlock(),
|
||
createBlock(
|
||
o,
|
||
{
|
||
key: 0,
|
||
severity: `error`,
|
||
closable: !1,
|
||
},
|
||
{
|
||
default: withCtx(() => [
|
||
createTextVNode(toDisplayString(unref(M)), 1),
|
||
]),
|
||
_: 1,
|
||
},
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
]),
|
||
_: 1,
|
||
},
|
||
8,
|
||
[`visible`, `header`, `closable`, `closeOnEscape`],
|
||
),
|
||
createVNode(
|
||
x,
|
||
{
|
||
modelValue: unref(P),
|
||
"onUpdate:modelValue": (r[6] ||= (e) =>
|
||
isRef(P) ? (P.value = e) : null),
|
||
"order-id": unref(F),
|
||
amount: unref(He),
|
||
onSuccess: st,
|
||
},
|
||
null,
|
||
8,
|
||
[`modelValue`, `order-id`, `amount`],
|
||
),
|
||
])
|
||
);
|
||
}
|
||
);
|
||
},
|
||
}),
|
||
[[`__scopeId`, `data-v-11d8a39a`]],
|
||
);
|
||
export {};
|
||
export default P;
|
||
</script>
|