Files
2026-09-21 23:13:14 +08:00

299 lines
8.5 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FMS - 数字化管理系统</title>
<script>
// 首帧前同步应用的暗色偏好(pinia-plugin-persistedstate 以 store id 持久化到 localStorage),
// 让加载屏与用户选择的主题一致,避免暗色用户先看到浅色闪屏
try {
var persisted = JSON.parse(localStorage.getItem('app') || '{}')
if (persisted && persisted.darkMode) {
document.documentElement.classList.add('boot-dark')
}
} catch (e) {
/* 忽略:无持久化数据时按浅色默认渲染 */
}
</script>
<style>
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
/* ===== 开屏加载屏 =====
配色对齐 src/theme/tokens.css 浅色主题(默认):背景 #f1f3f6 / 文字 #1f2329 /
次级文字 #6f7784 / 主色 #0f172b / 边框 #dfe5ec;
深色(html.boot-dark,由上方脚本按持久化偏好写入)对齐 .dark 覆盖值。 */
.app-boot {
display: flex;
background-color: #f1f3f6;
position: fixed;
left: 0;
top: 0;
height: 100%;
width: 100%;
z-index: 9999;
transition: opacity 0.3s ease-in;
opacity: 1;
pointer-events: none;
/* 锁定字体,避免 Vue 注入的 web 字体就绪后替换导致文字重定位抖动 */
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "PingFang SC",
"Microsoft YaHei", sans-serif;
}
.boot-dark .app-boot {
background-color: #0f1115;
}
.app-boot--hide {
opacity: 0;
}
/* 加载失败态:浅红渐变背景 + 错误提示;错误时需要可交互(重试按钮可点击) */
.app-boot--error {
background: radial-gradient(circle at 50% 50%, #fef2f2 0%, #fee2e2 100%);
color: #b91c1c;
pointer-events: auto;
}
.boot-dark .app-boot--error {
background: radial-gradient(circle at 50% 50%, #2a1215 0%, #1c0e10 100%);
color: #f87171;
}
.app-boot__container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
flex-grow: 1;
user-select: none;
-webkit-user-select: none;
}
/* 品牌区:对齐登录页 login-logo 的版式(48px 圆角方块 + 主色底 + 白图标) */
.app-boot__logo {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
margin-bottom: 16px;
border-radius: 12px;
background: #0f172b;
color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.app-boot__logo svg {
width: 26px;
height: 26px;
}
.app-boot__name {
font-size: 24px;
font-weight: 600;
line-height: 32px;
color: #1f2329;
}
.boot-dark .app-boot__name {
color: #e6e8eb;
}
.app-boot__sub-title {
margin-top: 4px;
font-size: 14px;
color: #6f7784;
}
.boot-dark .app-boot__sub-title,
.boot-dark .app-boot__hint {
color: #9aa3af;
}
/* 加载圈:浅色轨道 + 主色弧,旋转为合成属性,不掉帧 */
.app-boot__loading {
width: 22px;
height: 22px;
margin-top: 32px;
border-radius: 50%;
border: 2px solid #e5e6eb;
border-top-color: #0f172b;
opacity: 0;
animation:
app-boot-fade 0.45s ease-out 0.15s forwards,
app-boot-spin 0.9s linear infinite;
}
.boot-dark .app-boot__loading {
border-color: #2a2f3a;
border-top-color: #e6e8eb;
}
@keyframes app-boot-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.app-boot__hint {
margin-top: 12px;
font-size: 12px;
color: #6f7784;
}
/* 内容入场:轻微上浮 + 淡入,逐项错开,避免生硬 */
.app-boot__logo,
.app-boot__name,
.app-boot__sub-title,
.app-boot__hint {
opacity: 0;
animation: app-boot-rise 0.45s ease-out forwards;
}
.app-boot__name {
animation-delay: 0.05s;
}
.app-boot__sub-title {
animation-delay: 0.1s;
}
.app-boot__hint {
animation-delay: 0.2s;
}
@keyframes app-boot-rise {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes app-boot-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* 错误态:隐藏正常加载内容,展示提示与重试按钮 */
.app-boot--error .app-boot__logo,
.app-boot--error .app-boot__name,
.app-boot--error .app-boot__sub-title,
.app-boot--error .app-boot__loading,
.app-boot--error .app-boot__hint {
display: none;
}
.app-boot__message {
font-size: 14px;
font-weight: 600;
}
.app-boot__retry {
margin-top: 16px;
padding: 6px 18px;
border: 1px solid currentColor;
border-radius: 6px;
background: transparent;
color: inherit;
font-size: 14px;
font-family: inherit;
cursor: pointer;
transition: background 150ms ease;
}
.app-boot__retry:hover {
background: rgba(185, 28, 28, 0.08);
}
.boot-dark .app-boot__retry:hover {
background: rgba(248, 113, 113, 0.1);
}
@media (prefers-reduced-motion: reduce) {
.app-boot__logo,
.app-boot__name,
.app-boot__sub-title,
.app-boot__hint,
.app-boot__loading {
opacity: 1;
animation: none;
}
}
</style>
</head>
<body>
<div id="app">
<script>
// 模块加载失败时(如直接 file:// 打开)展示错误,避免永远卡在加载屏。
// 忽略良性噪音错误(ResizeObserver 循环等浏览器警告也会走 error 事件),
// 否则 DevTools 展开/收起等触发 resize 时会把整个页面误替换成错误页。
window.addEventListener('error', (e) => {
const msg = e && e.message ? String(e.message) : ''
if (/ResizeObserver loop/i.test(msg)) return
const app = document.getElementById('app')
if (app) {
app.innerHTML =
'<div id="app-boot-error" class="app-boot app-boot--error" style="position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;font-family:sans-serif">' +
'<div style="font-size:20px;font-weight:bold">加载失败</div>' +
'<div style="font-size:14px">请通过本地开发服务器访问(如 npm run dev),不要直接以 file:// 方式打开本文件。</div>' +
(msg ? '<div style="font-size:12px;opacity:.7">' + msg + '</div>' : '') +
'</div>'
}
})
</script>
</div>
<div id="app-boot" class="app-boot" role="status" aria-busy="true">
<div class="app-boot__container">
<div class="app-boot__logo" aria-hidden="true">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M7 2h10" />
<path d="M5 6h14" />
<rect width="18" height="12" x="3" y="10" rx="2" />
</svg>
</div>
<div class="app-boot__name">FMS</div>
<div class="app-boot__sub-title">数字化管理系统</div>
<div class="app-boot__loading" aria-hidden="true"></div>
<div class="app-boot__hint">正在加载,请稍候…</div>
<div class="app-boot__message" id="app-boot-message" hidden></div>
<button class="app-boot__retry" id="app-boot-retry" type="button" hidden>重新加载</button>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>