docs: annotate payment, host operations and shared components

This commit is contained in:
cieanyau committed 2026-11-06 00:29:13 +08:00
1 parent d37ce57d09
commit dc32a74d7a
17 files changed
+100

No files matched your search

+3
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 监听 Nuxt 版本清单与分块加载失败,提示用户保存表单后确认刷新,不自动打断操作。 */
import "~/assets/styles/BuildUpdateNotice.CuqdqvlF.css"; import "~/assets/styles/BuildUpdateNotice.CuqdqvlF.css";
import { reloadNuxtApp as y } from "#app"; import { reloadNuxtApp as y } from "#app";
import { useNuxtApp } from "#app"; import { useNuxtApp } from "#app";
@@ -45,6 +46,7 @@ var b = {
((C.value = !0), (h.value = !0)); ((C.value = !0), (h.value = !0));
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
// hook 返回取消注册函数;卸载后必须解绑,避免重复提示。
(T(), E()); (T(), E());
}); });
function D() { function D() {
@@ -54,6 +56,7 @@ var b = {
v.value = !0; v.value = !0;
} }
function k() { function k() {
// 确认后强制完整刷新且不持久化 Nuxt 状态,避免新版本继续使用旧内存状态。
!S.value && !S.value &&
v.value && v.value &&
((S.value = !0), ((S.value = !0),
+4
View File
@@ -1,4 +1,5 @@
<script setup> <script setup>
/** 共用折线图容器:父级负责 option/数据,组件补充主题、可见尺寸和卸载清理。 */
import { computed, onMounted, onBeforeUnmount, ref } from "vue"; import { computed, onMounted, onBeforeUnmount, ref } from "vue";
import { use } from "echarts/core"; import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers"; import { CanvasRenderer } from "echarts/renderers";
@@ -13,6 +14,7 @@ import { t as useTheme } from "~/lib/useTheme.js";
import "~/assets/styles/ChartBox.CYP_mhat.css"; import "~/assets/styles/ChartBox.CYP_mhat.css";
use([ use([
// 仅注册实际使用的折线图、Canvas 和基础组件;新增图表类型时须同步显式注册。
CanvasRenderer, CanvasRenderer,
LineChart, LineChart,
GridComponent, GridComponent,
@@ -29,6 +31,7 @@ const container = ref(null);
const visible = ref(false); const visible = ref(false);
let observer; let observer;
onMounted(() => { onMounted(() => {
// 折叠面板初始尺寸可能为零;等容器可测量后创建图表,避免 ECharts 零宽初始化。
observer = new ResizeObserver(([entry]) => { observer = new ResizeObserver(([entry]) => {
if (entry?.contentRect.width > 0 && entry.contentRect.height > 0) if (entry?.contentRect.width > 0 && entry.contentRect.height > 0)
visible.value = true; visible.value = true;
@@ -37,6 +40,7 @@ onMounted(() => {
}); });
onBeforeUnmount(() => observer?.disconnect()); onBeforeUnmount(() => observer?.disconnect());
const themedOption = computed(() => { const themedOption = computed(() => {
// 在不修改父级 option 的前提下合成坐标轴主题,兼容单个轴与多轴数组。
const dark = theme.isDark.value; const dark = theme.isDark.value;
const color = dark ? "#a1a1aa" : "#94a3b8"; const color = dark ? "#a1a1aa" : "#94a3b8";
const grid = dark ? "rgba(255,255,255,.08)" : "rgba(0,0,0,.06)"; const grid = dark ? "rgba(255,255,255,.08)" : "rgba(0,0,0,.06)";
+1
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 控制台固定备案页脚;运营主体、备案号及许可号为展示内容,更新需先核实实际登记信息。 */
import "~/assets/styles/ConsoleLegalFooter.DAYWmXIB.css"; import "~/assets/styles/ConsoleLegalFooter.DAYWmXIB.css";
import { createElementBlock } from "vue"; import { createElementBlock } from "vue";
import { createElementVNode } from "vue"; import { createElementVNode } from "vue";
+28
View File
@@ -1,4 +1,10 @@
<script> <script>
/**
* 服务器列表的共享业务组件,供云服务器、独立服务器及动态后台菜单复用。
* modules/menuId 决定数据范围,title/tabs/tabParams 决定列表展示与筛选映射;
* 页面自身负责列表、按模块补充状态/流量、备注、批量电源操作及批量续费。
* 恢复后的短变量名仍与渲染树对应,修改时重点保持身份代次、分页快照及模块接口契约。
*/
import "~/assets/styles/PaymentDialog.BNFKQF1U.css"; import "~/assets/styles/PaymentDialog.BNFKQF1U.css";
import "~/assets/styles/OsIcon.CAi4Iz2S.css"; import "~/assets/styles/OsIcon.CAi4Iz2S.css";
import "~/assets/styles/HostList.B3U15sJm.css"; import "~/assets/styles/HostList.B3U15sJm.css";
@@ -72,6 +78,7 @@ import { n as renewal_quote_n } from "~/lib/renewal-quote.js";
import { r as renewal_quote_r } from "~/lib/renewal-quote.js"; import { r as renewal_quote_r } from "~/lib/renewal-quote.js";
import { t as renewal_quote_t } from "~/lib/renewal-quote.js"; import { t as renewal_quote_t } from "~/lib/renewal-quote.js";
var T = new WeakMap(); var T = new WeakMap();
// 列表筛选快照按 auth 实例和菜单/模块分别保存;切换 token 时清空,避免串用账号状态。
function We(e, t) { function We(e, t) {
let n = T.get(e); let n = T.get(e);
if (!n) { if (!n) {
@@ -114,6 +121,7 @@ function We(e, t) {
}; };
} }
function Ge(e, t) { function Ge(e, t) {
// 机电状态查询器:每轮重建请求队列,最多三个并发,并用身份/版本/请求号拒绝过期响应。
let n = ref({}), let n = ref({}),
r = 0, r = 0,
i = !1, i = !1,
@@ -132,6 +140,7 @@ function Ge(e, t) {
(r++, (d = []), l.clear(), (n.value = {})); (r++, (d = []), l.clear(), (n.value = {}));
} }
function m(e) { function m(e) {
// 实例必须仍在当前列表且为 Active,才能将异步返回的电源状态写回。
return ( return (
!i && !i &&
e.version === r && e.version === r &&
@@ -217,6 +226,7 @@ function Ge(e, t) {
g(); g();
} }
function _(e) { function _(e) {
// 电源操作成功后优先刷新对应实例,并先显示 operating,不能假定指令已执行完成。
if (i || t.loading.value || t.error.value) return; if (i || t.loading.value || t.error.value) return;
let a = new Set(e); let a = new Set(e);
d = d.filter((e) => !a.has(e.id)); d = d.filter((e) => !a.has(e.id));
@@ -294,12 +304,14 @@ for (let e of [
actions: D, actions: D,
}; };
function Ke(e) { function Ke(e) {
// 不同模块支持的批量动作不同;列表中的 re 前缀模块仍使用对应接口能力映射。
return O[e] || O[`re${e}`]; return O[e] || O[`re${e}`];
} }
function qe(e) { function qe(e) {
return typeof e == `string` ? O[e] : void 0; return typeof e == `string` ? O[e] : void 0;
} }
function Je(e, t) { function Je(e, t) {
// 每个请求 ID 必须恰好得到一项明确结果;缺失、重复或非 200 响应一律不能算成功。
let n = Array.isArray(e) ? e : []; let n = Array.isArray(e) ? e : [];
return t.map((e) => { return t.map((e) => {
let t = n.filter((t) => t && typeof t == `object` && Number(t.id) === e), let t = n.filter((t) => t && typeof t == `object` && Number(t.id) === e),
@@ -512,6 +524,7 @@ var Ye = {
tabParams: {}, tabParams: {},
}, },
setup(c) { setup(c) {
// 共享列表适配器负责分页和缓存,本组件补充菜单字段配置及实例业务操作。
let m = c, let m = c,
b = useApi(), b = useApi(),
w = useAuth(), w = useAuth(),
@@ -522,6 +535,7 @@ var Ye = {
D = ref(null), D = ref(null),
O = 0; O = 0;
async function rn(e) { async function rn(e) {
// 异步返回的动态列只属于当前请求与当前账号,不能覆盖切换后的菜单字段。
let t = ++O, let t = ++O,
n = w.token, n = w.token,
r = await E(e); r = await E(e);
@@ -554,6 +568,7 @@ var Ye = {
); );
} }
function on(e, t) { function on(e, t) {
// 动态自定义列仅展示标量;密码字段始终遮盖,不因表格刷新而暴露原值。
let n = e.self_defined_field?.[t.id]; let n = e.self_defined_field?.[t.id];
return n == null || n === `` return n == null || n === ``
? `—` ? `—`
@@ -675,6 +690,7 @@ var Ye = {
wn = Cn.statusOf, wn = Cn.statusOf,
Tn = T?.scrollY ?? null; Tn = T?.scrollY ?? null;
function En() { function En() {
// 离开详情前保存筛选、分页及滚动位置;快照仅存在当前账号的内存中。
Oe.save({ Oe.save({
tab: A.value, tab: A.value,
keywords: j.value, keywords: j.value,
@@ -742,6 +758,7 @@ var Ye = {
}, },
)); ));
function Dn() { function Dn() {
// 业务操作后的强刷新同时失效列表、状态和流量补充数据,防止混合新旧快照。
return ( return (
Cn.reset(), Cn.reset(),
E.invalidate(!0), E.invalidate(!0),
@@ -800,6 +817,7 @@ var Ye = {
); );
} }
async function Rn(e, t = !1) { async function Rn(e, t = !1) {
// 快快云流量单独取数:同一实例合并在途请求,以刷新版本阻止旧数据回写。
if ( if (
e.module !== `kky_ecs` || e.module !== `kky_ecs` ||
e.status === `Unpaid` || e.status === `Unpaid` ||
@@ -882,6 +900,8 @@ var Ye = {
: n; : n;
} }
async function Bn(e) { async function Bn(e) {
// 按实例真实模块补齐系统与流量信息;列表缺少 module 时先查询 host/view。
// 不同财务模块的配置结构并不相同,不能把此处接口路径直接统一成一种格式。
let t = R; let t = R;
if (!(Fn.value[e.id]?.version === t || In.has(e.id))) { if (!(Fn.value[e.id]?.version === t || In.has(e.id))) {
In.set(e.id, t); In.set(e.id, t);
@@ -962,6 +982,7 @@ var Ye = {
} }
} }
async function Vn(e) { async function Vn(e) {
// 附加数据按三路工作队列加载;翻页或卸载推进代次,不再继续启动旧队列任务。
let t = ++Ln; let t = ++Ln;
if (dn.value) return; if (dn.value) return;
async function n(n) { async function n(n) {
@@ -1009,6 +1030,7 @@ var Ye = {
W.value || ((U.value = null), (G.value = ``)); W.value || ((U.value = null), (G.value = ``));
} }
async function qn() { async function qn() {
// 单条备注保存绑定提交时身份,成功后刷新列表,避免仅本地乐观更新留下旧缓存。
let e = U.value; let e = U.value;
if (!e || W.value) return; if (!e || W.value) return;
let t = Wn.value, let t = Wn.value,
@@ -1130,6 +1152,7 @@ var Ye = {
Q.value || (er.value = null); Q.value || (er.value = null);
} }
async function sr() { async function sr() {
// 批量备注要求成功/失败/总数与所选 ID 严格对齐,结果不完整时回读列表而不报全成功。
let e = er.value; let e = er.value;
if (!e || Q.value || X || e.token !== w.token || e.epoch !== Y) if (!e || Q.value || X || e.token !== w.token || e.epoch !== Y)
return; return;
@@ -1225,6 +1248,8 @@ var Ye = {
(cr(), vn(e)); (cr(), vn(e));
} }
async function hr() { async function hr() {
// 批量电源操作先逐台核对真实管理模块,再按接口分组提交,保留失败实例供重试。
// 请求过程绑定 token 与操作代次;“指令下发”不等于已开关机,完成后另刷电源状态。
let e = J.value; let e = J.value;
if ( if (
!e || !e ||
@@ -1333,6 +1358,7 @@ var Ye = {
_r.value.filter((e) => e.renew_forbidden !== 1), _r.value.filter((e) => e.renew_forbidden !== 1),
); );
async function Dr() { async function Dr() {
// 批量续费先取可用周期和报价,所选实例改变、弹窗关闭或卸载后放弃旧报价。
if (xr.value || Tr) return; if (xr.value || Tr) return;
let e = ++wr, let e = ++wr,
t = K.value.map((e) => e.id), t = K.value.map((e) => e.id),
@@ -1385,6 +1411,7 @@ var Ye = {
), ),
Ar = ref(null); Ar = ref(null);
async function jr() { async function jr() {
// 只提交允许续费的实例、周期及非空优惠码;未支付订单交给 PaymentDialog 接续处理。
if (Er.value.length) { if (Er.value.length) {
((xr.value = !0), (Sr.value = ``), (Cr.value = ``)); ((xr.value = !0), (Sr.value = ``), (Cr.value = ``));
try { try {
@@ -1435,6 +1462,7 @@ var Ye = {
e && Ar.value && (Pr.value = Ar.value.id); e && Ar.value && (Pr.value = Ar.value.id);
}), }),
(e, r) => { (e, r) => {
// 渲染区域依次包含筛选表格、备注/批量操作弹窗、续费报价及共用支付面板。
let a = ListSegments, let a = ListSegments,
s = inputtext, s = inputtext,
u = button, u = button,
+1
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 无数据状态的展示组件;icon/title/description 由父级提供,默认插槽可放置创建或重试操作。 */
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { normalizeClass } from "vue"; import { normalizeClass } from "vue";
import { renderSlot } from "vue"; import { renderSlot } from "vue";
+1
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 列表分段筛选器:options 使用 label/value,点击只更新 v-model,由父级决定何时请求数据。 */
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { renderList } from "vue"; import { renderList } from "vue";
import { normalizeClass } from "vue"; import { normalizeClass } from "vue";
+2
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 根据站点强制安全方式决定是否渲染操作密码插槽;实际密码校验仍由提交接口负责。 */
import { onMounted } from "vue"; import { onMounted } from "vue";
import { renderSlot } from "vue"; import { renderSlot } from "vue";
import { createCommentVNode } from "vue"; import { createCommentVNode } from "vue";
@@ -11,6 +12,7 @@ var s = Object.assign(
__name: `OperatePasswordGate`, __name: `OperatePasswordGate`,
setup(a) { setup(a) {
let { config: s, load: c } = useSiteConfig_t(), let { config: s, load: c } = useSiteConfig_t(),
// 配置未加载或未开启 operate_password 时不显示,不自行推测站点策略。
l = computed( l = computed(
() => () =>
Array.isArray(s.value?.home_enforce_safe_method) && Array.isArray(s.value?.home_enforce_safe_method) &&
+2
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 将父级 message/severity/details 转交给 operations 通知组;默认插槽仍正常渲染。 */
import { onMounted } from "vue"; import { onMounted } from "vue";
import { renderSlot } from "vue"; import { renderSlot } from "vue";
import { watch } from "vue"; import { watch } from "vue";
@@ -20,6 +21,7 @@ var a = Object.assign(
let a = r, let a = r,
o = useToast(); o = useToast();
function s() { function s() {
// 有逐项结果时不自动消失,便于用户检查;普通成功/失败通知按不同寿命自动关闭。
a.message.trim() && a.message.trim() &&
o.add({ o.add({
group: `operations`, group: `operations`,
+2
View File
@@ -1,4 +1,5 @@
<script> <script>
/** operations 通知组的统一展示层,最多显示三条,批量操作明细通过可展开区域呈现。 */
import "~/assets/styles/OperationToasts.DfA_b8om.css"; import "~/assets/styles/OperationToasts.DfA_b8om.css";
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { renderList } from "vue"; import { renderList } from "vue";
@@ -29,6 +30,7 @@ var h = {
let _ = useToast(), let _ = useToast(),
v = useAuth(); v = useAuth();
watch( watch(
// 账号改变即移除旧通知,避免下一位用户看到上一账号的操作结果。
() => v.token, () => v.token,
() => _.removeGroup(`operations`), () => _.removeGroup(`operations`),
); );
+1
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 页面静态说明容器:内容来自默认插槽,使用 note 而非实时警报,避免反复朗读。 */
import "~/assets/styles/PageInfoNotice._16xRFrB.css"; import "~/assets/styles/PageInfoNotice._16xRFrB.css";
import { renderSlot } from "vue"; import { renderSlot } from "vue";
import { createBlock } from "vue"; import { createBlock } from "vue";
+1
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 只读步骤条:steps 为文案数组,current 从 1 开始;小于当前步骤的项显示完成标记。 */
import "~/assets/styles/PageSteps.B5K7G9zF.css"; import "~/assets/styles/PageSteps.B5K7G9zF.css";
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { renderList } from "vue"; import { renderList } from "vue";
+23
View File
@@ -1,4 +1,10 @@
<script> <script>
/**
* 订单支付面板:支持弹窗与内嵌两种展示,共用同一套订单、余额及代金券状态。
* 父级传入 orderId/amount,以 modelValue 控制可见性;success 表示服务端已确认支付,
* updated 用于通知父级刷新抵扣后的订单。amount 仅作初始展示,支付前会重新读取订单。
* 本文件保留恢复后的渲染函数与短变量名;金额换算和异步代次是维护时的关键约束。
*/
import "~/assets/styles/PaymentDialog.BNFKQF1U.css"; import "~/assets/styles/PaymentDialog.BNFKQF1U.css";
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { renderList } from "vue"; import { renderList } from "vue";
@@ -295,6 +301,7 @@ var ue = {
), ),
emits: mergeModels([`success`, `updated`], [`update:modelValue`]), emits: mergeModels([`success`, `updated`], [`update:modelValue`]),
setup(i, { emit: _ }) { setup(i, { emit: _ }) {
// C 为支付步骤,O 为已支付标记;余额和订单金额在内部统一使用整数分。
let b = useModel(i, `modelValue`), let b = useModel(i, `modelValue`),
x = i, x = i,
Et = _, Et = _,
@@ -358,6 +365,7 @@ var ue = {
: `可用余额 ¥0.00`, : `可用余额 ¥0.00`,
); );
function H(e) { function H(e) {
// 后端金额以元返回;拒绝空值、负值及超出安全整数范围的金额,避免错误抵扣。
if (e == null || String(e).trim() === ``) return null; if (e == null || String(e).trim() === ``) return null;
let t = Number(e), let t = Number(e),
n = Math.round(t * 100); n = Math.round(t * 100);
@@ -366,6 +374,7 @@ var ue = {
: null; : null;
} }
function U(e) { function U(e) {
// 仅在展示及接口金额文本边界将分转换回两位小数的元。
return (e / 100).toFixed(2); return (e / 100).toFixed(2);
} }
let W = ref([]), let W = ref([]),
@@ -384,6 +393,7 @@ var ue = {
paymentPoll = null, paymentPoll = null,
Z = (e, t) => e === X && b.value && String(x.orderId) === String(t); Z = (e, t) => e === X && b.value && String(x.orderId) === String(t);
function stopPaymentPolling() { function stopPaymentPolling() {
// 一个支付面板只保留一个轮询会话,同时取消周期任务和绝对截止定时器。
const session = paymentPoll; const session = paymentPoll;
paymentPoll = null; paymentPoll = null;
if (!session) return; if (!session) return;
@@ -391,6 +401,7 @@ var ue = {
clearTimeout(session.deadlineTimer); clearTimeout(session.deadlineTimer);
} }
function It() { function It() {
// 支付成功只通知一次;刷新账户概览失败不能反向撤销服务端支付结果。
O.value || O.value ||
(stopPaymentPolling(), (stopPaymentPolling(),
(O.value = !0), (O.value = !0),
@@ -398,6 +409,7 @@ var ue = {
Et(`success`)); Et(`success`));
} }
async function $(e = X, t = x.orderId, n = !1) { async function $(e = X, t = x.orderId, n = !1) {
// 以订单 ID 和面板代次验证响应归属,关闭弹窗或切换订单后丢弃旧结果。
if (!t) return !1; if (!t) return !1;
let r = await S.get(`/order/${t}`); let r = await S.get(`/order/${t}`);
if (!Z(e, t)) return !1; if (!Z(e, t)) return !1;
@@ -425,6 +437,7 @@ var ue = {
); );
} }
async function Lt(e = X, t = x.orderId) { async function Lt(e = X, t = x.orderId) {
// 余额读取失败保留“不可用”状态,而不是把未知余额当作可支付的零余额。
if (t) { if (t) {
M.value = ``; M.value = ``;
try { try {
@@ -444,6 +457,7 @@ var ue = {
} }
} }
async function Rt(e = X, t = x.orderId) { async function Rt(e = X, t = x.orderId) {
// credit 由独立余额开关管理;只有唯一外部支付渠道时才自动选中该渠道。
if (t) if (t)
try { try {
let n = await S.get(`/gateway`); let n = await S.get(`/gateway`);
@@ -458,6 +472,7 @@ var ue = {
} }
} }
async function zt(e = X, t = x.orderId, n = !0) { async function zt(e = X, t = x.orderId, n = !0) {
// 代金券可用列表与已使用状态都以服务端为准,不能在前端自行扣减订单金额。
if (t) { if (t) {
(n && (J.value = !0), (Y.value = ``)); (n && (J.value = !0), (Y.value = ``));
try { try {
@@ -493,6 +508,7 @@ var ue = {
return t === null ? `` : t > 0 ? `满 ¥${U(t)} 可用` : `无金额门槛`; return t === null ? `` : t > 0 ? `满 ¥${U(t)} 可用` : `无金额门槛`;
} }
async function Ht() { async function Ht() {
// 打开支付面板时并行读取订单、账户余额与渠道;订单类型决定是否再加载代金券。
// A visible dialog can outlive session invalidation until route teardown. // A visible dialog can outlive session invalidation until route teardown.
if (!Dt.isLoggedIn) return; if (!Dt.isLoggedIn) return;
let e = x.orderId, let e = x.orderId,
@@ -543,6 +559,7 @@ var ue = {
((T.value = e), (k.value = ``))); ((T.value = e), (k.value = ``)));
} }
async function Kt(e) { async function Kt(e) {
// 切换代金券后重新获取订单;失败时也回读券状态,覆盖服务端可能已完成的更新。
let t = x.orderId, let t = x.orderId,
n = X; n = X;
if (!(!t || D.value || A.value || J.value)) { if (!(!t || D.value || A.value || J.value)) {
@@ -576,6 +593,7 @@ var ue = {
Kt(e.value ?? null); Kt(e.value ?? null);
} }
(watch( (watch(
// 可见性、订单或登录身份任一变化都会推进代次,使此前请求和轮询立即失效。
[b, () => x.orderId, () => Dt.token], [b, () => x.orderId, () => Dt.token],
() => { () => {
(X++, (X++,
@@ -614,6 +632,8 @@ var ue = {
(X++, stopPaymentPolling()); (X++, stopPaymentPolling());
})); }));
async function Jt() { async function Jt() {
// 真正发起支付前再次确认订单状态、金额和余额,防止用户依据过期快照付款。
// 组合支付先提交余额抵扣,再让 /pay 处理剩余款项;Paid 必须来自后端确认。
if (D.value || A.value || J.value || !b.value || !x.orderId) return; if (D.value || A.value || J.value || !b.value || !x.orderId) return;
if (!Mt.value) { if (!Mt.value) {
k.value = I.value k.value = I.value
@@ -688,6 +708,8 @@ var ue = {
} }
} }
function startPaymentPolling(generation, orderId) { function startPaymentPolling(generation, orderId) {
// 轮询绑定面板代次、订单和 JWT;最多五分钟,且同一时刻只允许一个查询在途。
// 单次请求的超时不超过会话剩余寿命,切换账号或卸载后不会继续报告旧订单成功。
stopPaymentPolling(); stopPaymentPolling();
const session = { const session = {
identity: S.identity(), identity: S.identity(),
@@ -749,6 +771,7 @@ var ue = {
session.interval = setInterval(pollStatus, 2000); session.interval = setInterval(pollStatus, 2000);
} }
return (n, a) => { return (n, a) => {
// 内嵌与弹窗仅展示外壳不同,支付选择、错误提示及下一步动作复用上面的状态。
let o = NuxtLink, let o = NuxtLink,
c = button, c = button,
d = select, d = select,
+2
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 分类订购说明:兼容被 HTML 实体编码的文本,清理富文本后仅在存在可见内容时展示。 */
import "~/assets/styles/ProductCategoryNotice.CwaR3eXN.css"; import "~/assets/styles/ProductCategoryNotice.CwaR3eXN.css";
import { normalizeClass } from "vue"; import { normalizeClass } from "vue";
import { createCommentVNode } from "vue"; import { createCommentVNode } from "vue";
@@ -28,6 +29,7 @@ var u = {
setup(o) { setup(o) {
let c = o, let c = o,
f = computed(() => { f = computed(() => {
// 最多解码两轮纯文本包装,随后统一消毒;图片也算有效内容,空白占位不显示。
let e = typeof c.description == `string` ? c.description : ``; let e = typeof c.description == `string` ? c.description : ``;
for (let t = 0; t < 2; t++) { for (let t = 0; t < 2; t++) {
let t = new DOMParser().parseFromString(e, `text/html`); let t = new DOMParser().parseFromString(e, `text/html`);
+2
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 商品分组标题:约定名称中首个 -N 后的非空文本作为徽标,其余情况完整显示原名称。 */
import "~/assets/styles/ProductGroupLabel.B1kWpFA-.css"; import "~/assets/styles/ProductGroupLabel.B1kWpFA-.css";
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { createCommentVNode } from "vue"; import { createCommentVNode } from "vue";
@@ -10,6 +11,7 @@ import { defineComponent } from "vue";
import { openBlock } from "vue"; import { openBlock } from "vue";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js"; import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
function l(e) { function l(e) {
// 只在名称和徽标两部分均存在时拆分;导出的 n 也被其他商品展示逻辑复用。
let t = typeof e == `string` ? e.trim() : ``, let t = typeof e == `string` ? e.trim() : ``,
n = t.indexOf(`-N`); n = t.indexOf(`-N`);
if (n < 0) if (n < 0)
+7
View File
@@ -1,4 +1,8 @@
<script> <script>
/**
* 敏感文本展示器:按 kind 掩码手机号、邮箱、姓名、证件或普通文本。
* 仅当父级确实传入未掩码原文时提供“显示”按钮;它是界面保护,不替代后端访问控制。
*/
import "~/assets/styles/SensitiveValue.BSZlaRHT.css"; import "~/assets/styles/SensitiveValue.BSZlaRHT.css";
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { normalizeClass } from "vue"; import { normalizeClass } from "vue";
@@ -14,12 +18,14 @@ import { openBlock } from "vue";
import { useRoute } from "#app"; import { useRoute } from "#app";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js"; import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
function p(e) { function p(e) {
// 已由后端掩码的值不可在浏览器恢复;识别常见星号和圆点,避免误显示切换按钮。
return /[**•●]/u.test(e ?? ``); return /[**•●]/u.test(e ?? ``);
} }
function m(e) { function m(e) {
return !!e?.trim() && !p(e); return !!e?.trim() && !p(e);
} }
function h(e, t, n) { function h(e, t, n) {
// Array.from 按 Unicode 码点截取,短文本全部隐藏,避免前后保留区重叠泄漏原文。
let r = Array.from(e); let r = Array.from(e);
return r.length <= t + n return r.length <= t + n
? `*`.repeat(Math.max(r.length, 1)) ? `*`.repeat(Math.max(r.length, 1))
@@ -102,6 +108,7 @@ var _ = {
}); });
return ( return (
watch( watch(
// 数据、身份重置键或路由改变时恢复隐藏,不能延续上一条记录的展开状态。
[ [
() => f.value, () => f.value,
() => f.kind, () => f.kind,
+2
View File
@@ -1,4 +1,5 @@
<script> <script>
/** 加载 /ticket/config 中启用的工单通知;失败可手动重试,富文本经过统一清理后展示。 */
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { onMounted } from "vue"; import { onMounted } from "vue";
import { onBeforeUnmount } from "vue"; import { onBeforeUnmount } from "vue";
@@ -27,6 +28,7 @@ var _ = [`innerHTML`],
x = ref(!1), x = ref(!1),
S = !1; S = !1;
async function C() { async function C() {
// loading 防止重复请求,S 标记卸载,异步响应不应写入已离开的页面。
if (!x.value) { if (!x.value) {
((x.value = !0), (b.value = ``)); ((x.value = !0), (b.value = ``));
try { try {
+18
View File
@@ -1,4 +1,9 @@
<script> <script>
/**
* 同源远程控制台:父级提供实例、模块及操作密码,通过 update:visible 关闭面板。
* 浏览器只向 /remote-console/sessions 申请短期会话,再以服务端返回的同源路径连接 noVNC。
* 这里不直连供应商 VNC 地址;部署时 HTTP 与 WebSocket 路径都需要网关反向代理。
*/
import "~/assets/styles/RemoteConsole.DLmX5UZu.css"; import "~/assets/styles/RemoteConsole.DLmX5UZu.css";
import { toDisplayString } from "vue"; import { toDisplayString } from "vue";
import { onMounted } from "vue"; import { onMounted } from "vue";
@@ -27,6 +32,7 @@ import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helpe
import inputtext from "primevue/inputtext"; import inputtext from "primevue/inputtext";
var T = /^[a-f0-9]{64}$/; var T = /^[a-f0-9]{64}$/;
function E(e, n) { function E(e, n) {
// 独立连接状态机;d 是连接代次,h 保存需要删除的网关会话,g 是当前 RFB 客户端。
let r = useAuth(), let r = useAuth(),
o = ref(`idle`), o = ref(`idle`),
c = ref(``), c = ref(``),
@@ -43,6 +49,7 @@ function E(e, n) {
(m !== null && clearTimeout(m), (m = null)); (m !== null && clearTimeout(m), (m = null));
} }
async function y(e, t) { async function y(e, t) {
// 使用创建会话时的 token 做尽力释放;keepalive 允许页面离开时仍发送清理请求。
if (T.test(e)) if (T.test(e))
try { try {
await fetch(`/remote-console/sessions/${e}`, { await fetch(`/remote-console/sessions/${e}`, {
@@ -57,6 +64,7 @@ function E(e, n) {
} catch {} } catch {}
} }
function S() { function S() {
// 统一释放入口先推进代次,再中止请求、断开 RFB、移除事件并删除远端会话。
if ((d++, p?.abort(), (p = null), v(), g)) { if ((d++, p?.abort(), (p = null), v(), g)) {
for (let [e, t] of _) g.removeEventListener(e, t); for (let [e, t] of _) g.removeEventListener(e, t);
try { try {
@@ -76,12 +84,14 @@ function E(e, n) {
(S(), (c.value = e), (o.value = `error`)); (S(), (c.value = e), (o.value = `error`));
} }
function E(e) { function E(e) {
// 建立连接或提交凭据后均有 45 秒上限,旧连接定时器不能终止新的连接。
(v(), (v(),
(m = setTimeout(() => { (m = setTimeout(() => {
e === d && !f && w(`连接超时,请重新连接。`); e === d && !f && w(`连接超时,请重新连接。`);
}, 45e3))); }, 45e3)));
} }
function D(e, t) { function D(e, t) {
// 仅展示有限长度的中文网关错误,避免把供应商地址、凭据或内部异常原样暴露。
if (t === 401) return `登录已失效,请重新登录后打开控制台。`; if (t === 401) return `登录已失效,请重新登录后打开控制台。`;
if (t === 403) return `暂时无法访问此实例的控制台,请核对账号后重试。`; if (t === 403) return `暂时无法访问此实例的控制台,请核对账号后重试。`;
let n = e && typeof e == `object` && `error` in e ? e.error : null; let n = e && typeof e == `object` && `error` in e ? e.error : null;
@@ -91,6 +101,7 @@ function E(e, n) {
: `控制台暂时无法连接,请稍后重试。`; : `控制台暂时无法连接,请稍后重试。`;
} }
async function O() { async function O() {
// 登录状态、实例、模块和挂载节点共同构成连接身份;异步阶段每次回写前都重新核对。
if ((S(), (c.value = ``), f || !n.visible() || !e.value)) { if ((S(), (c.value = ``), f || !n.visible() || !e.value)) {
o.value = `idle`; o.value = `idle`;
return; return;
@@ -146,6 +157,7 @@ function E(e, n) {
? r.sessionId ? r.sessionId
: ``; : ``;
if (!x()) { if (!x()) {
// 创建成功但用户已关闭或切换实例时,仍要回收刚生成的服务端会话。
d && y(d, t); d && y(d, t);
return; return;
} }
@@ -165,6 +177,7 @@ function E(e, n) {
return; return;
} }
let { default: f } = await loadModule( let { default: f } = await loadModule(
// 只在实际连接时加载 noVNC,避免把远控依赖放进所有页面的首屏执行路径。
async () => { async () => {
let { default: e } = await import("@novnc/novnc"); let { default: e } = await import("@novnc/novnc");
return { return {
@@ -226,6 +239,7 @@ function E(e, n) {
} }
} }
function k(e) { function k(e) {
// 只提交服务器明确要求的完整凭据;密码仅保留当前面板内存,不写入持久化存储。
if (!g || o.value !== `credentials` || u.value.some((t) => !e[t])) if (!g || o.value !== `credentials` || u.value.some((t) => !e[t]))
return !1; return !1;
try { try {
@@ -273,6 +287,7 @@ function E(e, n) {
}, },
)); ));
function j(e) { function j(e) {
// 其他标签页退出或更换账号时立即断开,不能等用户下一次远控操作才识别身份变化。
(e.key === `jwt` || e.key === null) && (e.key === `jwt` || e.key === null) &&
n.visible() && n.visible() &&
(S(), (o.value = `idle`), n.close()); (S(), (o.value = `idle`), n.close());
@@ -360,6 +375,7 @@ var D = [`aria-labelledby`],
}, },
emits: [`update:visible`], emits: [`update:visible`],
setup(g, { emit: b }) { setup(g, { emit: b }) {
// 面板状态负责凭据表单、全屏与显示文案,连接生命周期由上面的状态机统一管理。
let x = g, let x = g,
C = b, C = b,
T = useId(), T = useId(),
@@ -404,6 +420,7 @@ var D = [`aria-labelledby`],
), ),
Z = computed(() => x.hostName?.trim() || `服务器实例`); Z = computed(() => x.hostName?.trim() || `服务器实例`);
function Q() { function Q() {
// 状态或可见性变化就清除输入,避免旧会话的凭据留在重新打开的表单中。
((z.value = ``), (B.value = ``), (V.value = ``), (H.value = ``)); ((z.value = ``), (B.value = ``), (V.value = ``), (H.value = ``));
} }
watch([W, () => x.visible], Q); watch([W, () => x.visible], Q);
@@ -422,6 +439,7 @@ var D = [`aria-labelledby`],
(J(), Q(), C(`update:visible`, !1)); (J(), Q(), C(`update:visible`, !1));
} }
async function me() { async function me() {
// 全屏是浏览器交互能力;失败只显示提示,不影响现有远程连接。
U.value = ``; U.value = ``;
try { try {
document.fullscreenElement === L.value document.fullscreenElement === L.value