mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-04-01 07:59:44 +08:00
feat: 增强主题支持与自定义样式功能
* 移除旧的主题颜色变量,改为支持自定义 CSS 样式,提升用户界面的灵活性。 * 在 `App.vue` 中实现自定义 CSS 的加载与应用,确保用户设置的实时更新。 * 更新 `SettingsModal.vue`,允许用户输入自定义 CSS,增强个性化体验。 * 优化多个组件的样式,确保在不同主题下的视觉一致性。 * 删除不再使用的主题相关文件,简化代码结构。
This commit is contained in:
127
THEME_SYSTEM.md
Normal file
127
THEME_SYSTEM.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 主题系统说明
|
||||
|
||||
## 概述
|
||||
|
||||
本项目采用简化的主题系统,**自动跟随操作系统主题设置**。
|
||||
|
||||
## 自动跟随系统
|
||||
|
||||
### 工作原理
|
||||
|
||||
应用会自动检测并应用操作系统的主题偏好:
|
||||
- 🌞 系统为浅色模式 → 应用显示浅色主题
|
||||
- 🌙 系统为深色模式 → 应用显示深色主题
|
||||
- 🔄 系统主题变化 → 应用自动切换
|
||||
|
||||
### 实现原理
|
||||
|
||||
使用 Tailwind CSS 的 `dark:` 变体来定义暗色模式样式:
|
||||
|
||||
```css
|
||||
/* 浅色模式样式 */
|
||||
.bg-white { background: white; }
|
||||
|
||||
/* 深色模式样式 */
|
||||
.dark .bg-white { background: rgb(15, 23, 42); }
|
||||
```
|
||||
|
||||
当 `<html>` 元素添加 `dark` class 时,所有使用 `dark:` 前缀的样式会自动生效。
|
||||
|
||||
### 系统监听
|
||||
|
||||
使用 `prefers-color-scheme` 媒体查询监听系统主题变化:
|
||||
|
||||
```javascript
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
mediaQuery.addEventListener('change', (e) => {
|
||||
applyTheme(e.matches ? 'dark' : 'light')
|
||||
})
|
||||
```
|
||||
|
||||
## 自定义样式
|
||||
|
||||
### 使用方法
|
||||
|
||||
用户可以在"设置"中添加自定义 CSS 代码来个性化界面:
|
||||
|
||||
```css
|
||||
/* 自定义按钮样式 */
|
||||
.my-custom-button {
|
||||
background: linear-gradient(to right, #ec4899, #8b5cf6);
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* 深色模式下的样式 */
|
||||
.dark .my-custom-button {
|
||||
background: linear-gradient(to right, #a855f7, #8b5cf6);
|
||||
}
|
||||
```
|
||||
|
||||
### 存储和应用
|
||||
|
||||
- 自定义 CSS 保存在 `localStorage` 中(key: `searchgal_custom_css`)
|
||||
- 页面加载时自动应用到 `<style id="custom-user-styles">` 标签中
|
||||
- 支持完整的 CSS 语法和 Tailwind 暗色模式变体
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心文件
|
||||
|
||||
- `src/utils/theme.ts` - 主题管理工具
|
||||
- `src/styles/base.css` - 基础样式和暗色模式定义
|
||||
- `src/components/SettingsModal.vue` - 自定义CSS编辑器
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
// 获取系统主题
|
||||
getSystemTheme(): 'light' | 'dark'
|
||||
|
||||
// 应用主题
|
||||
applyTheme(theme: 'light' | 'dark'): void
|
||||
|
||||
// 监听系统主题变化
|
||||
watchSystemTheme(callback): () => void
|
||||
|
||||
// 加载自定义CSS
|
||||
loadCustomCSS(): string
|
||||
|
||||
// 应用自定义CSS
|
||||
applyCustomCSS(css: string): void
|
||||
|
||||
// 保存自定义CSS
|
||||
saveCustomCSS(css: string): void
|
||||
```
|
||||
|
||||
## 背景图片
|
||||
|
||||
- 浅色模式:显示粉色纹理背景
|
||||
- 深色模式:显示深色纹理背景,透明度允许背景图片显示
|
||||
- 随机图片切换:每10秒自动切换一张随机动漫图片
|
||||
|
||||
## 如何更改系统主题
|
||||
|
||||
### macOS
|
||||
1. 打开"系统设置"
|
||||
2. 点击"外观"
|
||||
3. 选择"浅色"、"深色"或"自动"
|
||||
|
||||
### Windows 11
|
||||
1. 打开"设置"
|
||||
2. 点击"个性化" → "颜色"
|
||||
3. 选择"浅色"、"深色"或"自定义"
|
||||
|
||||
### Linux (GNOME)
|
||||
1. 打开"设置"
|
||||
2. 点击"外观"
|
||||
3. 选择"浅色"或"深色"
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. 使用 Tailwind 的 `dark:` 变体定义暗色样式
|
||||
2. 避免硬编码颜色,优先使用语义化的 Tailwind 类名
|
||||
3. 自定义CSS应该同时定义浅色和深色两种样式
|
||||
4. 测试两种模式下的显示效果,确保可读性
|
||||
|
||||
41
index.html
41
index.html
@@ -62,22 +62,11 @@
|
||||
<link rel="dns-prefetch" href="https://status.searchgal.homes" />
|
||||
|
||||
<style>
|
||||
/* ============================================
|
||||
CSS 变量定义(由 JavaScript 动态修改)
|
||||
============================================ */
|
||||
:root {
|
||||
/* 主题配色 - 默认值(玫瑰粉),会被 themeColors.ts 动态覆盖 */
|
||||
--theme-primary: #ec4899;
|
||||
--theme-primary-dark: #db2777;
|
||||
--theme-accent: #8b5cf6;
|
||||
--theme-accent-dark: #7c3aed;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
第三方库样式覆盖
|
||||
============================================ */
|
||||
|
||||
/* Pace.js 加载进度条 */
|
||||
/* Pace.js 加载进度条 - 艳粉主色调 */
|
||||
.pace {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
@@ -86,23 +75,23 @@
|
||||
display: none;
|
||||
}
|
||||
.pace .pace-progress {
|
||||
background: linear-gradient(90deg, var(--theme-primary), var(--theme-accent));
|
||||
background: linear-gradient(90deg, #ff1493, #d946ef);
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
right: 100%;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
box-shadow: 0 0 10px color-mix(in srgb, var(--theme-primary) 50%, transparent);
|
||||
box-shadow: 0 0 10px rgba(255, 20, 147, 0.5);
|
||||
}
|
||||
|
||||
/* Fancybox 图片查看器 */
|
||||
/* Fancybox 图片查看器 - 艳粉主色调 */
|
||||
.fancybox-custom .fancybox__backdrop {
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.fancybox-custom .fancybox__container {
|
||||
--fancybox-accent-color: var(--theme-primary);
|
||||
--fancybox-accent-color: #ff1493;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
@@ -122,27 +111,27 @@
|
||||
background: rgba(30, 41, 59, 0.3);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg, var(--theme-primary), var(--theme-accent));
|
||||
background: linear-gradient(180deg, #ff1493, #d946ef);
|
||||
border-radius: 10px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg, var(--theme-accent), var(--theme-accent-dark));
|
||||
background: linear-gradient(180deg, #ff69b4, #e879f9);
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg, var(--theme-primary-dark), var(--theme-accent-dark));
|
||||
background: linear-gradient(180deg, #c71585, #c026d3);
|
||||
}
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg, var(--theme-accent-dark), var(--theme-accent));
|
||||
background: linear-gradient(180deg, #ff1493, #d946ef);
|
||||
}
|
||||
|
||||
/* 文本选中高亮 */
|
||||
/* 文本选中高亮 - 艳粉主色调 */
|
||||
::selection {
|
||||
background: color-mix(in srgb, var(--theme-primary) 30%, transparent);
|
||||
background: rgba(255, 20, 147, 0.3);
|
||||
color: inherit;
|
||||
}
|
||||
.dark ::selection {
|
||||
background: color-mix(in srgb, var(--theme-accent) 40%, transparent);
|
||||
background: rgba(217, 70, 239, 0.4);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -210,17 +199,17 @@
|
||||
100% { opacity: 1; transform: scale(1) rotate(0deg); }
|
||||
}
|
||||
|
||||
/* 背景图层伪元素(默认纹理和遮罩层) */
|
||||
/* 背景图层伪元素(默认纹理和遮罩层) - 艳粉主色调 */
|
||||
#background-layer::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: #f0bbbb url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='%23000000' fill-opacity='0.05' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l-2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E") center;
|
||||
background: #ffb3d9 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='%23000000' fill-opacity='0.05' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l-2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E") center;
|
||||
z-index: -1;
|
||||
transition: opacity 0.8s ease-in-out;
|
||||
}
|
||||
.dark #background-layer::before {
|
||||
background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='%23ffffff' fill-opacity='0.03' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l-2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E") center;
|
||||
background: rgb(15, 23, 42) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='192' height='192' viewBox='0 0 192 192'%3E%3Cpath fill='%23ffffff' fill-opacity='0.03' d='M192 15v2a11 11 0 0 0-11 11c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H145v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11 13 13 0 1 1 .02 26 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43a6.1 6.1 0 0 0-3.03 4.87V143h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 181 164a11 11 0 0 0 11 11v2a13 13 0 0 1-13-13 12 12 0 0 1 3.11-7.53l-2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84a6.1 6.1 0 0 0-4.87-3.03H145v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 124 181a11 11 0 0 0-11 11h-2a13 13 0 0 1 13-13c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43a6.1 6.1 0 0 0 3.03-4.87V145h-35.02a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 107 124a11 11 0 0 0-22 0c0 1.94 1.16 4.75 2.53 6.11l2.36 2.36a6.93 6.93 0 0 1 1.22 7.56l-.43.84a8.08 8.08 0 0 1-6.66 4.13H49v35.02a6.1 6.1 0 0 0 3.03 4.87l.84.43c1.58.79 4 .4 5.24-.85l2.36-2.36a12.04 12.04 0 0 1 7.51-3.11A13 13 0 0 1 81 192h-2a11 11 0 0 0-11-11c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V145H11.98a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 0 1 0 177v-2a11 11 0 0 0 11-11c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H47v-35.02a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 28 109a13 13 0 1 1 0-26c2.47 0 5.79 1.37 7.53 3.11l2.36 2.36a4.94 4.94 0 0 0 5.24.85l.84-.43A6.1 6.1 0 0 0 47 84.02V49H11.98a8.08 8.08 0 0 1-6.66-4.13l-.43-.84a6.91 6.91 0 0 1 1.22-7.56l2.36-2.36A10.06 10.06 0 0 0 11 28 11 11 0 0 0 0 17v-2a13 13 0 0 1 13 13c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84A6.1 6.1 0 0 0 11.98 47H47V11.98a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 68 11 11 11 0 0 0 79 0h2a13 13 0 0 1-13 13 12 12 0 0 1-7.53-3.11l-2.36-2.36a4.93 4.93 0 0 0-5.24-.85l-.84.43A6.1 6.1 0 0 0 49 11.98V47h35.02a8.08 8.08 0 0 1 6.66 4.13l.43.84a6.91 6.91 0 0 1-1.22 7.56l-2.36 2.36A10.06 10.06 0 0 0 85 68a11 11 0 0 0 22 0c0-1.94-1.16-4.75-2.53-6.11l-2.36-2.36a6.93 6.93 0 0 1-1.22-7.56l.43-.84a8.08 8.08 0 0 1 6.66-4.13H143V11.98a6.1 6.1 0 0 0-3.03-4.87l-.84-.43c-1.59-.8-4-.4-5.24.85l-2.36 2.36A12 12 0 0 1 124 13a13 13 0 0 1-13-13h2a11 11 0 0 0 11 11c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V47h35.02a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 179 28a13 13 0 0 1 13-13zM84.02 143a6.1 6.1 0 0 0 4.87-3.03l.43-.84c.8-1.59.4-4-.85-5.24l-2.36-2.36A12 12 0 0 1 83 124a13 13 0 1 1 26 0c0 2.47-1.37 5.79-3.11 7.53l-2.36 2.36a4.94 4.94 0 0 0-.85 5.24l.43.84a6.1 6.1 0 0 0 4.87 3.03H143v-35.02a8.08 8.08 0 0 1 4.13-6.66l.84-.43a6.91 6.91 0 0 1 7.56 1.22l2.36 2.36A10.06 10.06 0 0 0 164 107a11 11 0 0 0 0-22c-1.94 0-4.75 1.16-6.11 2.53l-2.36 2.36a6.93 6.93 0 0 1-7.56 1.22l-.84-.43a8.08 8.08 0 0 1-4.13-6.66V49h-35.02a6.1 6.1 0 0 0-4.87 3.03l-.43.84c-.79 1.58-.4 4 .85 5.24l2.36 2.36a12.04 12.04 0 0 1 3.11 7.51A13 13 0 1 1 83 68a12 12 0 0 1 3.11-7.53l2.36-2.36a4.93 4.93 0 0 0 .85-5.24l-.43-.84A6.1 6.1 0 0 0 84.02 49H49v35.02a8.08 8.08 0 0 1-4.13 6.66l-.84.43a6.91 6.91 0 0 1-7.56-1.22l-2.36-2.36A10.06 10.06 0 0 0 28 85a11 11 0 0 0 0 22c1.94 0 4.75-1.16 6.11-2.53l2.36-2.36a6.93 6.93 0 0 1 7.56-1.22l.84.43a8.08 8.08 0 0 1 4.13 6.66V143h35.02z'%3E%3C/path%3E%3C/svg%3E") center;
|
||||
}
|
||||
#background-layer.has-image::before {
|
||||
opacity: 0;
|
||||
|
||||
38
src/App.vue
38
src/App.vue
@@ -27,6 +27,7 @@
|
||||
<SettingsModal
|
||||
:is-open="isSettingsOpen"
|
||||
:custom-api="searchStore.customApi"
|
||||
:custom-c-s-s="customCSS"
|
||||
@close="closeSettings"
|
||||
@save="saveSettings"
|
||||
/>
|
||||
@@ -38,8 +39,13 @@
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { imageDB } from '@/utils/imageDB'
|
||||
import { useSearchStore } from '@/stores/search'
|
||||
import { initTheme } from '@/utils/themeColors'
|
||||
import type { ThemePresetKey } from '@/types/theme'
|
||||
import {
|
||||
getSystemTheme,
|
||||
applyTheme,
|
||||
watchSystemTheme,
|
||||
loadCustomCSS,
|
||||
applyCustomCSS,
|
||||
} from '@/utils/theme'
|
||||
import StatsCorner from '@/components/StatsCorner.vue'
|
||||
import TopToolbar from '@/components/TopToolbar.vue'
|
||||
import SearchHeader from '@/components/SearchHeader.vue'
|
||||
@@ -57,9 +63,11 @@ const imageBlobUrls = ref<Map<string, string>>(new Map()) // URL -> Blob URL 映
|
||||
const shuffledQueue = ref<string[]>([])
|
||||
let fetchInterval: number | null = null
|
||||
let displayInterval: number | null = null
|
||||
let systemThemeCleanup: (() => void) | null = null
|
||||
|
||||
// 设置模态框
|
||||
const isSettingsOpen = ref(false)
|
||||
const customCSS = ref('')
|
||||
|
||||
const MAX_CACHE_SIZE = 10000 // 最大缓存 10000 张图片
|
||||
const CLEANUP_BATCH_SIZE = 2000 // 每次清理 2000 张
|
||||
@@ -360,8 +368,18 @@ function stopAllIntervals() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 初始化主题
|
||||
initTheme()
|
||||
// 初始化主题 - 跟随系统
|
||||
const systemTheme = getSystemTheme()
|
||||
applyTheme(systemTheme)
|
||||
|
||||
// 加载并应用自定义CSS
|
||||
customCSS.value = loadCustomCSS()
|
||||
applyCustomCSS(customCSS.value)
|
||||
|
||||
// 监听系统主题变化
|
||||
systemThemeCleanup = watchSystemTheme((theme) => {
|
||||
applyTheme(theme)
|
||||
})
|
||||
|
||||
// 恢复保存的搜索状态
|
||||
searchStore.restoreState()
|
||||
@@ -385,6 +403,12 @@ onMounted(async () => {
|
||||
onUnmounted(() => {
|
||||
stopAllIntervals()
|
||||
|
||||
// 清理系统主题监听
|
||||
if (systemThemeCleanup) {
|
||||
systemThemeCleanup()
|
||||
systemThemeCleanup = null
|
||||
}
|
||||
|
||||
// 清理所有 Blob URL
|
||||
imageBlobUrls.value.forEach(blobUrl => {
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
@@ -404,10 +428,12 @@ function closeSettings() {
|
||||
isSettingsOpen.value = false
|
||||
}
|
||||
|
||||
function saveSettings(customApi: string, theme: ThemePresetKey) {
|
||||
function saveSettings(customApi: string, newCustomCSS: string) {
|
||||
// 保存自定义 API 到 store
|
||||
searchStore.setCustomApi(customApi)
|
||||
// 主题已经在 SettingsModal 中保存到 localStorage
|
||||
// 保存并应用自定义CSS
|
||||
customCSS.value = newCustomCSS
|
||||
applyCustomCSS(newCustomCSS)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ function initArtalk() {
|
||||
pageTitle: 'Galgame 聚合搜索',
|
||||
server: 'https://artalk.saop.cc',
|
||||
site: 'Galgame 聚合搜索',
|
||||
darkMode: "auto",
|
||||
} as any)
|
||||
} catch (error) {
|
||||
// 静默处理错误
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
<li>
|
||||
• 如果您觉得咱这小工具好用,请移步
|
||||
<a
|
||||
href="https://github.com/Moe-Sakura/SearchGal"
|
||||
href="https://github.com/Moe-Sakura"
|
||||
target="_blank"
|
||||
class="text-indigo-600 hover:underline font-semibold"
|
||||
>GitHub</a>
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
@click.stop
|
||||
>
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-4 sm:py-5 border-b border-theme-primary/20 dark:border-slate-700">
|
||||
<i class="fas fa-cog text-theme-primary dark:text-theme-accent text-xl sm:text-2xl" />
|
||||
<div class="flex items-center gap-2 sm:gap-3 px-4 sm:px-6 py-4 sm:py-5 border-b border-gray-200/50 dark:border-slate-700">
|
||||
<i class="fas fa-cog text-[#ff1493] dark:text-[#ff69b4] text-xl sm:text-2xl" />
|
||||
<h2 class="text-lg sm:text-xl font-bold text-gray-800 dark:text-slate-100 flex-1">设置</h2>
|
||||
<button
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-theme-primary/10 text-gray-500 hover:text-theme-primary dark:text-slate-400 dark:hover:bg-slate-700/50 dark:hover:text-theme-accent transition-all duration-200"
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-gray-200/50 dark:hover:bg-slate-700/50 text-gray-500 hover:text-[#ff1493] dark:text-slate-400 dark:hover:text-[#ff69b4] transition-all duration-200"
|
||||
@click="close"
|
||||
>
|
||||
<i class="fas fa-times text-xl" />
|
||||
@@ -42,69 +42,49 @@
|
||||
<!-- 内容区域 -->
|
||||
<div class="flex-1 overflow-y-auto p-4 sm:p-6 custom-scrollbar">
|
||||
<div class="space-y-6">
|
||||
<!-- 主题配色 -->
|
||||
<!-- 自定义样式 -->
|
||||
<div class="setting-section">
|
||||
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-slate-100 mb-3 flex items-center gap-2">
|
||||
<i class="fas fa-palette" style="color: var(--theme-primary)" />
|
||||
<span>主题配色</span>
|
||||
<i class="fas fa-paint-brush text-[#ff1493] dark:text-[#ff69b4]" />
|
||||
<span>自定义样式</span>
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<p class="text-sm text-gray-600 dark:text-slate-400">
|
||||
选择您喜欢的主题色温,让界面更符合您的审美
|
||||
自定义 CSS 样式代码,支持 Tailwind CSS 的暗色模式变体(dark:)
|
||||
</p>
|
||||
|
||||
<!-- 主题色卡 -->
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
<button
|
||||
v-for="(preset, key) in THEME_PRESETS"
|
||||
:key="key"
|
||||
class="theme-card group relative p-4 rounded-xl border-2 transition-all duration-300 hover:scale-105"
|
||||
:class="[
|
||||
localTheme === key
|
||||
? 'border-current shadow-lg'
|
||||
: 'border-gray-200 dark:border-slate-700 hover:border-gray-300 dark:hover:border-slate-600'
|
||||
]"
|
||||
:style="{
|
||||
color: preset.colors.primary
|
||||
}"
|
||||
@click="selectTheme(key as ThemePresetKey)"
|
||||
>
|
||||
<!-- 选中标记 -->
|
||||
<div
|
||||
v-if="localTheme === key"
|
||||
class="absolute -top-2 -right-2 w-6 h-6 rounded-full flex items-center justify-center text-white text-xs shadow-lg"
|
||||
:style="{ backgroundColor: preset.colors.primary }"
|
||||
>
|
||||
<i class="fas fa-check" />
|
||||
</div>
|
||||
|
||||
<!-- 颜色预览 -->
|
||||
<div class="flex gap-2 mb-3">
|
||||
<div
|
||||
class="w-8 h-8 rounded-lg shadow-md transition-transform group-hover:scale-110"
|
||||
:style="{ backgroundColor: preset.colors.primary }"
|
||||
/>
|
||||
<div
|
||||
class="w-8 h-8 rounded-lg shadow-md transition-transform group-hover:scale-110"
|
||||
:style="{ backgroundColor: preset.colors.accent }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 主题名称 -->
|
||||
<p class="text-sm font-medium text-gray-800 dark:text-slate-100">
|
||||
{{ preset.name }}
|
||||
</p>
|
||||
</button>
|
||||
<!-- CSS 代码编辑器 -->
|
||||
<div class="relative">
|
||||
<textarea
|
||||
v-model="localCustomCSS"
|
||||
placeholder="/* 输入自定义 CSS 代码 */
|
||||
.my-custom-class {
|
||||
background: linear-gradient(to right, #ff1493, #d946ef);
|
||||
}
|
||||
|
||||
.dark .my-custom-class {
|
||||
background: linear-gradient(to right, #ff69b4, #e879f9);
|
||||
}"
|
||||
rows="10"
|
||||
class="w-full px-4 py-3 text-sm font-mono rounded-xl bg-white dark:bg-slate-700/50 backdrop-blur-md shadow-md focus:shadow-lg focus:scale-[1.01] transition-all outline-none border-2 border-transparent text-gray-900 dark:text-slate-100 placeholder:text-gray-400 dark:placeholder:text-slate-500 resize-none"
|
||||
@focus="$event.target.style.borderColor = '#ff1493'"
|
||||
@blur="$event.target.style.borderColor = 'transparent'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 预览提示 -->
|
||||
<div class="bg-gradient-to-r from-theme-primary/5 to-theme-accent/5 dark:from-theme-primary/10 dark:to-theme-accent/10 border border-theme-primary/30 dark:border-theme-primary/50 rounded-xl p-4">
|
||||
<!-- 提示信息 -->
|
||||
<div class="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800/50 rounded-xl p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<i class="fas fa-lightbulb" style="color: var(--theme-primary)" />
|
||||
<div class="flex-1 text-sm text-gray-700 dark:text-slate-300">
|
||||
<p class="font-semibold mb-1">实时预览</p>
|
||||
<p>选择主题后,界面会立即更新颜色。点击"保存"按钮以保留您的选择。</p>
|
||||
<i class="fas fa-info-circle text-blue-500 dark:text-blue-400 text-lg mt-0.5" />
|
||||
<div class="flex-1 text-sm text-blue-700 dark:text-blue-300">
|
||||
<p class="font-semibold mb-1">使用说明</p>
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
<li>支持标准 CSS 语法</li>
|
||||
<li>使用 <code class="px-1 py-0.5 bg-blue-100 dark:bg-blue-900/50 rounded">.dark</code> 选择器定义暗色模式样式</li>
|
||||
<li>可以覆盖现有样式或添加新样式</li>
|
||||
<li>修改后点击"保存"即可应用</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +94,7 @@
|
||||
<!-- API 设置 -->
|
||||
<div class="setting-section">
|
||||
<h3 class="text-base sm:text-lg font-semibold text-gray-800 dark:text-slate-100 mb-3 flex items-center gap-2">
|
||||
<i class="fas fa-server" style="color: var(--theme-primary)" />
|
||||
<i class="fas fa-server text-[#ff1493] dark:text-[#ff69b4]" />
|
||||
<span>API 设置</span>
|
||||
</h3>
|
||||
|
||||
@@ -131,8 +111,7 @@
|
||||
type="url"
|
||||
placeholder="https://cfapi.searchgal.homes"
|
||||
class="w-full pl-10 sm:pl-12 pr-4 py-3 sm:py-4 text-sm sm:text-base rounded-xl bg-white dark:bg-slate-700/50 backdrop-blur-md shadow-md focus:shadow-lg focus:scale-[1.01] transition-all outline-none border-2 border-transparent text-gray-900 dark:text-slate-100 placeholder:text-gray-400 dark:placeholder:text-slate-400"
|
||||
style="border-color: transparent"
|
||||
@focus="$event.target.style.borderColor = 'var(--theme-primary)'"
|
||||
@focus="$event.target.style.borderColor = '#ff1493'"
|
||||
@blur="$event.target.style.borderColor = 'transparent'"
|
||||
/>
|
||||
</div>
|
||||
@@ -166,10 +145,7 @@
|
||||
重置
|
||||
</button>
|
||||
<button
|
||||
class="px-6 py-2 rounded-xl text-white font-semibold shadow-lg hover:shadow-xl hover:scale-105 transition-all"
|
||||
:style="{
|
||||
background: `linear-gradient(to right, var(--theme-primary), var(--theme-primary-dark))`
|
||||
}"
|
||||
class="px-6 py-2 rounded-xl text-white font-semibold shadow-lg hover:shadow-xl hover:scale-105 transition-all bg-gradient-to-r from-[#ff1493] to-[#d946ef]"
|
||||
@click="save"
|
||||
>
|
||||
<i class="fas fa-check mr-2" />
|
||||
@@ -184,71 +160,57 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { THEME_PRESETS, DEFAULT_THEME, type ThemePresetKey } from '@/types/theme'
|
||||
import { getCurrentTheme, applyThemeColors, saveTheme } from '@/utils/themeColors'
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean
|
||||
customApi: string
|
||||
customCSS: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
save: [customApi: string, theme: ThemePresetKey]
|
||||
save: [customApi: string, customCSS: string]
|
||||
}>()
|
||||
|
||||
const localCustomApi = ref(props.customApi)
|
||||
const localTheme = ref<ThemePresetKey>(getCurrentTheme())
|
||||
const originalTheme = ref<ThemePresetKey>(getCurrentTheme())
|
||||
const localCustomCSS = ref(props.customCSS)
|
||||
const originalCustomCSS = ref(props.customCSS)
|
||||
|
||||
// 监听外部变化
|
||||
watch(() => props.customApi, (newValue) => {
|
||||
localCustomApi.value = newValue
|
||||
})
|
||||
|
||||
watch(() => props.customCSS, (newValue) => {
|
||||
localCustomCSS.value = newValue
|
||||
})
|
||||
|
||||
// 监听打开状态,同步数据
|
||||
watch(() => props.isOpen, (isOpen) => {
|
||||
if (isOpen) {
|
||||
localCustomApi.value = props.customApi
|
||||
localTheme.value = getCurrentTheme()
|
||||
originalTheme.value = getCurrentTheme()
|
||||
localCustomCSS.value = props.customCSS
|
||||
originalCustomCSS.value = props.customCSS
|
||||
}
|
||||
})
|
||||
|
||||
function selectTheme(themeKey: ThemePresetKey) {
|
||||
localTheme.value = themeKey
|
||||
// 实时预览
|
||||
applyThemeColors(themeKey)
|
||||
}
|
||||
|
||||
function close() {
|
||||
// 如果取消,恢复原来的主题
|
||||
if (localTheme.value !== originalTheme.value) {
|
||||
applyThemeColors(originalTheme.value)
|
||||
}
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function save() {
|
||||
// 保存主题到 localStorage
|
||||
saveTheme(localTheme.value)
|
||||
// 更新原始主题,避免关闭时恢复
|
||||
originalTheme.value = localTheme.value
|
||||
// 发出保存事件
|
||||
emit('save', localCustomApi.value, localTheme.value)
|
||||
// 关闭模态框(不会恢复主题,因为 originalTheme 已更新)
|
||||
emit('save', localCustomApi.value, localCustomCSS.value)
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function reset() {
|
||||
localCustomApi.value = ''
|
||||
localTheme.value = DEFAULT_THEME
|
||||
applyThemeColors(DEFAULT_THEME)
|
||||
localCustomCSS.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 自定义滚动条 - 使用主题色 */
|
||||
/* 自定义滚动条 */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
@@ -259,13 +221,13 @@ function reset() {
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg, var(--theme-primary), var(--theme-accent));
|
||||
background: linear-gradient(180deg, #ff1493, #d946ef);
|
||||
border-radius: 10px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg, var(--theme-primary-dark), var(--theme-accent-dark));
|
||||
background: linear-gradient(180deg, #c71585, #c026d3);
|
||||
}
|
||||
|
||||
/* 暗色模式滚动条 */
|
||||
@@ -274,11 +236,11 @@ function reset() {
|
||||
}
|
||||
|
||||
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg, var(--theme-accent), var(--theme-accent-dark));
|
||||
background: linear-gradient(180deg, #ff69b4, #e879f9);
|
||||
}
|
||||
|
||||
.dark .custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg, var(--theme-accent-dark), var(--theme-accent));
|
||||
background: linear-gradient(180deg, #ff1493, #d946ef);
|
||||
}
|
||||
|
||||
/* 设置区块 */
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<!-- GitHub 按钮 -->
|
||||
<a
|
||||
href="https://github.com/Moe-Sakura/SearchGal"
|
||||
href="https://github.com/Moe-Sakura"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="访问 GitHub 仓库"
|
||||
@@ -41,73 +41,13 @@
|
||||
>
|
||||
<i class="fas fa-cog text-lg sm:text-xl" />
|
||||
</button>
|
||||
|
||||
<!-- 主题切换按钮 -->
|
||||
<button
|
||||
:aria-label="`切换主题: ${getThemeLabel(themeMode)}`"
|
||||
class="toolbar-button theme-button"
|
||||
@click="cycleTheme"
|
||||
>
|
||||
<Transition
|
||||
mode="out-in"
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 scale-50 rotate-90"
|
||||
enter-to-class="opacity-100 scale-100 rotate-0"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 scale-100 rotate-0"
|
||||
leave-to-class="opacity-0 scale-50 -rotate-90"
|
||||
>
|
||||
<i
|
||||
v-if="themeMode === 'light'"
|
||||
key="light"
|
||||
class="fas fa-sun text-yellow-500 text-lg sm:text-xl"
|
||||
/>
|
||||
<i
|
||||
v-else-if="themeMode === 'dark'"
|
||||
key="dark"
|
||||
class="fas fa-moon text-indigo-500 text-lg sm:text-xl"
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
key="auto"
|
||||
class="fas fa-circle-half-stroke text-gray-600 dark:text-gray-400 text-lg sm:text-xl"
|
||||
/>
|
||||
</Transition>
|
||||
</button>
|
||||
|
||||
<!-- 主题提示 -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 translate-x-2"
|
||||
enter-to-class="opacity-100 translate-x-0"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-x-0"
|
||||
leave-to-class="opacity-0 translate-x-2"
|
||||
>
|
||||
<div
|
||||
v-if="showThemeTip"
|
||||
class="absolute top-1/2 -translate-y-1/2 right-full mr-3 px-3 py-1.5 rounded-lg bg-white/75 dark:bg-gray-800/75 backdrop-blur-xl shadow-lg border border-gray-200 dark:border-gray-700 whitespace-nowrap"
|
||||
>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ getThemeLabel(themeMode) }}
|
||||
</span>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useSearchStore } from '@/stores/search'
|
||||
import { generateShareURL } from '@/utils/urlParams'
|
||||
import {
|
||||
type ThemeMode,
|
||||
loadThemePreference,
|
||||
saveThemePreference,
|
||||
getEffectiveTheme,
|
||||
applyTheme,
|
||||
watchSystemTheme,
|
||||
} from '@/utils/theme'
|
||||
|
||||
const searchStore = useSearchStore()
|
||||
|
||||
@@ -124,47 +64,11 @@ const emit = defineEmits<{
|
||||
// 状态
|
||||
const showSaveTip = ref(false)
|
||||
const showCopiedTip = ref(false)
|
||||
const showThemeTip = ref(false)
|
||||
const themeMode = ref<ThemeMode>('auto')
|
||||
|
||||
let systemThemeCleanup: (() => void) | null = null
|
||||
let tipTimeout: number | null = null
|
||||
|
||||
// 计算属性
|
||||
const hasBackgroundImage = computed(() => !!props.currentBackgroundUrl)
|
||||
const hasSearchResults = computed(() => searchStore.hasResults)
|
||||
|
||||
// 主题相关函数
|
||||
function getThemeLabel(mode: ThemeMode): string {
|
||||
const labels = {
|
||||
light: '白天模式',
|
||||
dark: '黑夜模式',
|
||||
auto: '跟随系统',
|
||||
}
|
||||
return labels[mode]
|
||||
}
|
||||
|
||||
function cycleTheme() {
|
||||
const modes: ThemeMode[] = ['light', 'dark', 'auto']
|
||||
const currentIndex = modes.indexOf(themeMode.value)
|
||||
const nextIndex = (currentIndex + 1) % modes.length
|
||||
themeMode.value = modes[nextIndex]
|
||||
|
||||
showThemeTip.value = true
|
||||
if (tipTimeout) {
|
||||
clearTimeout(tipTimeout)
|
||||
}
|
||||
tipTimeout = window.setTimeout(() => {
|
||||
showThemeTip.value = false
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
function updateTheme() {
|
||||
const effectiveTheme = getEffectiveTheme(themeMode.value)
|
||||
applyTheme(effectiveTheme)
|
||||
saveThemePreference(themeMode.value)
|
||||
}
|
||||
|
||||
// 分享搜索
|
||||
async function shareSearch() {
|
||||
const shareURL = generateShareURL({
|
||||
@@ -281,31 +185,6 @@ async function saveBackgroundImage() {
|
||||
// 静默处理
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
themeMode.value = loadThemePreference()
|
||||
updateTheme()
|
||||
|
||||
systemThemeCleanup = watchSystemTheme(() => {
|
||||
if (themeMode.value === 'auto') {
|
||||
updateTheme()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (systemThemeCleanup) {
|
||||
systemThemeCleanup()
|
||||
}
|
||||
if (tipTimeout) {
|
||||
clearTimeout(tipTimeout)
|
||||
}
|
||||
})
|
||||
|
||||
watch(themeMode, () => {
|
||||
updateTheme()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -5,9 +5,6 @@ import App from './App.vue'
|
||||
// 全局基础样式(Tailwind CSS @layer base)
|
||||
import './styles/base.css'
|
||||
|
||||
// 主题色全局样式(CSS 变量)
|
||||
import './styles/theme.css'
|
||||
|
||||
// Font Awesome - 高优先级
|
||||
import '@fortawesome/fontawesome-free/css/all.min.css'
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
/* 背景色 - 白天模式(粉色调) */
|
||||
background: rgb(255, 251, 254);
|
||||
/* 背景色 - 白天模式(艳粉调) */
|
||||
background: rgb(255, 245, 250);
|
||||
|
||||
/* 文字颜色 - 白天模式 */
|
||||
color: rgb(29, 27, 30);
|
||||
@@ -38,7 +38,7 @@
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* 暗色模式 - 移除背景色,不遮挡随机图 */
|
||||
/* 暗色模式 - 透明背景,不遮挡随机图 */
|
||||
.dark body {
|
||||
background: transparent;
|
||||
color: rgb(226, 232, 240);
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
/**
|
||||
* 主题色全局样式
|
||||
* 使用 CSS 变量实现动态主题切换
|
||||
*/
|
||||
|
||||
/* 主题色工具类 - 使用 CSS 变量 */
|
||||
.text-theme-primary {
|
||||
color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.text-theme-primary-dark {
|
||||
color: var(--theme-primary-dark);
|
||||
}
|
||||
|
||||
.text-theme-accent {
|
||||
color: var(--theme-accent);
|
||||
}
|
||||
|
||||
.text-theme-accent-dark {
|
||||
color: var(--theme-accent-dark);
|
||||
}
|
||||
|
||||
.bg-theme-primary {
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.bg-theme-primary-dark {
|
||||
background-color: var(--theme-primary-dark);
|
||||
}
|
||||
|
||||
.bg-theme-accent {
|
||||
background-color: var(--theme-accent);
|
||||
}
|
||||
|
||||
.bg-theme-accent-dark {
|
||||
background-color: var(--theme-accent-dark);
|
||||
}
|
||||
|
||||
.border-theme-primary {
|
||||
border-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.border-theme-accent {
|
||||
border-color: var(--theme-accent);
|
||||
}
|
||||
|
||||
/* 渐变背景 */
|
||||
.bg-gradient-theme {
|
||||
background: linear-gradient(to right, var(--theme-primary), var(--theme-accent));
|
||||
}
|
||||
|
||||
.bg-gradient-theme-vertical {
|
||||
background: linear-gradient(to bottom, var(--theme-primary), var(--theme-accent));
|
||||
}
|
||||
|
||||
/* Hover 状态 */
|
||||
.hover\:text-theme-primary:hover {
|
||||
color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.hover\:text-theme-primary-dark:hover {
|
||||
color: var(--theme-primary-dark);
|
||||
}
|
||||
|
||||
.hover\:bg-theme-primary:hover {
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.hover\:bg-theme-primary-dark:hover {
|
||||
background-color: var(--theme-primary-dark);
|
||||
}
|
||||
|
||||
.hover\:border-theme-primary:hover {
|
||||
border-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
/* Focus 状态 */
|
||||
.focus\:border-theme-primary:focus {
|
||||
border-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.focus\:ring-theme-primary:focus {
|
||||
--tw-ring-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
/* 带透明度的主题色 */
|
||||
.bg-theme-primary\/80 {
|
||||
background-color: color-mix(in srgb, var(--theme-primary) 80%, transparent);
|
||||
}
|
||||
|
||||
.bg-theme-primary\/70 {
|
||||
background-color: color-mix(in srgb, var(--theme-primary) 70%, transparent);
|
||||
}
|
||||
|
||||
.bg-theme-primary\/60 {
|
||||
background-color: color-mix(in srgb, var(--theme-primary) 60%, transparent);
|
||||
}
|
||||
|
||||
.bg-theme-primary\/10 {
|
||||
background-color: color-mix(in srgb, var(--theme-primary) 10%, transparent);
|
||||
}
|
||||
|
||||
.bg-theme-primary\/5 {
|
||||
background-color: color-mix(in srgb, var(--theme-primary) 5%, transparent);
|
||||
}
|
||||
|
||||
.bg-theme-accent\/80 {
|
||||
background-color: color-mix(in srgb, var(--theme-accent) 80%, transparent);
|
||||
}
|
||||
|
||||
.bg-theme-accent\/70 {
|
||||
background-color: color-mix(in srgb, var(--theme-accent) 70%, transparent);
|
||||
}
|
||||
|
||||
.bg-theme-accent\/30 {
|
||||
background-color: color-mix(in srgb, var(--theme-accent) 30%, transparent);
|
||||
}
|
||||
|
||||
.bg-theme-accent\/10 {
|
||||
background-color: color-mix(in srgb, var(--theme-accent) 10%, transparent);
|
||||
}
|
||||
|
||||
.text-theme-primary\/80 {
|
||||
color: color-mix(in srgb, var(--theme-primary) 80%, transparent);
|
||||
}
|
||||
|
||||
.text-theme-accent\/70 {
|
||||
color: color-mix(in srgb, var(--theme-accent) 70%, transparent);
|
||||
}
|
||||
|
||||
.border-theme-primary\/60 {
|
||||
border-color: color-mix(in srgb, var(--theme-primary) 60%, transparent);
|
||||
}
|
||||
|
||||
.border-theme-primary\/30 {
|
||||
border-color: color-mix(in srgb, var(--theme-primary) 30%, transparent);
|
||||
}
|
||||
|
||||
.border-theme-primary\/20 {
|
||||
border-color: color-mix(in srgb, var(--theme-primary) 20%, transparent);
|
||||
}
|
||||
|
||||
.border-theme-primary\/10 {
|
||||
border-color: color-mix(in srgb, var(--theme-primary) 10%, transparent);
|
||||
}
|
||||
|
||||
/* Hover 状态带透明度 */
|
||||
.hover\:bg-theme-primary\/10:hover {
|
||||
background-color: color-mix(in srgb, var(--theme-primary) 10%, transparent);
|
||||
}
|
||||
|
||||
.hover\:border-theme-primary\/30:hover {
|
||||
border-color: color-mix(in srgb, var(--theme-primary) 30%, transparent);
|
||||
}
|
||||
|
||||
.group:hover .group-hover\:text-theme-primary {
|
||||
color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.group:hover .group-hover\:text-theme-primary\/40 {
|
||||
color: color-mix(in srgb, var(--theme-primary) 40%, transparent);
|
||||
}
|
||||
|
||||
/* 渐变背景(带透明度) */
|
||||
.from-theme-primary\/70 {
|
||||
--tw-gradient-from: color-mix(in srgb, var(--theme-primary) 70%, transparent);
|
||||
--tw-gradient-to: rgb(255 255 255 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
|
||||
.to-theme-accent\/70 {
|
||||
--tw-gradient-to: color-mix(in srgb, var(--theme-accent) 70%, transparent);
|
||||
}
|
||||
|
||||
.from-theme-primary\/80 {
|
||||
--tw-gradient-from: color-mix(in srgb, var(--theme-primary) 80%, transparent);
|
||||
--tw-gradient-to: rgb(255 255 255 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
|
||||
.to-theme-accent\/80 {
|
||||
--tw-gradient-to: color-mix(in srgb, var(--theme-accent) 80%, transparent);
|
||||
}
|
||||
|
||||
.from-theme-primary\/90 {
|
||||
--tw-gradient-from: color-mix(in srgb, var(--theme-primary) 90%, transparent);
|
||||
--tw-gradient-to: rgb(255 255 255 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
|
||||
.to-theme-accent\/90 {
|
||||
--tw-gradient-to: color-mix(in srgb, var(--theme-accent) 90%, transparent);
|
||||
}
|
||||
|
||||
.from-theme-primary {
|
||||
--tw-gradient-from: var(--theme-primary);
|
||||
--tw-gradient-to: rgb(255 255 255 / 0);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
||||
}
|
||||
|
||||
.to-theme-accent {
|
||||
--tw-gradient-to: var(--theme-accent);
|
||||
}
|
||||
|
||||
.to-theme-primary-dark {
|
||||
--tw-gradient-to: var(--theme-primary-dark);
|
||||
}
|
||||
|
||||
/* 暗色模式下的主题色 */
|
||||
.dark .dark\:text-theme-accent {
|
||||
color: var(--theme-accent);
|
||||
}
|
||||
|
||||
.dark .dark\:text-theme-primary {
|
||||
color: var(--theme-primary);
|
||||
}
|
||||
|
||||
.dark .dark\:from-theme-accent\/70 {
|
||||
--tw-gradient-from: color-mix(in srgb, var(--theme-accent) 70%, transparent);
|
||||
}
|
||||
|
||||
.dark .dark\:to-theme-accent-dark\/70 {
|
||||
--tw-gradient-to: color-mix(in srgb, var(--theme-accent-dark) 70%, transparent);
|
||||
}
|
||||
|
||||
.dark .dark\:from-theme-accent\/80 {
|
||||
--tw-gradient-from: color-mix(in srgb, var(--theme-accent) 80%, transparent);
|
||||
}
|
||||
|
||||
.dark .dark\:to-theme-accent-dark\/80 {
|
||||
--tw-gradient-to: color-mix(in srgb, var(--theme-accent-dark) 80%, transparent);
|
||||
}
|
||||
|
||||
.dark .dark\:focus\:border-theme-accent:focus {
|
||||
border-color: var(--theme-accent);
|
||||
}
|
||||
|
||||
.dark .dark\:focus\:border-theme-accent\/60:focus {
|
||||
border-color: color-mix(in srgb, var(--theme-accent) 60%, transparent);
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
// 主题配色方案类型定义
|
||||
export interface ThemeColors {
|
||||
primary: string
|
||||
primaryDark: string
|
||||
accent: string
|
||||
accentDark: string
|
||||
}
|
||||
|
||||
// 预设主题配色
|
||||
export const THEME_PRESETS = {
|
||||
rose: {
|
||||
name: '玫瑰粉',
|
||||
colors: {
|
||||
primary: '#ec4899', // pink-500
|
||||
primaryDark: '#db2777', // pink-600
|
||||
accent: '#8b5cf6', // purple-500
|
||||
accentDark: '#7c3aed', // purple-600
|
||||
},
|
||||
},
|
||||
sky: {
|
||||
name: '天空蓝',
|
||||
colors: {
|
||||
primary: '#0ea5e9', // sky-500
|
||||
primaryDark: '#0284c7', // sky-600
|
||||
accent: '#06b6d4', // cyan-500
|
||||
accentDark: '#0891b2', // cyan-600
|
||||
},
|
||||
},
|
||||
emerald: {
|
||||
name: '翡翠绿',
|
||||
colors: {
|
||||
primary: '#10b981', // emerald-500
|
||||
primaryDark: '#059669', // emerald-600
|
||||
accent: '#14b8a6', // teal-500
|
||||
accentDark: '#0d9488', // teal-600
|
||||
},
|
||||
},
|
||||
amber: {
|
||||
name: '琥珀橙',
|
||||
colors: {
|
||||
primary: '#f59e0b', // amber-500
|
||||
primaryDark: '#d97706', // amber-600
|
||||
accent: '#f97316', // orange-500
|
||||
accentDark: '#ea580c', // orange-600
|
||||
},
|
||||
},
|
||||
violet: {
|
||||
name: '紫罗兰',
|
||||
colors: {
|
||||
primary: '#8b5cf6', // violet-500
|
||||
primaryDark: '#7c3aed', // violet-600
|
||||
accent: '#a855f7', // purple-500
|
||||
accentDark: '#9333ea', // purple-600
|
||||
},
|
||||
},
|
||||
red: {
|
||||
name: '热情红',
|
||||
colors: {
|
||||
primary: '#ef4444', // red-500
|
||||
primaryDark: '#dc2626', // red-600
|
||||
accent: '#f43f5e', // rose-500
|
||||
accentDark: '#e11d48', // rose-600
|
||||
},
|
||||
},
|
||||
indigo: {
|
||||
name: '靛青蓝',
|
||||
colors: {
|
||||
primary: '#6366f1', // indigo-500
|
||||
primaryDark: '#4f46e5', // indigo-600
|
||||
accent: '#8b5cf6', // violet-500
|
||||
accentDark: '#7c3aed', // violet-600
|
||||
},
|
||||
},
|
||||
fuchsia: {
|
||||
name: '洋红紫',
|
||||
colors: {
|
||||
primary: '#d946ef', // fuchsia-500
|
||||
primaryDark: '#c026d3', // fuchsia-600
|
||||
accent: '#ec4899', // pink-500
|
||||
accentDark: '#db2777', // pink-600
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
export type ThemePresetKey = keyof typeof THEME_PRESETS
|
||||
|
||||
// 默认主题
|
||||
export const DEFAULT_THEME: ThemePresetKey = 'rose'
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/**
|
||||
* 主题管理工具
|
||||
* 支持白天/黑夜模式和跟随系统
|
||||
* 自动跟随系统主题
|
||||
*/
|
||||
|
||||
export type ThemeMode = 'light' | 'dark' | 'auto'
|
||||
|
||||
const THEME_STORAGE_KEY = 'searchgal_theme'
|
||||
const CUSTOM_CSS_STORAGE_KEY = 'searchgal_custom_css'
|
||||
|
||||
/**
|
||||
* 获取系统主题偏好
|
||||
@@ -17,16 +15,6 @@ export function getSystemTheme(): 'light' | 'dark' {
|
||||
return 'light'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前应该应用的主题(考虑 auto 模式)
|
||||
*/
|
||||
export function getEffectiveTheme(mode: ThemeMode): 'light' | 'dark' {
|
||||
if (mode === 'auto') {
|
||||
return getSystemTheme()
|
||||
}
|
||||
return mode
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用主题到 DOM
|
||||
*/
|
||||
@@ -40,32 +28,6 @@ export function applyTheme(theme: 'light' | 'dark'): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存主题偏好
|
||||
*/
|
||||
export function saveThemePreference(mode: ThemeMode): void {
|
||||
try {
|
||||
localStorage.setItem(THEME_STORAGE_KEY, mode)
|
||||
} catch (error) {
|
||||
// 静默处理
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载主题偏好
|
||||
*/
|
||||
export function loadThemePreference(): ThemeMode {
|
||||
try {
|
||||
const saved = localStorage.getItem(THEME_STORAGE_KEY)
|
||||
if (saved === 'light' || saved === 'dark' || saved === 'auto') {
|
||||
return saved
|
||||
}
|
||||
} catch (error) {
|
||||
// 静默处理
|
||||
}
|
||||
return 'auto' // 默认跟随系统
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听系统主题变化
|
||||
*/
|
||||
@@ -87,3 +49,44 @@ export function watchSystemTheme(callback: (theme: 'light' | 'dark') => void): (
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存自定义CSS
|
||||
*/
|
||||
export function saveCustomCSS(css: string): void {
|
||||
try {
|
||||
localStorage.setItem(CUSTOM_CSS_STORAGE_KEY, css)
|
||||
} catch (error) {
|
||||
// 静默处理
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载自定义CSS
|
||||
*/
|
||||
export function loadCustomCSS(): string {
|
||||
try {
|
||||
return localStorage.getItem(CUSTOM_CSS_STORAGE_KEY) || ''
|
||||
} catch (error) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用自定义CSS到页面
|
||||
*/
|
||||
export function applyCustomCSS(css: string): void {
|
||||
// 移除旧的自定义样式
|
||||
const oldStyle = document.getElementById('custom-user-styles')
|
||||
if (oldStyle) {
|
||||
oldStyle.remove()
|
||||
}
|
||||
|
||||
// 如果有新的CSS,添加到页面
|
||||
if (css.trim()) {
|
||||
const style = document.createElement('style')
|
||||
style.id = 'custom-user-styles'
|
||||
style.textContent = css
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { THEME_PRESETS, DEFAULT_THEME, type ThemePresetKey } from '@/types/theme'
|
||||
|
||||
const THEME_STORAGE_KEY = 'searchgal-theme-preset'
|
||||
|
||||
/**
|
||||
* 应用主题颜色到 CSS 变量
|
||||
*/
|
||||
export function applyThemeColors(themeKey: ThemePresetKey) {
|
||||
const theme = THEME_PRESETS[themeKey]
|
||||
if (!theme) {
|
||||
return
|
||||
}
|
||||
|
||||
const root = document.documentElement
|
||||
const { colors } = theme
|
||||
|
||||
// 设置 CSS 变量
|
||||
root.style.setProperty('--theme-primary', colors.primary)
|
||||
root.style.setProperty('--theme-primary-dark', colors.primaryDark)
|
||||
root.style.setProperty('--theme-accent', colors.accent)
|
||||
root.style.setProperty('--theme-accent-dark', colors.accentDark)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前主题
|
||||
*/
|
||||
export function getCurrentTheme(): ThemePresetKey {
|
||||
try {
|
||||
const saved = localStorage.getItem(THEME_STORAGE_KEY)
|
||||
if (saved && saved in THEME_PRESETS) {
|
||||
return saved as ThemePresetKey
|
||||
}
|
||||
} catch {
|
||||
// 忽略错误
|
||||
}
|
||||
return DEFAULT_THEME
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存主题设置
|
||||
*/
|
||||
export function saveTheme(themeKey: ThemePresetKey) {
|
||||
try {
|
||||
localStorage.setItem(THEME_STORAGE_KEY, themeKey)
|
||||
applyThemeColors(themeKey)
|
||||
} catch {
|
||||
// 忽略错误
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化主题
|
||||
*/
|
||||
export function initTheme() {
|
||||
const currentTheme = getCurrentTheme()
|
||||
applyThemeColors(currentTheme)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题颜色的 RGB 值(用于 Tailwind 的 opacity 修饰符)
|
||||
*/
|
||||
export function hexToRgb(hex: string): string {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
if (!result) {
|
||||
return '0, 0, 0'
|
||||
}
|
||||
const r = parseInt(result[1], 16)
|
||||
const g = parseInt(result[2], 16)
|
||||
const b = parseInt(result[3], 16)
|
||||
return `${r}, ${g}, ${b}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user