修复首屏:增加预加载占位并提前恢复主题

This commit is contained in:
cieanyau committed 2026-11-07 05:47:34 +08:00
1 parent 83b7484558
commit 30cbfd3a9c
7 files changed
+395 -23

No files matched your search

+3 -3
View File
@@ -3,7 +3,7 @@
* 全站入口:公共购买页可在未登录时使用独立布局,其余页面沿用路由指定的布局。
* 路由放行与组件实际挂载同时受会话约束,防止重定向等待期间仍执行受保护页面请求。
*/
import { computed, onMounted, onBeforeUnmount } from "vue";
import { computed, onBeforeUnmount } from "vue";
import { useRouter } from "#app";
import { NuxtLayout, NuxtPage, NuxtRouteAnnouncer } from "#components";
import BuildUpdateNotice from "~/components/BuildUpdateNotice.vue";
@@ -32,8 +32,8 @@ const routeSessionKey = computed(() =>
? route.value.path
: auth.token || "anonymous",
);
// 主题监听由入口统一注册和释放,避免每次页面导航重复绑定系统主题事件。
onMounted(theme.init);
// 在子组件首次渲染前同步主题;入口唯一持有监听,卸载时释放,不随导航重复注册。
theme.init();
onBeforeUnmount(theme.dispose);
</script>
+9 -20
View File
@@ -1,14 +1,7 @@
/** 浅色/深色/跟随系统三态主题,使用共享 Nuxt 状态与 html.app-dark 驱动组件和原生控件。 */
import { computed } from "vue";
import { useState } from "#app";
// 存储不可用或值未知时安全回落 auto,不让浏览器隐私限制阻断页面初始化。
function n(e) {
try {
let t = (e ?? globalThis.localStorage)?.getItem(`theme`);
if (t === `light` || t === `dark`) return t;
} catch {}
return `auto`;
}
import { applyTheme } from "~/utils/theme-appearance.js";
var r = [`light`, `dark`, `auto`],
i = {
light: `浅色`,
@@ -22,7 +15,7 @@ var r = [`light`, `dark`, `auto`],
},
o = `theme`;
function s() {
let s = useState(`theme-mode`, () => n()),
let s = useState(`theme-mode`, () => applyTheme().mode),
c = useState(
`theme-dark`,
() =>
@@ -30,17 +23,8 @@ function s() {
document.documentElement.classList.contains(`app-dark`),
),
l = () => window.matchMedia(`(prefers-color-scheme: dark)`);
// 仅 auto 读取系统偏好;显式 light/dark 不受系统变化影响。
function u() {
c.value = s.value === `dark` || (s.value === `auto` && l().matches);
}
function d() {
let e = document.documentElement;
(e.classList.toggle(`app-dark`, c.value),
(e.style.colorScheme = c.value ? `dark` : `light`));
}
function f() {
(u(), d());
c.value = applyTheme(s.value).dark;
}
let p = null,
m = () => {
@@ -51,7 +35,12 @@ function s() {
(p?.removeEventListener(`change`, m), (p = null));
}
function g() {
(h(), (s.value = n()), f(), (p = l()), p.addEventListener(`change`, m));
h();
const appearance = applyTheme();
s.value = appearance.mode;
c.value = appearance.dark;
p = l();
p.addEventListener(`change`, m);
}
// 调用方传入已知模式;持久化失败不影响当前页面切换。
function _(e) {
+62
View File
@@ -0,0 +1,62 @@
<!-- 业务脚本未就绪时使用独立样式;Nuxt 在首个页面解析完成后自动移除此模板。 -->
<div
id="console-startup"
role="status"
aria-label="页面加载中"
aria-live="polite"
>
<img src="/images/brand/logo.png" alt="破碎工坊云" width="168" height="36" />
<span class="startup-spinner" aria-hidden="true"></span>
<p>正在加载控制台</p>
<noscript>请启用 JavaScript 后刷新页面。</noscript>
</div>
<style>
#console-startup {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
box-sizing: border-box;
padding: 24px;
background: #f8fafc;
color: #334155;
font:
14px/1.5 system-ui,
sans-serif;
}
.app-dark #console-startup {
background: #18181b;
color: #e4e4e7;
}
#console-startup img {
width: 168px;
height: 36px;
object-fit: contain;
}
#console-startup p {
margin: 0;
}
#console-startup .startup-spinner {
width: 28px;
height: 28px;
box-sizing: border-box;
border: 3px solid #94a3b833;
border-top-color: #2563eb;
border-radius: 50%;
animation: startup-spin 0.8s linear infinite;
}
@keyframes startup-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
#console-startup .startup-spinner {
animation: none;
}
}
</style>
+15
View File
@@ -0,0 +1,15 @@
/** 同一套规则用于首屏内联启动和运行时切换,避免两次绘制使用不同主题。 */
export function applyTheme(mode) {
if (mode === undefined) {
try {
mode = localStorage.getItem("theme");
} catch {}
}
if (mode !== "light" && mode !== "dark") mode = "auto";
const dark =
mode === "dark" ||
(mode === "auto" && matchMedia("(prefers-color-scheme: dark)").matches);
document.documentElement.classList.toggle("app-dark", dark);
document.documentElement.style.colorScheme = dark ? "dark" : "light";
return { mode, dark };
}