first commit
This commit is contained in:
commit
db5762abd4
687 files changed
+171307
No files matched your search
+856
@@ -0,0 +1,856 @@
|
||||
import CryptoJS from "crypto-js";
|
||||
import { defineStore } from "pinia";
|
||||
import { navigateTo, useRuntimeConfig } from "#app";
|
||||
import { computed, ref } from "vue";
|
||||
var ce = [
|
||||
[/产品类型错误.{0,30}安全组[^\n]*/g, `该商品未接入安全组`],
|
||||
[/商品未关联.{0,20}模块接口/g, `该商品暂不可用`],
|
||||
[/未关联.{0,12}模块接口/g, `该商品暂不可用`],
|
||||
[/尚未接入.{0,12}模块/g, `该商品暂不可用`],
|
||||
];
|
||||
var le = [
|
||||
[/快快云-弹性云|弹性云/g, ``],
|
||||
[/快快云|快快/g, ``],
|
||||
[/魔方云|魔方财务|智简魔方|智简/g, ``],
|
||||
[/美得云|美德云|meidecloud/gi, ``],
|
||||
[/dwvps[a-z0-9_]*/gi, ``],
|
||||
[/miloce[a-z0-9_]*/gi, ``],
|
||||
[/jinghanyun[a-z0-9_]*/gi, ``],
|
||||
[/kkidc[a-z0-9_]*/gi, ``],
|
||||
[/kky[a-z0-9_]*/gi, ``],
|
||||
[/idcsmart[a-z0-9_]*/gi, ``],
|
||||
[/mf[_-]?cloud[a-z0-9_]*/gi, ``],
|
||||
[/mf[_-]?dcim[a-z0-9_]*/gi, ``],
|
||||
[/common[_-]?cloud[a-z0-9_]*/gi, ``],
|
||||
[/pve[_-]?cloud[a-z0-9_]*/gi, ``],
|
||||
];
|
||||
var ue =
|
||||
/模块接口|自定义配置|模块未关联|模块标识|上游|第三方云|[a-z]{2,}[_-][a-z0-9_]+|\bDCIM\b|whmcs|reserver|https?:\/\//i;
|
||||
function W(e) {
|
||||
let t = typeof e == `string` ? e : ``;
|
||||
if (!t) return ``;
|
||||
let n = t;
|
||||
for (let [e, t] of ce) n = n.replace(e, t);
|
||||
for (let [e, t] of le) n = n.replace(e, t);
|
||||
return (
|
||||
(n = n
|
||||
.replace(/\s{2,}/g, ` `)
|
||||
.replace(/[,,、]{2,}/g, `,`)
|
||||
.replace(/^[\s,,、::;;-]+|[\s,,、::;;-]+$/g, ``)
|
||||
.trim()),
|
||||
n.length < 2 || ue.test(n) ? `操作失败,请稍后重试或联系客服` : n
|
||||
);
|
||||
}
|
||||
function de(e) {
|
||||
let t = typeof e == `string` ? e : ``;
|
||||
return /^\s*上游熔断解除[::]/u.test(t)
|
||||
? `服务连接已恢复正常`
|
||||
: /^\s*上游熔断[::]/u.test(t)
|
||||
? `服务连接暂时异常,请稍后重试`
|
||||
: t;
|
||||
}
|
||||
var fe = 32;
|
||||
var pe = 200;
|
||||
var me = 6e4;
|
||||
var G = new Map();
|
||||
var he = Symbol(`uninitialized`);
|
||||
var ge = 0;
|
||||
function _e() {
|
||||
(ge++, G.clear());
|
||||
}
|
||||
function ve() {
|
||||
return ge;
|
||||
}
|
||||
function ye(e, t, n = ``) {
|
||||
return JSON.stringify([e, n, t], (e, t) =>
|
||||
t && typeof t == `object` && !Array.isArray(t)
|
||||
? Object.fromEntries(
|
||||
Object.keys(t)
|
||||
.sort()
|
||||
.map((e) => [e, t[e]]),
|
||||
)
|
||||
: t,
|
||||
);
|
||||
}
|
||||
function be(e) {
|
||||
return JSON.parse(JSON.stringify(e));
|
||||
}
|
||||
function xe(e, t, n = Date.now()) {
|
||||
he !== e && (_e(), (he = e));
|
||||
for (let [e, t] of G) t.expires <= n && G.delete(e);
|
||||
let r = G.get(t);
|
||||
if (r) return (G.delete(t), G.set(t, r), be(r.data));
|
||||
}
|
||||
function Se(e, t, n, r, i = Date.now()) {
|
||||
if (e === he && r === ge) {
|
||||
if (!Array.isArray(n.list) || n.list.length > pe) {
|
||||
G.delete(t);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let e = be(n);
|
||||
(G.delete(t),
|
||||
G.size >= fe && G.delete(G.keys().next().value),
|
||||
G.set(t, {
|
||||
data: e,
|
||||
expires: i + me,
|
||||
}));
|
||||
} catch {
|
||||
G.delete(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
var Ce = 2147483647;
|
||||
function K(e, t) {
|
||||
let n =
|
||||
typeof e == `number`
|
||||
? e
|
||||
: typeof e == `string` && /^\d+$/.test(e)
|
||||
? Number(e)
|
||||
: NaN;
|
||||
if (!Number.isSafeInteger(n) || n < t || n > Ce)
|
||||
throw Error(`购买信息无效,请重新确认后重试`);
|
||||
return n;
|
||||
}
|
||||
function we(e) {
|
||||
if (!e || typeof e != `object` || Array.isArray(e))
|
||||
throw Error(`购买信息无效,请重新确认后重试`);
|
||||
return e;
|
||||
}
|
||||
function Te(e) {
|
||||
let t = we(e);
|
||||
return {
|
||||
product_id: K(t.product_id, 1),
|
||||
qty: K(t.qty, 1),
|
||||
};
|
||||
}
|
||||
function Ee(e, t, n) {
|
||||
if (e === `POST` && t === `/product/settle`)
|
||||
return {
|
||||
mode: `single`,
|
||||
...Te(n),
|
||||
};
|
||||
if (e === `POST` && t === `/cart`) {
|
||||
let e = we(n);
|
||||
if (e.products !== void 0) {
|
||||
if (
|
||||
!Array.isArray(e.products) ||
|
||||
!e.products.length ||
|
||||
e.products.length > 1e3
|
||||
)
|
||||
throw Error(`购买信息无效,请重新确认后重试`);
|
||||
return {
|
||||
mode: `cart_add`,
|
||||
products: e.products.map(Te),
|
||||
};
|
||||
}
|
||||
return {
|
||||
mode: `cart_add`,
|
||||
...Te(e),
|
||||
};
|
||||
}
|
||||
if (e === `POST` && t === `/cart/settle`) {
|
||||
let e = we(n);
|
||||
if (!Array.isArray(e.positions) || !e.positions.length)
|
||||
throw Error(`请选择要结算的商品`);
|
||||
return {
|
||||
mode: `cart_settle`,
|
||||
positions: [...new Set(e.positions.map((e) => K(e, 0)))],
|
||||
};
|
||||
}
|
||||
let r = e === `PUT` ? /^\/cart\/(\d+)(\/qty)?$/.exec(t) : null;
|
||||
if (r) {
|
||||
let e = we(n);
|
||||
return {
|
||||
mode: `cart_update`,
|
||||
position: K(r[1], 0),
|
||||
qty: K(e.qty, 1),
|
||||
...(r[2]
|
||||
? {}
|
||||
: {
|
||||
product_id: K(e.product_id, 1),
|
||||
}),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function De(e) {
|
||||
if (!e || typeof e != `object` || Array.isArray(e)) return !1;
|
||||
let t = e;
|
||||
return (
|
||||
typeof t.allowed == `boolean` &&
|
||||
typeof t.message == `string` &&
|
||||
(t.allowed || !!t.message.trim())
|
||||
);
|
||||
}
|
||||
class ApiError extends Error {
|
||||
status;
|
||||
data;
|
||||
constructor(status, message, data) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.data = data;
|
||||
this.name = "ApiError";
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
||||
const FILE_TRANSFER_TIMEOUT_MS = 120_000;
|
||||
|
||||
function requestTimeoutError() {
|
||||
return new ApiError(408, "请求超时,请稍后重试");
|
||||
}
|
||||
|
||||
async function withRequestDeadline(
|
||||
operation,
|
||||
timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
controller = new AbortController(),
|
||||
) {
|
||||
const duration =
|
||||
Number.isFinite(timeoutMs) && timeoutMs > 0 && timeoutMs <= 2_147_483_647
|
||||
? timeoutMs
|
||||
: DEFAULT_REQUEST_TIMEOUT_MS;
|
||||
let timer;
|
||||
const deadline = new Promise((_, reject) => {
|
||||
timer = setTimeout(() => {
|
||||
controller.abort();
|
||||
reject(requestTimeoutError());
|
||||
}, duration);
|
||||
});
|
||||
try {
|
||||
// The deadline also covers response-body decoding and any purchase preflight.
|
||||
return await Promise.race([
|
||||
Promise.resolve().then(() => operation(controller.signal)),
|
||||
deadline,
|
||||
]);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
function assertRequestActive(signal) {
|
||||
if (signal.aborted) throw requestTimeoutError();
|
||||
}
|
||||
|
||||
function normalizeRequestError(error) {
|
||||
return error instanceof ApiError
|
||||
? error
|
||||
: new ApiError(0, "网络异常,请检查网络后重试");
|
||||
}
|
||||
|
||||
function useApi() {
|
||||
const config = useRuntimeConfig();
|
||||
const auth = useAuth();
|
||||
|
||||
function clearUnauthorizedSession(requestToken) {
|
||||
if (
|
||||
!requestToken ||
|
||||
localStorage.getItem("jwt") !== requestToken ||
|
||||
auth.token !== requestToken
|
||||
)
|
||||
return;
|
||||
|
||||
auth.clear();
|
||||
if (window.location.pathname === "/login") return;
|
||||
const redirect = window.location.pathname + window.location.search;
|
||||
try {
|
||||
void Promise.resolve(
|
||||
navigateTo({
|
||||
path: "/login",
|
||||
query: redirect === "/" ? undefined : { redirect },
|
||||
}),
|
||||
).catch(() => {});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function responseObject(value) {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? value
|
||||
: {};
|
||||
}
|
||||
|
||||
function responseError(response, payload, requestToken, fallbackMessage) {
|
||||
const envelope = responseObject(payload);
|
||||
const status =
|
||||
response.status === 401
|
||||
? 401
|
||||
: typeof envelope.status === "number" && envelope.status !== 200
|
||||
? envelope.status
|
||||
: response.status;
|
||||
if (status === 401) clearUnauthorizedSession(requestToken);
|
||||
return new ApiError(
|
||||
status,
|
||||
W(envelope.msg || envelope.message) || fallbackMessage,
|
||||
envelope.data,
|
||||
);
|
||||
}
|
||||
|
||||
async function decodeResponse(
|
||||
response,
|
||||
requestToken,
|
||||
fallbackMessage,
|
||||
signal,
|
||||
allowStatuslessDataArray = false,
|
||||
) {
|
||||
const payload = await response.json().catch(() => null);
|
||||
assertRequestActive(signal);
|
||||
const envelope = responseObject(payload);
|
||||
if (response.ok && envelope.status === 200) return payload;
|
||||
if (
|
||||
response.ok &&
|
||||
allowStatuslessDataArray &&
|
||||
!("status" in envelope) &&
|
||||
Array.isArray(envelope.data)
|
||||
) {
|
||||
return { status: 200, msg: "", data: envelope.data };
|
||||
}
|
||||
throw responseError(response, payload, requestToken, fallbackMessage);
|
||||
}
|
||||
|
||||
function apiUrl(path, query, keepEmpty = false) {
|
||||
let url = `${String(config.public.apiBase ?? "")}/console/v1${path}`;
|
||||
const search = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(query ?? {})) {
|
||||
if (value == null || (value === "" && !keepEmpty)) continue;
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
if (item != null && item !== "")
|
||||
search.append(`${key}[]`, String(item));
|
||||
}
|
||||
} else {
|
||||
search.append(key, String(value));
|
||||
}
|
||||
}
|
||||
if (search.size) url += `?${search}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
async function request(method, path, options = {}) {
|
||||
// Capture identity before the first await, including when logout clears it immediately.
|
||||
const requestToken = auth.token;
|
||||
const headers = { Accept: "application/json" };
|
||||
if (requestToken) headers.Authorization = `Bearer ${requestToken}`;
|
||||
if (options.body !== undefined)
|
||||
headers["Content-Type"] = "application/json";
|
||||
try {
|
||||
const serializedBody =
|
||||
options.body === undefined ? undefined : JSON.stringify(options.body);
|
||||
return await withRequestDeadline(async (signal) => {
|
||||
if (requestToken) {
|
||||
let purchase;
|
||||
try {
|
||||
purchase = Ee(method, path, options.body);
|
||||
} catch (error) {
|
||||
throw new ApiError(
|
||||
422,
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "购买信息无效,请重新确认后重试",
|
||||
);
|
||||
}
|
||||
if (purchase) {
|
||||
const authToken = auth.token;
|
||||
const permission = await request("POST", "/purchase_limit/check", {
|
||||
body: purchase,
|
||||
timeoutMs: options.timeoutMs ?? 15_000,
|
||||
});
|
||||
assertRequestActive(signal);
|
||||
if (
|
||||
localStorage.getItem("jwt") !== requestToken ||
|
||||
auth.token !== authToken
|
||||
) {
|
||||
throw new ApiError(409, "登录状态已变化,请重新确认后重试");
|
||||
}
|
||||
if (JSON.stringify(options.body) !== serializedBody) {
|
||||
throw new ApiError(409, "购买信息已变化,请重新确认后重试");
|
||||
}
|
||||
if (!De(permission))
|
||||
throw new ApiError(502, "限购验证暂不可用,请稍后重试");
|
||||
if (!permission.allowed)
|
||||
throw new ApiError(422, permission.message);
|
||||
}
|
||||
}
|
||||
assertRequestActive(signal);
|
||||
const response = await fetch(
|
||||
apiUrl(path, options.query, options.keepEmpty),
|
||||
{
|
||||
method,
|
||||
headers,
|
||||
body: serializedBody,
|
||||
signal,
|
||||
},
|
||||
);
|
||||
assertRequestActive(signal);
|
||||
const envelope = await decodeResponse(
|
||||
response,
|
||||
requestToken,
|
||||
response.ok
|
||||
? "响应格式异常,请稍后重试"
|
||||
: `请求失败(HTTP ${response.status})`,
|
||||
signal,
|
||||
options.allowStatuslessDataArray,
|
||||
);
|
||||
assertRequestActive(signal);
|
||||
if (
|
||||
method !== "GET" &&
|
||||
path !== "/purchase_limit/check" &&
|
||||
!/^\/product\/\d+\/config_option$/.test(path)
|
||||
) {
|
||||
_e();
|
||||
}
|
||||
return options.raw ? envelope : envelope.data;
|
||||
}, options.timeoutMs);
|
||||
} catch (error) {
|
||||
throw normalizeRequestError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function upload(file) {
|
||||
const body = new FormData();
|
||||
body.append("file", file);
|
||||
const headers = { Accept: "application/json" };
|
||||
const requestToken = auth.token;
|
||||
if (requestToken) headers.Authorization = `Bearer ${requestToken}`;
|
||||
try {
|
||||
return await withRequestDeadline(async (signal) => {
|
||||
const response = await fetch(apiUrl("/upload"), {
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
signal,
|
||||
});
|
||||
assertRequestActive(signal);
|
||||
const envelope = await decodeResponse(
|
||||
response,
|
||||
requestToken,
|
||||
"上传失败",
|
||||
signal,
|
||||
);
|
||||
assertRequestActive(signal);
|
||||
return envelope.data;
|
||||
}, FILE_TRANSFER_TIMEOUT_MS);
|
||||
} catch (error) {
|
||||
throw normalizeRequestError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function download(path, data, filename = "export.xlsx", options = {}) {
|
||||
const method = options.method ?? "GET";
|
||||
const headers = { Accept: "*/*" };
|
||||
const requestToken = auth.token;
|
||||
if (requestToken) headers.Authorization = `Bearer ${requestToken}`;
|
||||
if (method === "POST") headers["Content-Type"] = "application/json";
|
||||
try {
|
||||
await withRequestDeadline(async (signal) => {
|
||||
const response = await fetch(
|
||||
apiUrl(path, method === "GET" ? data : undefined),
|
||||
{
|
||||
method,
|
||||
headers,
|
||||
...(method === "POST" ? { body: JSON.stringify(data ?? {}) } : {}),
|
||||
signal,
|
||||
},
|
||||
);
|
||||
assertRequestActive(signal);
|
||||
const isJson = /\bapplication\/(?:[\w.-]+\+)?json\b/i.test(
|
||||
response.headers.get("content-type") || "",
|
||||
);
|
||||
if (!response.ok || isJson) {
|
||||
const payload = await response
|
||||
.clone()
|
||||
.json()
|
||||
.catch(() => null);
|
||||
assertRequestActive(signal);
|
||||
const envelope = responseObject(payload);
|
||||
if (
|
||||
!response.ok ||
|
||||
(typeof envelope.status === "number" && envelope.status !== 200)
|
||||
) {
|
||||
throw responseError(
|
||||
response,
|
||||
payload,
|
||||
requestToken,
|
||||
`下载失败(HTTP ${response.status})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const disposition = response.headers.get("content-disposition") || "";
|
||||
const match = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(disposition);
|
||||
if (match?.[1]) {
|
||||
try {
|
||||
filename = decodeURIComponent(match[1]);
|
||||
} catch {
|
||||
filename = match[1];
|
||||
}
|
||||
}
|
||||
const blob = await response.blob();
|
||||
assertRequestActive(signal);
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
try {
|
||||
link.href = objectUrl;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
} finally {
|
||||
link.remove();
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
}, options.timeoutMs ?? FILE_TRANSFER_TIMEOUT_MS);
|
||||
} catch (error) {
|
||||
throw normalizeRequestError(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
identity: () => auth.token,
|
||||
request,
|
||||
upload,
|
||||
download,
|
||||
get: (path, query, keepEmpty = false, options = {}) =>
|
||||
request("GET", path, {
|
||||
...options,
|
||||
query,
|
||||
keepEmpty,
|
||||
}),
|
||||
getRaw: (path, query, options = {}) =>
|
||||
request("GET", path, {
|
||||
...options,
|
||||
query,
|
||||
raw: true,
|
||||
}),
|
||||
post: (path, body, raw = false, options = {}) =>
|
||||
request("POST", path, {
|
||||
...options,
|
||||
body,
|
||||
raw,
|
||||
}),
|
||||
put: (path, body, query) => request("PUT", path, { body, query }),
|
||||
del: (path, query, body) => request("DELETE", path, { query, body }),
|
||||
};
|
||||
}
|
||||
var ot = CryptoJS.enc.Utf8.parse(`idcsmart.finance`);
|
||||
var st = CryptoJS.enc.Utf8.parse(`9311019310287172`);
|
||||
function ct(e) {
|
||||
return CryptoJS.AES.encrypt(e, ot, {
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
iv: st,
|
||||
}).toString();
|
||||
}
|
||||
function lt(e, t = `登录失败,请稍后重试`) {
|
||||
let n = e && typeof e == `object` ? e : {},
|
||||
r = typeof n.message == `string` ? n.message.trim() : ``,
|
||||
i = n.data && typeof n.data == `object` ? n.data : {};
|
||||
return i.need_security_verify || i.ip_exception_verify
|
||||
? `本次登录需要额外的安全验证,请联系客服协助完成验证。`
|
||||
: n.status === 429
|
||||
? `登录尝试过于频繁,请稍后再试`
|
||||
: n.status === 408 || /超时|timeout/i.test(r)
|
||||
? `登录请求超时,请检查网络后重试`
|
||||
: n.status === 0 ||
|
||||
/网络异常|Failed to fetch|NetworkError|Load failed/i.test(r)
|
||||
? `网络连接失败,请检查网络后重试`
|
||||
: (n.status ?? 0) >= 500 ||
|
||||
/页面错误|响应格式异常|Internal Server Error|Bad Gateway|Service Unavailable/i.test(
|
||||
r,
|
||||
)
|
||||
? `登录服务暂时异常,请稍后重试;若持续失败,请联系客服。`
|
||||
: !r ||
|
||||
/<[^>]+>|SQLSTATE|\b(?:TypeError|ReferenceError|Exception)\b/.test(
|
||||
r,
|
||||
)
|
||||
? t
|
||||
: r;
|
||||
}
|
||||
function ut(e) {
|
||||
return (
|
||||
typeof e == `string` && e.length > 0 && e.length <= 8192 && !/\s/.test(e)
|
||||
);
|
||||
}
|
||||
function dt(e) {
|
||||
if (!e || typeof e != `object`) return null;
|
||||
let t = e,
|
||||
n = [t, t.data, t.error, t.response];
|
||||
for (let e of n) {
|
||||
if (!e || typeof e != `object`) continue;
|
||||
let t = e,
|
||||
n = t.data && typeof t.data == `object` ? t.data : t;
|
||||
if (
|
||||
n.need_security_verify === !0 ||
|
||||
n.need_security_verify === 1 ||
|
||||
n.need_security_verify === `1` ||
|
||||
(Array.isArray(n.available_methods) && n.available_methods.length > 0)
|
||||
)
|
||||
return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function ft(e) {
|
||||
let t = dt(e);
|
||||
if (t)
|
||||
throw Object.assign(Error(`安全验证尚未完成,请重试`), {
|
||||
data: t,
|
||||
});
|
||||
}
|
||||
var pt = {
|
||||
operate_password: `操作密码`,
|
||||
email_code: `邮箱验证码`,
|
||||
phone_code: `手机验证码`,
|
||||
certification: `实名认证`,
|
||||
};
|
||||
function mt(e) {
|
||||
return (
|
||||
Array.isArray(e.available_methods) ? e.available_methods : []
|
||||
).flatMap((t) => {
|
||||
let n =
|
||||
typeof t == `string`
|
||||
? {
|
||||
value: t,
|
||||
}
|
||||
: t && typeof t == `object`
|
||||
? t
|
||||
: {},
|
||||
r = String(n.value ?? ``);
|
||||
return pt[r]
|
||||
? [
|
||||
{
|
||||
value: r,
|
||||
label: pt[r],
|
||||
tip: String(n.tip ?? ``),
|
||||
account: String(n.account ?? ``),
|
||||
phone_code: String(n.phone_code ?? ``),
|
||||
security_verify_token: String(
|
||||
n.security_verify_token ?? e.security_verify_token ?? ``,
|
||||
),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
});
|
||||
}
|
||||
const useAuth = defineStore("auth", () => {
|
||||
const token = ref(localStorage.getItem("jwt"));
|
||||
const overview = ref(null);
|
||||
const loading = ref(false);
|
||||
const legacyLoginPending = ref(false);
|
||||
const isLoggedIn = computed(() => !!token.value);
|
||||
let overviewPromise = null;
|
||||
let sessionVersion = 0;
|
||||
let legacySession = null;
|
||||
|
||||
function loginWithJwt(jwt) {
|
||||
if (!ut(jwt))
|
||||
throw Error("登录响应无效,请重新登录;若持续失败,请联系客服。");
|
||||
resetSessionState();
|
||||
token.value = jwt;
|
||||
localStorage.setItem("jwt", jwt);
|
||||
}
|
||||
|
||||
function prepareLegacyLogin(jwt = localStorage.getItem("jwt")) {
|
||||
clear();
|
||||
legacySession = {
|
||||
jwt: ut(jwt) ? jwt : null,
|
||||
version: sessionVersion,
|
||||
controller: null,
|
||||
promise: null,
|
||||
};
|
||||
legacyLoginPending.value = true;
|
||||
}
|
||||
|
||||
function cancelLegacyLogin() {
|
||||
legacySession?.controller?.abort();
|
||||
legacySession = null;
|
||||
legacyLoginPending.value = false;
|
||||
}
|
||||
|
||||
function verifyLegacyLogin() {
|
||||
const session = legacySession;
|
||||
if (!session?.jwt) {
|
||||
return Promise.reject(
|
||||
Error("缺少有效登录凭证,请返回原平台重新进入,或使用账号登录。"),
|
||||
);
|
||||
}
|
||||
if (session.promise) return session.promise;
|
||||
const apiBase = String(useRuntimeConfig().public.apiBase ?? "");
|
||||
const controller = new AbortController();
|
||||
session.controller = controller;
|
||||
const isCurrentSession = () =>
|
||||
legacySession === session && sessionVersion === session.version;
|
||||
const promise = withRequestDeadline(
|
||||
async (signal) => {
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(`${apiBase}/console/v1/index`, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${session.jwt}`,
|
||||
},
|
||||
credentials: "omit",
|
||||
cache: "no-store",
|
||||
referrerPolicy: "no-referrer",
|
||||
signal,
|
||||
});
|
||||
} catch {
|
||||
throw Error("网络连接失败或验证超时,请检查网络后重试。");
|
||||
}
|
||||
const envelope = await response.json().catch(() => null);
|
||||
assertRequestActive(signal);
|
||||
if (!isCurrentSession()) throw Error("登录状态已变化,请重新进入。");
|
||||
if (response.status === 401 || envelope?.status === 401) {
|
||||
throw Error("登录凭证已失效,请返回原平台重新进入,或使用账号登录。");
|
||||
}
|
||||
const account = envelope?.data?.account;
|
||||
if (
|
||||
!response.ok ||
|
||||
envelope?.status !== 200 ||
|
||||
!account ||
|
||||
typeof account !== "object" ||
|
||||
!Number.isSafeInteger(account.id) ||
|
||||
account.id <= 0
|
||||
)
|
||||
throw Error("暂时无法验证登录身份,请稍后重试。");
|
||||
loginWithJwt(session.jwt);
|
||||
overview.value = account;
|
||||
},
|
||||
15_000,
|
||||
controller,
|
||||
)
|
||||
.catch((error) => {
|
||||
if (error instanceof ApiError && error.status === 408) {
|
||||
throw Error("网络连接失败或验证超时,请检查网络后重试。");
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
if (isCurrentSession()) {
|
||||
session.controller = null;
|
||||
session.promise = null;
|
||||
}
|
||||
});
|
||||
session.promise = promise;
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function login(
|
||||
account,
|
||||
password,
|
||||
captcha = "",
|
||||
captchaToken = "",
|
||||
type = "password",
|
||||
code = "",
|
||||
phoneCode = "86",
|
||||
verification,
|
||||
) {
|
||||
if (type === "code" && account.includes("@"))
|
||||
throw Error("验证码登录仅支持手机号");
|
||||
const api = useApi();
|
||||
const version = sessionVersion;
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await api.post(
|
||||
"/login",
|
||||
{
|
||||
type,
|
||||
account,
|
||||
...(account.includes("@") ? {} : { phone_code: phoneCode }),
|
||||
code,
|
||||
password: type === "password" ? ct(password) : "",
|
||||
remember_password: "0",
|
||||
captcha,
|
||||
token: captchaToken,
|
||||
...(verification || {}),
|
||||
},
|
||||
false,
|
||||
{ timeoutMs: 15_000 },
|
||||
);
|
||||
if (version !== sessionVersion) throw Error("登录状态已变化,请重试");
|
||||
ft(result);
|
||||
if (!ut(result?.jwt))
|
||||
throw Error("登录响应无效,请重新登录;若持续失败,请联系客服。");
|
||||
loginWithJwt(result.jwt);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadOverview() {
|
||||
if (!overviewPromise) {
|
||||
const version = sessionVersion;
|
||||
const promise = useApi()
|
||||
.get("/index", undefined, false, { timeoutMs: 10_000 })
|
||||
.then((result) => {
|
||||
if (version !== sessionVersion) throw Error("登录状态已变化,请重试");
|
||||
overview.value = result.account;
|
||||
return result.account;
|
||||
})
|
||||
.finally(() => {
|
||||
if (overviewPromise === promise) overviewPromise = null;
|
||||
});
|
||||
overviewPromise = promise;
|
||||
}
|
||||
return overviewPromise;
|
||||
}
|
||||
|
||||
async function fetchOverview() {
|
||||
return overview.value ?? loadOverview();
|
||||
}
|
||||
|
||||
async function refreshOverview() {
|
||||
return loadOverview();
|
||||
}
|
||||
|
||||
function logout() {
|
||||
// request() captures the original Bearer synchronously; remote revocation never owns local logout.
|
||||
const revocation = useApi().post("/logout");
|
||||
clear();
|
||||
void revocation.catch(() => {});
|
||||
}
|
||||
|
||||
function resetSessionState() {
|
||||
_e();
|
||||
cancelLegacyLogin();
|
||||
sessionVersion += 1;
|
||||
overview.value = null;
|
||||
overviewPromise = null;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
resetSessionState();
|
||||
token.value = null;
|
||||
localStorage.removeItem("jwt");
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
overview,
|
||||
loading,
|
||||
isLoggedIn,
|
||||
login,
|
||||
loginWithJwt,
|
||||
fetchOverview,
|
||||
refreshOverview,
|
||||
logout,
|
||||
clear,
|
||||
legacyLoginPending,
|
||||
prepareLegacyLogin,
|
||||
verifyLegacyLogin,
|
||||
cancelLegacyLogin,
|
||||
};
|
||||
});
|
||||
export {
|
||||
lt as loginErrorMessage,
|
||||
ApiError,
|
||||
ye as apiCacheKey,
|
||||
ve as apiCacheVersion,
|
||||
W as sanitizeMessage,
|
||||
de as operationMessage,
|
||||
mt as securityMethods,
|
||||
useApi,
|
||||
Se as writeApiCache,
|
||||
ft as assertSecurityComplete,
|
||||
ut as isValidJwt,
|
||||
xe as readApiCache,
|
||||
dt as securityChallenge,
|
||||
useAuth,
|
||||
_e as clearApiCache,
|
||||
};
|
||||
Reference in new issue
Block a user