Skip to content

Commit 1a315d4

Browse files
committed
feat(webui): 引入 mdui 2.x 设计规范,添加 SPA 页面过渡与骨架屏
将原 MPA 硬导航改为 SPA fetch + 原地 DOM 替换,彻底消除切换页面时的白屏闪烁, 并按 mdui 2.x / Material Design 3 规范补充页面过渡动画与脉冲式骨架屏。 实现细节: - 新增 css/qtine-motion.css:MD3 motion 令牌(emphasized/standard 缓动曲线)、 页面入场动画、脉冲 shimmer 骨架屏、View Transitions 同文档过渡样式、 prefers-reduced-motion 无障碍降级。 - 新增 js/qtine-motion.js:SPA 导航核心(fetch 目标页 HTML → 替换 body 与 per-page style → 串行重执行脚本,避免 chart.js 等依赖未就绪)、侧栏点击 事件委托、history pushState/popstate、定时器基线清理(防止轮询泄漏)、 6 个骨架渲染助手(qtineSkel/SkelTable/SkelText/SkelBlock/SkelStats/SkelFields)。 - 10 个页面接入:dashboard/plugins/market/adapters/tasks/messages/logs/ settings/about/login,统一引入 mdui 与共享文件,列表页用骨架屏替换 原先的"加载中..."文字占位。 无白屏原理:导航不触发整页 navigation,旧 DOM 在新内容就绪前始终可见; 不支持 View Transitions 的浏览器自动回退为直接替换(仍无白屏)。 不修改任何 Python 后端逻辑、配置或数据,仅前端静态文件。
1 parent d3ec5d0 commit 1a315d4

12 files changed

Lines changed: 499 additions & 22 deletions

File tree

qtine/web/static/about.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@
4646
.footer-note{font-size:12px;color:var(--on-sv);text-align:center;padding:8px 0 16px}
4747
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:var(--olv);border-radius:3px}
4848
@media(max-width:768px){.nav{width:56px}.nav-item{width:48px;padding:8px 1px;font-size:10px}.main{padding:12px}.hero h1{font-size:28px}.hero .logo{width:72px;height:72px}}
49-
</style></head><body>
49+
</style>
50+
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css">
51+
<link rel="stylesheet" href="/static/css/qtine-motion.css">
52+
<script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
53+
<script src="/static/js/qtine-motion.js" defer></script>
54+
</head><body>
5055
<header class="top-bar">
5156
<div style="display:flex;align-items:center;flex:1">
5257
<svg width="26" height="26" viewBox="0 0 24 24" fill="var(--p)"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>

