mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-21 18:19:03 +08:00
feat: 实现依赖注入系统、插件架构和窗口管理
- 引入ExamAware的DI系统并适配SecScore项目 - 添加服务注册表和窗口管理器 - 实现基于Koishi的Disposable设计的插件系统 - 集成Winston日志服务 - 分离设置窗口为独立页面 - 将非核心功能改为内置插件 - 更新README文件 - 添加相关hooks和工具函数
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import { useEffect, useState, useCallback } from "react"
|
||||
import {
|
||||
BuiltinPluginMeta,
|
||||
getAvailablePlugins,
|
||||
getInstalledPlugins,
|
||||
installPlugin,
|
||||
uninstallPlugin,
|
||||
enablePlugin,
|
||||
disablePlugin,
|
||||
isPluginEnabled,
|
||||
} from "../plugins/builtin"
|
||||
|
||||
/**
|
||||
* React Hook: 使用内置插件系统
|
||||
*/
|
||||
export function useBuiltinPlugins() {
|
||||
const [plugins, setPlugins] = useState<
|
||||
Array<BuiltinPluginMeta & { installed: boolean; enabled: boolean }>
|
||||
>([])
|
||||
const [installedPlugins, setInstalledPlugins] = useState<
|
||||
Array<BuiltinPluginMeta & { enabled: boolean; installedAt?: number }>
|
||||
>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const refreshPlugins = useCallback(async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const [available, installed] = await Promise.all([
|
||||
getAvailablePlugins(),
|
||||
getInstalledPlugins(),
|
||||
])
|
||||
setPlugins(available)
|
||||
setInstalledPlugins(installed)
|
||||
} catch (error) {
|
||||
console.error("Failed to refresh plugins:", error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
refreshPlugins()
|
||||
|
||||
// 监听插件状态变化
|
||||
const handleStateChanged = () => {
|
||||
refreshPlugins()
|
||||
}
|
||||
|
||||
window.addEventListener("ss:plugins-state-changed", handleStateChanged)
|
||||
window.addEventListener("ss:plugin-installed", handleStateChanged)
|
||||
window.addEventListener("ss:plugin-uninstalled", handleStateChanged)
|
||||
window.addEventListener("ss:plugin-enabled", handleStateChanged)
|
||||
window.addEventListener("ss:plugin-disabled", handleStateChanged)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("ss:plugins-state-changed", handleStateChanged)
|
||||
window.removeEventListener("ss:plugin-installed", handleStateChanged)
|
||||
window.removeEventListener("ss:plugin-uninstalled", handleStateChanged)
|
||||
window.removeEventListener("ss:plugin-enabled", handleStateChanged)
|
||||
window.removeEventListener("ss:plugin-disabled", handleStateChanged)
|
||||
}
|
||||
}, [refreshPlugins])
|
||||
|
||||
const install = useCallback(
|
||||
async (pluginId: string) => {
|
||||
const result = await installPlugin(pluginId)
|
||||
if (result) {
|
||||
await refreshPlugins()
|
||||
}
|
||||
return result
|
||||
},
|
||||
[refreshPlugins]
|
||||
)
|
||||
|
||||
const uninstall = useCallback(
|
||||
async (pluginId: string) => {
|
||||
const result = await uninstallPlugin(pluginId)
|
||||
if (result) {
|
||||
await refreshPlugins()
|
||||
}
|
||||
return result
|
||||
},
|
||||
[refreshPlugins]
|
||||
)
|
||||
|
||||
const enable = useCallback(
|
||||
async (pluginId: string) => {
|
||||
const result = await enablePlugin(pluginId)
|
||||
if (result) {
|
||||
await refreshPlugins()
|
||||
}
|
||||
return result
|
||||
},
|
||||
[refreshPlugins]
|
||||
)
|
||||
|
||||
const disable = useCallback(
|
||||
async (pluginId: string) => {
|
||||
const result = await disablePlugin(pluginId)
|
||||
if (result) {
|
||||
await refreshPlugins()
|
||||
}
|
||||
return result
|
||||
},
|
||||
[refreshPlugins]
|
||||
)
|
||||
|
||||
const isEnabled = useCallback(async (pluginId: string) => {
|
||||
return await isPluginEnabled(pluginId)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
plugins,
|
||||
installedPlugins,
|
||||
loading,
|
||||
refreshPlugins,
|
||||
install,
|
||||
uninstall,
|
||||
enable,
|
||||
disable,
|
||||
isEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* React Hook: 检查特定插件是否启用
|
||||
*/
|
||||
export function usePluginEnabled(pluginId: string): boolean {
|
||||
const [enabled, setEnabled] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
const checkEnabled = async () => {
|
||||
try {
|
||||
const result = await isPluginEnabled(pluginId)
|
||||
if (mounted) {
|
||||
setEnabled(result)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to check plugin ${pluginId} state:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
checkEnabled()
|
||||
|
||||
const handleStateChanged = () => {
|
||||
checkEnabled()
|
||||
}
|
||||
|
||||
window.addEventListener("ss:plugins-state-changed", handleStateChanged)
|
||||
window.addEventListener("ss:plugin-enabled", handleStateChanged)
|
||||
window.addEventListener("ss:plugin-disabled", handleStateChanged)
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
window.removeEventListener("ss:plugins-state-changed", handleStateChanged)
|
||||
window.removeEventListener("ss:plugin-enabled", handleStateChanged)
|
||||
window.removeEventListener("ss:plugin-disabled", handleStateChanged)
|
||||
}
|
||||
}, [pluginId])
|
||||
|
||||
return enabled
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useEffect, useState, useCallback } from "react"
|
||||
import { ConfigService, ConfigSpec } from "../services/ConfigService"
|
||||
|
||||
export function useConfig<K extends keyof ConfigSpec>(
|
||||
key: K,
|
||||
defaultValue?: ConfigSpec[K]
|
||||
): [ConfigSpec[K], (value: ConfigSpec[K]) => Promise<void>, boolean] {
|
||||
const [value, setValue] = useState<ConfigSpec[K]>(
|
||||
ConfigService.has(key) ? ConfigService.getAll()[key] : (defaultValue as ConfigSpec[K])
|
||||
)
|
||||
const [loading, setLoading] = useState(!ConfigService.has(key))
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
await ConfigService.initialize()
|
||||
const currentValue = ConfigService.getAll()[key]
|
||||
if (mounted) {
|
||||
setValue(currentValue ?? defaultValue)
|
||||
setLoading(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load config:", error)
|
||||
if (mounted) {
|
||||
setValue(defaultValue as ConfigSpec[K])
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
const unsubscribe = ConfigService.onChange((event) => {
|
||||
if (event.key === key && mounted) {
|
||||
setValue(event.value)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
unsubscribe()
|
||||
}
|
||||
}, [key, defaultValue])
|
||||
|
||||
const updateValue = useCallback(
|
||||
async (newValue: ConfigSpec[K]) => {
|
||||
try {
|
||||
await ConfigService.set(key, newValue)
|
||||
setValue(newValue)
|
||||
} catch (error) {
|
||||
console.error("Failed to update config:", error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
[key]
|
||||
)
|
||||
|
||||
return [value, updateValue, loading]
|
||||
}
|
||||
|
||||
export function useConfigAll(): [ConfigSpec, (key: keyof ConfigSpec, value: any) => Promise<void>] {
|
||||
const [config, setConfig] = useState<ConfigSpec>(ConfigService.getAll())
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
await ConfigService.initialize()
|
||||
if (mounted) {
|
||||
setConfig(ConfigService.getAll())
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load config:", error)
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
const unsubscribe = ConfigService.onChange(() => {
|
||||
if (mounted) {
|
||||
setConfig(ConfigService.getAll())
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
unsubscribe()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const updateConfig = useCallback(async (key: keyof ConfigSpec, value: any) => {
|
||||
await ConfigService.set(key, value)
|
||||
}, [])
|
||||
|
||||
return [config, updateConfig]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useCallback } from "react"
|
||||
import { logger, LogLevel } from "../services/LoggerService"
|
||||
|
||||
/**
|
||||
* React Hook: 使用日志服务
|
||||
*/
|
||||
export function useLogger(source?: string) {
|
||||
const log = useCallback(
|
||||
(_level: LogLevel, message: string, meta?: any) => {
|
||||
if (source) {
|
||||
const childLogger = logger.createChild(source)
|
||||
childLogger.debug(message, meta)
|
||||
} else {
|
||||
logger.debug(message, meta)
|
||||
}
|
||||
},
|
||||
[source]
|
||||
)
|
||||
|
||||
const debug = useCallback(
|
||||
(message: string, meta?: any) => {
|
||||
log("debug", message, meta)
|
||||
},
|
||||
[log]
|
||||
)
|
||||
|
||||
const info = useCallback(
|
||||
(message: string, meta?: any) => {
|
||||
log("info", message, meta)
|
||||
},
|
||||
[log]
|
||||
)
|
||||
|
||||
const warn = useCallback(
|
||||
(message: string, meta?: any) => {
|
||||
log("warn", message, meta)
|
||||
},
|
||||
[log]
|
||||
)
|
||||
|
||||
const error = useCallback(
|
||||
(message: string, meta?: any) => {
|
||||
log("error", message, meta)
|
||||
},
|
||||
[log]
|
||||
)
|
||||
|
||||
return {
|
||||
debug,
|
||||
info,
|
||||
warn,
|
||||
error,
|
||||
log,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user