测试
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 778 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 867 B After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 430 B After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
|
Before Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 485 KiB |
|
Before Width: | Height: | Size: 161 B After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 919 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -21,18 +21,17 @@ const displayModules = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<aside
|
<aside
|
||||||
class="w-17 flex flex-col items-center py-4 border-r border-border transition-all duration-300"
|
class="w-17 shrink-0 flex flex-col items-center py-4 border-r border-border transition-[background-color,border-color] duration-300"
|
||||||
:style="{ backgroundColor: 'var(--effect-bg)', backdropFilter: 'var(--effect-blur)' }"
|
:style="{ backgroundColor: 'var(--effect-bg)', backdropFilter: 'var(--effect-blur)' }"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<TransitionGroup name="module-flip">
|
|
||||||
<div v-for="module in displayModules" :key="module.id">
|
<div v-for="module in displayModules" :key="module.id">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<Button
|
<Button
|
||||||
:variant="activeModule === module.id ? 'default' : 'ghost'"
|
:variant="activeModule === module.id ? 'default' : 'ghost'"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="h-10 w-10 rounded-lg transition-all duration-300"
|
class="h-10 w-10 rounded-lg transition-[background-color,color,box-shadow] duration-300"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-primary text-primary-foreground shadow-md': activeModule === module.id,
|
'bg-primary text-primary-foreground shadow-md': activeModule === module.id,
|
||||||
'hover:bg-secondary/50': activeModule !== module.id
|
'hover:bg-secondary/50': activeModule !== module.id
|
||||||
@@ -48,7 +47,6 @@ const displayModules = computed(() => {
|
|||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</TransitionGroup>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
@@ -58,7 +56,7 @@ const displayModules = computed(() => {
|
|||||||
<Button
|
<Button
|
||||||
:variant="activeModule === 'settings' ? 'default' : 'ghost'"
|
:variant="activeModule === 'settings' ? 'default' : 'ghost'"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="h-10 w-10 rounded-lg transition-all duration-300"
|
class="h-10 w-10 rounded-lg transition-[background-color,color,box-shadow] duration-300"
|
||||||
:class="{
|
:class="{
|
||||||
'bg-primary text-primary-foreground shadow-md': activeModule === 'settings',
|
'bg-primary text-primary-foreground shadow-md': activeModule === 'settings',
|
||||||
'hover:bg-secondary/50': activeModule !== 'settings'
|
'hover:bg-secondary/50': activeModule !== 'settings'
|
||||||
@@ -75,9 +73,3 @@ const displayModules = computed(() => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.module-flip-move {
|
|
||||||
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -9,22 +9,18 @@ import { useSearchStore } from '@/stores/searchStore'
|
|||||||
import { useProcessStore } from '@/stores/processStore'
|
import { useProcessStore } from '@/stores/processStore'
|
||||||
import { getModuleIcon } from '@/modules/icons'
|
import { getModuleIcon } from '@/modules/icons'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { VueDraggable } from 'vue-draggable-plus'
|
import { VueDraggable } from 'vue-draggable-plus'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const searchStore = useSearchStore()
|
const searchStore = useSearchStore()
|
||||||
const processStore = useProcessStore()
|
const processStore = useProcessStore()
|
||||||
|
|
||||||
// 始终反映系统真实的深浅色偏好,用于"跟随系统"卡片色块
|
// 系统真实深浅色偏好,来自 appStore(应用启动时初始化,仅通过 onThemeChanged 更新,
|
||||||
const systemDark = ref(window.matchMedia('(prefers-color-scheme: dark)').matches)
|
// 不受 setTheme 污染),用于"跟随系统"卡片色块。
|
||||||
const systemMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
const systemDark = computed(() => appStore.systemDark)
|
||||||
const handleSystemMediaChange = (e: MediaQueryListEvent) => {
|
|
||||||
systemDark.value = e.matches
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
systemMediaQuery.addEventListener('change', handleSystemMediaChange)
|
|
||||||
searchStore.registerAction('settings', 0, () => appStore.setTheme('light'))
|
searchStore.registerAction('settings', 0, () => appStore.setTheme('light'))
|
||||||
searchStore.registerAction('settings', 1, () => appStore.setTheme('dark'))
|
searchStore.registerAction('settings', 1, () => appStore.setTheme('dark'))
|
||||||
searchStore.registerAction('settings', 2, () => appStore.setTheme('system'))
|
searchStore.registerAction('settings', 2, () => appStore.setTheme('system'))
|
||||||
@@ -34,10 +30,6 @@ onMounted(() => {
|
|||||||
searchStore.registerAction('settings', 6, () => appStore.toggleAutoStart())
|
searchStore.registerAction('settings', 6, () => appStore.toggleAutoStart())
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
systemMediaQuery.removeEventListener('change', handleSystemMediaChange)
|
|
||||||
})
|
|
||||||
|
|
||||||
const themes: Array<{ id: Theme; name: string; color: string; icon: typeof Sun }> = [
|
const themes: Array<{ id: Theme; name: string; color: string; icon: typeof Sun }> = [
|
||||||
{ id: 'light', name: '浅色模式', color: '#f8fafc', icon: Sun },
|
{ id: 'light', name: '浅色模式', color: '#f8fafc', icon: Sun },
|
||||||
{ id: 'dark', name: '深色模式', color: '#1e293b', icon: Moon },
|
{ id: 'dark', name: '深色模式', color: '#1e293b', icon: Moon },
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
const modules = ref<ModuleInfo[]>(initModulesFromRegistry())
|
const modules = ref<ModuleInfo[]>(initModulesFromRegistry())
|
||||||
const moduleOrder = ref<string[]>(initModuleOrder())
|
const moduleOrder = ref<string[]>(initModuleOrder())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统真实的深浅色偏好(不受应用 setTheme 影响)。
|
||||||
|
* 应用启动时用 matchMedia 初始化(此时未调用 setTheme,结果准确),
|
||||||
|
* 之后仅通过 Tauri onThemeChanged 事件更新(该事件反映系统主题变化,不受 setTheme 影响)。
|
||||||
|
* 用于"跟随系统"卡片色块等需要反映系统真实状态的 UI。
|
||||||
|
*/
|
||||||
|
const systemDark = ref(window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||||
|
|
||||||
/** 正在处理切换的模块 ID 集合(防止重复点击) */
|
/** 正在处理切换的模块 ID 集合(防止重复点击) */
|
||||||
const togglingModules = ref<Set<string>>(new Set())
|
const togglingModules = ref<Set<string>>(new Set())
|
||||||
|
|
||||||
@@ -322,7 +330,14 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
isDark = true
|
isDark = true
|
||||||
root.classList.add('dark')
|
root.classList.add('dark')
|
||||||
} else if (theme.value === 'system') {
|
} else if (theme.value === 'system') {
|
||||||
|
// 通过 Tauri 获取系统真实主题,避免 matchMedia 被 setTheme 污染
|
||||||
|
try {
|
||||||
|
const tauriWindow = getCurrentWindow()
|
||||||
|
const sysTheme = await tauriWindow.theme()
|
||||||
|
isDark = sysTheme === 'dark'
|
||||||
|
} catch {
|
||||||
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
}
|
||||||
if (isDark) {
|
if (isDark) {
|
||||||
root.classList.add('dark')
|
root.classList.add('dark')
|
||||||
}
|
}
|
||||||
@@ -330,7 +345,13 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const tauriWindow = getCurrentWindow()
|
const tauriWindow = getCurrentWindow()
|
||||||
|
if (theme.value === 'system') {
|
||||||
|
// 让窗口原生跟随系统主题,而不是固定成当前匹配值
|
||||||
|
// 固定主题会导致系统切换深浅色时窗口标题栏等原生 UI 不同步
|
||||||
|
await tauriWindow.setTheme(null)
|
||||||
|
} else {
|
||||||
await tauriWindow.setTheme(isDark ? 'dark' : 'light')
|
await tauriWindow.setTheme(isDark ? 'dark' : 'light')
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 非 Tauri 环境下忽略
|
// 非 Tauri 环境下忽略
|
||||||
}
|
}
|
||||||
@@ -372,22 +393,26 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSystemThemeChange = async (e: MediaQueryListEvent) => {
|
// 系统主题变化时,若当前为"跟随系统"模式,同步更新 DOM 和窗口效果。
|
||||||
|
// 使用 Tauri 的 onThemeChanged 事件(而非 matchMedia),因为 setTheme('dark'/'light')
|
||||||
|
// 会改变 WebView 的 prefers-color-scheme 媒体查询结果,导致 matchMedia 误触发。
|
||||||
|
//
|
||||||
|
// 注意:onThemeChanged 也会被 setTheme('dark'/'light') 触发(非系统真实变化)。
|
||||||
|
// 因此 systemDark 只在 theme.value === 'system' 时更新:
|
||||||
|
// - system 模式下 setTheme(null) 不固定主题,onThemeChanged 反映系统真实色
|
||||||
|
// - light/dark 模式下 setTheme 引起的 onThemeChanged 被忽略,systemDark 保持不变
|
||||||
|
// - 用户切回 system 模式时,setTheme(null) 会触发 onThemeChanged 反映系统真实色,自动修正
|
||||||
|
const handleSystemThemeChange = async (newTheme: 'light' | 'dark' | null) => {
|
||||||
if (theme.value === 'system') {
|
if (theme.value === 'system') {
|
||||||
|
systemDark.value = newTheme === 'dark'
|
||||||
|
|
||||||
const root = document.documentElement
|
const root = document.documentElement
|
||||||
if (e.matches) {
|
if (newTheme === 'dark') {
|
||||||
root.classList.add('dark')
|
root.classList.add('dark')
|
||||||
} else {
|
} else {
|
||||||
root.classList.remove('dark')
|
root.classList.remove('dark')
|
||||||
}
|
}
|
||||||
|
// 窗口已通过 setTheme(null) 原生跟随系统,无需再手动 setTheme
|
||||||
try {
|
|
||||||
const tauriWindow = getCurrentWindow()
|
|
||||||
await tauriWindow.setTheme(e.matches ? 'dark' : 'light')
|
|
||||||
} catch (e) {
|
|
||||||
// 非 Tauri 环境下忽略
|
|
||||||
}
|
|
||||||
|
|
||||||
await applyEffect()
|
await applyEffect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,8 +444,21 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
|
|
||||||
await applyTheme()
|
await applyTheme()
|
||||||
|
|
||||||
|
// 使用 Tauri 的 onThemeChanged 监听系统主题变化,而非 matchMedia。
|
||||||
|
// 因为 setTheme('dark'/'light') 会改变 WebView 的 prefers-color-scheme 媒体查询,
|
||||||
|
// 导致 matchMedia 在非 system 模式下也误触发(虽有 theme.value 守卫,但更可靠)。
|
||||||
|
try {
|
||||||
|
const tauriWindow = getCurrentWindow()
|
||||||
|
await tauriWindow.onThemeChanged(({ payload }) => {
|
||||||
|
handleSystemThemeChange(payload)
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// 非 Tauri 环境回退到 matchMedia
|
||||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
mediaQuery.addEventListener('change', handleSystemThemeChange)
|
mediaQuery.addEventListener('change', (e) => {
|
||||||
|
handleSystemThemeChange(e.matches ? 'dark' : 'light')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
isInitialized.value = true
|
isInitialized.value = true
|
||||||
} finally {
|
} finally {
|
||||||
@@ -436,6 +474,7 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
return {
|
return {
|
||||||
theme,
|
theme,
|
||||||
effect,
|
effect,
|
||||||
|
systemDark,
|
||||||
isAutoStart,
|
isAutoStart,
|
||||||
isInitialized,
|
isInitialized,
|
||||||
modules,
|
modules,
|
||||||
|
|||||||