Skip to content

Commit d5ba218

Browse files
committed
feat: 添加主题根据系统切换功能
feat: #387
1 parent e489ab4 commit d5ba218

File tree

7 files changed

+122
-9
lines changed

7 files changed

+122
-9
lines changed

src/i18n/lang/en-US/settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export default {
1515
basic: {
1616
themeMode: 'Theme Mode',
1717
themeModeDesc: 'Switch between light/dark theme',
18+
autoTheme: 'Follow System',
19+
manualTheme: 'Manual Switch',
1820
language: 'Language Settings',
1921
languageDesc: 'Change display language',
2022
font: 'Font Settings',

src/i18n/lang/zh-CN/settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export default {
1515
basic: {
1616
themeMode: '主题模式',
1717
themeModeDesc: '切换日间/夜间主题',
18+
autoTheme: '跟随系统',
19+
manualTheme: '手动切换',
1820
language: '语言设置',
1921
languageDesc: '切换显示语言',
2022
font: '字体设置',

src/i18n/lang/zh-Hant/settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export default {
1515
basic: {
1616
themeMode: '主題模式',
1717
themeModeDesc: '切換日間/夜間主題',
18+
autoTheme: '跟隨系統',
19+
manualTheme: '手動切換',
1820
language: '語言設定',
1921
languageDesc: '切換顯示語言',
2022
font: '字體設定',

src/main/set.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@
2525
"enableMusicUnblock": true,
2626
"enabledMusicSources": ["migu", "kugou", "pyncmd", "bilibili"],
2727
"showTopAction": false,
28-
"contentZoomFactor": 1
28+
"contentZoomFactor": 1,
29+
"autoTheme": false,
30+
"manualTheme": "light"
2931
}

src/renderer/store/modules/settings.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ref } from 'vue';
44

55
import setDataDefault from '@/../main/set.json';
66
import { isElectron } from '@/utils';
7-
import { applyTheme, getCurrentTheme, ThemeType } from '@/utils/theme';
7+
import { applyTheme, getCurrentTheme, getSystemTheme, watchSystemTheme, ThemeType } from '@/utils/theme';
88

99
export const useSettingsStore = defineStore('settings', () => {
1010
const theme = ref<ThemeType>(getCurrentTheme());
@@ -18,6 +18,9 @@ export const useSettingsStore = defineStore('settings', () => {
1818
]);
1919
const showDownloadDrawer = ref(false);
2020

21+
// 系统主题监听器清理函数
22+
let systemThemeCleanup: (() => void) | null = null;
23+
2124
// 先声明 setData ref 但不初始化
2225
const setData = ref<any>({});
2326

@@ -56,8 +59,57 @@ export const useSettingsStore = defineStore('settings', () => {
5659
setData.value = getInitialSettings();
5760

5861
const toggleTheme = () => {
59-
theme.value = theme.value === 'dark' ? 'light' : 'dark';
60-
applyTheme(theme.value);
62+
if (setData.value.autoTheme) {
63+
// 如果是自动模式,切换到手动模式并设置相反的主题
64+
const newTheme = theme.value === 'dark' ? 'light' : 'dark';
65+
setSetData({
66+
autoTheme: false,
67+
manualTheme: newTheme
68+
});
69+
theme.value = newTheme;
70+
applyTheme(newTheme);
71+
// 停止监听系统主题
72+
if (systemThemeCleanup) {
73+
systemThemeCleanup();
74+
systemThemeCleanup = null;
75+
}
76+
} else {
77+
// 手动模式下正常切换
78+
const newTheme = theme.value === 'dark' ? 'light' : 'dark';
79+
theme.value = newTheme;
80+
setSetData({ manualTheme: newTheme });
81+
applyTheme(newTheme);
82+
}
83+
};
84+
85+
const setAutoTheme = (auto: boolean) => {
86+
setSetData({ autoTheme: auto });
87+
88+
if (auto) {
89+
// 启用自动模式
90+
const systemTheme = getSystemTheme();
91+
theme.value = systemTheme;
92+
applyTheme(systemTheme);
93+
94+
// 开始监听系统主题变化
95+
systemThemeCleanup = watchSystemTheme((newTheme) => {
96+
if (setData.value.autoTheme) {
97+
theme.value = newTheme;
98+
applyTheme(newTheme);
99+
}
100+
});
101+
} else {
102+
// 切换到手动模式
103+
const manualTheme = setData.value.manualTheme || 'light';
104+
theme.value = manualTheme;
105+
applyTheme(manualTheme);
106+
107+
// 停止监听系统主题
108+
if (systemThemeCleanup) {
109+
systemThemeCleanup();
110+
systemThemeCleanup = null;
111+
}
112+
}
61113
};
62114

63115
const setMiniMode = (value: boolean) => {
@@ -106,7 +158,14 @@ export const useSettingsStore = defineStore('settings', () => {
106158
};
107159

108160
const initializeTheme = () => {
109-
applyTheme(theme.value);
161+
// 根据设置初始化主题
162+
if (setData.value.autoTheme) {
163+
setAutoTheme(true);
164+
} else {
165+
const manualTheme = setData.value.manualTheme || getCurrentTheme();
166+
theme.value = manualTheme;
167+
applyTheme(manualTheme);
168+
}
110169
};
111170

112171
const initializeSystemFonts = async () => {
@@ -133,6 +192,7 @@ export const useSettingsStore = defineStore('settings', () => {
133192
showDownloadDrawer,
134193
setSetData,
135194
toggleTheme,
195+
setAutoTheme,
136196
setMiniMode,
137197
setShowUpdateModal,
138198
setShowArtistDrawer,

src/renderer/utils/theme.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
export type ThemeType = 'dark' | 'light';
22

3+
// 检测系统主题
4+
export const getSystemTheme = (): ThemeType => {
5+
if (typeof window !== 'undefined' && window.matchMedia) {
6+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
7+
}
8+
return 'light';
9+
};
10+
311
// 应用主题
412
export const applyTheme = (theme: ThemeType) => {
513
// 使用 Tailwind 的暗色主题类
@@ -17,3 +25,21 @@ export const applyTheme = (theme: ThemeType) => {
1725
export const getCurrentTheme = (): ThemeType => {
1826
return (localStorage.getItem('theme') as ThemeType) || 'light';
1927
};
28+
29+
// 监听系统主题变化
30+
export const watchSystemTheme = (callback: (theme: ThemeType) => void) => {
31+
if (typeof window !== 'undefined' && window.matchMedia) {
32+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
33+
const handler = (e: MediaQueryListEvent) => {
34+
callback(e.matches ? 'dark' : 'light');
35+
};
36+
37+
mediaQuery.addEventListener('change', handler);
38+
39+
// 返回清理函数
40+
return () => {
41+
mediaQuery.removeEventListener('change', handler);
42+
};
43+
}
44+
return () => {};
45+
};

src/renderer/views/set/index.vue

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,25 @@
2525
<div class="set-item-title">{{ t('settings.basic.themeMode') }}</div>
2626
<div class="set-item-content">{{ t('settings.basic.themeModeDesc') }}</div>
2727
</div>
28-
<n-switch v-model:value="isDarkTheme">
29-
<template #checked><i class="ri-moon-line"></i></template>
30-
<template #unchecked><i class="ri-sun-line"></i></template>
31-
</n-switch>
28+
<div class="flex items-center gap-3">
29+
<div class="flex items-center gap-2">
30+
<n-switch v-model:value="setData.autoTheme" @update:value="handleAutoThemeChange">
31+
<template #checked><i class="ri-smartphone-line"></i></template>
32+
<template #unchecked><i class="ri-settings-line"></i></template>
33+
</n-switch>
34+
<span class="text-sm text-gray-500">
35+
{{ setData.autoTheme ? t('settings.basic.autoTheme') : t('settings.basic.manualTheme') }}
36+
</span>
37+
</div>
38+
<n-switch
39+
v-model:value="isDarkTheme"
40+
:disabled="setData.autoTheme"
41+
:class="{ 'opacity-50': setData.autoTheme }"
42+
>
43+
<template #checked><i class="ri-moon-line"></i></template>
44+
<template #unchecked><i class="ri-sun-line"></i></template>
45+
</n-switch>
46+
</div>
3247
</div>
3348

3449
<!-- 语言设置 -->
@@ -590,6 +605,10 @@ const isDarkTheme = computed({
590605
set: () => settingsStore.toggleTheme()
591606
});
592607
608+
const handleAutoThemeChange = (value: boolean) => {
609+
settingsStore.setAutoTheme(value);
610+
};
611+
593612
const openAuthor = () => {
594613
window.open(setData.value.authorUrl);
595614
};

0 commit comments

Comments
 (0)