优化
This commit is contained in:
@@ -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!,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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 },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface AdminInfo {
|
||||
username: string
|
||||
nickname: string
|
||||
role: number
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user