qtine/web/static/adapters.html

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@
2525
.snackbar.show{opacity:1;transform:translateX(-50%) translateY(0)}
2626
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:var(--olv);border-radius:3px}
2727
@media(max-width:768px){.nav{width:56px}.nav-item{width:48px;padding:8px 1px;font-size:10px}.grid{grid-template-columns:1fr}.main{padding:12px}}
28-
</style></head><body>
28+
</style>
29+
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css">
30+
<link rel="stylesheet" href="/static/css/qtine-motion.css">
31+
<script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
32+
<script src="/static/js/qtine-motion.js" defer></script>
33+
</head><body>
2934
<header class="top-bar">
3035
<div style="display:flex;align-items:center;flex:1">
3136
<svg width="26" height="26" viewBox="0 0 24 24" fill="var(--p)"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>
@@ -49,16 +54,18 @@
4954
<a class="nav-item" href="/webui/about"><svg viewBox="0 0 24 24"><path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>关于</a>
5055
<div class="nav-spacer"></div>
5156
</nav>
52-
<main class="main"><div class="grid" id="grid"><div class="card"><p style="color:var(--on-sv)">加载中...</p></div></div></main>
57+
<main class="main"><div class="grid" id="grid"></div></main>
5358
</div>
5459
<div class="snackbar" id="sb"></div>
5560
<script>
5661
(function(){
5762
function logout(){localStorage.removeItem('qtine_auth');location.href='/webui/login'}
5863
function toast(m){var s=document.getElementById('sb');s.textContent=m;s.classList.add('show');clearTimeout(s._t);s._t=setTimeout(function(){s.classList.remove('show')},3000)}
5964
function load(){
65+
qtineSkelTable('#grid',3);
6066
fetch('/api/adapters').then(function(r){return r.json()}).then(function(d){
61-
var g=document.getElementById('grid');g.innerHTML='';
67+
qtineSkelClear('#grid');
68+
var g=document.getElementById('grid');
6269
var statusMap={connected:'已连接',disconnected:'已断开',connecting:'连接中',error:'错误'};
6370
(d||[]).forEach(function(a){
6471
var c=document.createElement('div');c.className='card';
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*!
2+
* Qtine WebUI Motion & Skeleton
3+
* 依照 mdui 2.x (Material Design 3) 设计规范:
4+
* - 复用 MD3 emphasized / standard 缓动曲线
5+
* - 骨架屏采用 MD3 推荐的脉冲式 shimmer(而非线性进度条)
6+
* - 跨页过渡使用 View Transitions API,不支持时自动回退
7+
* 本文件自托管,mdui CDN 仅提供组件令牌默认值,缺失也能工作。
8+
*/
9+
10+
/* ── MD3 / mdui motion 令牌(本地 fallback,避免 CDN 失败时动画丢失)────── */
11+
:root{
12+
/* 缓动曲线:MD3 motion */
13+
--mdui-motion-easing-linear:cubic-bezier(0,0,1,1);
14+
--mdui-motion-easing-standard:cubic-bezier(.2,0,0,1);
15+
--mdui-motion-easing-emphasized:cubic-bezier(.3,0,0,1);
16+
--mdui-motion-easing-emphasized-accelerate:cubic-bezier(.3,0,1,1);
17+
--mdui-motion-easing-emphasized-decelerate:cubic-bezier(.05,.7,.1,1);
18+
/* 时长(ms) */
19+
--mdui-motion-duration-short1:50ms;
20+
--mdui-motion-duration-short2:100ms;
21+
--mdui-motion-duration-short3:150ms;
22+
--mdui-motion-duration-short4:200ms;
23+
--mdui-motion-duration-medium1:250ms;
24+
--mdui-motion-duration-medium2:300ms;
25+
--mdui-motion-duration-medium3:350ms;
26+
--mdui-motion-duration-medium4:400ms;
27+
--mdui-motion-duration-long1:450ms;
28+
--mdui-motion-duration-long2:500ms;
29+
/* 骨架屏专用 */
30+
--qtine-skel-base:#2B2930; /* = 现有 --sfc */
31+
--qtine-skel-hi:#3A3742; /* shimmer 高光 */
32+
}
33+
34+
/* ── 页面入场动画(首次加载 / 真正刷新时使用)──────────────────────────── */
35+
@keyframes qtine-page-enter{
36+
from{opacity:0;transform:translateY(8px)}
37+
to{opacity:1;transform:translateY(0)}
38+
}
39+
.main{
40+
animation:qtine-page-enter var(--mdui-motion-duration-medium1)
41+
var(--mdui-motion-easing-emphasized-decelerate) both;
42+
}
43+
#login-page{
44+
animation:qtine-page-enter var(--mdui-motion-duration-medium2)
45+
var(--mdui-motion-easing-emphasized-decelerate) both;
46+
}
47+
48+
/* ── View Transitions API:SPA 同文档过渡(无白屏)───────────────────────
49+
* 不使用 @view-transition{navigation:auto}(那是跨文档 MPA 方案,会导致白屏)。
50+
* 这里配合 JS 的 startViewTransition(callback) 同文档替换 <body>,
51+
* 旧 root 快照在新内容就绪前始终可见,彻底消除白屏闪烁。
52+
*/
53+
@keyframes qtine-vt-fade-out{
54+
from{opacity:1}
55+
to{opacity:.6}
56+
}
57+
@keyframes qtine-vt-fade-in{
58+
from{opacity:0;transform:translateY(6px)}
59+
to{opacity:1;transform:translateY(0)}
60+
}
61+
::view-transition-old(root){
62+
animation:qtine-vt-fade-out var(--mdui-motion-duration-short3)
63+
var(--mdui-motion-easing-emphasized-accelerate) both;
64+
}
65+
::view-transition-new(root){
66+
animation:qtine-vt-fade-in var(--mdui-motion-duration-medium1)
67+
var(--mdui-motion-easing-emphasized-decelerate) both;
68+
}
69+
70+
/* ── 骨架屏:MD3 脉冲式 shimmer ─────────────────────────────────────── */
71+
@keyframes qtine-skel-wave{
72+
0%{background-position:-200% 0}
73+
100%{background-position:200% 0}
74+
}
75+
.skel{
76+
background:linear-gradient(
77+
90deg,
78+
var(--qtine-skel-base) 0%,
79+
var(--qtine-skel-hi) 50%,
80+
var(--qtine-skel-base) 100%
81+
);
82+
background-size:200% 100%;
83+
animation:qtine-skel-wave 1.6s
84+
var(--mdui-motion-easing-emphasized) infinite;
85+
border-radius:var(--rad-s,8px);
86+
display:block;
87+
}
88+
89+
/* 单行文字骨架 */
90+
.skel-line{
91+
height:14px;
92+
margin-bottom:8px;
93+
border-radius:var(--rad-s,8px);
94+
}
95+
.skel-line.w90{width:90%}
96+
.skel-line.w80{width:80%}
97+
.skel-line.w70{width:70%}
98+
.skel-line.w60{width:60%}
99+
.skel-line.w50{width:50%}
100+
.skel-line.w40{width:40%}
101+
.skel-line.w30{width:30%}
102+
103+
/* 卡片骨架:复用现有 .card 容器 */
104+
.skel-card{
105+
display:flex;
106+
flex-direction:column;
107+
gap:10px;
108+
}
109+
.skel-card .skel-title{height:18px;width:55%}
110+
.skel-card .skel-meta{height:12px;width:35%}
111+
.skel-card .skel-actions{display:flex;gap:8px;margin-top:6px}
112+
.skel-card .skel-actions .skel{height:28px;border-radius:var(--rad-f,9999px)}
113+
.skel-card .skel-actions .skel.a1{width:64px}
114+
.skel-card .skel-actions .skel.a2{width:56px}
115+
.skel-card .skel-actions .skel.a3{width:56px}
116+
117+
/* 表格行骨架(adapters 用) */
118+
.skel-card .skel-table{display:flex;flex-direction:column;gap:8px;margin-top:6px}
119+
.skel-card .skel-table .skel{height:14px;width:100%}
120+
121+
/* 表单字段骨架(settings 用) */
122+
.skel-field{margin-bottom:18px}
123+
.skel-field .skel-label{height:12px;width:80px;margin-bottom:8px}
124+
.skel-field .skel-input{height:40px;width:100%;border-radius:var(--rad-s,8px)}
125+
126+
/* 块状骨架(logs/dashboard 图表用) */
127+
.skel-block{height:200px;border-radius:var(--rad-m,12px)}
128+
.skel-stat{height:64px;border-radius:var(--rad-m,12px)}
129+
130+
/* ── 无障碍:减少动作偏好 ───────────────────────────────────────────── */
131+
@media (prefers-reduced-motion:reduce){
132+
.main{animation:none}
133+
.skel{animation:none;background:var(--qtine-skel-base)}
134+
::view-transition-old(root),
135+
::view-transition-new(root){animation:none}
136+
}

qtine/web/static/dashboard.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@
3737
.chip.ok{background:rgba(125,215,141,.15);color:var(--su)}.chip.err{background:rgba(242,184,181,.15);color:var(--e)}.chip.warn{background:rgba(255,217,112,.15);color:var(--w)}
3838
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:var(--olv);border-radius:3px}
3939
@media(max-width:768px){.nav{width:56px}.nav-item{width:48px;padding:8px 1px;font-size:10px}.grid{grid-template-columns:1fr}.main{padding:12px}}
40-
</style></head><body>
40+
</style>
41+
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css">
42+
<link rel="stylesheet" href="/static/css/qtine-motion.css">
43+
<script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
44+
<script src="/static/js/qtine-motion.js" defer></script>
45+
</head><body>
4146
<header class="top-bar">
4247
<div style="display:flex;align-items:center;flex:1">
4348
<svg width="26" height="26" viewBox="0 0 24 24" fill="var(--p)"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>

0 commit comments

Comments
 (0)