主界面修改及代理模块初始化

This commit is contained in:
zhongluofeng
2026-07-15 18:22:07 +08:00
parent 29a5f456cb
commit fb361aff9a
39 changed files with 4768 additions and 396 deletions
+65 -44
View File
@@ -1,64 +1,68 @@
<script setup lang="ts">
import { ref, onMounted, shallowRef, markRaw, type Component } from 'vue'
import { ref, onMounted, shallowRef, computed, watch, type Component } from 'vue'
import TitleBar from '@/components/layout/TitleBar.vue'
import Sidebar from '@/components/layout/Sidebar.vue'
import ModuleContainer from '@/components/layout/ModuleContainer.vue'
import GeneralSettings from '@/modules/general/GeneralSettings.vue'
import { Toaster } from '@/components/ui/sonner'
import { useAppStore } from '@/stores/appStore'
import { TooltipProvider } from '@/components/ui/tooltip'
import { moduleRegistry } from '@/modules/registry'
import type { ModuleMeta } from '@/types/module'
const appStore = useAppStore()
interface ModuleMeta {
/** 侧边栏 / 标题栏需要的模块信息(id + name + icon */
interface NavModule {
id: string
name: string
icon: string
// 同步加载的模块直接传 Component;懒加载的传 import 工厂
loader?: () => Promise<{ default: Component }>
component?: Component
}
// 常规设置是默认可见且轻量的,直接同步引入
// 其他业务模块较大且首屏不一定需要,懒加载
const modules: ModuleMeta[] = [
{ id: 'proxy', name: '代理管理', icon: 'proxy', loader: () => import('@/modules/proxy/ProxyModule.vue') },
{ id: 'clipboard', name: '剪贴板', icon: 'clipboard', loader: () => import('@/modules/clipboard/ClipboardModule.vue') },
{ id: 'screenshot', name: '截图', icon: 'screenshot', loader: () => import('@/modules/screenshot/ScreenshotModule.vue') },
{ id: 'monitor', name: '硬件监控', icon: 'monitor', loader: () => import('@/modules/monitor/MonitorModule.vue') },
{ id: 'downloader', name: '下载器', icon: 'downloader', loader: () => import('@/modules/downloader/DownloaderModule.vue') },
{ id: 'finder', name: '文件搜索', icon: 'finder', loader: () => import('@/modules/finder/FinderModule.vue') },
{ id: 'settings', name: '常规设置', icon: 'settings', component: markRaw(GeneralSettings) }
]
/** 从注册表元信息转换为导航用的精简结构 */
const toNavModule = (meta: ModuleMeta): NavModule => ({
id: meta.id,
name: meta.name,
icon: meta.icon
})
const activeModule = ref('proxy')
// 当前激活的组件实例(shallowRef 适合大组件)
const activeComponent = shallowRef<Component | null>(null)
// 加载模块组件
/** 当前可显示的模块(内置模块 + 已启用的用户模块),按用户排序显示 */
const availableModules = computed<NavModule[]>(() => {
const enabledIds = appStore.enabledModules.map(m => m.id)
const allModules = moduleRegistry
.getAllMetas()
.filter(m => m.builtin || enabledIds.includes(m.id))
.map(toNavModule)
// 按 moduleOrder 排序,settings 始终在末尾
return allModules.sort((a, b) => {
if (a.id === 'settings') return 1
if (b.id === 'settings') return -1
const aIdx = appStore.moduleOrder.indexOf(a.id)
const bIdx = appStore.moduleOrder.indexOf(b.id)
if (aIdx === -1) return 1
if (bIdx === -1) return -1
return aIdx - bIdx
})
})
const loadModule = async (moduleId: string) => {
const m = modules.find(mod => mod.id === moduleId)
if (!m) {
activeComponent.value = null
return
}
if (m.component) {
activeComponent.value = m.component
return
}
if (m.loader) {
try {
const mod = await m.loader()
// 缓存到 component,避免重复加载
m.component = markRaw(mod.default)
activeComponent.value = m.component
} catch (e) {
console.error(`Failed to load module ${moduleId}:`, e)
}
}
const component = await moduleRegistry.loadComponent(moduleId)
activeComponent.value = component
// 调用模块的 onActivate 生命周期钩子
const config = moduleRegistry.getConfig(moduleId)
config?.lifecycle?.onActivate?.()
}
const handleModuleChange = (moduleId: string) => {
// 调用上一个模块的 onDeactivate 钩子
const prevConfig = moduleRegistry.getConfig(activeModule.value)
prevConfig?.lifecycle?.onDeactivate?.()
activeModule.value = moduleId
loadModule(moduleId)
}
@@ -68,8 +72,24 @@ const handleSearch = (moduleId: string) => {
loadModule(moduleId)
}
const getFallbackModule = () => {
const enabledIds = appStore.enabledModules.map(m => m.id)
const fallback = moduleRegistry.getAllMetas().find(
m => !m.builtin && enabledIds.includes(m.id)
)
return fallback?.id || 'settings'
}
watch(() => appStore.enabledModules.length, () => {
const enabledIds = appStore.enabledModules.map(m => m.id)
if (activeModule.value !== 'settings' && !enabledIds.includes(activeModule.value)) {
const fallback = getFallbackModule()
activeModule.value = fallback
loadModule(fallback)
}
})
onMounted(() => {
// 首次加载默认模块
loadModule(activeModule.value)
appStore.init().catch(e => console.error('App init error:', e))
})
@@ -78,15 +98,16 @@ onMounted(() => {
<template>
<TooltipProvider>
<div class="flex flex-col h-screen w-screen overflow-hidden">
<TitleBar :modules="modules" @search="handleSearch" />
<TitleBar :modules="availableModules" @search="handleSearch" />
<div class="flex-1 flex overflow-hidden">
<Sidebar
:modules="modules"
:active-module="activeModule"
<Sidebar
:modules="availableModules"
:active-module="activeModule"
@change="handleModuleChange"
/>
<ModuleContainer :active-component="activeComponent" :active-module="activeModule" />
</div>
</div>
<Toaster position="bottom-right" rich-colors close-button />
</TooltipProvider>
</template>
</template>