feat: 增强主题支持与自定义样式功能

* 移除旧的主题颜色变量,改为支持自定义 CSS 样式,提升用户界面的灵活性。
* 在 `App.vue` 中实现自定义 CSS 的加载与应用,确保用户设置的实时更新。
* 更新 `SettingsModal.vue`,允许用户输入自定义 CSS,增强个性化体验。
* 优化多个组件的样式,确保在不同主题下的视觉一致性。
* 删除不再使用的主题相关文件,简化代码结构。
This commit is contained in:
AdingApkgg
2025-11-27 04:24:41 +08:00
parent 92246b2139
commit fcd823ade3
13 changed files with 279 additions and 697 deletions

127
THEME_SYSTEM.md Normal file
View 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. 测试两种模式下的显示效果,确保可读性

View File

@@ -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;

View File

@@ -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>

View File

@@ -91,6 +91,7 @@ function initArtalk() {
pageTitle: 'Galgame 聚合搜索',
server: 'https://artalk.saop.cc',
site: 'Galgame 聚合搜索',
darkMode: "auto",
} as any)
} catch (error) {
// 静默处理错误

View File

@@ -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>

View File

@@ -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);
}
/* 设置区块 */

View File

@@ -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>

View File

@@ -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'

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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'

View File

@@ -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)
}
}

View File

@@ -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}`
}