Files
springboot-demo/target/classes/static/learning-shell.js

330 lines
12 KiB
JavaScript
Raw Normal View History

(function () {
const STORAGE = {
token: "learning-demo-token",
username: "learning-demo-username",
language: "learning-demo-language"
};
const TEXT = {
zh: {
brand: "Spring \u5b66\u4e60\u5de5\u4f5c\u53f0",
home: "\u9996\u9875",
access: "\u767b\u5f55\u9875",
loginReady: "\u5df2\u767b\u5f55\uff0c\u53ef\u8bbf\u95ee\u53d7\u4fdd\u62a4\u5b9e\u9a8c",
loginMissing: "\u672a\u767b\u5f55\uff0c\u53d7\u4fdd\u62a4\u5b9e\u9a8c\u4f1a\u8fd4\u56de 401",
currentUser: "\u5f53\u524d\u7528\u6237",
logout: "\u9000\u51fa\u767b\u5f55",
login: "\u53bb\u767b\u5f55",
languageToggle: "EN",
unauthorized: "\u672a\u767b\u5f55\u6216\u4ee4\u724c\u5df2\u5931\u6548\uff0c\u8bf7\u5148\u6253\u5f00\u767b\u5f55\u9875\u5b8c\u6210\u6f14\u793a\u767b\u5f55\u3002",
requestFailed: "\u8bf7\u6c42\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u3002"
},
en: {
brand: "Spring Learning Workspace",
home: "Home",
access: "Login",
loginReady: "Authenticated, protected labs are available",
loginMissing: "Not logged in, protected labs will return 401",
currentUser: "User",
logout: "Logout",
login: "Login",
languageToggle: "\u4e2d\u6587",
unauthorized: "Not logged in or token expired. Open the login page first.",
requestFailed: "Request failed. Please try again."
}
};
function normalizeLanguage(language) {
return language === "en" ? "en" : "zh";
}
function getLanguage() {
return normalizeLanguage(localStorage.getItem(STORAGE.language));
}
function setLanguage(language) {
const normalized = normalizeLanguage(language);
localStorage.setItem(STORAGE.language, normalized);
document.documentElement.lang = normalized === "zh" ? "zh-CN" : "en";
window.dispatchEvent(new CustomEvent("learning-language-changed", {
detail: { language: normalized }
}));
}
function t(key) {
const lang = getLanguage();
return (TEXT[lang] && TEXT[lang][key]) || key;
}
function getToken() {
return localStorage.getItem(STORAGE.token) || "";
}
function getUsername() {
return localStorage.getItem(STORAGE.username) || "";
}
function isLoggedIn() {
return Boolean(getToken());
}
function saveAuth(token, username) {
localStorage.setItem(STORAGE.token, token || "");
localStorage.setItem(STORAGE.username, username || "");
window.dispatchEvent(new CustomEvent("learning-auth-changed", {
detail: { loggedIn: isLoggedIn(), username: getUsername() }
}));
}
function clearAuth() {
localStorage.removeItem(STORAGE.token);
localStorage.removeItem(STORAGE.username);
window.dispatchEvent(new CustomEvent("learning-auth-changed", {
detail: { loggedIn: false, username: "" }
}));
}
function ensureStyle() {
if (document.getElementById("learning-shell-style")) {
return;
}
const style = document.createElement("style");
style.id = "learning-shell-style";
style.textContent = [
".learning-shell {",
" max-width: 1380px;",
" margin: 18px auto 0;",
" padding: 0 24px;",
"}",
".learning-shell-card {",
" display: flex;",
" justify-content: space-between;",
" align-items: center;",
" gap: 14px;",
" flex-wrap: wrap;",
" padding: 14px 18px;",
" border-radius: 22px;",
" border: 1px solid rgba(216, 228, 240, 0.9);",
" background: rgba(255, 255, 255, 0.88);",
" box-shadow: 0 12px 28px rgba(18, 32, 51, 0.08);",
" backdrop-filter: blur(10px);",
"}",
".learning-shell-brand {",
" display: flex;",
" flex-direction: column;",
" gap: 4px;",
"}",
".learning-shell-brand strong {",
" font-size: 15px;",
" color: #122033;",
"}",
".learning-shell-brand span {",
" font-size: 13px;",
" color: #5d7288;",
"}",
".learning-shell-actions {",
" display: flex;",
" align-items: center;",
" gap: 10px;",
" flex-wrap: wrap;",
"}",
".learning-shell-link,",
".learning-shell-button,",
".learning-shell-badge {",
" display: inline-flex;",
" align-items: center;",
" justify-content: center;",
" min-height: 36px;",
" padding: 0 14px;",
" border-radius: 999px;",
" text-decoration: none;",
" font-size: 13px;",
" font-weight: 700;",
"}",
".learning-shell-link {",
" color: #0f67b5;",
" background: rgba(15, 103, 181, 0.09);",
"}",
".learning-shell-button {",
" border: 0;",
" cursor: pointer;",
" color: #fff;",
" background: linear-gradient(135deg, #177245, #35a465);",
"}",
".learning-shell-button.alt {",
" color: #0f67b5;",
" background: rgba(15, 103, 181, 0.09);",
"}",
".learning-shell-badge {",
" color: #33485e;",
" background: rgba(18, 32, 51, 0.06);",
"}",
"@media (max-width: 780px) {",
" .learning-shell {",
" padding: 0 14px;",
" }",
"}"
].join("\n");
document.head.appendChild(style);
}
function mountShell(options) {
ensureStyle();
if (document.querySelector(".learning-shell")) {
if (options && typeof options.onLanguageChange === "function") {
options.onLanguageChange(getLanguage());
}
return;
}
const shell = document.createElement("div");
shell.className = "learning-shell";
shell.innerHTML = [
'<div class="learning-shell-card">',
' <div class="learning-shell-brand">',
' <strong data-role="brand"></strong>',
' <span data-role="status"></span>',
" </div>",
' <div class="learning-shell-actions">',
' <a class="learning-shell-link" href="/" data-role="home"></a>',
' <a class="learning-shell-link" href="/access.html" data-role="access"></a>',
' <span class="learning-shell-badge" data-role="user"></span>',
' <button class="learning-shell-button alt" type="button" data-role="language"></button>',
' <button class="learning-shell-button alt" type="button" data-role="login"></button>',
' <button class="learning-shell-button alt" type="button" data-role="logout"></button>',
" </div>",
"</div>"
].join("");
const firstPage = document.querySelector(".page");
if (firstPage) {
document.body.insertBefore(shell, firstPage);
} else {
document.body.insertBefore(shell, document.body.firstChild);
}
const els = {
brand: shell.querySelector('[data-role="brand"]'),
status: shell.querySelector('[data-role="status"]'),
home: shell.querySelector('[data-role="home"]'),
access: shell.querySelector('[data-role="access"]'),
user: shell.querySelector('[data-role="user"]'),
language: shell.querySelector('[data-role="language"]'),
login: shell.querySelector('[data-role="login"]'),
logout: shell.querySelector('[data-role="logout"]')
};
function render() {
const loggedIn = isLoggedIn();
const username = getUsername();
els.brand.textContent = t("brand");
els.status.textContent = loggedIn ? t("loginReady") : t("loginMissing");
els.home.textContent = t("home");
els.access.textContent = t("access");
els.user.textContent = loggedIn ? t("currentUser") + ": " + username : t("loginMissing");
els.language.textContent = t("languageToggle");
els.login.textContent = t("login");
els.logout.textContent = t("logout");
els.login.style.display = loggedIn ? "none" : "inline-flex";
els.logout.style.display = loggedIn ? "inline-flex" : "none";
if (options && typeof options.onLanguageChange === "function") {
options.onLanguageChange(getLanguage());
}
if (options && typeof options.onAuthChange === "function") {
options.onAuthChange({ loggedIn: loggedIn, username: username });
}
}
els.language.addEventListener("click", function () {
setLanguage(getLanguage() === "zh" ? "en" : "zh");
});
els.login.addEventListener("click", function () {
window.location.href = "/access.html";
});
els.logout.addEventListener("click", function () {
clearAuth();
render();
});
window.addEventListener("learning-auth-changed", render);
window.addEventListener("learning-language-changed", render);
render();
}
async function fetchWithAuth(url, options) {
const requestOptions = options || {};
const headers = new Headers(requestOptions.headers || {});
const token = getToken();
if (token && !headers.has("Authorization")) {
headers.set("Authorization", "Bearer " + token);
}
return fetch(url, Object.assign({}, requestOptions, { headers: headers }));
}
async function requestJson(url, options) {
const response = await fetchWithAuth(url, options);
const contentType = response.headers.get("content-type") || "";
const payload = contentType.includes("application/json")
? await response.json()
: await response.text();
if (!response.ok) {
const error = new Error(
typeof payload === "object" && payload && payload.message
? payload.message
: t("requestFailed")
);
error.status = response.status;
error.payload = payload;
error.details = typeof payload === "object" && payload ? payload.data : null;
throw error;
}
if (
typeof payload === "object" &&
payload &&
Object.prototype.hasOwnProperty.call(payload, "code") &&
payload.code !== 0
) {
const error = new Error(payload.message || t("requestFailed"));
error.status = payload.code;
error.payload = payload;
error.details = payload.data;
throw error;
}
return payload;
}
function describeError(error) {
if (error && Number(error.status) === 401) {
return t("unauthorized");
}
return error && error.message ? error.message : t("requestFailed");
}
setLanguage(getLanguage());
window.learningShell = {
mountShell: mountShell,
getLanguage: getLanguage,
setLanguage: setLanguage,
getToken: getToken,
getUsername: getUsername,
isLoggedIn: isLoggedIn,
saveAuth: saveAuth,
clearAuth: clearAuth,
fetchWithAuth: fetchWithAuth,
requestJson: requestJson,
describeError: describeError
};
})();