主界面修改及代理模块初始化
This commit is contained in:
+65
-44
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user