update
This commit is contained in:
@@ -3,16 +3,20 @@ import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
NCard, NButton, NSpace, NTag, NStatistic, NGrid, NGi,
|
||||
NEmpty, NSpin, NIcon, NSwitch, useMessage
|
||||
NEmpty, NSpin, NIcon, NSwitch, NTabs, NTabPane, useMessage
|
||||
} from 'naive-ui'
|
||||
import { ArrowBackOutline, ExtensionPuzzleOutline } from '@vicons/ionicons5'
|
||||
import { getPlugins, enablePlugin, disablePlugin } from '../api'
|
||||
import type { PluginInfo } from '../types'
|
||||
import { ArrowBackOutline, ExtensionPuzzleOutline, StorefrontOutline } from '@vicons/ionicons5'
|
||||
import { getPlugins, enablePlugin, disablePlugin, getStorePlugins } from '../api'
|
||||
import type { PluginInfo, StorePluginInfo } from '../types'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const plugins = ref<PluginInfo[]>([])
|
||||
const storePlugins = ref<StorePluginInfo[]>([])
|
||||
const storeUrl = ref('')
|
||||
const loading = ref(true)
|
||||
const storeLoading = ref(false)
|
||||
const activeTab = ref('installed')
|
||||
|
||||
const loadPlugins = async () => {
|
||||
try {
|
||||
@@ -25,6 +29,19 @@ const loadPlugins = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadStorePlugins = async () => {
|
||||
storeLoading.value = true
|
||||
try {
|
||||
const { data } = await getStorePlugins()
|
||||
storePlugins.value = data.plugins || []
|
||||
storeUrl.value = data.store_url || ''
|
||||
} catch (e) {
|
||||
console.error('Failed to load store plugins', e)
|
||||
} finally {
|
||||
storeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const proxyPlugins = computed(() =>
|
||||
plugins.value.filter(p => p.type === 'proxy')
|
||||
)
|
||||
@@ -68,6 +85,12 @@ const getTypeColor = (type: string) => {
|
||||
return colors[type] || 'default'
|
||||
}
|
||||
|
||||
const handleTabChange = (tab: string) => {
|
||||
if (tab === 'store' && storePlugins.value.length === 0) {
|
||||
loadStorePlugins()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadPlugins)
|
||||
</script>
|
||||
|
||||
@@ -75,8 +98,8 @@ onMounted(loadPlugins)
|
||||
<div class="plugins-view">
|
||||
<n-space justify="space-between" align="center" style="margin-bottom: 24px;">
|
||||
<div>
|
||||
<h2 style="margin: 0 0 8px 0;">插件管理</h2>
|
||||
<p style="margin: 0; color: #666;">查看和管理已注册的插件</p>
|
||||
<h2 style="margin: 0 0 8px 0;">扩展商店</h2>
|
||||
<p style="margin: 0; color: #666;">管理已安装扩展和浏览扩展商店</p>
|
||||
</div>
|
||||
<n-button quaternary @click="router.push('/')">
|
||||
<template #icon><n-icon><ArrowBackOutline /></n-icon></template>
|
||||
@@ -84,54 +107,92 @@ onMounted(loadPlugins)
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
<n-spin :show="loading">
|
||||
<n-grid :cols="3" :x-gap="16" :y-gap="16" style="margin-bottom: 24px;">
|
||||
<n-gi>
|
||||
<n-card>
|
||||
<n-statistic label="总插件数" :value="plugins.length" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-card>
|
||||
<n-statistic label="协议插件" :value="proxyPlugins.length" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-card>
|
||||
<n-statistic label="应用插件" :value="appPlugins.length" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-tabs v-model:value="activeTab" type="line" @update:value="handleTabChange">
|
||||
<!-- 已安装扩展 -->
|
||||
<n-tab-pane name="installed" tab="已安装">
|
||||
<n-spin :show="loading">
|
||||
<n-grid :cols="3" :x-gap="16" :y-gap="16" style="margin-bottom: 24px;">
|
||||
<n-gi>
|
||||
<n-card>
|
||||
<n-statistic label="总插件数" :value="plugins.length" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-card>
|
||||
<n-statistic label="协议插件" :value="proxyPlugins.length" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-card>
|
||||
<n-statistic label="应用插件" :value="appPlugins.length" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-empty v-if="!loading && plugins.length === 0" description="暂无插件" />
|
||||
<n-empty v-if="!loading && plugins.length === 0" description="暂无已安装扩展" />
|
||||
|
||||
<n-grid v-else :cols="3" :x-gap="16" :y-gap="16" responsive="screen" cols-s="1" cols-m="2">
|
||||
<n-gi v-for="plugin in plugins" :key="plugin.name">
|
||||
<n-card hoverable>
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<n-icon size="24" color="#18a058"><ExtensionPuzzleOutline /></n-icon>
|
||||
<span>{{ plugin.name }}</span>
|
||||
</n-space>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<n-switch :value="plugin.enabled" @update:value="togglePlugin(plugin)" />
|
||||
</template>
|
||||
<n-space vertical :size="8">
|
||||
<n-space>
|
||||
<n-tag size="small">v{{ plugin.version }}</n-tag>
|
||||
<n-tag size="small" :type="getTypeColor(plugin.type)">
|
||||
{{ getTypeLabel(plugin.type) }}
|
||||
</n-tag>
|
||||
<n-tag size="small" :type="plugin.source === 'builtin' ? 'default' : 'warning'">
|
||||
{{ plugin.source === 'builtin' ? '内置' : 'WASM' }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
<p style="margin: 0; color: #666;">{{ plugin.description }}</p>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-spin>
|
||||
<n-grid v-else :cols="3" :x-gap="16" :y-gap="16" responsive="screen" cols-s="1" cols-m="2">
|
||||
<n-gi v-for="plugin in plugins" :key="plugin.name">
|
||||
<n-card hoverable>
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<img v-if="plugin.icon" :src="plugin.icon" style="width: 24px; height: 24px;" />
|
||||
<n-icon v-else size="24" color="#18a058"><ExtensionPuzzleOutline /></n-icon>
|
||||
<span>{{ plugin.name }}</span>
|
||||
</n-space>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<n-switch :value="plugin.enabled" @update:value="togglePlugin(plugin)" />
|
||||
</template>
|
||||
<n-space vertical :size="8">
|
||||
<n-space>
|
||||
<n-tag size="small">v{{ plugin.version }}</n-tag>
|
||||
<n-tag size="small" :type="getTypeColor(plugin.type)">
|
||||
{{ getTypeLabel(plugin.type) }}
|
||||
</n-tag>
|
||||
<n-tag size="small" :type="plugin.source === 'builtin' ? 'default' : 'warning'">
|
||||
{{ plugin.source === 'builtin' ? '内置' : 'WASM' }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
<p style="margin: 0; color: #666;">{{ plugin.description }}</p>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-spin>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- 扩展商店 -->
|
||||
<n-tab-pane name="store" tab="扩展商店">
|
||||
<n-spin :show="storeLoading">
|
||||
<n-empty v-if="!storeUrl" description="未配置扩展商店URL,请在配置文件中设置 plugin_store.url" />
|
||||
<n-empty v-else-if="!storeLoading && storePlugins.length === 0" description="扩展商店暂无可用扩展" />
|
||||
|
||||
<n-grid v-else :cols="3" :x-gap="16" :y-gap="16" responsive="screen" cols-s="1" cols-m="2">
|
||||
<n-gi v-for="plugin in storePlugins" :key="plugin.name">
|
||||
<n-card hoverable>
|
||||
<template #header>
|
||||
<n-space align="center">
|
||||
<img v-if="plugin.icon" :src="plugin.icon" style="width: 24px; height: 24px;" />
|
||||
<n-icon v-else size="24" color="#18a058"><StorefrontOutline /></n-icon>
|
||||
<span>{{ plugin.name }}</span>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-space vertical :size="8">
|
||||
<n-space>
|
||||
<n-tag size="small">v{{ plugin.version }}</n-tag>
|
||||
<n-tag size="small" :type="getTypeColor(plugin.type)">
|
||||
{{ getTypeLabel(plugin.type) }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
<p style="margin: 0; color: #666;">{{ plugin.description }}</p>
|
||||
<p style="margin: 0; color: #999; font-size: 12px;">作者: {{ plugin.author }}</p>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-spin>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user