This commit is contained in:
2026-03-22 14:03:51 +08:00
parent a072128b17
commit 5399d03478
11 changed files with 34 additions and 241 deletions

View File

@@ -1,41 +1,24 @@
import {
AudioWaveform,
Command,
GalleryVerticalEnd,
} from 'lucide-vue-next'
import { useSidebar } from '@/composables/use-sidebar'
import { useAuthStore } from '@/stores/auth'
import type { SidebarData, Team, User } from '../types'
const user: User = {
name: 'shadcn',
email: 'm@example.com',
avatar: '/avatars/shadcn.jpg',
}
const teams: Team[] = [
{
name: 'Acme Inc',
logo: GalleryVerticalEnd,
plan: 'Enterprise',
},
{
name: 'Acme Corp.',
logo: AudioWaveform,
plan: 'Startup',
},
{
name: 'Evil Corp.',
logo: Command,
plan: 'Free',
},
]
import type { SidebarData, User } from '../types'
const { navData } = useSidebar()
const authStore = useAuthStore()
// 从 auth store 获取用户信息,转换为 sidebar User 类型
const user = computed<User>(() => {
const adminInfo = authStore.adminInfo
return {
name: adminInfo?.nickname || adminInfo?.username || 'Admin',
email: adminInfo?.username || '',
avatar: adminInfo?.avatar || '/avatars/admin.jpg',
}
})
export const sidebarData: SidebarData = {
user,
teams,
get user() {
return user.value
},
navMain: navData.value!,
}

View File

@@ -1,18 +1,18 @@
<script lang="ts" setup>
import { sidebarData } from './data/sidebar-data'
import NavFooter from './nav-footer.vue'
import NavTeam from './nav-team.vue'
import NavMain from './nav-main.vue'
import TeamSwitcher from './team-switcher.vue'
</script>
<template>
<UiSidebar collapsible="icon" class="z-50">
<UiSidebarHeader>
<TeamSwitcher :teams="sidebarData.teams" />
<TeamSwitcher />
</UiSidebarHeader>
<UiSidebarContent>
<NavTeam :nav-main="sidebarData.navMain" />
<NavMain :nav-main="sidebarData.navMain" />
</UiSidebarContent>
<UiSidebarFooter>

View File

