性能优化

This commit is contained in:
zhongluofeng
2026-08-06 10:33:16 +08:00
parent c7578a2e6b
commit e66c53e66d
105 changed files with 7273 additions and 5002 deletions
+345
View File
@@ -0,0 +1,345 @@
<script setup lang="ts">
import { Monitor, Sun, Moon, Sparkles, Layers, LogOut, Package, GripVertical } from '@lucide/vue'
import { Switch } from '@/components/ui/switch'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useAppStore, type Theme, type EffectType, type ModuleInfo } from '@/stores/appStore'
import { useSearchStore } from '@/stores/searchStore'
import { useProcessStore } from '@/stores/processStore'
import { getModuleIcon } from '@/modules/icons'
import { invoke } from '@tauri-apps/api/core'
import { computed, onMounted, ref, watch } from 'vue'
import { VueDraggable } from 'vue-draggable-plus'
const appStore = useAppStore()
const searchStore = useSearchStore()
const processStore = useProcessStore()
// 系统真实深浅色偏好,来自 appStore(应用启动时初始化,仅通过 onThemeChanged 更新,
// 不受 setTheme 污染),用于"跟随系统"卡片色块。
const systemDark = computed(() => appStore.systemDark)
onMounted(() => {
searchStore.registerAction('settings', 0, () => appStore.setTheme('light'))
searchStore.registerAction('settings', 1, () => appStore.setTheme('dark'))
searchStore.registerAction('settings', 2, () => appStore.setTheme('system'))
searchStore.registerAction('settings', 3, () => appStore.setEffect('normal'))
searchStore.registerAction('settings', 4, () => appStore.setEffect('mica'))
searchStore.registerAction('settings', 5, () => appStore.setEffect('acrylic'))
searchStore.registerAction('settings', 6, () => appStore.toggleAutoStart())
})
const themes: Array<{ id: Theme; name: string; color: string; icon: typeof Sun }> = [
{ id: 'light', name: '浅色模式', color: '#f8fafc', icon: Sun },
{ id: 'dark', name: '深色模式', color: '#1e293b', icon: Moon },
{ id: 'system', name: '跟随系统', color: '#64748b', icon: Monitor }
]
const effects: Array<{ id: EffectType; name: string; color: string; darkColor: string; description: string }> = [
{ id: 'normal', name: '普通模式', color: '#ffffff', darkColor: '#0f172a', description: '标准背景效果' },
{ id: 'mica', name: 'Win 云母', color: '#f1f5f9', darkColor: '#1e293b', description: 'Windows 11 云母效果' },
{ id: 'acrylic', name: 'Win 亚克力', color: '#cbd5e1', darkColor: '#18181b', description: '仅跟随系统主题可用' }
]
// 亚克力仅在"跟随系统"主题下能正确同步深浅色(无深浅枚举,依赖系统级主题广播刷新)
const isAcrylicDisabled = () => appStore.theme !== 'system'
// 应用当前是否为深色模式(响应式,随主题与系统偏好变化)
const isAppDark = computed(() => {
if (appStore.theme === 'dark') return true
if (appStore.theme === 'light') return false
return systemDark.value
})
// 色块阴影:浅色主题用黑色阴影;深色主题黑色阴影不可见,改用微弱亮色高光模拟凸起感
const blockShadow = computed(() =>
isAppDark.value
? '0 4px 10px rgba(255, 255, 255, 0.08)'
: '0 4px 10px rgba(0, 0, 0, 0.2)'
)
// 效果卡片色块预览色:根据当前深浅模式返回对应颜色,尽量接近实际材质效果
const getEffectColor = (effectId: EffectType) => {
const eff = effects.find(e => e.id === effectId)
if (!eff) return '#ffffff'
return isAppDark.value ? eff.darkColor : eff.color
}
const getThemeColor = (themeId: Theme) => {
if (themeId === 'system') {
// 始终使用系统真实的深浅色,而非应用当前主题
return systemDark.value ? '#1e293b' : '#f8fafc'
}
const theme = themes.find(t => t.id === themeId)
return theme?.color || '#f8fafc'
}
const quitApp = async () => {
await invoke('quit_app')
}
/** 判断模块开关是否处于处理中状态 */
const isModuleToggling = (moduleId: string): boolean => {
return appStore.togglingModules.has(moduleId)
}
/** 获取模块的进程状态文本 */
const getProcessStatusText = (moduleId: string): string | null => {
const module = appStore.modules.find(m => m.id === moduleId)
if (!module?.hasProcess) return null
const status = processStore.getProcessStatus(module.id)
if (!status) return '未启动'
switch (status.status) {
case 'running': return '运行中'
case 'stopped': return '已停止'
case 'crashed': return '已崩溃'
case 'starting': return '启动中...'
default: return '未知'
}
}
// ===== 模块拖拽排序 =====
/** 可拖拽的模块列表(仅用户模块,按 moduleOrder 排序)—— 浅拷贝以支持 VueDraggable 原地修改 */
const dragList = ref<ModuleInfo[]>(
appStore.moduleOrder
.map(id => appStore.getModule(id))
.filter((m): m is ModuleInfo => !!m && !m.builtin)
.map(m => ({ ...m }))
)
/** 监听 store 中模块状态变化,同步 enabled 到本地拖拽列表 */
watch(() => appStore.modules, () => {
dragList.value.forEach(item => {
const storeModule = appStore.getModule(item.id)
if (storeModule) {
item.enabled = storeModule.enabled
}
})
}, { deep: true })
/** 拖拽结束时,将新顺序同步到 store */
const onDragEnd = () => {
appStore.reorderModules(dragList.value.map(m => m.id))
}
</script>
<template>
<div class="h-full p-6 overflow-y-auto">
<div class="max-w-3xl mx-auto space-y-6">
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Sparkles class="h-5 w-5 text-primary" />
常规设置
</CardTitle>
</CardHeader>
<CardContent>
<div class="flex items-center justify-between py-2">
<div class="space-y-1">
<Label class="text-base font-medium">开机自启</Label>
<p class="text-sm text-muted-foreground">启动 Windows 时自动运行应用</p>
</div>
<Switch
:model-value="appStore.isAutoStart"
@update:model-value="(checked: boolean) => appStore.toggleAutoStart(checked)"
/>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Layers class="h-5 w-5 text-primary" />
主题切换
</CardTitle>
</CardHeader>
<CardContent>
<div class="grid grid-cols-3 gap-4">
<button
v-for="theme in themes"
:key="theme.id"
class="group relative rounded-lg overflow-hidden border-2 transition-all duration-300 hover:shadow-lg"
:class="appStore.theme === theme.id ? 'border-primary shadow-md' : 'border-border hover:border-primary/50'"
@click="appStore.setTheme(theme.id)"
>
<div
class="h-16 w-full flex items-center justify-center transition-all duration-300"
:style="{
backgroundColor: getThemeColor(theme.id),
boxShadow: blockShadow
}"
>
<component
:is="theme.icon"
class="h-8 w-8 transition-colors duration-300"
:class="theme.id === 'dark' || (theme.id === 'system' && systemDark) ? 'text-white' : 'text-gray-800'"
/>
</div>
<div class="h-10 flex items-center justify-center">
<span class="text-sm font-medium">{{ theme.name }}</span>
</div>
<div
v-if="appStore.theme === theme.id"
class="absolute top-2 right-2 w-5 h-5 bg-primary dark:bg-white rounded-full flex items-center justify-center"
>
<svg class="w-3 h-3 text-white dark:text-slate-900" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</button>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Sparkles class="h-5 w-5 text-primary" />
效果切换
</CardTitle>
</CardHeader>
<CardContent>
<div class="grid grid-cols-3 gap-4">
<button
v-for="effect in effects"
:key="effect.id"
:disabled="effect.id === 'acrylic' && isAcrylicDisabled()"
class="group relative rounded-lg overflow-hidden border-2 transition-all duration-300"
:class="[
appStore.effect === effect.id ? 'border-primary shadow-md' : 'border-border',
effect.id === 'acrylic' && isAcrylicDisabled()
? 'opacity-50 cursor-not-allowed'
: 'hover:shadow-lg hover:border-primary/50'
]"
@click="appStore.setEffect(effect.id)"
>
<div
class="h-16 w-full transition-all duration-300"
:style="{
backgroundColor: getEffectColor(effect.id),
boxShadow: blockShadow
}"
></div>
<div class="h-14 flex flex-col items-center justify-center p-2">
<span class="text-sm font-medium">{{ effect.name }}</span>
<span class="text-xs text-muted-foreground">{{ effect.description }}</span>
</div>
<div
v-if="appStore.effect === effect.id"
class="absolute top-2 right-2 w-5 h-5 bg-primary dark:bg-white rounded-full flex items-center justify-center"
>
<svg class="w-3 h-3 text-white dark:text-slate-900" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</button>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Package class="h-5 w-5 text-primary" />
模块管理
</CardTitle>
</CardHeader>
<CardContent>
<VueDraggable
v-model="dragList"
:animation="200"
:force-fallback="true"
handle=".drag-handle"
ghost-class="opacity-40"
chosen-class="drag-chosen"
class="space-y-2"
@end="onDragEnd"
>
<div
v-for="module in dragList"
:key="module.id"
class="flex items-center justify-between py-2 px-3 rounded-lg border border-border/50 hover:bg-secondary/30 transition-colors group"
:class="{ 'opacity-60': isModuleToggling(module.id) }"
>
<div class="flex items-center gap-3">
<Tooltip>
<TooltipTrigger as-child>
<div
class="drag-handle cursor-grab active:cursor-grabbing text-muted-foreground/40 hover:text-muted-foreground transition-colors"
>
<GripVertical class="h-4 w-4 no-native-drag" />
</div>
</TooltipTrigger>
<TooltipContent>拖拽排序</TooltipContent>
</Tooltip>
<div
class="w-9 h-9 rounded-lg flex items-center justify-center"
:class="module.enabled ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground'"
>
<component :is="getModuleIcon(module.icon)" class="h-5 w-5" />
</div>
<div>
<div class="font-medium text-sm flex items-center gap-2">
{{ module.name }}
<span
v-if="getProcessStatusText(module.id)"
class="text-xs px-1.5 py-0.5 rounded-full"
:class="module.enabled ? 'bg-green-500/10 text-green-600 dark:text-green-400' : 'bg-muted text-muted-foreground'"
>
{{ getProcessStatusText(module.id) }}
</span>
</div>
<div class="text-xs text-muted-foreground">
{{ module.description }}
</div>
</div>
</div>
<Switch
:model-value="module.enabled"
:disabled="module.builtin || isModuleToggling(module.id)"
@update:model-value="(checked: boolean) => appStore.toggleModule(module.id, checked)"
/>
</div>
</VueDraggable>
<p class="mt-4 text-xs text-muted-foreground">
拖拽手柄可调整模块顺序,禁用模块将从侧边栏隐藏并停止后台进程以减少内存占用。更改后立即生效。
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<LogOut class="h-5 w-5 text-destructive" />
退出程序
</CardTitle>
</CardHeader>
<CardContent>
<Button
variant="outline"
class="text-destructive border-destructive/20 hover:bg-destructive/10"
@click="quitApp"
>
<LogOut class="h-4 w-4 mr-2" />
彻底退出
</Button>
</CardContent>
</Card>
</div>
</div>
</template>
<style scoped>
.no-native-drag {
-webkit-user-drag: none;
user-select: none;
}
.drag-chosen {
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.3);
}
</style>