Skip to content

Commit d51ee4a

Browse files
author
Taois
committed
feat: 主题
1 parent d00d494 commit d51ee4a

12 files changed

Lines changed: 409 additions & 45 deletions

File tree

dashboard/src/components/CategoryNavigation.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,16 @@ onBeforeUnmount(() => {
269269
border-bottom: none;
270270
}
271271
272+
.category-tabs :deep(.arco-tabs-nav::before),
273+
.category-tabs :deep(.arco-tabs-nav-tab::before),
274+
.category-tabs :deep(.arco-tabs-tab::before) {
275+
display: none !important;
276+
}
277+
278+
.category-tabs :deep(.arco-tabs-content) {
279+
border-top: none !important;
280+
}
281+
272282
.category-tabs :deep(.arco-tabs-nav-tab) {
273283
padding: 0;
274284
margin-right: 8px;

dashboard/src/components/Header.vue

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@
7878
</div>
7979

8080
<div class="header-right">
81+
<a-button class="theme-toggle-btn" shape="circle" @click="toggleTheme" :title="themeToggleTitle">
82+
<template #icon>
83+
<span class="theme-toggle-icon">{{ resolvedTheme === THEME_MODE.DARK ? '浅' : '深' }}</span>
84+
</template>
85+
</a-button>
8186
<a-button shape="circle" @click="minimize" title="退出全屏">
8287
<template #icon>
8388
<icon-shrink />
@@ -125,6 +130,7 @@ import { defineComponent, ref, computed, watch, onBeforeUnmount } from 'vue';
125130
import { useRoute, useRouter } from 'vue-router';
126131
import { Message } from '@arco-design/web-vue';
127132
import SearchSettingsModal from './SearchSettingsModal.vue';
133+
import { getStoredThemeMode, resolveThemeMode, setStoredThemeMode, THEME_MODE } from '@/utils/theme';
128134
129135
export default defineComponent({
130136
components: {
@@ -144,6 +150,13 @@ export default defineComponent({
144150
const searchValue = ref('');
145151
const showSearchSettings = ref(false);
146152
const forceUpdate = ref(0);
153+
const resolvedTheme = ref(resolveThemeMode(getStoredThemeMode()));
154+
155+
const themeToggleTitle = computed(() => resolvedTheme.value === THEME_MODE.DARK ? '切换到浅色模式' : '切换到深色模式');
156+
157+
const updateThemeState = (event) => {
158+
resolvedTheme.value = event?.detail?.resolvedTheme || resolveThemeMode(getStoredThemeMode());
159+
};
147160
148161
const isSearchAggregationPage = computed(() => route.name === 'SearchAggregation');
149162
@@ -191,6 +204,7 @@ export default defineComponent({
191204
};
192205
193206
window.addEventListener('storage', updateSearchAggregationStatus);
207+
window.addEventListener('themeChanged', updateThemeState);
194208
const checkInterval = setInterval(updateSearchAggregationStatus, 1000);
195209
196210
watch(() => route.query.keyword, (keyword) => {
@@ -207,6 +221,7 @@ export default defineComponent({
207221
208222
onBeforeUnmount(() => {
209223
window.removeEventListener('storage', updateSearchAggregationStatus);
224+
window.removeEventListener('themeChanged', updateThemeState);
210225
clearInterval(checkInterval);
211226
});
212227
@@ -217,10 +232,17 @@ export default defineComponent({
217232
showSearchSettings,
218233
isSearchAggregationPage,
219234
hasSearchResults,
235+
resolvedTheme,
236+
themeToggleTitle,
237+
THEME_MODE,
220238
router
221239
};
222240
},
223241
methods: {
242+
toggleTheme() {
243+
const nextMode = this.resolvedTheme === THEME_MODE.DARK ? THEME_MODE.LIGHT : THEME_MODE.DARK;
244+
this.resolvedTheme = setStoredThemeMode(nextMode);
245+
},
224246
goBack() {
225247
Message.info('后退按钮');
226248
},
@@ -457,6 +479,12 @@ export default defineComponent({
457479
color: #fff;
458480
}
459481
482+
.theme-toggle-icon {
483+
font-size: 13px;
484+
line-height: 1;
485+
font-weight: 800;
486+
}
487+
460488
.search-container {
461489
display: flex;
462490
align-items: center;

dashboard/src/components/Layout.vue

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
>
1010
<div class="fixed-header">
1111
<Header v-if="!isMobile" />
12-
<div v-else class="mobile-top-bar">
12+
<div v-else class="mobile-top-bar" :class="{ 'home-theme-visible': showHomeThemeToggle }">
1313
<button v-if="showMobileBack" class="mobile-icon-btn" type="button" title="返回" @click="goBack">
1414
<span class="mobile-back-icon">‹</span>
1515
</button>
@@ -22,6 +22,9 @@
2222
<div v-if="mobileSubtitle" class="mobile-subtitle">{{ mobileSubtitle }}</div>
2323
</div>
2424

25+
<button v-if="showHomeThemeToggle" class="mobile-icon-btn mobile-theme-toggle" type="button" :title="themeToggleTitle" @click="toggleTheme">
26+
<span class="mobile-theme-text">{{ resolvedTheme === THEME_MODE.DARK ? '浅' : '深' }}</span>
27+
</button>
2528
<button class="mobile-search-pill" type="button" title="搜索" @click="goSearch">
2629
<svg class="mobile-action-icon"><use href="#icon-sousuo"></use></svg>
2730
<span>搜索</span>
@@ -183,6 +186,7 @@ import { desktopMenuItems, mobilePrimaryItems, mobileMoreGroups } from '@/config
183186
import logoPc from '@/assets/logo-pc.png';
184187
import logoMobile from '@/assets/logo-mobile.png';
185188
import '@/assets/icon_font/iconfont.js';
189+
import { getStoredThemeMode, resolveThemeMode, setStoredThemeMode, THEME_MODE } from '@/utils/theme';
186190
187191
export default defineComponent({
188192
components: {
@@ -202,6 +206,7 @@ export default defineComponent({
202206
const isImmersivePage = computed(() => Boolean(route.meta?.immersive));
203207
const showMobileBottomNav = computed(() => route.meta?.showBottomNav !== false && !isImmersivePage.value);
204208
const showMobileBack = computed(() => isImmersivePage.value || route.name === 'SearchAggregation');
209+
const showHomeThemeToggle = computed(() => route.name === 'Home');
205210
const mobileTitle = computed(() => route.meta?.title || 'DrPlayer');
206211
const mobileSubtitle = computed(() => {
207212
if (route.name === 'Home') return '影视、直播、阅读与工具';
@@ -214,6 +219,13 @@ export default defineComponent({
214219
const siderCollapsed = ref(JSON.parse(localStorage.getItem('sidebar-collapsed') || 'false'));
215220
const mobileMoreOpen = ref(false);
216221
const isMobile = ref(false);
222+
const resolvedTheme = ref(resolveThemeMode(getStoredThemeMode()));
223+
224+
const themeToggleTitle = computed(() => resolvedTheme.value === THEME_MODE.DARK ? '切换到浅色模式' : '切换到深色模式');
225+
226+
const updateThemeState = (event) => {
227+
resolvedTheme.value = event?.detail?.resolvedTheme || resolveThemeMode(getStoredThemeMode());
228+
};
217229
218230
const menuItems = ref(desktopMenuItems);
219231
const logoSrc = computed(() => isMobile.value ? logoMobile : logoPc);
@@ -242,6 +254,11 @@ export default defineComponent({
242254
mobileMoreOpen.value = !mobileMoreOpen.value;
243255
};
244256
257+
const toggleTheme = () => {
258+
const nextMode = resolvedTheme.value === THEME_MODE.DARK ? THEME_MODE.LIGHT : THEME_MODE.DARK;
259+
resolvedTheme.value = setStoredThemeMode(nextMode);
260+
};
261+
245262
const isRouteActive = (routeName) => route.name === routeName;
246263
247264
const goBack = () => {
@@ -284,11 +301,14 @@ export default defineComponent({
284301
285302
onMounted(() => {
286303
updateIsMobile();
304+
updateThemeState();
287305
window.addEventListener('resize', updateIsMobile);
306+
window.addEventListener('themeChanged', updateThemeState);
288307
});
289308
290309
onBeforeUnmount(() => {
291310
window.removeEventListener('resize', updateIsMobile);
311+
window.removeEventListener('themeChanged', updateThemeState);
292312
});
293313
294314
return {
@@ -299,6 +319,7 @@ export default defineComponent({
299319
isImmersivePage,
300320
showMobileBottomNav,
301321
showMobileBack,
322+
showHomeThemeToggle,
302323
mobileTitle,
303324
mobileSubtitle,
304325
menuItems,
@@ -308,6 +329,10 @@ export default defineComponent({
308329
isMoreRouteActive,
309330
logoSrc,
310331
logoDesc,
332+
resolvedTheme,
333+
themeToggleTitle,
334+
THEME_MODE,
335+
toggleTheme,
311336
onClickMenuItem,
312337
onSiderCollapse,
313338
closeMobileMore,
@@ -538,6 +563,10 @@ export default defineComponent({
538563
background: var(--dp-bg-shell);
539564
}
540565
566+
.mobile-top-bar.home-theme-visible {
567+
grid-template-columns: 40px minmax(0, 1fr) 36px minmax(76px, auto) 40px;
568+
}
569+
541570
.mobile-brand,
542571
.mobile-icon-btn {
543572
width: 36px;
@@ -571,6 +600,17 @@ export default defineComponent({
571600
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
572601
}
573602
603+
.mobile-theme-toggle {
604+
border: 1px solid var(--dp-border-subtle);
605+
color: var(--dp-primary-readable);
606+
font-weight: 800;
607+
}
608+
609+
.mobile-theme-text {
610+
font-size: 13px;
611+
line-height: 1;
612+
}
613+
574614
.mobile-brand-logo {
575615
width: 24px;
576616
height: 24px;

dashboard/src/components/actions/styles.css

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,30 @@
8484
--action-shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.2);
8585
}
8686

87-
/* 基础重置 */
87+
body[arco-theme='dark'] {
88+
--action-primary: var(--color-primary-6);
89+
--action-primary-hover: var(--color-primary-5);
90+
--action-primary-active: var(--color-primary-7);
91+
--action-success: var(--color-success-6);
92+
--action-warning: var(--color-warning-6);
93+
--action-error: var(--color-danger-6);
94+
--action-text: var(--color-text-1);
95+
--action-text-secondary: var(--color-text-2);
96+
--action-text-disabled: var(--color-text-4);
97+
--action-border: var(--color-border-2);
98+
--action-border-hover: var(--color-primary-5);
99+
--action-background: var(--color-bg-2);
100+
--action-background-light: var(--color-fill-2);
101+
--action-background-disabled: var(--color-fill-1);
102+
--action-color-secondary: var(--color-text-2);
103+
--action-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
104+
--action-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.45);
105+
--action-shadow-small: 0 1px 3px rgba(0, 0, 0, 0.28);
106+
--action-shadow-medium: 0 4px 6px rgba(0, 0, 0, 0.32);
107+
--action-shadow-large: 0 10px 25px rgba(0, 0, 0, 0.42);
108+
--action-shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.48);
109+
}
110+
88111
.action-component * {
89112
box-sizing: border-box;
90113
}

dashboard/src/components/players/PlayerHeader.vue

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ onUnmounted(() => {
569569
margin: 0;
570570
font-size: 16px;
571571
font-weight: 600;
572-
color: #2c3e50;
572+
color: var(--dp-text-primary);
573573
flex: 1;
574574
overflow: hidden;
575575
text-overflow: ellipsis;
@@ -587,10 +587,10 @@ onUnmounted(() => {
587587
display: flex;
588588
align-items: center;
589589
gap: 2px;
590-
background: #f8f9fa;
590+
background: var(--dp-bg-surface-muted);
591591
border-radius: 6px;
592592
padding: 2px;
593-
border: 1px solid #e9ecef;
593+
border: 1px solid var(--dp-border-subtle);
594594
}
595595
596596
.compact-btn {
@@ -605,14 +605,14 @@ onUnmounted(() => {
605605
border: none;
606606
font-size: 12px;
607607
font-weight: 500;
608-
color: #495057;
608+
color: var(--dp-text-secondary);
609609
min-height: 28px;
610610
position: relative;
611611
}
612612
613613
.compact-btn:hover {
614-
background: #e9ecef;
615-
color: #212529;
614+
background: var(--dp-bg-hover);
615+
color: var(--dp-text-primary);
616616
transform: translateY(-1px);
617617
}
618618
@@ -627,21 +627,21 @@ onUnmounted(() => {
627627
}
628628
629629
.compact-btn.close-btn {
630-
color: #dc3545;
630+
color: var(--dp-danger-readable);
631631
}
632632
633633
.compact-btn.close-btn:hover {
634-
background: #f8d7da;
635-
color: #721c24;
634+
background: color-mix(in srgb, var(--dp-danger-readable) 14%, transparent);
635+
color: var(--dp-danger-readable);
636636
}
637637
638638
.compact-btn.debug-btn {
639-
color: #6f42c1;
639+
color: var(--dp-primary-readable);
640640
}
641641
642642
.compact-btn.debug-btn:hover {
643-
background: #e2d9f3;
644-
color: #5a2d91;
643+
background: color-mix(in srgb, var(--dp-primary-readable) 12%, transparent);
644+
color: var(--dp-primary-readable);
645645
}
646646
647647
.btn-icon {
@@ -671,11 +671,17 @@ onUnmounted(() => {
671671
.compact-select :deep(.arco-select-view) {
672672
border: none !important;
673673
background: transparent !important;
674+
color: var(--dp-text-secondary) !important;
674675
padding: 6px 10px;
675676
font-size: 11px;
676677
font-weight: 500;
677678
}
678679
680+
.compact-select :deep(.arco-select-view:hover),
681+
.selector-btn:hover .compact-select :deep(.arco-select-view) {
682+
color: var(--dp-text-primary) !important;
683+
}
684+
679685
.compact-select :deep(.arco-select-view-suffix) {
680686
color: currentColor;
681687
}

dashboard/src/main.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {createApp} from 'vue'
22
import './style.css'
3+
import { initTheme } from '@/utils/theme'
34
import App from './App.vue'
45
import router from './router' // 引入路由
56
import ArcoVue from '@arco-design/web-vue'
@@ -38,6 +39,8 @@ use([
3839
TitleComponent
3940
])
4041

42+
initTheme()
43+
4144
const app = createApp(App)
4245
app.use(router)
4346
app.use(ArcoVue);

dashboard/src/services/backupService.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Message } from '@arco-design/web-vue'
77
import siteService from '@/api/services/site'
8+
import { applyTheme, normalizeThemeMode } from '@/utils/theme'
89

910
// 备份数据版本号,用于兼容性检查
1011
const BACKUP_VERSION = '1.0.0'
@@ -303,9 +304,11 @@ export const restoreBackupData = (backupData) => {
303304
let restoredCount = 0
304305
let failedCount = 0
305306
const errors = []
306-
307+
let restoredThemeMode = null
308+
307309
// 还原设置数据
308310
if (backupData.settings) {
311+
restoredThemeMode = normalizeThemeMode(backupData.settings.appSettings?.themeMode)
309312
for (const [key, value] of Object.entries(backupData.settings)) {
310313
if (key === 'parsers') {
311314
// 特殊处理解析器数据
@@ -458,6 +461,10 @@ export const restoreBackupData = (backupData) => {
458461
}
459462
}
460463

464+
if (restoredThemeMode) {
465+
applyTheme(restoredThemeMode)
466+
}
467+
461468
// 显示还原结果
462469
if (failedCount === 0) {
463470
Message.success(`数据还原成功!共还原 ${restoredCount} 项数据`)

0 commit comments

Comments
 (0)