完善初始化更新
This commit is contained in:
187
sproutgate-frontend/src/config.js
Normal file
187
sproutgate-frontend/src/config.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import { marked } from "marked";
|
||||
|
||||
const DEFAULT_DEV_API_BASE = "http://localhost:8080";
|
||||
const DEFAULT_PROD_API_BASE = "https://auth.api.shumengya.top";
|
||||
|
||||
export const API_BASE = (() => {
|
||||
const configuredBase = import.meta.env.VITE_API_BASE?.trim();
|
||||
const fallbackBase = import.meta.env.DEV ? DEFAULT_DEV_API_BASE : DEFAULT_PROD_API_BASE;
|
||||
return (configuredBase || fallbackBase).replace(/\/+$/, "");
|
||||
})();
|
||||
|
||||
/** `public/logo192.png`,含 Vite `base` 前缀,避免子路径部署时顶栏/开屏裂图 */
|
||||
export const LOGO_192_SRC = `${import.meta.env.BASE_URL}logo192.png`;
|
||||
|
||||
/** 浏览器侧 IP/地理(与后端写入「最后访问」字段配合使用) */
|
||||
export const CLIENT_GEO_LOOKUP_URL = "https://cf-ip-geo.smyhub.com/api";
|
||||
|
||||
export function formatVisitDisplayLocation(payload) {
|
||||
const g = payload?.geo;
|
||||
if (!g || typeof g !== "object") return "";
|
||||
const parts = [g.countryName, g.regionName, g.cityName].filter(
|
||||
(x) => typeof x === "string" && x.trim()
|
||||
);
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
export async function fetchClientVisitMeta(timeoutMs = 2800) {
|
||||
const ctrl = new AbortController();
|
||||
const id = setTimeout(() => ctrl.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch(CLIENT_GEO_LOOKUP_URL, {
|
||||
credentials: "omit",
|
||||
signal: ctrl.signal
|
||||
});
|
||||
clearTimeout(id);
|
||||
if (!res.ok) return { ip: "", displayLocation: "" };
|
||||
const data = await res.json();
|
||||
const ip = typeof data.ip === "string" ? data.ip.trim() : "";
|
||||
const displayLocation = formatVisitDisplayLocation(data);
|
||||
return { ip, displayLocation };
|
||||
} catch {
|
||||
clearTimeout(id);
|
||||
return { ip: "", displayLocation: "" };
|
||||
}
|
||||
}
|
||||
|
||||
marked.setOptions({ breaks: true });
|
||||
export { marked };
|
||||
|
||||
export const emptyForm = {
|
||||
account: "",
|
||||
password: "",
|
||||
username: "",
|
||||
email: "",
|
||||
level: 0,
|
||||
sproutCoins: 0,
|
||||
secondaryEmails: "",
|
||||
phone: "",
|
||||
avatarUrl: "",
|
||||
websiteUrl: "",
|
||||
bio: "",
|
||||
banned: false,
|
||||
banReason: ""
|
||||
};
|
||||
|
||||
/** 后端封禁响应:`error` + 可选 `banReason`(登录/me 等 403) */
|
||||
export function formatAuthBanMessage(data) {
|
||||
const base = (data && data.error && String(data.error).trim()) || "account is banned";
|
||||
const reason = data && data.banReason != null && String(data.banReason).trim();
|
||||
return reason ? `${base}:${reason}` : base;
|
||||
}
|
||||
|
||||
/** 展示用:从完整 URL 得到「主机 + 路径」短文案 */
|
||||
export function formatWebsiteLabel(url) {
|
||||
if (!url || typeof url !== "string") return "";
|
||||
try {
|
||||
const u = new URL(url);
|
||||
const path = u.pathname === "/" ? "" : u.pathname;
|
||||
return u.host + path;
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/** 用户 `createdAt`(RFC3339)→ 本地可读注册时间 */
|
||||
export function formatUserRegisteredAt(iso) {
|
||||
if (!iso || typeof iso !== "string") return "未知";
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return iso;
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
dateStyle: "long",
|
||||
timeStyle: "short"
|
||||
}).format(d);
|
||||
}
|
||||
|
||||
/** RFC3339 / 后端存储时间 → 本地可读(用于应用接入记录等) */
|
||||
export function formatIsoDateTimeReadable(iso) {
|
||||
if (!iso || typeof iso !== "string") return "—";
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return iso;
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "medium"
|
||||
}).format(d);
|
||||
}
|
||||
|
||||
export const parseEmailList = (value) =>
|
||||
value.split(",").map((item) => item.trim()).filter(Boolean);
|
||||
|
||||
/** 可点击发信:`mailto:`,排除明显非法地址 */
|
||||
export function mailtoHref(address) {
|
||||
const a = typeof address === "string" ? address.trim() : "";
|
||||
if (!a || /\s/.test(a) || a.includes("<") || a.includes(">")) return null;
|
||||
const at = a.indexOf("@");
|
||||
if (at < 1 || at === a.length - 1) return null;
|
||||
return `mailto:${a}`;
|
||||
}
|
||||
|
||||
export function getPublicAccountFromPathname(pathname) {
|
||||
if (pathname !== "/user" && !pathname.startsWith("/user/")) return "";
|
||||
const raw = pathname === "/user" ? "" : pathname.slice("/user/".length).split("/")[0];
|
||||
if (!raw) return "";
|
||||
try {
|
||||
return decodeURIComponent(raw).trim();
|
||||
} catch {
|
||||
return raw.trim();
|
||||
}
|
||||
}
|
||||
|
||||
export function getAuthFlowFromSearch(search) {
|
||||
const params = new URLSearchParams(search);
|
||||
const redirectUri = (params.get("redirect_uri") || params.get("return_url") || "").trim();
|
||||
return {
|
||||
redirectUri,
|
||||
state: (params.get("state") || "").trim(),
|
||||
prompt: (params.get("prompt") || "").trim(),
|
||||
clientId: (params.get("client_id") || "").trim(),
|
||||
clientName: (params.get("client_name") || "").trim()
|
||||
};
|
||||
}
|
||||
|
||||
const AUTH_CLIENT_ID_KEY = "sproutgate_auth_client_id";
|
||||
const AUTH_CLIENT_NAME_KEY = "sproutgate_auth_client_name";
|
||||
|
||||
/** 从统一登录 URL 上的 `client_id` / `client_name` 写入 session,后续请求带 `X-Auth-Client` 头以便记录接入应用 */
|
||||
export function persistAuthClientFromFlow(authFlow) {
|
||||
if (!authFlow?.clientId) return;
|
||||
try {
|
||||
sessionStorage.setItem(AUTH_CLIENT_ID_KEY, authFlow.clientId);
|
||||
if (authFlow.clientName) sessionStorage.setItem(AUTH_CLIENT_NAME_KEY, authFlow.clientName);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export function authClientFetchHeaders() {
|
||||
try {
|
||||
const id = (sessionStorage.getItem(AUTH_CLIENT_ID_KEY) || "").trim();
|
||||
const name = (sessionStorage.getItem(AUTH_CLIENT_NAME_KEY) || "").trim();
|
||||
const h = {};
|
||||
if (id) h["X-Auth-Client"] = id;
|
||||
if (name) h["X-Auth-Client-Name"] = name;
|
||||
return h;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function clearAuthClientContext() {
|
||||
try {
|
||||
sessionStorage.removeItem(AUTH_CLIENT_ID_KEY);
|
||||
sessionStorage.removeItem(AUTH_CLIENT_NAME_KEY);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAuthCallbackUrl(redirectUri, payload) {
|
||||
const url = new URL(redirectUri, window.location.href);
|
||||
const hashParams = new URLSearchParams(url.hash ? url.hash.slice(1) : "");
|
||||
Object.entries(payload).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null || value === "") return;
|
||||
hashParams.set(key, String(value));
|
||||
});
|
||||
url.hash = hashParams.toString();
|
||||
return url.toString();
|
||||
}
|
||||
Reference in New Issue
Block a user