diff --git a/packages/napcat-onebot/network/plugin/types.ts b/packages/napcat-onebot/network/plugin/types.ts index 448b276b..5469816d 100644 --- a/packages/napcat-onebot/network/plugin/types.ts +++ b/packages/napcat-onebot/network/plugin/types.ts @@ -13,6 +13,7 @@ export interface PluginPackageJson { main?: string; description?: string; author?: string; + homepage?: string; } // ==================== 插件配置 Schema ==================== diff --git a/packages/napcat-webui-backend/src/api/Plugin.ts b/packages/napcat-webui-backend/src/api/Plugin.ts index 0367ba2b..d1ca0b98 100644 --- a/packages/napcat-webui-backend/src/api/Plugin.ts +++ b/packages/napcat-webui-backend/src/api/Plugin.ts @@ -72,6 +72,7 @@ export const GetPluginListHandler: RequestHandler = async (_req, res) => { version: string; description: string; author: string; + homepage?: string; status: string; hasConfig: boolean; hasPages: boolean; @@ -109,6 +110,7 @@ export const GetPluginListHandler: RequestHandler = async (_req, res) => { version: p.version || '0.0.0', description: p.packageJson?.description || '', author: p.packageJson?.author || '', + homepage: p.packageJson?.homepage, status, hasConfig: !!(p.runtime.module?.plugin_config_schema || p.runtime.module?.plugin_config_ui), hasPages diff --git a/packages/napcat-webui-frontend/src/components/display_card/container.tsx b/packages/napcat-webui-frontend/src/components/display_card/container.tsx index 790d09b9..806bbe07 100644 --- a/packages/napcat-webui-frontend/src/components/display_card/container.tsx +++ b/packages/napcat-webui-frontend/src/components/display_card/container.tsx @@ -22,6 +22,7 @@ export interface DisplayCardProps { const DisplayCardContainer: React.FC = ({ title: _title, + tag, action, enableSwitch, children, @@ -45,7 +46,8 @@ const DisplayCardContainer: React.FC = ({ -
{enableSwitch}
+ {tag &&
{tag}
} + {enableSwitch &&
{enableSwitch}
} {children} {action} diff --git a/packages/napcat-webui-frontend/src/components/display_card/plugin_card.tsx b/packages/napcat-webui-frontend/src/components/display_card/plugin_card.tsx index e79ab221..d884dc92 100644 --- a/packages/napcat-webui-frontend/src/components/display_card/plugin_card.tsx +++ b/packages/napcat-webui-frontend/src/components/display_card/plugin_card.tsx @@ -1,6 +1,7 @@ import { Button } from '@heroui/button'; import { Switch } from '@heroui/switch'; import { Chip } from '@heroui/chip'; +import { Tooltip } from '@heroui/tooltip'; import { useState } from 'react'; import { MdDeleteForever, MdSettings } from 'react-icons/md'; @@ -41,28 +42,27 @@ const PluginDisplayCard: React.FC = ({ -
+
+ -
+ {hasConfig && ( + + )} {installStatus === 'installed' && ( = ({ } > -
-
- - 作者 - -
- {author || '未知'} -
+
+ {/* 作者和包名 */} +
+ 作者: {author || '未知'} + · + + {id} +
-
- - 版本 - -
- v{version} -
-
-
- - 描述 - -
+ + {/* 描述 */} +
+
{description || '暂无描述'}
- {id && ( -
- - 包名 - -
- {id || '包名'} -
-
- )} - {tags && tags.length > 0 && ( -
- - 标签 - -
- {tags.slice(0, 2).join(' · ')} -
-
- )} + + {/* 标签 */} +
+ {tags && tags.length > 0 ? ( + tags.slice(0, 3).map((tag, index) => ( + + {tag} + + )) + ) : ( + 暂无标签 + )} +
); diff --git a/packages/napcat-webui-frontend/src/controllers/plugin_manager.ts b/packages/napcat-webui-frontend/src/controllers/plugin_manager.ts index 1e36a917..d805ad3d 100644 --- a/packages/napcat-webui-frontend/src/controllers/plugin_manager.ts +++ b/packages/napcat-webui-frontend/src/controllers/plugin_manager.ts @@ -16,6 +16,8 @@ export interface PluginItem { description: string; /** 作者 */ author: string; + /** 主页链接 */ + homepage?: string; /** 状态: active-运行中, disabled-已禁用, stopped-已停止 */ status: PluginStatus; /** 是否有配置项 */ diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/plugin.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/plugin.tsx index 667ab4b6..3857fecb 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/plugin.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/plugin.tsx @@ -1,5 +1,5 @@ import { Button } from '@heroui/button'; -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useState, useRef, useMemo } from 'react'; import toast from 'react-hot-toast'; import { IoMdRefresh } from 'react-icons/io'; import { FiUpload } from 'react-icons/fi'; @@ -10,9 +10,11 @@ import PluginDisplayCard from '@/components/display_card/plugin_card'; import PluginManager, { PluginItem } from '@/controllers/plugin_manager'; import useDialog from '@/hooks/use-dialog'; import PluginConfigModal from '@/pages/dashboard/plugin_config_modal'; +import { PluginStoreItem } from '@/types/plugin-store'; export default function PluginPage () { const [plugins, setPlugins] = useState([]); + const [storePlugins, setStorePlugins] = useState([]); const [loading, setLoading] = useState(false); const [pluginManagerNotFound, setPluginManagerNotFound] = useState(false); const dialog = useDialog(); @@ -25,7 +27,11 @@ export default function PluginPage () { setLoading(true); setPluginManagerNotFound(false); try { - const listResult = await PluginManager.getPluginList(); + // 并行加载本地插件列表和商店插件列表 + const [listResult, storeResult] = await Promise.all([ + PluginManager.getPluginList(), + PluginManager.getPluginStoreList().catch(() => ({ plugins: [] })) + ]); if (listResult.pluginManagerNotFound) { setPluginManagerNotFound(true); @@ -33,6 +39,7 @@ export default function PluginPage () { } else { setPlugins(listResult.plugins); } + setStorePlugins(storeResult.plugins || []); } catch (e: any) { toast.error(e.message); } finally { @@ -40,6 +47,25 @@ export default function PluginPage () { } }; + // 创建一个 Map 用于快速查找商店插件的 homepage + const storeHomepageMap = useMemo(() => { + const map = new Map(); + for (const plugin of storePlugins) { + if (plugin.homepage) { + map.set(plugin.id, plugin.homepage); + } + } + return map; + }, [storePlugins]); + + // 合并本地插件和商店数据中的 homepage + const pluginsWithHomepage = useMemo(() => { + return plugins.map(plugin => ({ + ...plugin, + homepage: plugin.homepage || storeHomepageMap.get(plugin.id) + })); + }, [plugins, storeHomepageMap]); + useEffect(() => { loadPlugins(); }, []); @@ -172,6 +198,7 @@ export default function PluginPage () { isOpen={isOpen} onOpenChange={onOpenChange} pluginId={currentPluginId} + homepage={storeHomepageMap.get(currentPluginId)} />
@@ -211,11 +238,11 @@ export default function PluginPage () { 插件管理器未加载,请检查 plugins 目录是否存在

- ) : plugins.length === 0 ? ( + ) : pluginsWithHomepage.length === 0 ? (
暂时没有安装插件
) : (
- {plugins.map(plugin => ( + {pluginsWithHomepage.map(plugin => ( void; /** 插件包名 (id) */ pluginId: string; + /** 插件主页 URL */ + homepage?: string; } /** Schema 更新事件类型 */ @@ -25,7 +29,7 @@ interface SchemaUpdateEvent { afterKey?: string; } -export default function PluginConfigModal ({ isOpen, onOpenChange, pluginId }: Props) { +export default function PluginConfigModal ({ isOpen, onOpenChange, pluginId, homepage }: Props) { const [loading, setLoading] = useState(false); const [schema, setSchema] = useState([]); const [config, setConfig] = useState>({}); @@ -373,6 +377,21 @@ export default function PluginConfigModal ({ isOpen, onOpenChange, pluginId }: P )} + {homepage && ( + + + + )}