feat: complete unified plugin admin v1.1.0
Business Plugins CI / check (plugin-admin) (push) Successful in 1m42s
Business Plugins CI / check (subscription-admin) (push) Successful in 1m30s

This commit is contained in:
Qiufeng
2026-08-30 12:10:04 +08:00
parent 3c1a17f4d7
commit ada4ab3c21
69 changed files with 6681 additions and 901 deletions
@@ -0,0 +1,94 @@
<template>
<t-layout class="app-shell">
<div v-if="mobileOpen" class="mobile-scrim" @click="mobileOpen = false" />
<t-aside :class="['app-aside', { 'app-aside-mobile-open': mobileOpen }]" width="252px">
<div class="brand">
<div class="brand-mark"><ControlPlatformIcon size="22px" /></div>
<div>
<div class="brand-title">Sub2API</div>
<div class="brand-subtitle">插件控制面</div>
</div>
</div>
<div class="nav-caption">控制台</div>
<t-menu :value="activeMenu" class="main-menu" @change="onMenuChange">
<t-menu-item value="/overview"><template #icon><DashboardIcon /></template>概览</t-menu-item>
<t-menu-item value="/plugins"><template #icon><AppIcon /></template>已安装插件</t-menu-item>
<t-menu-item value="/marketplace"><template #icon><ShopIcon /></template>插件市场</t-menu-item>
<t-menu-item value="/operations"><template #icon><HistoryIcon /></template>操作记录</t-menu-item>
<template v-if="subscriptionEnabled">
<t-divider class="nav-divider" />
<div class="nav-caption nav-caption-module">业务模块</div>
</template>
<t-menu-item v-if="subscriptionEnabled" value="/modules/subscription/overview">
<template #icon><ChartLineIcon /></template>订阅管理
</t-menu-item>
</t-menu>
<div class="aside-footer">
<t-tag theme="success" variant="light" class="status-tag"><CheckCircleIcon size="14px" /> Core 会话正常</t-tag>
<span class="version-text">Plugin Admin v1.1.0</span>
</div>
</t-aside>
<t-layout class="main-layout">
<t-header class="app-header">
<div class="header-left">
<t-button class="mobile-menu-button" variant="text" shape="square" aria-label="打开导航" @click="mobileOpen = true">
<template #icon><MenuIcon /></template>
</t-button>
<div class="breadcrumbs"><span>插件管理</span><ChevronRightIcon size="15px" /><strong>{{ pageTitle }}</strong></div>
</div>
<div class="header-right">
<t-tag theme="success" variant="light" class="secure-tag"><CheckCircleIcon size="14px" /> 安全会话</t-tag>
<t-dropdown :options="userOptions" trigger="click" @click="onUserAction">
<t-button variant="text" class="user-button">
<template #icon><UserCircleIcon /></template>
<span class="user-name">{{ displayName }}</span><ChevronDownIcon size="15px" />
</t-button>
</t-dropdown>
</div>
</t-header>
<t-content class="app-content"><router-view /></t-content>
</t-layout>
</t-layout>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
AppIcon, ChartLineIcon, CheckCircleIcon, ChevronDownIcon, ChevronRightIcon, ControlPlatformIcon,
DashboardIcon, HistoryIcon, MenuIcon, ShopIcon, UserCircleIcon,
} from 'tdesign-icons-vue-next'
import { MessagePlugin } from 'tdesign-vue-next'
import { authState, loadSubscriptionState, logout, moduleState } from '../services/api'
const route = useRoute()
const router = useRouter()
const mobileOpen = ref(false)
const subscriptionEnabled = computed(() => moduleState.subscriptionEnabled)
const pageTitle = computed(() => String(route.meta.title || (route.path.startsWith('/modules/subscription') ? '订阅管理' : '概览')))
const activeMenu = computed(() => route.path.startsWith('/plugins') ? '/plugins' : route.path.startsWith('/modules/subscription') ? '/modules/subscription/overview' : route.path)
const displayName = computed(() => String(authState.user?.email || authState.user?.username || '管理员'))
const userOptions = [{ content: '退出登录', value: 'logout' }]
async function loadModules() {
await loadSubscriptionState(true)
}
function onMenuChange(value: string | number) {
const target = String(value)
if (target === '/modules/subscription/overview' && !subscriptionEnabled.value) {
MessagePlugin.warning('请先安装并启用订阅模块')
return
}
router.push(target)
mobileOpen.value = false
}
async function onUserAction(data: any) {
if (String(data?.value || '') !== 'logout') return
await logout()
router.push('/login')
}
onMounted(loadModules)
</script>