mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-22 23:08:25 +00:00
feat(frontend): collapsible payment submenu in sidebar
Group the 4 payment admin pages under a single "订单管理" parent menu item that expands/collapses to show sub-items with indented tree line.
This commit is contained in:
@@ -29,30 +29,64 @@
|
||||
<template v-if="isAdmin">
|
||||
<!-- Admin Section -->
|
||||
<div class="sidebar-section">
|
||||
<router-link
|
||||
v-for="item in adminNavItems"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
class="sidebar-link mb-1"
|
||||
:class="{ 'sidebar-link-active': isActive(item.path) }"
|
||||
:title="sidebarCollapsed ? item.label : undefined"
|
||||
:id="
|
||||
item.path === '/admin/accounts'
|
||||
? 'sidebar-channel-manage'
|
||||
: item.path === '/admin/groups'
|
||||
? 'sidebar-group-manage'
|
||||
: item.path === '/admin/redeem'
|
||||
? 'sidebar-wallet'
|
||||
: undefined
|
||||
"
|
||||
@click="handleMenuItemClick(item.path)"
|
||||
>
|
||||
<span v-if="item.iconSvg" class="h-5 w-5 flex-shrink-0 sidebar-svg-icon" v-html="sanitizeSvg(item.iconSvg)"></span>
|
||||
<component v-else :is="item.icon" class="h-5 w-5 flex-shrink-0" />
|
||||
<transition name="fade">
|
||||
<span v-if="!sidebarCollapsed">{{ item.label }}</span>
|
||||
</transition>
|
||||
</router-link>
|
||||
<template v-for="item in adminNavItems" :key="item.path">
|
||||
<!-- Collapsible group (has children) -->
|
||||
<template v-if="item.children?.length">
|
||||
<button
|
||||
type="button"
|
||||
class="sidebar-link mb-1 w-full"
|
||||
:class="{ 'sidebar-link-active': isGroupActive(item) && !isGroupExpanded(item) }"
|
||||
:title="sidebarCollapsed ? item.label : undefined"
|
||||
@click="sidebarCollapsed ? undefined : toggleGroup(item)"
|
||||
>
|
||||
<component :is="item.icon" class="h-5 w-5 flex-shrink-0" />
|
||||
<transition name="fade">
|
||||
<span v-if="!sidebarCollapsed" class="flex flex-1 items-center justify-between">
|
||||
<span>{{ item.label }}</span>
|
||||
<ChevronDownIcon class="h-4 w-4 flex-shrink-0 transition-transform duration-200" :class="isGroupExpanded(item) ? 'rotate-180' : ''" />
|
||||
</span>
|
||||
</transition>
|
||||
</button>
|
||||
<!-- Children -->
|
||||
<div v-if="!sidebarCollapsed && isGroupExpanded(item)" class="mb-1 ml-4 border-l border-gray-200 pl-2 dark:border-dark-600">
|
||||
<router-link
|
||||
v-for="child in item.children"
|
||||
:key="child.path"
|
||||
:to="child.path"
|
||||
class="sidebar-link mb-0.5 py-1.5 text-sm"
|
||||
:class="{ 'sidebar-link-active': route.path === child.path }"
|
||||
@click="handleMenuItemClick(child.path)"
|
||||
>
|
||||
<component :is="child.icon" class="h-4 w-4 flex-shrink-0" />
|
||||
<span>{{ child.label }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Normal item (no children) -->
|
||||
<router-link
|
||||
v-else
|
||||
:to="item.path"
|
||||
class="sidebar-link mb-1"
|
||||
:class="{ 'sidebar-link-active': isActive(item.path) }"
|
||||
:title="sidebarCollapsed ? item.label : undefined"
|
||||
:id="
|
||||
item.path === '/admin/accounts'
|
||||
? 'sidebar-channel-manage'
|
||||
: item.path === '/admin/groups'
|
||||
? 'sidebar-group-manage'
|
||||
: item.path === '/admin/redeem'
|
||||
? 'sidebar-wallet'
|
||||
: undefined
|
||||
"
|
||||
@click="handleMenuItemClick(item.path)"
|
||||
>
|
||||
<span v-if="item.iconSvg" class="h-5 w-5 flex-shrink-0 sidebar-svg-icon" v-html="sanitizeSvg(item.iconSvg)"></span>
|
||||
<component v-else :is="item.icon" class="h-5 w-5 flex-shrink-0" />
|
||||
<transition name="fade">
|
||||
<span v-if="!sidebarCollapsed">{{ item.label }}</span>
|
||||
</transition>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Personal Section for Admin (hidden in simple mode) -->
|
||||
@@ -160,6 +194,7 @@ interface NavItem {
|
||||
icon: unknown
|
||||
iconSvg?: string
|
||||
hideInSimpleMode?: boolean
|
||||
children?: NavItem[]
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -175,6 +210,9 @@ const mobileOpen = computed(() => appStore.mobileOpen)
|
||||
const isAdmin = computed(() => authStore.isAdmin)
|
||||
const isDark = ref(document.documentElement.classList.contains('dark'))
|
||||
|
||||
// Track which parent nav groups are expanded
|
||||
const expandedGroups = ref<Set<string>>(new Set())
|
||||
|
||||
// Site settings from appStore (cached, no flicker)
|
||||
const siteName = computed(() => appStore.siteName)
|
||||
const siteLogo = computed(() => appStore.siteLogo)
|
||||
@@ -513,6 +551,21 @@ const ChevronDoubleRightIcon = {
|
||||
)
|
||||
}
|
||||
|
||||
const ChevronDownIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
'svg',
|
||||
{ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor', 'stroke-width': '1.5' },
|
||||
[
|
||||
h('path', {
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
d: 'm19.5 8.25-7.5 7.5-7.5-7.5'
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// User navigation items (for regular users)
|
||||
const userNavItems = computed((): NavItem[] => {
|
||||
const items: NavItem[] = [
|
||||
@@ -622,10 +675,18 @@ const adminNavItems = computed((): NavItem[] => {
|
||||
{ path: '/admin/promo-codes', label: t('nav.promoCodes'), icon: GiftIcon, hideInSimpleMode: true },
|
||||
...(adminSettingsStore.paymentEnabled
|
||||
? [
|
||||
{ path: '/admin/orders/dashboard', label: t('nav.paymentDashboard'), icon: ChartIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/orders', label: t('nav.orderManagement'), icon: OrderIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/orders/config', label: t('nav.paymentConfig'), icon: CogIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/orders/plans', label: t('nav.paymentPlans'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||
{
|
||||
path: '/admin/orders',
|
||||
label: t('nav.orderManagement'),
|
||||
icon: OrderIcon,
|
||||
hideInSimpleMode: true,
|
||||
children: [
|
||||
{ path: '/admin/orders/dashboard', label: t('nav.paymentDashboard'), icon: ChartIcon },
|
||||
{ path: '/admin/orders', label: t('nav.orderManagement'), icon: OrderIcon },
|
||||
{ path: '/admin/orders/config', label: t('nav.paymentConfig'), icon: CogIcon },
|
||||
{ path: '/admin/orders/plans', label: t('nav.paymentPlans'), icon: CreditCardIcon },
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ path: '/admin/usage', label: t('nav.usage'), icon: ChartIcon }
|
||||
@@ -689,6 +750,23 @@ function isActive(path: string): boolean {
|
||||
return route.path === path || route.path.startsWith(path + '/')
|
||||
}
|
||||
|
||||
function isGroupActive(item: NavItem): boolean {
|
||||
if (!item.children) return false
|
||||
return item.children.some(child => route.path === child.path)
|
||||
}
|
||||
|
||||
function isGroupExpanded(item: NavItem): boolean {
|
||||
return expandedGroups.value.has(item.path) || isGroupActive(item)
|
||||
}
|
||||
|
||||
function toggleGroup(item: NavItem) {
|
||||
if (expandedGroups.value.has(item.path)) {
|
||||
expandedGroups.value.delete(item.path)
|
||||
} else {
|
||||
expandedGroups.value.add(item.path)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user