mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-04 06:31:13 +00:00
移除插件相关接口中的可选主页字段,并优化展示组件以简化代码结构。更新了插件展示卡片的样式,确保更好的用户体验。
This commit is contained in:
parent
8a0912b5b9
commit
1d22f19fa6
@ -13,7 +13,6 @@ export interface PluginPackageJson {
|
||||
main?: string;
|
||||
description?: string;
|
||||
author?: string;
|
||||
homepage?: string;
|
||||
}
|
||||
|
||||
// ==================== 插件配置 Schema ====================
|
||||
|
||||
@ -72,7 +72,6 @@ export const GetPluginListHandler: RequestHandler = async (_req, res) => {
|
||||
version: string;
|
||||
description: string;
|
||||
author: string;
|
||||
homepage?: string;
|
||||
status: string;
|
||||
hasConfig: boolean;
|
||||
hasPages: boolean;
|
||||
@ -110,7 +109,6 @@ 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
|
||||
|
||||
@ -4,7 +4,7 @@ import clsx from 'clsx';
|
||||
import key from '@/const/key';
|
||||
|
||||
export interface ContainerProps {
|
||||
title: string;
|
||||
title: React.ReactNode;
|
||||
tag?: React.ReactNode;
|
||||
action: React.ReactNode;
|
||||
enableSwitch: React.ReactNode;
|
||||
|
||||
@ -2,8 +2,8 @@ import { Button } from '@heroui/button';
|
||||
import { Chip } from '@heroui/chip';
|
||||
import { Tooltip } from '@heroui/tooltip';
|
||||
import { useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { IoMdDownload, IoMdRefresh, IoMdCheckmarkCircle } from 'react-icons/io';
|
||||
import { FaGithub } from 'react-icons/fa';
|
||||
|
||||
import DisplayCardContainer from './container';
|
||||
import { PluginStoreItem } from '@/types/plugin-store';
|
||||
@ -59,28 +59,32 @@ const PluginStoreCard: React.FC<PluginStoreCardProps> = ({
|
||||
return (
|
||||
<DisplayCardContainer
|
||||
className='w-full max-w-[420px]'
|
||||
title={name}
|
||||
title={
|
||||
<div className="flex items-baseline gap-2">
|
||||
{homepage ? (
|
||||
<Tooltip content="打开插件主页">
|
||||
<span
|
||||
className="cursor-pointer hover:text-default-900 dark:hover:text-white transition-colors underline decoration-dashed decoration-default-400/70 underline-offset-4 hover:decoration-default-600"
|
||||
onClick={() => window.open(homepage, '_blank')}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span>{name}</span>
|
||||
)}
|
||||
<span className="text-[10px] font-normal text-default-400">v{version}</span>
|
||||
</div>
|
||||
}
|
||||
tag={
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
{homepage && (
|
||||
<Tooltip content="仓库主页">
|
||||
<Button
|
||||
isIconOnly
|
||||
size='sm'
|
||||
variant='light'
|
||||
className='min-w-6 w-6 h-6 text-default-500 hover:text-default-700'
|
||||
onPress={() => window.open(homepage, '_blank')}
|
||||
>
|
||||
<FaGithub size={14} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{installStatus === 'installed' && (
|
||||
<Chip
|
||||
color="success"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
startContent={<IoMdCheckmarkCircle size={14} />}
|
||||
className="h-6 text-[10px] bg-success-50 dark:bg-success-500/10 text-success-600"
|
||||
startContent={<IoMdCheckmarkCircle size={12} />}
|
||||
>
|
||||
已安装
|
||||
</Chip>
|
||||
@ -90,17 +94,11 @@ const PluginStoreCard: React.FC<PluginStoreCardProps> = ({
|
||||
color="warning"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
className="h-6 text-[10px] bg-warning-50 dark:bg-warning-500/10 text-warning-600"
|
||||
>
|
||||
可更新
|
||||
</Chip>
|
||||
)}
|
||||
<Chip
|
||||
color="primary"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
>
|
||||
v{version}
|
||||
</Chip>
|
||||
</div>
|
||||
}
|
||||
enableSwitch={undefined}
|
||||
@ -119,38 +117,46 @@ const PluginStoreCard: React.FC<PluginStoreCardProps> = ({
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className='flex flex-col gap-2 h-[120px]'>
|
||||
<div className='flex flex-col gap-2 h-[132px]'>
|
||||
{/* 作者和包名 */}
|
||||
<div className='flex items-center gap-2 text-xs text-default-500 dark:text-white/50'>
|
||||
<span>作者: <span className='text-default-700 dark:text-white/80'>{author || '未知'}</span></span>
|
||||
<span className='text-default-300'>·</span>
|
||||
<div className='flex items-center gap-2 text-[12px] text-default-400'>
|
||||
<span className="flex items-center gap-1">
|
||||
作者: <span className='text-default-600 dark:text-white/70 font-medium'>{author || '未知'}</span>
|
||||
</span>
|
||||
<span className='text-default-300'>/</span>
|
||||
<Tooltip content={id}>
|
||||
<span className='truncate max-w-[150px]'>{id}</span>
|
||||
<span className='truncate max-w-[140px] opacity-70 italic'>{id}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* 描述 */}
|
||||
<div className='flex-1 p-3 bg-default-100/50 dark:bg-white/10 rounded-xl'>
|
||||
<div className='text-sm text-default-700 dark:text-white/90 break-words line-clamp-2'>
|
||||
{description || '暂无描述'}
|
||||
<Tooltip content={description || '暂无描述'}>
|
||||
<div className='h-[62px] p-3 bg-default-100/30 dark:bg-white/5 rounded-xl border border-default-100 dark:border-white/5 flex items-center'>
|
||||
<div className='text-[14px] leading-relaxed text-default-600 dark:text-white/80 break-words line-clamp-2 text-center w-full'>
|
||||
{description || '暂无描述'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* 标签 */}
|
||||
<div className='flex flex-wrap gap-1 min-h-[24px]'>
|
||||
{/* 标签栏 - 优化后的极简风格 */}
|
||||
<div className='flex flex-wrap gap-1.5 min-h-[20px] items-center pt-1'>
|
||||
{tags && tags.length > 0 ? (
|
||||
tags.slice(0, 3).map((tag, index) => (
|
||||
<Chip
|
||||
tags.map((tag, index) => (
|
||||
<div
|
||||
key={index}
|
||||
size='sm'
|
||||
variant='flat'
|
||||
className='text-xs'
|
||||
className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-default-100/50 dark:bg-white/10 text-[11px] text-default-500 dark:text-white/60 border border-transparent hover:border-default-200 transition-all"
|
||||
>
|
||||
<span className={clsx(
|
||||
"w-1 h-1 rounded-full",
|
||||
tag === '官方' ? "bg-secondary-400" :
|
||||
tag === '工具' ? "bg-primary-400" :
|
||||
tag === '娱乐' ? "bg-warning-400" : "bg-default-400"
|
||||
)} />
|
||||
{tag}
|
||||
</Chip>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className='text-xs text-default-400'>暂无标签</span>
|
||||
<span className='text-[10px] text-default-300 italic'>no tags</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -16,8 +16,6 @@ export interface PluginItem {
|
||||
description: string;
|
||||
/** 作者 */
|
||||
author: string;
|
||||
/** 主页链接 */
|
||||
homepage?: string;
|
||||
/** 状态: active-运行中, disabled-已禁用, stopped-已停止 */
|
||||
status: PluginStatus;
|
||||
/** 是否有配置项 */
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Button } from '@heroui/button';
|
||||
import { useEffect, useState, useRef, useMemo } from 'react';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { IoMdRefresh } from 'react-icons/io';
|
||||
import { FiUpload } from 'react-icons/fi';
|
||||
@ -10,11 +10,9 @@ 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<PluginItem[]>([]);
|
||||
const [storePlugins, setStorePlugins] = useState<PluginStoreItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [pluginManagerNotFound, setPluginManagerNotFound] = useState(false);
|
||||
const dialog = useDialog();
|
||||
@ -27,11 +25,7 @@ export default function PluginPage () {
|
||||
setLoading(true);
|
||||
setPluginManagerNotFound(false);
|
||||
try {
|
||||
// 并行加载本地插件列表和商店插件列表
|
||||
const [listResult, storeResult] = await Promise.all([
|
||||
PluginManager.getPluginList(),
|
||||
PluginManager.getPluginStoreList().catch(() => ({ plugins: [] }))
|
||||
]);
|
||||
const listResult = await PluginManager.getPluginList();
|
||||
|
||||
if (listResult.pluginManagerNotFound) {
|
||||
setPluginManagerNotFound(true);
|
||||
@ -39,7 +33,6 @@ export default function PluginPage () {
|
||||
} else {
|
||||
setPlugins(listResult.plugins);
|
||||
}
|
||||
setStorePlugins(storeResult.plugins || []);
|
||||
} catch (e: any) {
|
||||
toast.error(e.message);
|
||||
} finally {
|
||||
@ -47,25 +40,6 @@ export default function PluginPage () {
|
||||
}
|
||||
};
|
||||
|
||||
// 创建一个 Map 用于快速查找商店插件的 homepage
|
||||
const storeHomepageMap = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
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();
|
||||
}, []);
|
||||
@ -198,7 +172,6 @@ export default function PluginPage () {
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
pluginId={currentPluginId}
|
||||
homepage={storeHomepageMap.get(currentPluginId)}
|
||||
/>
|
||||
|
||||
<div className='flex mb-6 items-center gap-4'>
|
||||
@ -238,11 +211,11 @@ export default function PluginPage () {
|
||||
插件管理器未加载,请检查 plugins 目录是否存在
|
||||
</p>
|
||||
</div>
|
||||
) : pluginsWithHomepage.length === 0 ? (
|
||||
) : plugins.length === 0 ? (
|
||||
<div className="text-default-400">暂时没有安装插件</div>
|
||||
) : (
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 justify-start items-stretch gap-x-2 gap-y-4'>
|
||||
{pluginsWithHomepage.map(plugin => (
|
||||
{plugins.map(plugin => (
|
||||
<PluginDisplayCard
|
||||
key={plugin.id}
|
||||
data={plugin}
|
||||
|
||||
@ -3,11 +3,9 @@ import { Button } from '@heroui/button';
|
||||
import { Input } from '@heroui/input';
|
||||
import { Select, SelectItem } from '@heroui/select';
|
||||
import { Switch } from '@heroui/switch';
|
||||
import { Tooltip } from '@heroui/tooltip';
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { IoMdOpen } from 'react-icons/io';
|
||||
import PluginManager, { PluginConfigSchemaItem } from '@/controllers/plugin_manager';
|
||||
import key from '@/const/key';
|
||||
|
||||
@ -16,8 +14,6 @@ interface Props {
|
||||
onOpenChange: () => void;
|
||||
/** 插件包名 (id) */
|
||||
pluginId: string;
|
||||
/** 插件主页 URL */
|
||||
homepage?: string;
|
||||
}
|
||||
|
||||
/** Schema 更新事件类型 */
|
||||
@ -29,7 +25,7 @@ interface SchemaUpdateEvent {
|
||||
afterKey?: string;
|
||||
}
|
||||
|
||||
export default function PluginConfigModal ({ isOpen, onOpenChange, pluginId, homepage }: Props) {
|
||||
export default function PluginConfigModal ({ isOpen, onOpenChange, pluginId }: Props) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [schema, setSchema] = useState<PluginConfigSchemaItem[]>([]);
|
||||
const [config, setConfig] = useState<Record<string, unknown>>({});
|
||||
@ -377,21 +373,6 @@ export default function PluginConfigModal ({ isOpen, onOpenChange, pluginId, hom
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{homepage && (
|
||||
<Tooltip content="反馈问题">
|
||||
<Button
|
||||
isIconOnly
|
||||
variant="light"
|
||||
onPress={() => {
|
||||
const issueUrl = homepage.includes('github.com') ? `${homepage}/issues` : homepage;
|
||||
window.open(issueUrl, '_blank');
|
||||
}}
|
||||
className="mr-auto"
|
||||
>
|
||||
<IoMdOpen size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Button color="danger" variant="light" onPress={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user