diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css index 847503d..c1e4ecb 100644 --- a/src/main/resources/static/css/main.css +++ b/src/main/resources/static/css/main.css @@ -272,6 +272,104 @@ body { flex: 1; } +/* 暗黑模式样式 */ +html.dark { + filter: invert(1) hue-rotate(180deg); +} + +/* 只有在没有View Transition时才使用CSS过渡 */ +html:not(.view-transition-active) { + transition: filter 300ms ease; +} + +html.dark img, +html.dark video, +html.dark .avatar, +html.dark .image, +html.dark .thumb, +html.dark [class*="icon-"], +html.dark .product-icon { + filter: invert(1) hue-rotate(180deg); +} + +/* 主题切换按钮样式 */ +.theme-toggle { + position: relative; + width: 50px; + height: 26px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 13px; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 4px; +} + +.theme-toggle::before { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 22px; + height: 22px; + background: white; + border-radius: 50%; + transition: all 0.3s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +html.dark .theme-toggle::before { + transform: translateX(24px); +} + +.theme-toggle .theme-icon { + font-size: 12px; + color: white; + z-index: 1; + transition: opacity 0.3s ease; +} + +.theme-toggle .sun-icon { + opacity: 1; +} + +.theme-toggle .moon-icon { + opacity: 0; +} + +html.dark .theme-toggle .sun-icon { + opacity: 0; +} + +html.dark .theme-toggle .moon-icon { + opacity: 1; +} + +/* View Transition API 动画样式 */ +::view-transition-old(root), +::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} + +::view-transition-old(root) { + z-index: 1; +} + +::view-transition-new(root) { + z-index: 2147483646; +} + +html.dark::view-transition-old(root) { + z-index: 2147483646; +} + +html.dark::view-transition-new(root) { + z-index: 1; +} + /* 移动端优化 */ @media (max-width: 768px) { .license-code-display { @@ -279,13 +377,13 @@ body { padding: 12px; max-height: 200px; } - + .notification { right: 10px; left: 10px; max-width: none; } - + .config-code { font-size: 11px; padding: 8px; @@ -293,36 +391,56 @@ body { word-break: break-all; overflow-wrap: break-word; } - + .config-section { padding: 12px; margin: 12px 0; } - + /* 配置步骤移动端优化 */ .config-step-mobile { flex-direction: column; space-y: 12px; } - + .config-step-mobile .config-step-content { margin-left: 0; margin-top: 8px; } - + /* 首页内容移动端优化 */ .home-section { padding: 16px; margin-bottom: 16px; } - + .home-title { font-size: 28px; line-height: 1.2; } - + .home-subtitle { font-size: 16px; line-height: 1.4; } + + /* 主题切换按钮移动端优化 */ + .theme-toggle { + width: 45px; + height: 24px; + border-radius: 12px; + } + + .theme-toggle::before { + width: 20px; + height: 20px; + } + + html.dark .theme-toggle::before { + transform: translateX(21px); + } + + .theme-toggle .theme-icon { + font-size: 10px; + } } \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index cf5fea8..b6c3c5b 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -87,9 +87,17 @@ - +
+ +
+ + +
+ + +
diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js index 27e5d69..0edf258 100644 --- a/src/main/resources/static/js/app.js +++ b/src/main/resources/static/js/app.js @@ -64,16 +64,19 @@ const App = { this.loadProducts() this.loadPlugins() this.setDefaultExpiryDate() - + + // 加载主题设置 + Utils.loadTheme() + // 监听滚动事件 const handleScroll = () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop this.showBackToTop = scrollTop > 300 } - + // 监听滚动事件 window.addEventListener('scroll', handleScroll) - + // 保存函数引用以便清理 this._handleScroll = handleScroll }, @@ -239,6 +242,11 @@ const App = { top: 0, behavior: 'smooth' }) + }, + + // 主题切换 + toggleTheme(event) { + Utils.toggleTheme(event) } } } diff --git a/src/main/resources/static/js/utils.js b/src/main/resources/static/js/utils.js index 5d2a39f..4110091 100644 --- a/src/main/resources/static/js/utils.js +++ b/src/main/resources/static/js/utils.js @@ -108,6 +108,78 @@ const Utils = { const date = new Date() date.setFullYear(date.getFullYear() + 1) return date.toISOString().split('T')[0] + }, + + // 主题切换功能 + toggleTheme(event) { + // 检查浏览器是否支持 View Transition API + if (!document.startViewTransition) { + // 不支持则直接切换主题,不添加动画 + document.documentElement.classList.toggle('dark') + this.saveTheme() + return + } + + // 标记正在使用View Transition,避免CSS过渡冲突 + document.documentElement.classList.add('view-transition-active') + + const transition = document.startViewTransition(() => { + document.documentElement.classList.toggle('dark') + this.saveTheme() + }) + + transition.ready.then(() => { + const { clientX, clientY } = event + + const endRadius = Math.hypot(Math.max(clientX, innerWidth - clientX), Math.max(clientY, innerHeight - clientY)) + + const clipPath = [`circle(0px at ${clientX}px ${clientY}px)`, `circle(${endRadius}px at ${clientX}px ${clientY}px)`] + + const isDark = document.documentElement.classList.contains('dark') + + document.documentElement.animate( + { + clipPath: isDark ? clipPath.reverse() : clipPath + }, + { + duration: 450, + easing: 'ease-in', + pseudoElement: isDark ? '::view-transition-old(root)' : '::view-transition-new(root)' + } + ) + }) + + // 动画完成后移除标记类 + transition.finished.finally(() => { + document.documentElement.classList.remove('view-transition-active') + }) + }, + + // 保存主题设置 + saveTheme() { + const isDark = document.documentElement.classList.contains('dark') + localStorage.setItem('theme', isDark ? 'dark' : 'light') + }, + + // 加载主题设置 + loadTheme() { + const savedTheme = localStorage.getItem('theme') + if (savedTheme === 'dark') { + document.documentElement.classList.add('dark') + } else if (savedTheme === 'light') { + document.documentElement.classList.remove('dark') + } else { + // 如果没有保存的主题,检查系统偏好 + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches + if (prefersDark) { + document.documentElement.classList.add('dark') + } + } + }, + + // 获取当前主题 + getCurrentTheme() { + return document.documentElement.classList.contains('dark') ? 'dark' : 'light' } }