mirror of
https://github.com/101island/lolisland.us.git
synced 2026-03-01 03:49:42 +08:00
feat: sync user avatar from /me on page load and optimize caching
This commit is contained in:
@@ -11,12 +11,12 @@ const { titleSvg } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="island-container">
|
||||
<TitleCard titleSvg={titleSvg} />
|
||||
<LoginView />
|
||||
<QQAuthView />
|
||||
<VerificationView />
|
||||
<RegisterView />
|
||||
<DashboardView />
|
||||
<TitleCard titleSvg={titleSvg}/>
|
||||
<LoginView/>
|
||||
<QQAuthView/>
|
||||
<VerificationView/>
|
||||
<RegisterView/>
|
||||
<DashboardView/>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -45,6 +45,7 @@ const { titleSvg } = Astro.props;
|
||||
// Actually, let's keep state here.
|
||||
let isLoggedIn = false;
|
||||
let currentQQ = "";
|
||||
let currentAvatar = "";
|
||||
let currentUser = "";
|
||||
let isPolling = false;
|
||||
|
||||
@@ -129,30 +130,27 @@ const { titleSvg } = Astro.props;
|
||||
function updateDashboard() {
|
||||
const displays = getDisplays();
|
||||
if (displays.dashAvatar) {
|
||||
if (currentUser) {
|
||||
displays.dashAvatar.src = `${BACKEND_API_BASE}/user/avatar/${currentUser}`;
|
||||
// Add onerror fallback to QQ or default
|
||||
if (currentAvatar && /^[0-9a-f]{16}$/.test(currentAvatar)) {
|
||||
displays.dashAvatar.src = `${BACKEND_API_BASE}/user/avatar/${currentAvatar}`;
|
||||
displays.dashAvatar.onerror = () => {
|
||||
if (currentQQ) {
|
||||
displays.dashAvatar.src = `https://q.qlogo.cn/headimg_dl?dst_uin=${currentQQ}&spec=640&img_type=jpg`;
|
||||
} else {
|
||||
displays.dashAvatar.src =
|
||||
"https://ui-avatars.com/api/?name=" +
|
||||
(currentUser || "User") +
|
||||
"&background=random";
|
||||
}
|
||||
displays.dashAvatar.onerror = null; // Prevent infinite loop
|
||||
// Fallback to UI Avatars if hash fails (shouldn't happen if valid, but maybe network error)
|
||||
displays.dashAvatar.src =
|
||||
"https://ui-avatars.com/api/?name=" +
|
||||
(currentUser || "User") +
|
||||
"&background=random";
|
||||
displays.dashAvatar.onerror = null;
|
||||
};
|
||||
} else if (currentQQ) {
|
||||
displays.dashAvatar.src = `https://q.qlogo.cn/headimg_dl?dst_uin=${currentQQ}&spec=640&img_type=jpg`;
|
||||
} else {
|
||||
// Fallback or default
|
||||
displays.dashAvatar.src =
|
||||
"https://ui-avatars.com/api/?name=User&background=random";
|
||||
"https://ui-avatars.com/api/?name=" +
|
||||
(currentUser || "User") +
|
||||
"&background=random";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleLoginSuccess(token: string, qq: string) {
|
||||
function handleLoginSuccess(token: string, qq: string, avatar: string) {
|
||||
localStorage.setItem("token", token);
|
||||
|
||||
// Extract username from token
|
||||
@@ -170,6 +168,11 @@ const { titleSvg } = Astro.props;
|
||||
localStorage.setItem("qq", String(qq));
|
||||
currentQQ = String(qq);
|
||||
}
|
||||
if (avatar) {
|
||||
localStorage.setItem("avatar", String(avatar));
|
||||
currentAvatar = String(avatar);
|
||||
}
|
||||
|
||||
isLoggedIn = true;
|
||||
updateDashboard();
|
||||
window.dispatchEvent(new CustomEvent("login-success"));
|
||||
@@ -221,10 +224,11 @@ const { titleSvg } = Astro.props;
|
||||
const data = (await res.json()) as {
|
||||
token?: string;
|
||||
qq?: string;
|
||||
avatar?: string;
|
||||
error?: string;
|
||||
};
|
||||
if (data.token) {
|
||||
handleLoginSuccess(data.token, data.qq || "");
|
||||
handleLoginSuccess(data.token, data.qq || "", data.avatar || "");
|
||||
if (inputs.loginUser) inputs.loginUser.value = "";
|
||||
if (inputs.loginPass) inputs.loginPass.value = "";
|
||||
} else {
|
||||
@@ -292,6 +296,7 @@ const { titleSvg } = Astro.props;
|
||||
status: string;
|
||||
token: string;
|
||||
qq: string;
|
||||
avatar: string;
|
||||
};
|
||||
|
||||
if (data.status === "verified") {
|
||||
@@ -299,7 +304,7 @@ const { titleSvg } = Astro.props;
|
||||
switchView("register");
|
||||
} else if (data.status === "authenticated") {
|
||||
isPolling = false;
|
||||
handleLoginSuccess(data.token, data.qq);
|
||||
handleLoginSuccess(data.token, data.qq, data.avatar || "");
|
||||
} else {
|
||||
// Status pending or other, retry immediately
|
||||
if (isPolling) poll();
|
||||
@@ -351,9 +356,14 @@ const { titleSvg } = Astro.props;
|
||||
const loginData = (await loginRes.json()) as {
|
||||
token?: string;
|
||||
qq?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
if (loginData.token) {
|
||||
handleLoginSuccess(loginData.token!, loginData.qq || "");
|
||||
handleLoginSuccess(
|
||||
loginData.token || "",
|
||||
loginData.qq || "",
|
||||
loginData.avatar || "",
|
||||
);
|
||||
} else {
|
||||
switchView("login");
|
||||
}
|
||||
@@ -371,9 +381,39 @@ const { titleSvg } = Astro.props;
|
||||
const token = localStorage.getItem("token");
|
||||
const storedQQ = localStorage.getItem("qq");
|
||||
const storedUser = localStorage.getItem("username");
|
||||
const storedAvatar = localStorage.getItem("avatar");
|
||||
isLoggedIn = !!token;
|
||||
if (storedQQ) currentQQ = storedQQ;
|
||||
if (storedUser) currentUser = storedUser;
|
||||
if (storedAvatar) currentAvatar = storedAvatar;
|
||||
|
||||
if (isLoggedIn) {
|
||||
// Sync user info to ensure local cache is up to date
|
||||
fetch(`${BACKEND_API_BASE}/me`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
.then((res) => res.json() as Promise<{ avatar?: string; qq?: string }>)
|
||||
.then((data) => {
|
||||
let needsUpdate = false;
|
||||
if (data.avatar && data.avatar !== currentAvatar) {
|
||||
currentAvatar = data.avatar;
|
||||
localStorage.setItem("avatar", currentAvatar);
|
||||
needsUpdate = true;
|
||||
}
|
||||
if (data.qq && data.qq !== currentQQ) {
|
||||
currentQQ = data.qq;
|
||||
localStorage.setItem("qq", currentQQ);
|
||||
// Sync updated QQ if changed
|
||||
currentQQ = data.qq;
|
||||
localStorage.setItem("qq", currentQQ);
|
||||
needsUpdate = true;
|
||||
}
|
||||
if (needsUpdate) {
|
||||
updateDashboard();
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
if (isLoggedIn) {
|
||||
updateDashboard();
|
||||
@@ -407,6 +447,8 @@ const { titleSvg } = Astro.props;
|
||||
isLoggedIn = !!token;
|
||||
const storedQQ = localStorage.getItem("qq");
|
||||
if (storedQQ) currentQQ = storedQQ;
|
||||
const storedAvatar = localStorage.getItem("avatar");
|
||||
if (storedAvatar) currentAvatar = storedAvatar;
|
||||
updateDashboard();
|
||||
if (isLoggedIn) switchView("dashboard");
|
||||
});
|
||||
@@ -416,6 +458,8 @@ const { titleSvg } = Astro.props;
|
||||
currentQQ = "";
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("qq");
|
||||
localStorage.removeItem("avatar");
|
||||
currentAvatar = "";
|
||||
switchView("title");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -161,6 +161,19 @@ const currentLabel = languages[lang as keyof typeof languages] || lang;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.item-flag {
|
||||
font-size: 1.2rem;
|
||||
border-radius: 2px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.menu-item.loading {
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -239,17 +252,4 @@ const currentLabel = languages[lang as keyof typeof languages] || lang;
|
||||
color: #7cfbff;
|
||||
border: 1px solid rgba(124, 251, 255, 0.2);
|
||||
}
|
||||
|
||||
.item-flag {
|
||||
font-size: 1.2rem;
|
||||
border-radius: 2px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,31 +41,43 @@ const t = getTranslations(lang);
|
||||
|
||||
async function updateAvatar() {
|
||||
let qq = localStorage.getItem("qq");
|
||||
let avatar = localStorage.getItem("avatar");
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
if (token) {
|
||||
if (!qq) {
|
||||
try {
|
||||
// Fetch user info from backend if token exists but QQ is missing
|
||||
const res = await fetch(`${BACKEND_API_BASE}/me`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data.qq) {
|
||||
qq = String(data.qq);
|
||||
localStorage.setItem("qq", qq);
|
||||
}
|
||||
// Sync user info
|
||||
try {
|
||||
// Fetch user info from backend if token exists
|
||||
const res = await fetch(`${BACKEND_API_BASE}/me`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as {
|
||||
qq?: string;
|
||||
avatar?: string;
|
||||
};
|
||||
if (data.qq) {
|
||||
qq = String(data.qq);
|
||||
localStorage.setItem("qq", qq);
|
||||
}
|
||||
if (data.avatar) {
|
||||
avatar = String(data.avatar);
|
||||
localStorage.setItem("avatar", avatar);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch user info", e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch user info", e);
|
||||
}
|
||||
|
||||
if (qq) {
|
||||
avatarImg.src = `https://q.qlogo.cn/headimg_dl?dst_uin=${qq}&spec=640&img_type=jpg`;
|
||||
if (avatar && /^[0-9a-f]{16}$/.test(avatar)) {
|
||||
avatarImg.src = `${BACKEND_API_BASE}/user/avatar/${avatar}`;
|
||||
avatarImg.onerror = () => {
|
||||
avatarImg.src =
|
||||
"https://ui-avatars.com/api/?name=User&background=random";
|
||||
avatarImg.onerror = null;
|
||||
};
|
||||
} else {
|
||||
// Default avatar or placeholder if QQ is missing and fetch failed
|
||||
// Default avatar or placeholder
|
||||
avatarImg.src =
|
||||
"https://ui-avatars.com/api/?name=User&background=random";
|
||||
}
|
||||
@@ -101,6 +113,7 @@ const t = getTranslations(lang);
|
||||
logoutBtn.addEventListener("click", () => {
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem("qq");
|
||||
localStorage.removeItem("avatar");
|
||||
userDropdown.classList.add("hidden");
|
||||
dropdownMenu.classList.remove("active");
|
||||
// Notify other components if necessary, e.g. show login button again
|
||||
|
||||
Reference in New Issue
Block a user