docs: annotate product configuration and quote lifecycles

This commit is contained in:
cieanyau committed 2026-11-06 00:29:13 +08:00
1 parent 3a53d2e58f
commit 60827b8bfb
7 files changed
+75

No files matched your search

@@ -1,4 +1,8 @@
<script>
/**
* 云商品分组与分页选择器:groups/products/库存文案由父级提供,不直接请求接口。
* 分组、商品、重试和分页分别发出意图事件;disabled 将购买提交期间的选择锁定。
*/
import "~/assets/styles/ProductGroupLabel.B1kWpFA-.css";
import "~/assets/styles/CloudProductPicker.CAm9rmUl.css";
import { toDisplayString } from "vue";
+10
View File
@@ -1,4 +1,5 @@
<script>
/** 通用 idcsmart 商品配置:支持高级联动、多选、可重复数量项、级联树和操作系统分组。 */
import "~/assets/styles/ConfigDiskCard.Dx9IqqQ_.css";
import "~/assets/styles/PromoCodeField.qFEkB6Hm.css";
import "~/assets/styles/ProductCustomFields.B2QMnPil.css";
@@ -59,6 +60,7 @@ var ce = {
"36:month": `triennially`,
};
function R(e, t = {}) {
// 业务状态按普通配置 u、级联 d、重复数量 p 分开存储,再按现有后端契约组装。
let n = useApi(),
r = t.edit,
a = ref(!r),
@@ -104,6 +106,7 @@ function R(e, t = {}) {
E = computed(() => T.value.options),
D = !1;
function ee() {
// 高级规则可能修正多个关联值;D 防止同步深度 watch 在回写时递归执行同一轮规则。
if (D) return;
if (T.value.error) throw Error(T.value.error);
let e = T.value.form;
@@ -201,6 +204,7 @@ function R(e, t = {}) {
return e.subs?.[0]?.id;
}
function ue() {
// 只初始化可显示配置,按字段类型选择默认值;免费/一次性/后付费周期使用协议关键字。
let e = l.value;
if (!e) return;
let t = {};
@@ -294,6 +298,7 @@ function R(e, t = {}) {
: ((p.value[String(e)] = n.filter((e, n) => n !== t)), H(), !0);
}
function B() {
// 可重复数量项必须按数组提交,普通项保持原值类型,不能把所有配置都转换成字符串。
let e = {};
for (let t of l.value?.configoptions ?? []) {
let n = u.value[String(t.id)];
@@ -425,6 +430,7 @@ function R(e, t = {}) {
for (let e of g.value) m.value[e.id] = String(f[e.id] ?? ``);
}
async function H() {
// 先应用高级规则,再请求报价;请求序号与输入快照共同防止旧报价覆盖新配置。
if ((L(), F || !a.value || !l.value || !z.value)) return;
try {
ee();
@@ -470,6 +476,7 @@ function R(e, t = {}) {
}
}
async function ve() {
// 重读商品配置后初始化默认值、校验并回填购物车,最终才开放报价与提交。
a.value = !1;
let i = ++re,
o = () => !F && i === re;
@@ -510,6 +517,7 @@ function R(e, t = {}) {
return U(e).find((e) => e.name === t)?.versions ?? [];
}
function U(e) {
// 将供应商不同写法的系统名称归入显示分组,版本 ID 去重但仍保留原 ID 提交。
let t = [
[/^cent\s*os(?=$|[\s\d._-])/i, `CentOS`],
[/^debian(?=$|[\s\d._-])/i, `Debian`],
@@ -544,6 +552,7 @@ function R(e, t = {}) {
return [...n.values()];
}
async function be() {
// 加购/编辑前确认报价与自定义字段;级联项按 item_id + quantity 独立提交。
(R(), ge());
let t = {
product_id: e,
@@ -873,6 +882,7 @@ var le = {
};
}
function $(e) {
// 只有能无歧义解析容量/单位的存储选项才转换成容量控件,其余保留显式选项选择。
if (
e.option_type === `quantity` ||
e.option_type === `quantity_range`
+10
View File
@@ -1,4 +1,5 @@
<script>
/** 财务对接型商品配置:将不同财务模块返回值归一化为资源、周期、自定义字段及级联选项。 */
import "~/assets/styles/ConfigDiskCard.Dx9IqqQ_.css";
import "~/assets/styles/PromoCodeField.qFEkB6Hm.css";
import "~/assets/styles/ProductCustomFields.B2QMnPil.css";
@@ -56,6 +57,7 @@ import { o as cart_edit_o } from "~/lib/cart-edit.js";
import { r as cart_edit_r } from "~/lib/cart-edit.js";
import ConfigDiskCard from "~/components/config/ConfigDiskCard.vue";
function ie(e, t = `remf_finance`, n = {}) {
// storage-capacity 模块负责协议解析与联动约束,此处负责请求编排和已确认报价状态。
let r = useApi(),
i = n.edit,
a = ref(!i),
@@ -140,6 +142,7 @@ function ie(e, t = `remf_finance`, n = {}) {
((P = !0), B++, K());
}));
function ie() {
// 当前周期、数量、库存及每项资源都必须属于有效集合,前端不能提交已被联动排除的值。
let e = l.value;
if (!e) throw Error(`请先加载商品配置`);
if (g.value.error) throw Error(g.value.error);
@@ -183,6 +186,7 @@ function ie(e, t = `remf_finance`, n = {}) {
}
}
function ae(e = !1) {
// 报价只提交配置与周期;真正下单时才按商品要求附带开通主机名和密码。
let t = l.value,
n = {
configoption: Object.fromEntries(
@@ -256,6 +260,7 @@ function ie(e, t = `remf_finance`, n = {}) {
b.value[e.id] = String(c[e.id] ?? ``);
}
function se() {
// 反复应用本地联动直到稳定;重复快照意味着规则冲突,必须阻止继续报价。
let e = new Set();
for (let t = 0; t <= (l.value?.options.length ?? 0) * 2 + 2; t++) {
let t = JSON.stringify(d.value);
@@ -286,6 +291,7 @@ function ie(e, t = `remf_finance`, n = {}) {
throw Error(`商品联动条件无法确定,请联系客服`);
}
async function ce(n) {
// 级联项在 HTTP 边界读取下一级资源,响应只属于当前配置快照和刷新代次。
for (let i of l.value?.options.filter((e) => e.kind === `cascade`) ?? []) {
let a = f.value[i.id],
o = d.value[i.id],
@@ -323,6 +329,7 @@ function ie(e, t = `remf_finance`, n = {}) {
return !0;
}
async function le() {
// 先让级联和联动达成一致,再校验与报价;未得到稳定配置不能接受服务端价格。
let t = ++H;
if ((K(), P || !l.value)) return;
j.value = ``;
@@ -385,6 +392,7 @@ function ie(e, t = `remf_finance`, n = {}) {
}
}
async function ue() {
// 仅支持明确列出的财务模块,读取原始 order_page 后统一解析,避免猜测接口数据结构。
((a.value = !i), H++);
let n = ++B;
(K(),
@@ -443,6 +451,7 @@ function ie(e, t = `remf_finance`, n = {}) {
}
}
async function de(t) {
// 最终提交包括开通信息、自定义字段及级联参数;编辑模式只能保存购物车不能直接结算。
if (i && t !== `/cart`) throw Error(`请先保存配置,再返回购物车结算`);
if (N.value) throw Error(`正在提交,请勿重复操作`);
if (!W.value) throw Error(`当前配置报价尚未确认,请等待或重新计算报价`);
@@ -756,6 +765,7 @@ var ae = {
return Pe(e).find((e) => t >= e.min && t <= e.max)?.step ?? e.step;
}
function Ge(e, t, n = !0) {
// 数量输入在所有有效区间中吸附,跨区间的单步操作寻找下一个可达档位。
if (Y.value || t === null || !Number.isFinite(t)) return;
let r = I.value[e.id],
i = Pe(e),
+10
View File
@@ -1,4 +1,5 @@
<script>
/** 智简云/独立服务器配置页;module 决定接口命名空间,jy_dcim 额外将带宽变体归并为硬件型号。 */
import "~/assets/styles/ConfigResourceOption.Dj4VopnA.css";
import "~/assets/styles/ConfigDiskAdd.C92e9ZuF.css";
import "~/assets/styles/ConfigBandwidth.CWSH1WJZ.css";
@@ -78,6 +79,7 @@ function I(e) {
.trim();
}
function le(e) {
// 同名、同 CPU/内存/磁盘的套餐按带宽排序为同一型号,切换带宽仍需选择真实套餐 ID。
let t = new Map();
for (let n of e) {
let e = I(n.name || ``),
@@ -152,6 +154,7 @@ function R(e, t, n, r) {
return Math.min(a, Math.max(i, i + Math.round((s - i) / o) * o));
}
function z(e, t, n = {}) {
// 配置、优惠码和自定义字段组成报价快照;只有快照与最近报价一致时才允许提交。
let r = useApi(),
a = n.edit,
s = cart_edit_r(r, a),
@@ -212,6 +215,7 @@ function z(e, t, n = {}) {
),
ae = computed(() => (A.value && Number(A.value.data_disk_max) > 0 ? 1 : 0));
function oe(e) {
// 套餐变化同步约束所有附属资源;protect/defense 两字段保留现有后端协议要求。
if (!e) return;
let t = h.value.package_config_id;
((h.value.package_config_id = e.id),
@@ -348,6 +352,7 @@ function z(e, t, n = {}) {
);
}
async function B() {
// 金额只接受有限非负数,响应还必须匹配当前代次与输入快照。
if (
(k(),
C ||
@@ -419,6 +424,7 @@ function z(e, t, n = {}) {
}
}
async function V(e = `simple`) {
// 线路或节点改变会同时失效套餐和镜像;普通资源变化只重取周期及价格。
let t = ++w;
(k(), (S.value = ``));
try {
@@ -442,6 +448,7 @@ function z(e, t, n = {}) {
}
}
async function pe(e) {
// 独立服务器换型号尽量保留当前带宽,否则选该型号默认变体,再重新确认报价。
if (t !== `jy_dcim`) return !1;
let n = j.value.find((t) => t.key === e.key);
if (!n) return !1;
@@ -455,6 +462,7 @@ function z(e, t, n = {}) {
);
}
async function me(e) {
// 独立服务器带宽是套餐变体,不是可任意填写的额外数值。
if (t !== `jy_dcim`) return !1;
let n = j.value
.find((e) => e.variants.some((e) => F(e.id, h.value.package_config_id)))
@@ -501,6 +509,7 @@ function z(e, t, n = {}) {
};
}
async function ye() {
// 自定义字段校验和报价检查先于加购;购物车编辑借助统一保存器防止覆盖已变化的配置。
(_e(), product_fields_i(m.value, p.value));
let e = ve();
if (a) {
@@ -1009,6 +1018,7 @@ var de = {
((M.value.protect = e), (M.value.defense = e), C.calcPrice());
}
async function It(e) {
// 组件只向父级发出结算结果;编辑成功返回购物车,普通加购显示短时确认。
if (at.value) {
((U.value = e), (it.value = ``), (W.value = ``), clearTimeout(G));
try {
+13
View File
@@ -1,4 +1,8 @@
<script>
/**
* 快快云 ECS 商品配置:区域/CPU 型号决定可售套餐,套餐再约束网络、磁盘、防护和周期。
* productId 确定商品,edit 表示购物车编辑;settled 只在创建即时购买订单后通知父级。
*/
import "~/assets/styles/ConfigDiskAdd.C92e9ZuF.css";
import "~/assets/styles/ConfigBandwidth.CWSH1WJZ.css";
import "~/assets/styles/ConfigDiskCard.Dx9IqqQ_.css";
@@ -62,6 +66,7 @@ function ue(e) {
};
}
function M(e, t) {
// 将磁盘容量吸附到 min + n*step,最大值必须是区间内可达档位。
let n = ue(t);
if (!n) return ``;
let r = e === `` || !Number.isFinite(Number(e)) ? n.min : Number(e),
@@ -98,6 +103,7 @@ function de() {
};
}
function fe(e, t = {}) {
// 配置状态与报价分开保存;N 为当前输入快照,P 为最近成功报价对应快照。
let n = useApi(),
i = t.edit,
o = ref(!i),
@@ -152,6 +158,7 @@ function fe(e, t = {}) {
return Number.isFinite(t) && t > 0 ? Math.floor(t) : 0;
});
function ie() {
// 切换套餐后重新约束系统盘和数据盘,并截断超出商品允许数量的附加磁盘。
let e = l.value;
if (!e) return;
let t = p.value,
@@ -258,6 +265,7 @@ function fe(e, t = {}) {
throw Error(`当前配置报价尚未确认,请等待或重新计算报价`);
}
function he() {
// 依据服务端可售集合修正级联选择:区域 → CPU 型号/套餐 → 网络 → 计费方式。
let e = l.value;
if (!e) return;
let t = p.value;
@@ -374,6 +382,7 @@ function fe(e, t = {}) {
return ((p.value.duration_name = a?.name ?? ``), !0);
}
async function B() {
// 报价绑定输入快照与请求序号;配置变化立即废弃旧价格,不能让迟到响应恢复购买按钮。
if (!o.value || (z(), F || !p.value.duration_id || !p.value.image_id))
return;
if (l.value && !T.value.some((e) => C(e.id, p.value.package_config_id))) {
@@ -416,6 +425,7 @@ function fe(e, t = {}) {
}
}
async function be() {
// 初始化并行读取镜像与可售配置,编辑场景再回填购物车和自定义字段,最后获取周期与报价。
o.value = !1;
let r = ++I;
(z(), (b.value = !0), (S.value = ``), i && ge(i.item));
@@ -464,6 +474,7 @@ function fe(e, t = {}) {
}
}
async function V(e = {}) {
// 上游选项改变时清空依赖选项,再按顺序重新读取配置、周期、价格。
let t = ++I;
(z(),
(S.value = ``),
@@ -483,6 +494,7 @@ function fe(e, t = {}) {
}
}
async function xe() {
// 加购与编辑都必须使用已确认报价和已校验字段;编辑保存期间仍核对快照未变化。
(me(), product_fields_i(h.value, m.value));
let t = {
product_id: e,
@@ -505,6 +517,7 @@ function fe(e, t = {}) {
return n.post(`/cart`, t);
}
async function Se() {
// 编辑模式只能先保存回购物车;即时购买将当前配置提交到 /product/settle。
if (i) throw Error(`请先保存配置,再返回购物车结算`);
return (
me(),
+19
View File
@@ -1,4 +1,8 @@
<script>
/**
* 魔方云系列商品配置:支持自选/推荐套餐、磁盘、网络、备份快照及商品开放的预付/按需模式。
* initialProduct/initialImageId 用于进入配置页时续接选择;真实成交价始终来自配置报价接口。
*/
import "~/assets/styles/ConfigResourceOption.Dj4VopnA.css";
import "~/assets/styles/ConfigDiskAdd.C92e9ZuF.css";
import "~/assets/styles/ConfigBandwidth.CWSH1WJZ.css";
@@ -65,6 +69,7 @@ function I(e) {
);
}
function L(e, t) {
// 区间内的值还必须落在步长网格上,不能仅凭最小/最大值认定配置可购买。
if (t == null || t === ``) return !1;
let n = Number(t);
return !e?.length || !Number.isFinite(n)
@@ -128,6 +133,7 @@ function pe() {
};
}
function R(e, t = `mf_cloud`, n = {}) {
// 这里集中维护报价、配置代次及购物车回填;下方 setup 只组织控件与意图事件。
let r = useApi(),
a = `/product/${e}/${t}`,
s = n.customFields === !0 || !!n.edit,
@@ -219,6 +225,7 @@ function R(e, t = `mf_cloud`, n = {}) {
e.peak_defence != null && (w.value.peak_defence = String(e.peak_defence));
}
function fe() {
// 商品若仅售套餐,必须选当前线路的可售推荐配置,不可绕过套餐约束使用自选资源。
if (w.value.recommend_config_id && !k.value)
throw Error(`所选套餐已不可用,请重新选择套餐`);
if (!w.value.recommend_config_id && oe.value) {
@@ -228,6 +235,7 @@ function R(e, t = `mf_cloud`, n = {}) {
} else k.value && de(k.value);
}
function M() {
// 备份、快照和 GPU 额度按商品已开放档位校验;推荐套餐自带 GPU 也保留其合法额度。
for (let [e, t, n] of [
[`备份`, w.value.backup_num, se.value.map((e) => e.num)],
[`快照`, w.value.snap_num, ce.value.map((e) => e.num)],
@@ -294,6 +302,7 @@ function R(e, t = `mf_cloud`, n = {}) {
!B.value,
);
function q() {
// 任意输入改变立即清空已确认报价与折扣,防止旧价在新配置上继续可提交。
((_e += 1),
(ve.value = ``),
(me.value = `—`),
@@ -338,6 +347,7 @@ function R(e, t = `mf_cloud`, n = {}) {
: 16;
});
function J(e, t, n = !1) {
// 磁盘选项结合介质、容量步长和赠送盘规则生成;赠送容量也需保留独立合法档位。
let r = _.value;
if (!r) return [];
let i = r.config,
@@ -403,6 +413,7 @@ function R(e, t = `mf_cloud`, n = {}) {
);
}
function Te() {
// 赠送盘按站点规则补入,并和普通数据盘区分,避免被删除或重复赠送。
let e = _.value?.config,
t = Number(e?.free_disk_size),
n = w.value.data_disk.find((e) => Number(e.is_free) === 1),
@@ -421,6 +432,7 @@ function R(e, t = `mf_cloud`, n = {}) {
} else w.value.data_disk = r;
}
function Ee() {
// 容量或介质变化后,原磁盘性能套餐可能失效,需要在报价前清除不匹配的 disk_limit_id。
for (let [e, t] of [
[`system`, [w.value.system_disk]],
[`data`, w.value.data_disk],
@@ -595,6 +607,7 @@ function R(e, t = `mf_cloud`, n = {}) {
((s = !0), (g.value = !1), _.value && (await Re()));
}
async function Fe(e = G) {
// 同一线路合并在途读取;写回前同时核对配置代次和当前线路,避免串用上一个线路价格。
let t = w.value.line_id;
if (!t) return !1;
if (b.value === t && y.value) return !0;
@@ -659,6 +672,7 @@ function R(e, t = `mf_cloud`, n = {}) {
return !0;
}
async function Le() {
// 报价前完成套餐/额度/线路校验;按需模式还必须获得合法运行费、流量费及保留费信息。
if ((q(), W || !g.value)) return;
if (!d.includes(w.value.billing_cycle)) {
V.value = `当前商品未开放所选计费方式`;
@@ -742,6 +756,7 @@ function R(e, t = `mf_cloud`, n = {}) {
}
}
async function Re(t) {
// 整体初始化重读商品及镜像;登录前选中的镜像若已失效,要求用户重新选择而非静默换系统。
g.value = !1;
let i = ++G;
(q(),
@@ -806,6 +821,7 @@ function R(e, t = `mf_cloud`, n = {}) {
}
}
async function ze(e) {
// 改计费模式必须重读商品配置并清空周期/流量/带宽,不能沿用另一模式的报价。
!W &&
e !== w.value.billing_cycle &&
d.includes(e) &&
@@ -854,6 +870,7 @@ function R(e, t = `mf_cloud`, n = {}) {
await Y(!0);
}
async function Ve() {
// 提交前检查报价快照与自定义字段;加购保持现有 cycle/configoption 协议形状。
(Se(), product_fields_i(F.value, P.value));
let t = {
product_id: e,
@@ -1124,6 +1141,7 @@ var me = {
),
);
watch(
// 将当前镜像选择登记到父级续接路径,watch 清理函数释放上一条登记。
Xe,
(e, t, n) => {
let r = Ye?.(e);
@@ -1474,6 +1492,7 @@ var me = {
}
}
async function At() {
// 只有报价已确认且未锁定身份/操作时才能创建订单,成功结果由父级接入支付流程。
if (et.value) {
((Z.value = `buy`), (Ze.value = ``), (Qe.value = ``));
try {
+9
View File
@@ -1,4 +1,5 @@
<script>
/** 魔方物理服务器订购:型号、线路和操作系统决定周期与报价,支持型号选配和购物车编辑。 */
import "~/assets/styles/ConfigDiskCard.Dx9IqqQ_.css";
import "~/assets/styles/PromoCodeField.qFEkB6Hm.css";
import "~/assets/styles/ProductCustomFields.B2QMnPil.css";
@@ -51,6 +52,7 @@ import { r as cart_edit_r } from "~/lib/cart-edit.js";
import ConfigDiskCard from "~/components/config/ConfigDiskCard.vue";
import slider from "primevue/slider";
function z(e, t = {}, n = `mf_dcim`, r = {}) {
// 商品库存与型号同步库存可能同时生效;可购数量取所有有效限制中的最小值。
let i = useApi(),
s = r.edit,
l = ref(!s),
@@ -194,6 +196,7 @@ function z(e, t = {}, n = `mf_dcim`, r = {}) {
: ``;
};
function ue() {
// 报价和提交共用前置校验,型号/线路/镜像与正整数数量缺一不可。
if (!j.value || !ne.value || !D.value.image_id)
throw Error(`请选择型号、线路和操作系统`);
if (!Number.isInteger(C.value) || C.value < 1)
@@ -201,6 +204,7 @@ function z(e, t = {}, n = `mf_dcim`, r = {}) {
if (!ie.value) throw Error(`所选型号库存不足,请减少数量或选择其他型号`);
}
async function de(e) {
// 网络档位来自线路详情;回填购物车时验证旧档位仍合法,不静默替用户换配置。
let t = D.value.line_id;
if (ce.value) return !0;
(!W || W.id !== t) &&
@@ -258,6 +262,7 @@ function z(e, t = {}, n = `mf_dcim`, r = {}) {
};
}
function pe(e) {
// 回填期间 H 暂停“切换型号即清空选配”的 watch,防止用户原有选配被初始化误删。
let t =
e.config_options &&
typeof e.config_options == `object` &&
@@ -300,6 +305,7 @@ function z(e, t = {}, n = `mf_dcim`, r = {}) {
for (let e of k.value) O.value[e.id] = String(a[e.id] ?? ``);
}
async function me(t) {
// 报价响应必须同时匹配配置代次、请求序号和快照,且金额必须为有效非负数。
if (!l.value) return;
let n = ++se,
r = F.value;
@@ -330,6 +336,7 @@ function z(e, t = {}, n = `mf_dcim`, r = {}) {
}
}
async function he() {
// 型号/网络变化先重新确定可售周期,再报价;无可购周期不能继续显示旧报价。
let e = ++z;
(K(), (x.value = ``), (b.value = !0));
try {
@@ -442,6 +449,7 @@ function z(e, t = {}, n = `mf_dcim`, r = {}) {
}
}
async function ve(t) {
// 最终提交前调用 validate_settle,再检查配置未变化;正在提交的状态阻止重复下单。
if (s && t !== `cart`) throw Error(`请先保存配置,再返回购物车结算`);
if (E.value) throw Error(`正在提交,请勿重复操作`);
if ((ue(), product_fields_i(k.value, O.value), !G.value))
@@ -820,6 +828,7 @@ var B = {
: []),
]);
async function xt(e) {
// 如配置了服务条款链接,必须先同意;即时购买发 settled,编辑成功返回购物车。
if (st.value) {
((it.value = e), (at.value = ``), (ot.value = ``));
try {