Skip to content

Commit 293ff96

Browse files
committed
支持自动深色模式
1 parent 49782b1 commit 293ff96

File tree

4 files changed

+98
-59
lines changed

4 files changed

+98
-59
lines changed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ ENABLE_NO_AUTH=true LISTEN_PORT=58080 ./tiny-nav
143143

144144
- [x] 支持只读模式:查看免登录,编辑需登录
145145
- [x] 数据有变化才拉取,避免重复加载
146-
- [ ] 自动深色模式
146+
- [x] 自动深色模式
147147
- [ ] 支持书签导入
148148
- [ ] 支持站内搜索
149149

@@ -288,11 +288,10 @@ Access: <http://localhost:58080>
288288

289289
- [x] Support read-only mode: view without login, edit requires login
290290
- [x] Pull data only on changes to avoid redundant loading
291-
- [ ] Automatic dark mode
291+
- [x] Automatic dark mode
292292
- [ ] Support bookmark import
293293
- [ ] Support in-site search
294294

295-
296295
## Star History
297296

298297
[![Star History Chart](https://api.star-history.com/svg?repos=hanxi/tiny-nav&type=Date)](https://star-history.com/#hanxi/tiny-nav&Date)

front/src/components/NavHeader.vue

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,26 @@
2828
<button v-if="showEdit" @click="$emit('add')"
2929
class="flex items-center gap-2 px-3 py-2 rounded-md bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700"
3030
title="添加网站">
31-
<div class="i-mdi-plus-circle text-gray-400 dark:text-gray-300">
32-
</div>
31+
<div class="i-mdi-plus-circle text-gray-400 dark:text-gray-300"></div>
3332
</button>
33+
34+
<!-- ✅ 改好的主题切换按钮 -->
3435
<button @click="themeStore.toggleTheme"
3536
class="flex items-center gap-2 px-3 py-2 rounded-md bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700"
36-
:title="themeStore.isDarkTheme ? '浅色模式' : '深色模式'">
37-
<div
38-
:class="themeStore.isDarkTheme ? 'i-mdi-white-balance-sunny text-blue-500 dark:text-blue-300' : 'i-mdi-moon-waxing-crescent text-gray-400 dark:text-gray-300'">
39-
</div>
37+
:title="themeTitle">
38+
<div :class="themeIcon"></div>
4039
</button>
4140

4241
<button v-if="showLogout" @click="handleLogout"
4342
class="flex items-center gap-2 px-3 py-2 rounded-md bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700"
4443
title="退出登录">
45-
<div class="i-mdi-logout text-gray-400 dark:text-gray-300">
46-
</div>
44+
<div class="i-mdi-logout text-gray-400 dark:text-gray-300"></div>
4745
</button>
4846

4947
<button v-if="showLogin" @click="$emit('login')"
5048
class="flex items-center gap-2 px-3 py-2 rounded-md bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700"
5149
title="登录">
52-
<div class="i-mdi-login text-gray-400 dark:text-gray-300">
53-
</div>
50+
<div class="i-mdi-login text-gray-400 dark:text-gray-300"></div>
5451
</button>
5552
</div>
5653
</div>
@@ -67,27 +64,25 @@
6764
</button>
6865
<button v-if="showEdit" @click="$emit('add')"
6966
class="flex items-center gap-3 px-3 py-2 rounded-md bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700">
70-
<div class="i-mdi-plus-circle text-gray-400 dark:text-gray-300">
71-
</div>
67+
<div class="i-mdi-plus-circle text-gray-400 dark:text-gray-300"></div>
7268
添加网站
7369
</button>
70+
71+
<!-- ✅ 移动端的主题按钮 -->
7472
<button @click="themeStore.toggleTheme"
7573
class="flex items-center gap-3 px-3 py-2 rounded-md bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700">
76-
<div
77-
:class="themeStore.isDarkTheme ? 'i-mdi-white-balance-sunny text-blue-500 dark:text-blue-300' : 'i-mdi-moon-waxing-crescent text-gray-400 dark:text-gray-300'">
78-
</div>
79-
{{ themeStore.isDarkTheme ? '浅色模式' : '深色模式' }}
74+
<div :class="themeIcon"></div>
75+
{{ themeTitle }}
8076
</button>
77+
8178
<button v-if="showLogout" @click="handleLogout"
8279
class="flex items-center gap-3 px-3 py-2 rounded-md bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700">
83-
<div class="i-mdi-logout text-gray-400 dark:text-gray-300">
84-
</div>
80+
<div class="i-mdi-logout text-gray-400 dark:text-gray-300"></div>
8581
登出
8682
</button>
8783
<button v-if="showLogin" @click="$emit('login')"
8884
class="flex items-center gap-3 px-3 py-2 rounded-md bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700">
89-
<div class="i-mdi-logout text-gray-400 dark:text-gray-300">
90-
</div>
85+
<div class="i-mdi-login text-gray-400 dark:text-gray-300"></div>
9186
登录
9287
</button>
9388
</div>
@@ -96,7 +91,7 @@
9691
</template>
9792

9893
<script setup lang="ts">
99-
import { ref, onMounted } from 'vue'
94+
import { ref, onMounted, computed } from 'vue'
10095
import { useThemeStore } from '@/stores/themeStore'
10196
import { useMainStore } from '@/stores'
10297
@@ -126,41 +121,49 @@ const toggleMobileMenu = () => {
126121
isMobileMenuOpen.value = !isMobileMenuOpen.value
127122
}
128123
129-
const showLogin = ref(false);
130-
const showLogout = ref(false);
131-
const showEdit = ref(false);
124+
const showLogin = ref(false)
125+
const showLogout = ref(false)
126+
const showEdit = ref(false)
132127
133128
async function updateAuthenticationStates() {
134-
// Check if no authentication is needed
135129
if (store.config.enableNoAuth) {
136-
showLogin.value = false;
137-
showLogout.value = false;
138-
showEdit.value = true;
130+
showLogin.value = false
131+
showLogout.value = false
132+
showEdit.value = true
139133
} else {
140-
// Perform async token validation
141134
try {
142-
const isValid = await store.validateToken();
143-
showLogin.value = !isValid;
144-
showLogout.value = isValid;
145-
showEdit.value = isValid;
135+
const isValid = await store.validateToken()
136+
showLogin.value = !isValid
137+
showLogout.value = isValid
138+
showEdit.value = isValid
146139
} catch (error) {
147-
console.error('Error validating token:', error);
148-
showLogin.value = true;
149-
showLogout.value = false;
150-
showEdit.value = false;
140+
console.error('Error validating token:', error)
141+
showLogin.value = true
142+
showLogout.value = false
143+
showEdit.value = false
151144
}
152145
}
153146
}
154147
155-
// Call the function on component mount
156148
onMounted(() => {
157149
updateAuthenticationStates()
158150
})
159151
160152
const handleLogout = () => {
161-
// Emit logout event
162153
emit('logout')
163154
updateAuthenticationStates()
164155
}
165156
157+
/* ====== 新增:主题模式按钮显示逻辑 ====== */
158+
const themeIcon = computed(() => {
159+
if (themeStore.mode === 'system') return 'i-mdi-monitor text-blue-400'
160+
return themeStore.isDarkTheme
161+
? 'i-mdi-moon-waxing-crescent text-gray-300'
162+
: 'i-mdi-white-balance-sunny text-yellow-500'
163+
})
164+
165+
const themeTitle = computed(() => {
166+
if (themeStore.mode === 'system') return '跟随系统'
167+
return themeStore.isDarkTheme ? '深色模式' : '浅色模式'
168+
})
166169
</script>

front/src/stores/themeStore.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,54 @@
11
import { defineStore } from 'pinia'
2-
import { ref, onMounted } from 'vue'
2+
import { ref, watch } from 'vue'
3+
4+
type ThemeMode = 'system' | 'light' | 'dark'
35

46
export const useThemeStore = defineStore('theme', () => {
7+
// 当前主题模式
8+
const mode = ref<ThemeMode>('system')
9+
// 实际是否为深色(根据系统 + 模式计算出来)
510
const isDarkTheme = ref(false)
611

7-
const toggleTheme = () => {
8-
isDarkTheme.value = !isDarkTheme.value
9-
document.documentElement.classList.toggle('dark', isDarkTheme.value)
10-
}
11-
12+
// 应用主题
1213
const applyTheme = () => {
14+
if (mode.value === 'system') {
15+
isDarkTheme.value = window.matchMedia('(prefers-color-scheme: dark)').matches
16+
} else {
17+
isDarkTheme.value = mode.value === 'dark'
18+
}
1319
document.documentElement.classList.toggle('dark', isDarkTheme.value)
1420
}
1521

16-
// Check the saved theme when the store is created
17-
onMounted(() => {
22+
// 切换主题模式(system → light → dark → system)
23+
const toggleTheme = () => {
24+
if (mode.value === 'system') {
25+
mode.value = 'light'
26+
} else if (mode.value === 'light') {
27+
mode.value = 'dark'
28+
} else {
29+
mode.value = 'system'
30+
}
1831
applyTheme()
32+
}
33+
34+
// 监听系统主题变化
35+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
36+
mediaQuery.addEventListener('change', () => {
37+
if (mode.value === 'system') {
38+
applyTheme()
39+
}
1940
})
2041

42+
// 每次 mode 改变都应用
43+
watch(mode, () => applyTheme(), { immediate: true })
44+
2145
return {
46+
mode,
2247
isDarkTheme,
2348
toggleTheme,
24-
applyTheme
49+
applyTheme,
2550
}
2651
}, {
27-
persist: true
52+
persist: true, // 依旧支持持久化
2853
})
2954

front/src/views/Login.vue

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
class="w-10 h-10 flex items-center justify-center rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-100 hover:bg-gray-300 dark:hover:bg-gray-600">
1010
<div class="i-mdi-arrow-left"></div>
1111
</button>
12+
13+
<!-- ✅ 改好的主题切换按钮 -->
1214
<button @click="themeStore.toggleTheme"
13-
class="w-10 h-10 flex items-center justify-center rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-100 hover:bg-gray-300 dark:hover:bg-gray-600">
14-
<div
15-
:class="themeStore.isDarkTheme ? 'i-mdi-white-balance-sunny' : 'i-mdi-moon-waxing-crescent'">
16-
</div>
15+
class="w-10 h-10 flex items-center justify-center rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-100 hover:bg-gray-300 dark:hover:bg-gray-600"
16+
:title="themeTitle">
17+
<div :class="themeIcon"></div>
1718
</button>
1819
</div>
1920
</div>
@@ -35,7 +36,7 @@
3536
</template>
3637

3738
<script setup lang="ts">
38-
import { ref, reactive } from 'vue'
39+
import { ref, reactive, computed } from 'vue'
3940
import { useRouter } from 'vue-router'
4041
import { useMainStore } from '@/stores'
4142
import { useThemeStore } from '@/stores/themeStore'
@@ -51,7 +52,7 @@ const loading = ref(false)
5152
5253
const form = reactive({
5354
username: '',
54-
password: ''
55+
password: '',
5556
})
5657
5758
const goBack = () => {
@@ -60,7 +61,6 @@ const goBack = () => {
6061
6162
const handleLogin = async () => {
6263
if (loading.value) return
63-
6464
loading.value = true
6565
try {
6666
const token = await api.login(form)
@@ -74,4 +74,16 @@ const handleLogin = async () => {
7474
}
7575
}
7676
77+
/* ====== 新增:主题模式按钮显示逻辑 ====== */
78+
const themeIcon = computed(() => {
79+
if (themeStore.mode === 'system') return 'i-mdi-monitor text-blue-400'
80+
return themeStore.isDarkTheme
81+
? 'i-mdi-moon-waxing-crescent text-gray-300'
82+
: 'i-mdi-white-balance-sunny text-yellow-500'
83+
})
84+
85+
const themeTitle = computed(() => {
86+
if (themeStore.mode === 'system') return '跟随系统'
87+
return themeStore.isDarkTheme ? '深色模式' : '浅色模式'
88+
})
7789
</script>

0 commit comments

Comments
 (0)