Files
console-dev/app/components/mfcloud/MfCloudResourceUpgrade.vue
T

587 lines
24 KiB
Vue

<script>
/**
* 云实例自选资源升降级:父级给出当前规格、商品和实例,前端加载可选资源后分别询价/下单。
* 成功仅发 pay 事件交给父级支付;本面板不直接修改实例资源,也不自行推算升级费用。
*/
import "~/assets/styles/ConfigBandwidth.CWSH1WJZ.css";
import "~/assets/styles/MfCloudResourceUpgrade.bSwLWjPg.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 { watch } 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 { useApi } from "~/lib/auth.js";
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 inputnumber from "primevue/inputnumber";
import OperationNotice from "~/components/OperationNotice.vue";
import skeleton from "primevue/skeleton";
import ConfigBandwidth from "~/components/config/ConfigBandwidth.vue";
var ne = {
key: 0,
class: `resource-upgrade`,
},
re = {
class: `config-field`,
},
ie = {
class: `choices`,
},
ae = [`aria-pressed`, `disabled`, `onClick`],
oe = {
class: `config-field`,
},
se = [`for`],
ce = {
key: 1,
class: `choices`,
},
le = [`aria-pressed`, `disabled`, `onClick`],
ue = {
key: 0,
class: `config-field`,
},
de = {
class: `choices`,
},
fe = [`aria-pressed`, `disabled`, `onClick`],
D = {
class: `upgrade-total`,
},
O = {
key: 2,
class: `muted`,
},
k = Object.assign(
_plugin_vue_export_helper_t(
defineComponent({
__name: `MfCloudResourceUpgrade`,
props: {
hostId: {},
productId: {},
current: {},
upgradeAllowed: {
type: Boolean,
default: !0,
},
module: {
default: `mf_cloud`,
},
},
emits: [`pay`],
setup(_, { emit: S }) {
let k = _,
A = computed(() => k.module || `mf_cloud`),
pe = S,
j = useApi(),
M = ref(``),
N = useId(),
P = ref(null),
F = ref(null),
I = ref(!1),
L = ref(!1),
R = ref(!1),
z = ref(``),
// 表单初始化为实例当前值;K 比较是否真的发生变更,未变更时不创建订单。
B = reactive({
cpu: Number(k.current.cpu || 0),
memory: Number(k.current.memory || 0),
bw: Number(k.current.bw || 0),
flow: Number(k.current.flow || 0),
peak_defence: Number(k.current.peak_defence || 0),
}),
V = ref(null),
H = 0,
U = 0,
W = ``,
G = () => JSON.stringify([k.hostId, A.value, B]),
K = computed(() =>
Object.entries(B).some(
([e, t]) => t !== Number(k.current[e] || 0),
),
);
/** CPU/离散内存候选展开并去重;步进候选最多收集 1000 项,避免巨大范围阻塞页面。 */
function q(e) {
let t = [];
for (let n of e || [])
if (
n.type === `step` ||
(n.min_value != null &&
n.max_value != null &&
n.type !== `radio`)
) {
let e = Math.max(1, Number(n.step || 1));
for (
let r = Number(n.min_value);
r <= Number(n.max_value) && t.length < 1e3;
r += e
)
t.push(r);
} else
Number.isFinite(Number(n.value)) && t.push(Number(n.value));
return [...new Set(t)];
}
let me = computed(() => q(P.value?.cpu)),
he = computed(() => q(P.value?.memory));
// 连续内存使用数值控件;全为单选档位时返回 null,改用下方离散按钮。
function ge(e) {
return !e?.length ||
e.every((e) => e.type === `radio` || e.min_value == null)
? null
: {
min: Math.min(
...e.map((e) => Number(e.min_value ?? e.value)),
),
max: Math.max(
...e.map((e) => Number(e.max_value ?? e.value)),
),
step: Math.max(
1,
Math.min(...e.map((e) => Number(e.step || 1))),
),
};
}
let J = computed(() => ge(P.value?.memory)),
Y = computed(() =>
(F.value?.bw || []).map((e) => ({
...e,
type:
e.type !== `radio` &&
e.min_value != null &&
e.max_value != null
? `step`
: e.type,
})),
),
X = computed(() =>
(F.value?.flow || []).map((e) => Number(e.value)),
);
/** 商品订购配置与当前线路并行加载,H 隔离实例/商品切换后的旧配置响应。 */
async function Z() {
if (k.upgradeAllowed === !1) return;
let e = ++H;
((I.value = !0), (z.value = ``), (V.value = null));
try {
let t = Number(k.current.line?.id || k.current.line_id),
[n, r] = await Promise.all([
j.get(`/product/${k.productId}/${A.value}/order_page`),
t
? j.get(`/product/${k.productId}/${A.value}/line/${t}`)
: Promise.resolve(null),
]);
if (e !== H) return;
((P.value = n), (F.value = r));
} catch (t) {
e === H &&
(z.value = t instanceof Error ? t.message : `升级配置加载失败`);
} finally {
e === H && (I.value = !1);
}
}
/** 每次询价先清旧价格,保存 host/module/表单快照;不接受无数值报价或过期响应。 */
async function Q() {
if (k.upgradeAllowed === !1) return;
let e = ++U,
t = G();
if (((V.value = null), (W = ``), (z.value = ``), !K.value)) {
L.value = !1;
return;
}
L.value = !0;
try {
let n = await j.get(`/${A.value}/${k.hostId}/common_config`, {
...B,
});
if (e !== U || G() !== t) return;
if (
n.price == null ||
n.price === `` ||
!Number.isFinite(Number(n.price))
)
throw Error(`报价无效,请重试`);
((V.value = n), (W = t));
} catch (t) {
e === U &&
(z.value = t instanceof Error ? t.message : `询价失败`);
} finally {
e === U && (L.value = !1);
}
}
// 普通档位选择后直接询价,下单期间不允许修改表单。
function $(e, t) {
k.upgradeAllowed === !1 || R.value || ((B[e] = t), Q());
}
// 带宽控件输入中先作废旧报价,等 change 确认后才询价,避免拖动每一步都发请求。
function _e(e) {
k.upgradeAllowed === !1 ||
R.value ||
((B.bw = e), U++, (V.value = null), (W = ``), (L.value = !1));
}
/** 下单要求实际变更且报价属于当前快照;后端返回合法订单号后交给父级。 */
async function ve() {
if (
k.upgradeAllowed === !1 ||
R.value ||
L.value ||
!V.value ||
W !== G() ||
!K.value
)
return;
((R.value = !0), (z.value = ``), (M.value = ``));
let e = H,
t = G(),
n = V.value.price;
try {
let r = await j.post(
`/${A.value}/${k.hostId}/common_config/order`,
{
...B,
},
);
if (e !== H || t !== G()) return;
if (!Number.isSafeInteger(Number(r.id)) || Number(r.id) <= 0)
throw Error(`订单信息无效,请重试`);
((V.value = null),
(W = ``),
pe(`pay`, {
order_id: r.id,
amount: n,
}));
} catch (e) {
M.value = e instanceof Error ? e.message : `提交失败`;
} finally {
R.value = !1;
}
}
return (
onMounted(Z),
watch(() => [k.hostId, k.module, k.productId], Z),
// 使仍在加载的配置/报价结果失效,不将卸载前的结果应用到后续面板。
onBeforeUnmount(() => {
(H++, U++);
}),
// 渲染 CPU、内存及线路对应的带宽/流量配置,统一在末尾展示报价与下单入口。
(n, r) => {
let a = OperationNotice,
o = skeleton,
c = inputnumber,
u = ConfigBandwidth,
d = button,
b = message;
return _.upgradeAllowed === !1
? createCommentVNode(``, !0)
: (openBlock(),
createElementBlock(`div`, ne, [
createVNode(
a,
{
message: unref(M),
severity: `error`,
},
null,
8,
[`message`],
),
unref(I)
? (openBlock(),
createBlock(o, {
key: 0,
height: `240px`,
}))
: unref(P)
? (openBlock(),
createElementBlock(
Fragment,
{
key: 1,
},
[
createElementVNode(`div`, re, [
(r[2] ||= createElementVNode(
`span`,
null,
`CPU`,
-1,
)),
createElementVNode(`div`, ie, [
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(me),
(t) => (
openBlock(),
createElementBlock(
`button`,
{
key: t,
type: `button`,
class: normalizeClass({
selected: unref(B).cpu === t,
}),
"aria-pressed": unref(B).cpu === t,
disabled: unref(R),
onClick: (e) => $(`cpu`, t),
},
toDisplayString(t) + ` 核`,
11,
ae,
)
),
),
128,
)),
]),
]),
createElementVNode(`div`, oe, [
createElementVNode(
`label`,
{
for: `${unref(N)}-memory`,
},
`内存`,
8,
se,
),
unref(J)
? (openBlock(),
createBlock(
c,
{
key: 0,
"input-id": `${unref(N)}-memory`,
"model-value": unref(B).memory,
min: unref(J).min,
max: unref(J).max,
step: unref(J).step,
suffix: ` ${unref(P).config?.memory_unit || `GB`}`,
disabled: unref(R),
"show-buttons": ``,
"onUpdate:modelValue": (r[0] ||= (
e,
) => {
e != null && $(`memory`, e);
}),
},
null,
8,
[
`input-id`,
`model-value`,
`min`,
`max`,
`step`,
`suffix`,
`disabled`,
],
))
: (openBlock(),
createElementBlock(`div`, ce, [
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(he),
(t) => (
openBlock(),
createElementBlock(
`button`,
{
key: t,
type: `button`,
class: normalizeClass({
selected:
unref(B).memory === t,
}),
"aria-pressed":
unref(B).memory === t,
disabled: unref(R),
onClick: (e) => $(`memory`, t),
},
toDisplayString(t) +
` ` +
toDisplayString(
unref(P).config
?.memory_unit || `GB`,
),
11,
le,
)
),
),
128,
)),
])),
]),
unref(F)?.bill_type === `flow` && unref(X).length
? (openBlock(),
createElementBlock(`div`, ue, [
(r[3] ||= createElementVNode(
`span`,
null,
`流量配额`,
-1,
)),
createElementVNode(`div`, de, [
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(X),
(t) => (
openBlock(),
createElementBlock(
`button`,
{
key: t,
type: `button`,
class: normalizeClass({
selected: unref(B).flow === t,
}),
"aria-pressed":
unref(B).flow === t,
disabled: unref(R),
onClick: (e) => $(`flow`, t),
},
toDisplayString(
t > 0 ? `${t} GB` : `不限量`,
),
11,
fe,
)
),
),
128,
)),
]),
]))
: createCommentVNode(``, !0),
unref(F)?.bill_type === `bw` && unref(Y).length
? (openBlock(),
createBlock(
u,
{
key: 1,
"model-value": unref(B).bw,
options: unref(Y),
disabled: unref(R),
"onUpdate:modelValue": _e,
onChange: Q,
},
null,
8,
[`model-value`, `options`, `disabled`],
))
: createCommentVNode(``, !0),
createElementVNode(`div`, D, [
createElementVNode(`span`, null, [
(r[4] ||= createTextVNode(`所需费用:`, -1)),
createElementVNode(
`b`,
null,
toDisplayString(
unref(L)
? `正在计算…`
: unref(V)
? `¥${unref(V).price}`
: unref(K)
? `等待有效报价`
: `未变更配置`,
),
1,
),
]),
createVNode(
d,
{
label: `确认升降级`,
icon: `pi pi-check`,
loading: unref(R),
disabled:
!unref(K) ||
!unref(V) ||
unref(L) ||
unref(R),
onClick: ve,
},
null,
8,
[`loading`, `disabled`],
),
]),
unref(V)?.renew_price_difference
? (openBlock(),
createElementBlock(
`p`,
O,
`续费差价:¥` +
toDisplayString(
unref(V).renew_price_difference,
),
1,
))
: createCommentVNode(``, !0),
],
64,
))
: createCommentVNode(``, !0),
unref(z)
? (openBlock(),
createBlock(
b,
{
key: 2,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(z)), 1),
createVNode(d, {
label: `重试`,
severity: `secondary`,
text: ``,
onClick: (r[1] ||= (e) =>
unref(P) ? Q() : Z()),
}),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
]));
}
);
},
}),
[[`__scopeId`, `data-v-c9212028`]],
),
{
__name: `MfCloudResourceUpgrade`,
},
);
export {};
export default k;
</script>