Skip to content

Commit 55aea54

Browse files
authored
feat: 新增「通用设置 > 外观设置 > 主题模式」配置项 (#583)
1 parent db542c3 commit 55aea54

9 files changed

Lines changed: 108 additions & 36 deletions

File tree

src-tauri/capabilities/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"core:window:allow-set-ignore-cursor-events",
1515
"core:window:allow-set-decorations",
1616
"core:window:allow-set-position",
17+
"core:window:allow-set-theme",
1718
"custom-window:default",
1819
"os:default",
1920
"process:default",

src/App.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
33
import { error } from '@tauri-apps/plugin-log'
44
import { openUrl } from '@tauri-apps/plugin-opener'
55
import { useEventListener } from '@vueuse/core'
6-
import { ConfigProvider } from 'ant-design-vue'
6+
import { ConfigProvider, theme } from 'ant-design-vue'
77
import zhCN from 'ant-design-vue/es/locale/zh_CN'
88
import { isString } from 'es-toolkit'
99
import isURL from 'is-url'
@@ -29,6 +29,7 @@ const generalStore = useGeneralStore()
2929
const shortcutStore = useShortcutStore()
3030
const appWindow = getCurrentWebviewWindow()
3131
const { isRestored, restoreState } = useWindowState()
32+
const { darkAlgorithm, defaultAlgorithm } = theme
3233
3334
onMounted(async () => {
3435
generateColorVars()
@@ -79,7 +80,12 @@ useEventListener('click', (event) => {
7980
</script>
8081

8182
<template>
82-
<ConfigProvider :locale="zhCN">
83+
<ConfigProvider
84+
:locale="zhCN"
85+
:theme="{
86+
algorithm: generalStore.appearance.isDark ? darkAlgorithm : defaultAlgorithm,
87+
}"
88+
>
8389
<RouterView v-if="isRestored" />
8490
</ConfigProvider>
8591
</template>

src/components/pro-list-item/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const hasDescription = computed(() => {
1818
<template>
1919
<Flex
2020
:align="vertical ? void 0 : 'center'"
21-
class="b b-color-2 rounded-lg b-solid bg-white p-4"
21+
class="b b-color-2 rounded-lg b-solid bg-color-3 p-4"
2222
gap="middle"
2323
justify="space-between"
2424
:vertical="vertical"

src/pages/main/index.vue

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { PhysicalSize } from '@tauri-apps/api/dpi'
44
import { Menu } from '@tauri-apps/api/menu'
55
import { sep } from '@tauri-apps/api/path'
66
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
7-
import { exists } from '@tauri-apps/plugin-fs'
7+
import { exists, readDir } from '@tauri-apps/plugin-fs'
88
import { useDebounceFn, useEventListener } from '@vueuse/core'
99
import { nth } from 'es-toolkit/compat'
1010
import { onMounted, onUnmounted, ref, watch } from 'vue'
@@ -17,7 +17,9 @@ import { hideWindow, setAlwaysOnTop, setTaskbarVisibility, showWindow } from '@/
1717
import { useCatStore } from '@/stores/cat'
1818
import { useGeneralStore } from '@/stores/general.ts'
1919
import { useModelStore } from '@/stores/model'
20+
import { isImage } from '@/utils/is'
2021
import { join } from '@/utils/path'
22+
import { clearObject } from '@/utils/shared'
2123
2224
const { startListening } = useDevice()
2325
const appWindow = getCurrentWebviewWindow()
@@ -47,15 +49,32 @@ useEventListener('resize', () => {
4749
})
4850
4951
watch(() => modelStore.currentModel, async (model) => {
50-
handleLoad()
51-
5252
if (!model) return
5353
54+
handleLoad()
55+
5456
const path = join(model.path, 'resources', 'background.png')
5557
5658
const existed = await exists(path)
5759
5860
backgroundImagePath.value = existed ? convertFileSrc(path) : void 0
61+
62+
clearObject([modelStore.supportKeys, modelStore.pressedKeys])
63+
64+
const resourcePath = join(model.path, 'resources')
65+
const groups = ['left-keys', 'right-keys']
66+
67+
for await (const groupName of groups) {
68+
const groupDir = join(resourcePath, groupName)
69+
const files = await readDir(groupDir).catch(() => [])
70+
const imageFiles = files.filter(file => isImage(file.name))
71+
72+
for (const file of imageFiles) {
73+
const fileName = file.name.split('.')[0]
74+
75+
modelStore.supportKeys[fileName] = join(groupDir, file.name)
76+
}
77+
}
5978
}, { deep: true, immediate: true })
6079
6180
watch([() => catStore.scale, modelSize], async () => {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script setup lang="ts">
2+
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
3+
import { Select, SelectOption } from 'ant-design-vue'
4+
import { onMounted, watch } from 'vue'
5+
6+
import ProListItem from '@/components/pro-list-item/index.vue'
7+
import { useGeneralStore } from '@/stores/general'
8+
9+
const generalStore = useGeneralStore()
10+
const appWindow = getCurrentWebviewWindow()
11+
12+
onMounted(() => {
13+
appWindow.onThemeChanged(async ({ payload }) => {
14+
if (generalStore.appearance.theme !== 'auto') return
15+
16+
generalStore.appearance.isDark = payload === 'dark'
17+
})
18+
})
19+
20+
watch(() => generalStore.appearance.theme, async (value) => {
21+
let nextTheme = value === 'auto' ? null : value
22+
23+
await appWindow.setTheme(nextTheme)
24+
25+
nextTheme = nextTheme ?? (await appWindow.theme())
26+
27+
generalStore.appearance.isDark = nextTheme === 'dark'
28+
}, { immediate: true })
29+
30+
watch(() => generalStore.appearance.isDark, (value) => {
31+
if (value) {
32+
document.documentElement.classList.add('dark')
33+
} else {
34+
document.documentElement.classList.remove('dark')
35+
}
36+
}, { immediate: true })
37+
</script>
38+
39+
<template>
40+
<ProListItem title="主题模式">
41+
<Select v-model:value="generalStore.appearance.theme">
42+
<SelectOption value="auto">
43+
跟随系统
44+
</SelectOption>
45+
<SelectOption value="light">
46+
亮色模式
47+
</SelectOption>
48+
<SelectOption value="dark">
49+
暗色模式
50+
</SelectOption>
51+
</Select>
52+
</ProListItem>
53+
</template>

src/pages/preference/components/general/index.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Switch } from 'ant-design-vue'
44
import { watch } from 'vue'
55
66
import MacosPermissions from './components/macos-permissions/index.vue'
7+
import ThemeMode from './components/theme-mode/index.vue'
78
89
import ProList from '@/components/pro-list/index.vue'
910
import ProListItem from '@/components/pro-list-item/index.vue'
@@ -40,6 +41,10 @@ watch(() => generalStore.autostart, async (value) => {
4041
</ProListItem>
4142
</ProList>
4243

44+
<ProList title="外观设置">
45+
<ThemeMode />
46+
</ProList>
47+
4348
<ProList title="更新设置">
4449
<ProListItem title="自动检查更新">
4550
<Switch v-model:checked="generalStore.autoCheckUpdate" />

src/pages/preference/index.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const menus = [
5353
<template>
5454
<Flex class="h-screen">
5555
<div
56-
class="h-full w-30 flex flex-col items-center gap-4 overflow-auto bg-gradient-from-primary-1 bg-gradient-to-black/1 bg-gradient-linear"
56+
class="h-full w-30 flex flex-col items-center gap-4 overflow-auto dark:(bg-color-3 bg-none) bg-gradient-from-primary-1 bg-gradient-to-black/1 bg-gradient-linear"
5757
:class="[isMac ? 'pt-8' : 'pt-4']"
5858
data-tauri-drag-region
5959
>
@@ -73,8 +73,8 @@ const menus = [
7373
<div
7474
v-for="(item, index) in menus"
7575
:key="item.label"
76-
class="size-20 flex flex-col cursor-pointer items-center justify-center gap-2 rounded-lg hover:bg-color-7 text-color-3 transition"
77-
:class="{ 'bg-white! text-primary-5 font-bold': current === index }"
76+
class="size-20 flex flex-col cursor-pointer items-center justify-center gap-2 rounded-lg hover:bg-color-7 dark:text-color-2 text-color-3 transition"
77+
:class="{ 'bg-color-2! text-primary-5 dark:text-primary-7 font-bold dark:bg-color-8!': current === index }"
7878
@click="current = index"
7979
>
8080
<div
@@ -91,7 +91,7 @@ const menus = [
9191
v-for="(item, index) in menus"
9292
v-show="current === index"
9393
:key="item.label"
94-
class="flex-1 overflow-auto bg-color-8 p-4"
94+
class="flex-1 overflow-auto bg-color-8 dark:bg-color-2 p-4"
9595
data-tauri-drag-region
9696
>
9797
<component :is="item.component" />

src/stores/general.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1+
import type { Theme } from '@tauri-apps/api/window'
2+
13
import { defineStore } from 'pinia'
2-
import { ref } from 'vue'
4+
import { reactive, ref } from 'vue'
5+
6+
interface Appearance {
7+
theme: 'auto' | Theme
8+
isDark: boolean
9+
}
310

411
export const useGeneralStore = defineStore('general', () => {
512
const autoCheckUpdate = ref(false)
613
const autostart = ref(false)
714
const taskbarVisibility = ref(false)
15+
const appearance = reactive<Appearance>({
16+
theme: 'auto',
17+
isDark: false,
18+
})
819

920
return {
1021
autoCheckUpdate,
1122
autostart,
1223
taskbarVisibility,
24+
appearance,
1325
}
1426
})

src/stores/model.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import { resolveResource } from '@tauri-apps/api/path'
2-
import { readDir } from '@tauri-apps/plugin-fs'
32
import { filter, find } from 'es-toolkit/compat'
43
import { nanoid } from 'nanoid'
54
import { defineStore } from 'pinia'
6-
import { reactive, ref, watch } from 'vue'
5+
import { reactive, ref } from 'vue'
76

8-
import { isImage } from '@/utils/is'
97
import { join } from '@/utils/path'
10-
import { clearObject } from '@/utils/shared'
118

129
export type ModelMode = 'standard' | 'keyboard' | 'gamepad'
1310

@@ -69,27 +66,6 @@ export const useModelStore = defineStore('model', () => {
6966
models.value = nextModels
7067
}
7168

72-
watch(currentModel, async (model) => {
73-
if (!model) return
74-
75-
clearObject([supportKeys, pressedKeys])
76-
77-
const resourcePath = join(model.path, 'resources')
78-
const groups = ['left-keys', 'right-keys']
79-
80-
for await (const groupName of groups) {
81-
const groupDir = join(resourcePath, groupName)
82-
const files = await readDir(groupDir).catch(() => [])
83-
const imageFiles = files.filter(file => isImage(file.name))
84-
85-
for (const file of imageFiles) {
86-
const fileName = file.name.split('.')[0]
87-
88-
supportKeys[fileName] = join(groupDir, file.name)
89-
}
90-
}
91-
}, { deep: true, immediate: true })
92-
9369
return {
9470
models,
9571
currentModel,

0 commit comments

Comments
 (0)