feat(public): 主题切换改为下拉菜单 + 新增4个主题 + 优化搜索与抽屉交互

- 主题切换由循环切换改为弹出下拉菜单,显示全部可用主题
- 新增紫韵/深海/青空/玫红 4 个主题,共 8 个主题 + 跟随系统
- 清理顶部 .actions 菜单栏与旧主题下拉的残余死 CSS/JS
- 搜索无结果时显示空态提示
- 移动端抽屉打开时锁定背景滚动
- 搜索输入防抖 120ms
- 超宽屏下卡片描述放宽到 2 行,搜索框同步放大
- 修复抽屉主题按钮缺少 JS 绑定的 bug
- .card .url 对比度由 --text-soft 改为 --text-muted
This commit is contained in:
2026-09-07 16:50:19 +08:00
parent 4d6325c70e
commit e10ef5f243
+210 -138
View File
@@ -91,6 +91,62 @@
--shadow: 0 2px 12px rgba(0,0,0,0.4); --shadow: 0 2px 12px rgba(0,0,0,0.4);
--shadow-hover: 0 8px 24px rgba(22, 186, 170, 0.25); --shadow-hover: 0 8px 24px rgba(22, 186, 170, 0.25);
} }
/* 紫韵(优雅紫色) */
[data-theme="lavender"] {
--brand: #8b5cf6;
--brand-2: #ec4899;
--bg: #faf5ff;
--card-bg: #ffffff;
--text: #2e1065;
--text-muted: #6d28d9;
--text-soft: #a78bfa;
--border: #e9d5ff;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(139,92,246,0.10);
--shadow-hover: 0 8px 24px rgba(139,92,246,0.25);
}
/* 深海(深蓝 navy */
[data-theme="ocean"] {
--brand: #0ea5e9;
--brand-2: #06b6d4;
--bg: #f0f9ff;
--card-bg: #ffffff;
--text: #0c4a6e;
--text-muted: #0369a1;
--text-soft: #7dd3fc;
--border: #bae6fd;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(14,165,233,0.10);
--shadow-hover: 0 8px 24px rgba(14,165,233,0.28);
}
/* 青空(明亮天蓝) */
[data-theme="sky"] {
--brand: #38bdf8;
--brand-2: #3b82f6;
--bg: #f0f9ff;
--card-bg: #ffffff;
--text: #0c4a6e;
--text-muted: #0284c7;
--text-soft: #7dd3fc;
--border: #e0f2fe;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(59,130,246,0.10);
--shadow-hover: 0 8px 24px rgba(59,130,246,0.28);
}
/* 玫红(热情玫红) */
[data-theme="rose"] {
--brand: #f43f5e;
--brand-2: #ec4899;
--bg: #fff1f2;
--card-bg: #ffffff;
--text: #4a040c;
--text-muted: #be123c;
--text-soft: #fda4af;
--border: #fecdd3;
--topbar-text: #ffffff;
--shadow: 0 2px 12px rgba(244,63,94,0.10);
--shadow-hover: 0 8px 24px rgba(244,63,94,0.28);
}
/* 系统暗色兜底:未设置 data-theme 时跟随系统 */ /* 系统暗色兜底:未设置 data-theme 时跟随系统 */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root:not([data-theme]) { :root:not([data-theme]) {
@@ -157,85 +213,15 @@
.topbar { padding: 68px 0 52px; } .topbar { padding: 68px 0 52px; }
.topbar h1 { font-size: 44px; } .topbar h1 { font-size: 44px; }
.topbar .sub { font-size: 17px; } .topbar .sub { font-size: 17px; }
.site-search { max-width: 680px; } .site-search { max-width: 760px; }
/* 卡片描述放宽到 2 行,提升信息密度 */
.card .desc { -webkit-line-clamp: 2; min-height: 40px; }
} }
@media (min-width: 2560px) { @media (min-width: 2560px) {
.topbar { padding: 84px 0 64px; } .topbar { padding: 84px 0 64px; }
.topbar h1 { font-size: 52px; } .topbar h1 { font-size: 52px; }
.site-search { max-width: 880px; }
} }
.topbar .actions { margin-top: 18px; display: flex; flex-wrap: wrap; gap: 8px; }
.topbar .actions a, .topbar .actions button {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 14px;
background: rgba(255,255,255,0.18);
border: 1px solid rgba(255,255,255,0.35);
color: var(--topbar-text);
border-radius: 999px;
font-size: 13px;
text-decoration: none;
cursor: pointer;
transition: background 0.2s;
font-family: inherit;
}
.topbar .actions a:hover, .topbar .actions button:hover { background: rgba(255,255,255,0.32); }
/* ===== 主题切换下拉 ===== */
.theme-picker { position: relative; display: inline-block; }
.theme-picker > button {
display: inline-flex; align-items: center; gap: 4px;
}
.theme-menu {
position: absolute;
top: calc(100% + 6px);
right: 0;
min-width: 180px;
margin: 0; padding: 4px;
list-style: none;
background: var(--card-bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: var(--shadow-hover);
z-index: 50;
opacity: 0;
transform: translateY(-6px);
pointer-events: none;
transition: opacity 0.15s, transform 0.15s;
}
.theme-menu.open {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.theme-menu li {
display: flex; align-items: center; gap: 10px;
padding: 8px 12px;
font-size: 14px;
border-radius: 6px;
cursor: pointer;
transition: background 0.12s;
color: var(--text);
}
.theme-menu li:hover { background: rgba(22,186,170,0.10); }
.theme-menu li.active { background: rgba(22,186,170,0.18); font-weight: 600; }
.theme-menu li .dot {
display: inline-block;
width: 18px; height: 18px;
border-radius: 50%;
border: 2px solid var(--border);
flex-shrink: 0;
}
.dot-default { background: linear-gradient(135deg, #16baaa 0%, #2e8de8 100%); }
.dot-sunset { background: linear-gradient(135deg, #ff7043 0%, #e91e63 100%); }
.dot-forest { background: linear-gradient(135deg, #2e7d32 0%, #00897b 100%); }
.dot-dark { background: linear-gradient(135deg, #0f1419 0%, #16baaa 100%); }
.dot-auto {
background: linear-gradient(90deg, #f5f7fa 0%, #f5f7fa 50%, #0f1419 50%, #0f1419 100%);
border-color: var(--text-muted);
}
/* 手机端分类入口按钮:默认桌面隐藏 */ /* 手机端分类入口按钮:默认桌面隐藏 */
.cat-trigger-mobile { .cat-trigger-mobile {
display: none; display: none;
@@ -251,6 +237,36 @@
} }
.cat-trigger-mobile:active { background: rgba(255,255,255,0.32); } .cat-trigger-mobile:active { background: rgba(255,255,255,0.32); }
/* ===== 主题下拉菜单 ===== */
.theme-picker-trigger { position: relative; }
.theme-menu {
display: none;
position: absolute;
top: calc(100% + 6px);
left: 0;
min-width: 150px;
margin: 0; padding: 4px;
list-style: none;
background: var(--card-bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: var(--shadow-hover);
z-index: 50;
}
.theme-menu.open { display: block; }
.theme-menu li {
display: block;
padding: 8px 12px;
font-size: 14px;
border-radius: 6px;
cursor: pointer;
transition: background 0.12s;
color: var(--text);
}
.theme-menu li:hover { background: rgba(22,186,170,0.10); }
.theme-menu li.active { background: rgba(22,186,170,0.18); font-weight: 600; }
/* ===== 顶部全局搜索 ===== */ /* ===== 顶部全局搜索 ===== */
.site-search { .site-search {
position: relative; position: relative;
@@ -307,7 +323,7 @@
-webkit-backdrop-filter: saturate(180%) blur(8px); -webkit-backdrop-filter: saturate(180%) blur(8px);
} }
.cat-nav-inner { .cat-nav-inner {
max-width: 1200px; max-width: 100%;
margin: 0 auto; margin: 0 auto;
padding: 8px 24px; padding: 8px 24px;
display: flex; display: flex;
@@ -358,6 +374,17 @@
background: rgba(0,0,0,0.5); background: rgba(0,0,0,0.5);
} }
.cat-drawer.open { display: block; } .cat-drawer.open { display: block; }
/* 抽屉打开时锁定背景滚动 */
body.drawer-open { overflow: hidden; }
/* 搜索无结果空态 */
.search-empty {
text-align: center;
color: var(--text-muted);
padding: 64px 16px;
font-size: 15px;
}
.search-empty .ico { font-size: 40px; display: block; margin-bottom: 12px; opacity: 0.6; }
.cat-drawer-panel { .cat-drawer-panel {
position: absolute; position: absolute;
left: 0; right: 0; bottom: 0; left: 0; right: 0; bottom: 0;
@@ -606,7 +633,7 @@
min-height: 20px; min-height: 20px;
} }
.card .url { .card .url {
font-size: 12px; color: var(--text-soft); font-size: 12px; color: var(--text-muted);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
direction: rtl; direction: rtl;
text-align: left; text-align: left;
@@ -785,7 +812,6 @@
gap: 10px; gap: 10px;
} }
.topbar-brand { display: none; } /* 手机顶部只保留分类按钮+搜索 */ .topbar-brand { display: none; } /* 手机顶部只保留分类按钮+搜索 */
.topbar .actions { display: none; }
.cat-trigger-mobile { display: inline-flex; } .cat-trigger-mobile { display: inline-flex; }
/* 顶部搜索框更紧凑,占满剩余空间 */ /* 顶部搜索框更紧凑,占满剩余空间 */
@@ -966,14 +992,28 @@
<li><a href="/system/passport/login"> <li><a href="/system/passport/login">
<i class="layui-icon side-ico layui-icon-password"></i> <i class="layui-icon side-ico layui-icon-password"></i>
<span class="side-txt">登录后台</span></a></li> <span class="side-txt">登录后台</span></a></li>
<li><a href="javascript:void(0)" id="side-theme-toggle"> <li class="theme-picker-trigger">
<i class="layui-icon side-ico layui-icon-light"></i> <a href="javascript:void(0)" id="side-theme-toggle">
<span class="side-txt">切换主题</span></a></li> <i class="layui-icon side-ico layui-icon-light"></i>
<span class="side-txt">切换主题</span></a>
<ul class="theme-menu" id="side-theme-menu">
<li data-theme="default">蓝绿</li>
<li data-theme="sunset">暮色</li>
<li data-theme="forest">森林</li>
<li data-theme="dark">暗色</li>
<li data-theme="lavender">紫韵</li>
<li data-theme="ocean">深海</li>
<li data-theme="sky">青空</li>
<li data-theme="rose">玫红</li>
<li data-theme="auto">跟随系统</li>
</ul>
</li>
</ul> </ul>
</div> </div>
</aside> </aside>
<main class="content"> <main class="content">
<div id="search-empty" class="search-empty" hidden></div>
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
</div> </div>
@@ -1064,10 +1104,23 @@
<i class="layui-icon cat-icon layui-icon-password"></i> <i class="layui-icon cat-icon layui-icon-password"></i>
<span>登录后台</span> <span>登录后台</span>
<span class="arrow"></span></a></li> <span class="arrow"></span></a></li>
<li><a href="javascript:void(0)" id="drawer-theme-toggle"> <li class="theme-picker-trigger">
<i class="layui-icon cat-icon layui-icon-light"></i> <a href="javascript:void(0)" id="drawer-theme-toggle">
<span>切换主题</span> <i class="layui-icon cat-icon layui-icon-light"></i>
<span class="arrow"></span></a></li> <span>切换主题</span>
<span class="arrow"></span></a>
<ul class="theme-menu" id="drawer-theme-menu">
<li data-theme="default">蓝绿</li>
<li data-theme="sunset">暮色</li>
<li data-theme="forest">森林</li>
<li data-theme="dark">暗色</li>
<li data-theme="lavender">紫韵</li>
<li data-theme="ocean">深海</li>
<li data-theme="sky">青空</li>
<li data-theme="rose">玫红</li>
<li data-theme="auto">跟随系统</li>
</ul>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -1080,13 +1133,6 @@
var THEME_KEY = 'bw-theme'; var THEME_KEY = 'bw-theme';
var FIXED_THEMES = ['default', 'sunset', 'forest', 'dark']; var FIXED_THEMES = ['default', 'sunset', 'forest', 'dark'];
var ALL = FIXED_THEMES.concat(['auto']); var ALL = FIXED_THEMES.concat(['auto']);
var LABEL_MAP = {
'default': '🎨 蓝绿',
'sunset': '🎨 暮色',
'forest': '🎨 森林',
'dark': '🎨 暗色',
'auto': '🎨 跟随系统'
};
function isSystemDark(){ function isSystemDark(){
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -1103,8 +1149,12 @@
if (ALL.indexOf(name) === -1) name = 'default'; if (ALL.indexOf(name) === -1) name = 'default';
var resolved = resolveTheme(name); var resolved = resolveTheme(name);
document.documentElement.setAttribute('data-theme', resolved); document.documentElement.setAttribute('data-theme', resolved);
// 顶部 meta 颜色随主题变化 // 顶部 meta 颜色随主题变化(暗色用深色,其他用对应品牌色)
var themeColor = (resolved === 'dark') ? '#0f1419' : '#16baaa'; var themeColor = (resolved === 'dark') ? '#0f1419' :
(resolved === 'lavender') ? '#8b5cf6' :
(resolved === 'ocean') ? '#0ea5e9' :
(resolved === 'sky') ? '#38bdf8' :
(resolved === 'rose') ? '#f43f5e' : '#16baaa';
var meta = document.querySelector('meta[name="theme-color"]'); var meta = document.querySelector('meta[name="theme-color"]');
if (meta) meta.setAttribute('content', themeColor); if (meta) meta.setAttribute('content', themeColor);
// 高亮菜单项 // 高亮菜单项
@@ -1112,9 +1162,6 @@
if (li.getAttribute('data-theme') === name) li.classList.add('active'); if (li.getAttribute('data-theme') === name) li.classList.add('active');
else li.classList.remove('active'); else li.classList.remove('active');
}); });
// 更新顶部按钮文字
var label = document.getElementById('theme-toggle-label');
if (label) label.textContent = LABEL_MAP[name] || '🎨 主题';
if (persist) localStorage.setItem(THEME_KEY, name); if (persist) localStorage.setItem(THEME_KEY, name);
} }
@@ -1227,9 +1274,11 @@
function openDrawer(){ function openDrawer(){
drawer.classList.add('open'); drawer.classList.add('open');
document.body.classList.add('drawer-open'); // 锁背景滚动
} }
function closeDrawer(){ function closeDrawer(){
drawer.classList.remove('open'); drawer.classList.remove('open');
document.body.classList.remove('drawer-open');
} }
if (triggerMobile) triggerMobile.addEventListener('click', openDrawer); if (triggerMobile) triggerMobile.addEventListener('click', openDrawer);
if (fab) fab.addEventListener('click', openDrawer); if (fab) fab.addEventListener('click', openDrawer);
@@ -1263,32 +1312,6 @@
link.addEventListener('click', closeDrawer); link.addEventListener('click', closeDrawer);
}); });
} }
// 抽屉内"切换主题"按钮:循环到下一个主题(含 auto)
var drawerThemeBtn = document.getElementById('drawer-theme-toggle');
if (drawerThemeBtn) {
drawerThemeBtn.addEventListener('click', function(e){
e.preventDefault();
var VALID = ['default', 'sunset', 'forest', 'dark', 'auto'];
var LABEL_MAP = {default:'🎨 蓝绿', sunset:'🎨 暮色', forest:'🎨 森林', dark:'🎨 暗色', auto:'🎨 跟随系统'};
var cur = localStorage.getItem('bw-theme') || 'default';
var idx = VALID.indexOf(cur);
var next = VALID[(idx + 1) % VALID.length];
// 解析 next 为实际生效值
var isSystemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
var resolved = (next === 'auto') ? (isSystemDark ? 'dark' : 'default') : next;
document.documentElement.setAttribute('data-theme', resolved);
var labelEl = document.getElementById('theme-toggle-label');
if (labelEl) labelEl.textContent = LABEL_MAP[next];
localStorage.setItem('bw-theme', next);
// 同步菜单高亮
document.querySelectorAll('.theme-menu li').forEach(function(li){
if (li.getAttribute('data-theme') === next) li.classList.add('active');
else li.classList.remove('active');
});
closeDrawer();
});
}
})(); })();
// ===== 导航卡片点击埋点:sendBeacon,不影响原 href 跳转 ===== // ===== 导航卡片点击埋点:sendBeacon,不影响原 href 跳转 =====
@@ -1526,10 +1549,26 @@
if (cnt) cnt.textContent = n ? String(n) : ''; if (cnt) cnt.textContent = n ? String(n) : '';
li.style.display = n ? '' : 'none'; li.style.display = n ? '' : 'none';
}); });
// 无结果空态
var empty = document.getElementById('search-empty');
if (empty) {
var totalVisible = document.querySelectorAll('.grid .card:not([style*="display: none"])').length;
if (keyword && totalVisible === 0) {
empty.innerHTML = '<span class="ico">🔍</span>未找到与「' + keyword + '」匹配的网址';
empty.hidden = false;
} else {
empty.hidden = true;
}
}
} }
// 输入防抖,避免每次按键都遍历全部卡片
var t;
input.addEventListener('input', function(){ input.addEventListener('input', function(){
filter(input.value); clearTimeout(t);
var v = input.value;
t = setTimeout(function(){ filter(v); }, 120);
}); });
form.addEventListener('submit', function(e){ form.addEventListener('submit', function(e){
@@ -1557,26 +1596,59 @@
filter(q || ''); filter(q || '');
})(); })();
// ===== 侧栏「切换主题」:循环切换到下一个主题 ===== // ===== 侧栏「切换主题」:弹出下拉菜单 =====
(function(){ (function(){
var btn = document.getElementById('side-theme-toggle'); var btn = document.getElementById('side-theme-toggle');
if (!btn) return; var menu = document.getElementById('side-theme-menu');
if (!btn || !menu) return;
function closeAll(){
document.querySelectorAll('.theme-menu.open').forEach(function(m){ m.classList.remove('open'); });
}
btn.addEventListener('click', function(e){ btn.addEventListener('click', function(e){
e.preventDefault(); e.preventDefault();
var VALID = ['default', 'sunset', 'forest', 'dark', 'auto']; e.stopPropagation();
var LABEL_MAP = {default:'🎨 蓝绿', sunset:'🎨 暮色', forest:'🎨 森林', dark:'🎨 暗色', auto:'🎨 跟随系统'}; var wasOpen = menu.classList.contains('open');
var cur = localStorage.getItem('bw-theme') || 'default'; closeAll();
var next = VALID[(VALID.indexOf(cur) + 1) % VALID.length]; if (!wasOpen) menu.classList.add('open');
var isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
var resolved = (next === 'auto') ? (isDark ? 'dark' : 'default') : next;
document.documentElement.setAttribute('data-theme', resolved);
var label = document.getElementById('theme-toggle-label');
if (label) label.textContent = LABEL_MAP[next];
localStorage.setItem('bw-theme', next);
document.querySelectorAll('.theme-menu li').forEach(function(li){
li.classList.toggle('active', li.getAttribute('data-theme') === next);
});
}); });
menu.addEventListener('click', function(e){
var li = e.target.closest('li[data-theme]');
if (!li) return;
e.stopPropagation();
applyTheme(li.getAttribute('data-theme'), true);
closeAll();
});
document.addEventListener('click', closeAll);
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeAll(); });
})();
// ===== 抽屉内「切换主题」:弹出下拉菜单 =====
(function(){
var btn = document.getElementById('drawer-theme-toggle');
var menu = document.getElementById('drawer-theme-menu');
if (!btn || !menu) return;
function closeAll(){
document.querySelectorAll('.theme-menu.open').forEach(function(m){ m.classList.remove('open'); });
}
btn.addEventListener('click', function(e){
e.preventDefault();
e.stopPropagation();
var wasOpen = menu.classList.contains('open');
closeAll();
if (!wasOpen) menu.classList.add('open');
});
menu.addEventListener('click', function(e){
var li = e.target.closest('li[data-theme]');
if (!li) return;
e.stopPropagation();
applyTheme(li.getAttribute('data-theme'), true);
closeAll();
// 关闭抽屉
var drawer = document.getElementById('cat-drawer');
if (drawer) drawer.classList.remove('open');
});
document.addEventListener('click', closeAll);
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeAll(); });
})(); })();
</script> </script>
</body> </body>