@@ -1,91 +0,0 @@
<script lang="ts" setup>
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import { toast } from 'vue-sonner'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { teamAddValidator } from './validators/team.validator'
const emits = defineEmits(['close'])
const teamAddFormSchema = toTypedSchema(teamAddValidator)
const { handleSubmit } = useForm({
validationSchema: teamAddFormSchema,
initialValues: {},
})
const onSubmit = handleSubmit((values) => {
toast('You submitted the following values:', {
position: 'top-center',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
emits('close')
})
</script>
<template>
<div>
<UiDialogHeader>
<UiDialogTitle>
Add New Team
</UiDialogTitle>
<UiDialogDescription>
Add a new team by your self.
</UiDialogDescription>
</UiDialogHeader>
<form class="space-y-4" @submit="onSubmit">
<FormField v-slot="{ componentField }" name="name">
<FormItem>
<FormLabel class="text-base">
Name
</FormLabel>
<FormControl>
<UiInput v-bind="componentField" />
</FormControl>
<FormDescription>
Set the name for the team.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="slug">
<FormItem>
<FormLabel class="text-base">
Slug
</FormLabel>
<FormControl>
<UiInput v-bind="componentField" />
</FormControl>
<FormDescription>
Set the slug for the team.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="logo">
<FormItem>
<FormLabel class="text-base">
Logo
</FormLabel>
<FormControl>
<UiInput v-bind="componentField" />
</FormControl>
<FormDescription>
Set the logo of the team.
</FormDescription>
<FormMessage />
</FormItem>
</FormField>
<div class="flex justify-start mt-4">
<UiButton type="submit">
Add team
</UiButton>
</div>
</form>
</div>
</template>

View File

@@ -1,100 +1,21 @@
<script lang="ts" setup>
import {
ChevronsUpDown,
Plus,
} from 'lucide-vue-next'
import { useSidebar } from '@/components/ui/sidebar'
import type { Team } from './types'
const { teams } = defineProps<{
teams: Team[]
}>()
const { isMobile, open } = useSidebar()
const activeTeam = ref<Team>(teams[0])
function setActiveTeam(team: Team) {
activeTeam.value = team
}
const isOpen = ref(false)
const showComponent = shallowRef<Component | null>(null)
type TComponent = 'team-add'
function handleSelect(command: TComponent) {
switch (command) {
case 'team-add':
showComponent.value = defineAsyncComponent(() => import('./nav-team-add.vue'))
break
}
}
const { isMobile } = useSidebar()
</script>
<template>
<UiSidebarMenu>
<UiSidebarMenuItem>
<UiDialog v-model:open="isOpen">
<UiDropdownMenu>
<UiDropdownMenuTrigger as-child>
<UiSidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div
class="flex items-center justify-center rounded-lg aspect-square size-8 bg-sidebar-primary text-sidebar-primary-foreground"
>
<component :is="activeTeam.logo" class="size-4" />
</div>
<div class="grid flex-1 text-sm leading-tight text-left">
<span class="font-semibold truncate">{{ activeTeam.name }}</span>
<span class="text-xs truncate">{{ activeTeam.plan }}</span>
</div>
<ChevronsUpDown class="ml-auto" />
</UiSidebarMenuButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent
class="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
align="start"
:side="(isMobile || open) ? 'bottom' : 'right'"
:side-offset="4"
>
<UiDropdownMenuLabel class="text-xs text-muted-foreground">
Teams
</UiDropdownMenuLabel>
<UiDropdownMenuItem
v-for="(team, index) in teams"
:key="team.name"
class="gap-2 p-2"
@click="setActiveTeam(team)"
>
<div class="flex items-center justify-center border rounded-sm size-6">
<component :is="team.logo" class="size-4 shrink-0" />
</div>
{{ team.name }}
<UiDropdownMenuShortcut>{{ index + 1 }}</UiDropdownMenuShortcut>
</UiDropdownMenuItem>
<UiDropdownMenuSeparator />
<UiDialogTrigger as-child>
<UiDropdownMenuItem class="gap-2 p-2" @click.stop="handleSelect('team-add')">
<div class="flex items-center justify-center border rounded-md size-6 bg-background">
<Plus class="size-4" />
</div>
<div class="font-medium text-muted-foreground">
Add team
</div>
</UiDropdownMenuItem>
</UiDialogTrigger>
</UiDropdownMenuContent>
</UiDropdownMenu>
<UiDialogContent>
<component :is="showComponent" @close="isOpen = false" />
</UiDialogContent>
</UiDialog>
<UiSidebarMenuButton size="lg">
<div class="flex items-center justify-center rounded-lg aspect-square size-8 bg-sidebar-primary text-sidebar-primary-foreground">
<img src="/logo.svg" alt="Monisuo" class="size-5">
</div>
<div class="grid flex-1 text-sm leading-tight text-left">
<span class="font-semibold truncate">Monisuo</span>
<span class="text-xs truncate">管理后台</span>
</div>
</UiSidebarMenuButton>
</UiSidebarMenuItem>
</UiSidebarMenu>
</template>

View File

@@ -29,14 +29,7 @@ export interface User {
email: string
}
export interface Team {
name: string
logo: NavIcon
plan: string
}
export interface SidebarData {
user: User
teams: Team[]
navMain: NavGroup[]
}

View File

@@ -1,17 +0,0 @@
import { z } from 'zod'
export const teamAddValidator = z.object({
name: z
.string()
.min(1, { error: 'Group name is required' })
.max(50, { error: 'Group name must be less than 50 characters' }),
slug: z
.string()
.min(1, { error: 'Group name is required' })
.max(50, { error: 'Group name must be less than 50 characters' }),
logo: z
.string()
.optional(),
})
export type TeamAddValidator = z.infer<typeof teamAddValidator>

View File

@@ -12,10 +12,10 @@ export function useSidebar() {
title: 'Monisuo 管理',
items: [
{ title: '数据看板', url: '/monisuo/dashboard', icon: DollarSign },
{ title: '业务分析', url: '/monisuo/analytics', icon: TrendingUp },
{ title: '用户管理', url: '/monisuo/users', icon: Users },
{ title: '币种管理', url: '/monisuo/coins', icon: Coins },
{ title: '订单审批', url: '/monisuo/orders', icon: Receipt },
{ title: '业务分析', url: '/monisuo/analytics', icon: TrendingUp },
],
},
{

View File

@@ -15,6 +15,7 @@ export interface AdminInfo {
username: string
nickname: string
role: number
avatar?: string
}
export interface LoginResponse {

View File

@@ -14,6 +14,7 @@ declare module 'vue' {
export interface GlobalComponents {
AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default']
AppSidebarNavMain: typeof import('./../components/app-sidebar/nav-main.vue')['default']
AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
AppSidebarNavTeamAdd: typeof import('./../components/app-sidebar/nav-team-add.vue')['default']
AppSidebarTeamSwitcher: typeof import('./../components/app-sidebar/team-switcher.vue')['default']
@@ -415,6 +416,7 @@ declare module 'vue' {
declare global {
const AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
const AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default']
const AppSidebarNavMain: typeof import('./../components/app-sidebar/nav-main.vue')['default']
const AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
const AppSidebarNavTeamAdd: typeof import('./../components/app-sidebar/nav-team-add.vue')['default']
const AppSidebarTeamSwitcher: typeof import('./../components/app-sidebar/team-switcher.vue')['default']

View File

@@ -73,6 +73,7 @@ public class AdminController {
adminInfo.put("username", username);
adminInfo.put("nickname", "超级管理员");
adminInfo.put("role", 1);
adminInfo.put("avatar", "/avatars/admin.jpg");
result.put("adminInfo", adminInfo);
return Result.success("登录成功", result);
}