Files

1223 lines
52 KiB
Vue

<script>
/**
* 物理服务器选型:汇总可售物理商品、机型与地区线路,支持规格/价格筛选,选择线路后进入独立定制页。
*/
import "~/assets/styles/dedicated-server.CiJfMYtk.css";
import { toDisplayString } from "vue";
import { renderList } from "vue";
import { onMounted } from "vue";
import { useId } from "vue";
import { normalizeClass } from "vue";
import { onBeforeUnmount } from "vue";
import { createBlock } from "vue";
import { ref } from "vue";
import { withCtx } from "vue";
import { createCommentVNode } from "vue";
import { computed } from "vue";
import { reactive } 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 { NuxtLink } from "#components";
import { useApi } from "~/lib/auth.js";
import button from "openvue/button";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
import message from "openvue/message";
import { n as product_name_n } from "~/lib/product-name.js";
import inputnumber from "openvue/inputnumber";
import tag from "openvue/tag";
import skeleton from "openvue/skeleton";
import { productStock } from "~/lib/product-inventory.js";
import { resourceOptionValue } from "~/lib/resource-option-value.js";
import { productId } from "~/lib/product-id.js";
// 只识别本地或资源模块映射到 mf_dcim 的物理商品;未知模块不强行套用配置器。
function O(e) {
let t = e.module?.trim() || ``;
return t === `mf_dcim` || t === `remf_dcim`
? t
: !t && e.res_module?.trim() === `mf_dcim`
? `remf_dcim`
: void 0;
}
// 封装选型数据、筛选和线路详情,列表请求与选中线路请求使用独立序号。
function k() {
let e = useApi(),
t = ref(!0),
n = ref(``),
r = ref(``),
i = ref([]),
o = ref(``),
c = ref({}),
l = ref(!1),
f = ref(``),
p = reactive({
minPrice: null,
maxPrice: null,
region: ``,
line: ``,
cpu: ``,
memory: ``,
disk: ``,
}),
m = (e) => `${e.product.id}:${e.model.id}`,
h = computed(() => i.value.find((e) => m(e) === o.value)),
g = computed(() => ({
region: [
...new Set(i.value.flatMap((e) => e.lines.map((e) => e.location))),
],
line: [...new Set(i.value.flatMap((e) => e.lines.map((e) => e.name)))],
cpu: [...new Set(i.value.map((e) => e.model.cpu).filter(Boolean))],
memory: [...new Set(i.value.map((e) => e.model.memory).filter(Boolean))],
disk: [...new Set(i.value.map((e) => e.model.disk).filter(Boolean))],
})),
_ = computed(() =>
i.value.filter((e) => {
let t =
e.product.price === void 0 || e.product.price === ``
? null
: Number(e.product.price);
return (
(p.minPrice === null ||
(t !== null && Number.isFinite(t) && t >= p.minPrice)) &&
(p.maxPrice === null ||
(t !== null && Number.isFinite(t) && t <= p.maxPrice)) &&
((!p.region && !p.line) ||
e.lines.some(
(e) =>
(!p.region || e.location === p.region) &&
(!p.line || e.name === p.line),
)) &&
(!p.cpu || e.model.cpu === p.cpu) &&
(!p.memory || e.model.memory === p.memory) &&
(!p.disk || e.model.disk === p.disk)
);
}),
),
v = computed(() =>
(h.value?.lines ?? []).filter(
(e) =>
(!p.region || e.location === p.region) &&
(!p.line || e.name === p.line),
),
),
y = 0,
b = 0,
S = !1;
onBeforeUnmount(() => {
((S = !0), y++, b++);
});
// 只清除筛选草稿,不修改服务端商品。
function C() {
Object.assign(p, {
minPrice: null,
maxPrice: null,
region: ``,
line: ``,
cpu: ``,
memory: ``,
disk: ``,
});
}
// 商品库存与自动同步的机型库存都满足时才允许选购。
function w(e) {
const stock = productStock(e.product, e.model);
return stock === null || stock > 0;
}
// 分页收集商品后并行加载物理模块配置;单个商品失败保留其余可用项并提示部分加载失败。
async function T() {
let a = ++y;
(b++,
(o.value = ``),
(c.value = {}),
(t.value = !0),
(n.value = ``),
(r.value = ``));
try {
let t = [],
n = 1;
for (;;) {
let r = await e.get(`/product`, {
page: n,
limit: 50,
});
if (S || a !== y) return;
let i = r.list ?? [];
if (
(t.push(...i), !i.length || t.length >= Number(r.count ?? t.length))
)
break;
n++;
}
let o = t.flatMap((e) => {
let t = O(e);
return e.hidden !== 1 && productId(e.id) !== null && t
? [
{
product: {
...e,
module: t,
},
module: t,
},
]
: [];
}),
s = await Promise.allSettled(
o.map(async ({ product: t, module: n }) => ({
product: t,
page: await e.get(`/product/${t.id}/${n}/order_page`),
})),
);
if (S || a !== y) return;
((i.value = s.flatMap((e) => {
if (e.status !== `fulfilled`) return [];
let { product: t, page: n } = e.value,
r = (n.data_center ?? []).flatMap((e) =>
(e.city ?? []).flatMap((t) =>
(t.area ?? []).flatMap((n) =>
(n.line ?? []).map((r) => ({
...r,
location: [t.name, n.name].filter(Boolean).join(` / `),
country: e.name,
})),
),
),
);
return (n.model_config ?? []).map((e) => ({
product: t,
model: e,
lines: r,
}));
})),
s.some((e) => e.status === `rejected`) &&
(r.value = `部分物理服务器配置暂时无法加载,请重试以获取完整列表。`));
} catch (e) {
!S &&
a === y &&
((i.value = []),
(n.value = e instanceof Error ? e.message : `加载物理服务器商品失败`));
} finally {
!S && a === y && (t.value = !1);
}
}
// 按当前机型读取各线路详情,快速改选时只接纳最新选择的结果。
async function E(t) {
o.value = m(t);
let n = ++b;
((c.value = {}), (f.value = ``), (l.value = !0));
let r = O(t.product) || `mf_dcim`,
i = await Promise.allSettled(
t.lines.map(async (n) => ({
id: n.id,
detail: await e.get(`/product/${t.product.id}/${r}/line/${n.id}`),
})),
);
if (!(S || n !== b)) {
for (let e of i)
e.status === `fulfilled` && (c.value[e.value.id] = e.value.detail);
(i.some((e) => e.status === `rejected`) &&
(f.value = `部分线路详情加载失败,可重试或进入配置页面重新加载。`),
(l.value = !1));
}
}
// 关闭机型选择并使尚未完成的线路请求失效。
function D() {
(b++, (o.value = ``), (c.value = {}), (l.value = !1), (f.value = ``));
}
return {
loading: t,
error: n,
partialError: r,
entries: i,
selected: h,
selectedKey: o,
lineDetails: c,
lineLoading: l,
lineError: f,
filters: p,
options: g,
filtered: _,
visibleLines: v,
keyOf: m,
inStock: w,
resetFilters: C,
load: T,
select: E,
closeSelection: D,
};
}
var te = {
class: `dedicated-catalog`,
},
ne = {
class: `catalog-head`,
},
re = {
class: `catalog-filters`,
},
ie = {
class: `filter-panel`,
},
ae = {
open: ``,
},
oe = {
class: `price-range`,
},
se = [`open`],
ce = {
class: `filter-options`,
},
le = [`aria-pressed`, `onClick`],
ue = [`aria-pressed`, `onClick`],
de = {
key: 0,
class: `filter-empty`,
},
fe = {
class: `catalog-products`,
},
A = {
key: 0,
class: `hardware-card`,
},
j = {
key: 1,
class: `catalog-state`,
},
M = {
class: `hardware-body`,
},
N = {
class: `hardware-spec`,
},
P = {
class: `hardware-spec`,
},
F = {
key: 0,
},
I = {
class: `hardware-buy`,
},
L = {
key: 0,
},
R = {
key: 0,
},
z = {
key: 1,
},
B = {
key: 2,
},
V = {
key: 1,
class: `catalog-state`,
},
pe = {
key: 0,
class: `catalog-lines`,
"aria-label": `可选线路`,
},
me = {
class: `line-heading`,
},
he = {
class: `line-panel`,
},
ge = {
class: `line-tags`,
},
_e = {
key: 1,
class: `line-specs`,
},
ve = {
class: `line-action`,
},
ye = {
key: 1,
class: `line-empty`,
},
H = _plugin_vue_export_helper_t(
defineComponent({
__name: `index`,
setup(a) {
let s = k(),
{
loading: u,
error: d,
partialError: _,
entries: x,
selected: C,
selectedKey: O,
lineDetails: H,
lineLoading: be,
lineError: U,
filters: W,
options: G,
filtered: K,
visibleLines: q,
} = s,
J = useId(),
Y = [
{
key: `region`,
label: `地区`,
},
{
key: `line`,
label: `线路`,
},
{
key: `cpu`,
label: `CPU`,
},
{
key: `memory`,
label: `内存`,
},
{
key: `disk`,
label: `硬盘`,
},
];
// 只有明确有效的商品价格才显示金额,缺失时提示配置后确认。
function xe(e) {
return e !== void 0 && e.trim() !== `` && Number.isFinite(Number(e))
? `¥ ${Number(e).toFixed(2)}`
: `价格待配置`;
}
// 定制链接携带当前商品、机型及所选线路 ID,最终仍由配置页加载并验证商品。
function Se(e) {
return `/products/dedicated-server/customize?id=${C.value?.product.id}&model_id=${C.value?.model.id}&line_id=${e}`;
}
return (
onMounted(s.load),
// 机型筛选卡、线路详情和定制入口共享当前选择;部分配置读取失败有单独反馈,不伪装成无库存。
(n, r) => {
let a = NuxtLink,
k = inputnumber,
X = button,
Z = skeleton,
Q = message,
$ = tag;
return (
openBlock(),
createElementBlock(`div`, te, [
createElementVNode(`header`, ne, [
(r[9] ||= createElementVNode(`h1`, null, `物理服务器`, -1)),
createVNode(
a,
{
to: `/products`,
},
{
default: withCtx(() => [
...(r[8] ||= [createTextVNode(`浏览产品`, -1)]),
]),
_: 1,
},
),
]),
createElementVNode(
`div`,
{
class: normalizeClass([
`catalog-layout`,
{
"has-selection": unref(C),
},
]),
},
[
createElementVNode(`aside`, re, [
(r[14] ||= createElementVNode(`h2`, null, `筛选`, -1)),
createElementVNode(`div`, ie, [
createElementVNode(`details`, ae, [
(r[11] ||= createElementVNode(
`summary`,
null,
[
createTextVNode(`价格`),
createElementVNode(`i`, {
class: `pi pi-chevron-down`,
"aria-hidden": `true`,
}),
],
-1,
)),
createElementVNode(`div`, oe, [
createVNode(
k,
{
modelValue: unref(W).minPrice,
"onUpdate:modelValue": (r[0] ||= (e) =>
(unref(W).minPrice = e)),
"input-id": `${unref(J)}-min-price`,
"aria-label": `最低商品起价`,
placeholder: `最低`,
min: 0,
"min-fraction-digits": 0,
"max-fraction-digits": 2,
},
null,
8,
[`modelValue`, `input-id`],
),
(r[10] ||= createElementVNode(
`span`,
null,
`—`,
-1,
)),
createVNode(
k,
{
modelValue: unref(W).maxPrice,
"onUpdate:modelValue": (r[1] ||= (e) =>
(unref(W).maxPrice = e)),
"input-id": `${unref(J)}-max-price`,
"aria-label": `最高商品起价`,
placeholder: `最高`,
min: 0,
"max-fraction-digits": 2,
},
null,
8,
[`modelValue`, `input-id`],
),
]),
(r[12] ||= createElementVNode(
`small`,
null,
`按商品起价筛选,最终价格以所选配置为准`,
-1,
)),
]),
(openBlock(),
createElementBlock(
Fragment,
null,
renderList(Y, (n, a) =>
createElementVNode(
`details`,
{
key: n.key,
open: a < 2,
},
[
createElementVNode(`summary`, null, [
createTextVNode(toDisplayString(n.label), 1),
(r[13] ||= createElementVNode(
`i`,
{
class: `pi pi-chevron-down`,
"aria-hidden": `true`,
},
null,
-1,
)),
]),
createElementVNode(`div`, ce, [
createElementVNode(
`button`,
{
type: `button`,
class: normalizeClass({
active: !unref(W)[n.key],
}),
"aria-pressed": !unref(W)[n.key],
onClick: (e) => (unref(W)[n.key] = ``),
},
`全部`,
10,
le,
),
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(G)[n.key],
(t) => (
openBlock(),
createElementBlock(
`button`,
{
key: t,
type: `button`,
"aria-pressed":
unref(W)[n.key] === t,
class: normalizeClass({
active: unref(W)[n.key] === t,
}),
onClick: (e) =>
(unref(W)[n.key] = t),
},
toDisplayString(t),
11,
ue,
)
),
),
128,
)),
unref(G)[n.key].length
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(
`span`,
de,
`暂无可选项`,
)),
]),
],
8,
se,
),
),
64,
)),
createVNode(X, {
label: `清除筛选`,
severity: `secondary`,
text: ``,
fluid: ``,
onClick: (r[2] ||= (e) => unref(s).resetFilters()),
}),
]),
]),
createElementVNode(`section`, fe, [
createElementVNode(
`h2`,
null,
toDisplayString(
unref(u)
? `正在加载产品`
: `${unref(K).length} 款产品`,
),
1,
),
unref(u)
? (openBlock(),
createElementBlock(`div`, A, [
createVNode(Z, {
height: `210px`,
}),
]))
: unref(d)
? (openBlock(),
createElementBlock(`div`, j, [
createVNode(
Q,
{
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(d)),
1,
),
]),
_: 1,
},
),
createVNode(X, {
label: `重新加载商品`,
icon: `pi pi-refresh`,
onClick: (r[3] ||= (e) => unref(s).load()),
}),
]))
: (openBlock(),
createElementBlock(
Fragment,
{
key: 2,
},
[
unref(_)
? (openBlock(),
createBlock(
Q,
{
key: 0,
severity: `warn`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(_)) + ` `,
1,
),
createVNode(X, {
label: `重试`,
text: ``,
onClick: (r[4] ||= (e) =>
unref(s).load()),
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(K),
(t) => (
openBlock(),
createElementBlock(
`article`,
{
key: unref(s).keyOf(t),
class: normalizeClass([
`hardware-card`,
{
selected:
unref(O) === unref(s).keyOf(t),
},
]),
},
[
createElementVNode(
`h3`,
null,
toDisplayString(t.model.name),
1,
),
createElementVNode(`div`, M, [
createElementVNode(`div`, N, [
(r[16] ||= createElementVNode(
`i`,
{
class: `pi pi-microchip`,
"aria-hidden": `true`,
},
null,
-1,
)),
createElementVNode(`div`, null, [
(r[15] ||= createElementVNode(
`span`,
null,
`CPU`,
-1,
)),
createElementVNode(
`strong`,
null,
toDisplayString(
t.model.cpu ||
`未提供处理器信息`,
),
1,
),
createElementVNode(
`small`,
null,
toDisplayString(
t.model.cpu_param,
),
1,
),
]),
]),
createElementVNode(`div`, P, [
(r[18] ||= createElementVNode(
`i`,
{
class: `pi pi-database`,
"aria-hidden": `true`,
},
null,
-1,
)),
createElementVNode(`div`, null, [
(r[17] ||= createElementVNode(
`span`,
null,
`存储`,
-1,
)),
createElementVNode(
`strong`,
null,
toDisplayString(
t.model.memory ||
`未提供内存信息`,
) + ` 内存`,
1,
),
createElementVNode(
`strong`,
null,
toDisplayString(
t.model.disk ||
`未提供硬盘信息`,
),
1,
),
t.model.gpu
? (openBlock(),
createElementBlock(
`small`,
F,
toDisplayString(
t.model.gpu,
),
1,
))
: createCommentVNode(``, !0),
]),
]),
createElementVNode(`div`, I, [
createElementVNode(`span`, null, [
(r[19] ||= createElementVNode(
`small`,
null,
`商品起价`,
-1,
)),
createElementVNode(
`strong`,
null,
toDisplayString(
xe(t.product.price),
),
1,
),
t.product.cycle
? (openBlock(),
createElementBlock(
`small`,
L,
` / ` +
toDisplayString(
t.product.cycle,
) +
`起`,
1,
))
: createCommentVNode(``, !0),
]),
createVNode(
X,
{
label: unref(s).inStock(t)
? `选择线路`
: `库存不足`,
disabled:
!unref(s).inStock(t) ||
!t.lines.length,
onClick: (e) =>
unref(s).select(t),
},
null,
8,
[
`label`,
`disabled`,
`onClick`,
],
),
]),
]),
createElementVNode(`footer`, null, [
createElementVNode(
`span`,
null,
toDisplayString(
unref(product_name_n)(
t.product.name,
),
),
1,
),
t.lines.length
? unref(s).inStock(t)
? productStock(
t.product,
t.model,
) !== null
? (openBlock(),
createElementBlock(
`span`,
B,
`剩余 ` +
toDisplayString(
productStock(
t.product,
t.model,
),
) +
` 台`,
1,
))
: createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(
`span`,
z,
`库存不足`,
))
: (openBlock(),
createElementBlock(
`span`,
R,
`暂无可选线路`,
)),
]),
],
2,
)
),
),
128,
)),
unref(K).length
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(`div`, V, [
(r[21] ||= createElementVNode(
`i`,
{
class: `pi pi-server`,
"aria-hidden": `true`,
},
null,
-1,
)),
createElementVNode(
`h3`,
null,
toDisplayString(
unref(x).length
? `没有符合筛选条件的物理服务器`
: unref(_)
? `暂时无法展示物理服务器配置`
: `暂无可订购的物理服务器`,
),
1,
),
createElementVNode(
`p`,
null,
toDisplayString(
unref(x).length
? `请调整筛选条件后再试。`
: `当前商品目录未提供可售型号,可稍后刷新或查看其他产品。`,
),
1,
),
unref(x).length
? (openBlock(),
createBlock(X, {
key: 0,
label: `清除筛选`,
severity: `secondary`,
outlined: ``,
onClick: (r[5] ||= (e) =>
unref(s).resetFilters()),
}))
: (openBlock(),
createBlock(
a,
{
key: 1,
to: `/products`,
},
{
default: withCtx(() => [
...(r[20] ||= [
createTextVNode(
`浏览产品`,
-1,
),
]),
]),
_: 1,
},
)),
])),
],
64,
)),
]),
unref(C)
? (openBlock(),
createElementBlock(`section`, pe, [
createElementVNode(`div`, me, [
createElementVNode(
`h2`,
null,
toDisplayString(unref(C).model.name) + ` 线路`,
1,
),
createVNode(X, {
icon: `pi pi-times`,
"aria-label": `关闭线路面板`,
severity: `secondary`,
text: ``,
onClick: (r[6] ||= (e) =>
unref(s).closeSelection()),
}),
]),
createElementVNode(`div`, he, [
unref(U)
? (openBlock(),
createBlock(
Q,
{
key: 0,
severity: `warn`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(U)) + ` `,
1,
),
createVNode(X, {
label: `重试线路`,
text: ``,
onClick: (r[7] ||= (e) =>
unref(s).select(unref(C))),
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(q),
(t) => (
openBlock(),
createElementBlock(
`article`,
{
key: t.id,
class: `line-card`,
},
[
createElementVNode(`div`, ge, [
createVNode(
$,
{
value: t.location,
severity: `info`,
},
null,
8,
[`value`],
),
createVNode(
$,
{
value: t.name,
severity: `info`,
},
null,
8,
[`value`],
),
]),
unref(be)
? (openBlock(),
createBlock(Z, {
key: 0,
height: `64px`,
}))
: (openBlock(),
createElementBlock(`div`, _e, [
createElementVNode(`div`, null, [
(r[23] ||= createElementVNode(
`i`,
{
class: `pi pi-calendar`,
"aria-hidden": `true`,
},
null,
-1,
)),
createElementVNode(`span`, null, [
(r[22] ||= createTextVNode(
`计费方式`,
-1,
)),
createElementVNode(
`strong`,
null,
toDisplayString(
(unref(H)[t.id]
?.bill_type ||
t.bill_type) === `flow`
? `流量计费`
: `固定带宽`,
),
1,
),
]),
]),
createElementVNode(`div`, null, [
(r[24] ||= createElementVNode(
`i`,
{
class: `pi pi-sort-alt`,
"aria-hidden": `true`,
},
null,
-1,
)),
createElementVNode(`span`, null, [
createTextVNode(
toDisplayString(
unref(H)[t.id]
?.bill_type === `flow`
? `流量`
: `带宽`,
),
1,
),
createElementVNode(
`strong`,
null,
toDisplayString(
unref(H)[t.id]
?.bill_type === `flow`
? `${resourceOptionValue(unref(H)[t.id]?.flow?.[0]) ?? `—`} GB`
: `${resourceOptionValue(unref(H)[t.id]?.bw?.[0]) ?? `—`} Mbps`,
),
1,
),
]),
]),
createElementVNode(`div`, null, [
(r[26] ||= createElementVNode(
`i`,
{
class: `pi pi-shield`,
"aria-hidden": `true`,
},
null,
-1,
)),
createElementVNode(`span`, null, [
(r[25] ||= createTextVNode(
`防护`,
-1,
)),
createElementVNode(
`strong`,
null,
toDisplayString(
unref(H)[t.id]?.defence?.[0]
?.desc || `按配置提供`,
),
1,
),
]),
]),
])),
createElementVNode(`div`, ve, [
(r[28] ||= createElementVNode(
`span`,
null,
`最终费用以配置报价为准`,
-1,
)),
createVNode(
X,
{
"as-child": ``,
icon: `pi pi-shopping-cart`,
},
{
default: withCtx((e) => [
createVNode(
a,
{
to: Se(t.id),
class: normalizeClass([
`line-order-link`,
e.class,
]),
},
{
default: withCtx(() => [
...(r[27] ||= [
createElementVNode(
`i`,
{
class: `pi pi-shopping-cart`,
"aria-hidden": `true`,
},
null,
-1,
),
createTextVNode(
`立即订购`,
-1,
),
]),
]),
_: 1,
},
8,
[`to`, `class`],
),
]),
_: 2,
},
1024,
),
]),
],
)
),
),
128,
)),
unref(q).length
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(
`p`,
ye,
`当前筛选下没有可选线路。`,
)),
]),
]))
: createCommentVNode(``, !0),
],
2,
),
])
);
}
);
},
}),
[[`__scopeId`, `data-v-e16eb167`]],
);
export {};
export default H;
</script>