优化
This commit is contained in:
@@ -1,41 +1,24 @@
|
|||||||
import {
|
|
||||||
AudioWaveform,
|
|
||||||
Command,
|
|
||||||
GalleryVerticalEnd,
|
|
||||||
} from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { useSidebar } from '@/composables/use-sidebar'
|
import { useSidebar } from '@/composables/use-sidebar'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
import type { SidebarData, Team, User } from '../types'
|
import type { SidebarData, 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',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const { navData } = useSidebar()
|
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 = {
|
export const sidebarData: SidebarData = {
|
||||||
user,
|
get user() {
|
||||||
teams,
|
return user.value
|
||||||
|
},
|
||||||
navMain: navData.value!,
|
navMain: navData.value!,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { sidebarData } from './data/sidebar-data'
|
import { sidebarData } from './data/sidebar-data'
|
||||||
import NavFooter from './nav-footer.vue'
|
import NavFooter from './nav-footer.vue'
|
||||||
import NavTeam from './nav-team.vue'
|
import NavMain from './nav-main.vue'
|
||||||
import TeamSwitcher from './team-switcher.vue'
|
import TeamSwitcher from './team-switcher.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UiSidebar collapsible="icon" class="z-50">
|
<UiSidebar collapsible="icon" class="z-50">
|
||||||
<UiSidebarHeader>
|
<UiSidebarHeader>
|
||||||
<TeamSwitcher :teams="sidebarData.teams" />
|
<TeamSwitcher />
|
||||||
</UiSidebarHeader>
|
</UiSidebarHeader>
|
||||||
|
|
||||||
<UiSidebarContent>
|
<UiSidebarContent>
|
||||||
<NavTeam :nav-main="sidebarData.navMain" />
|
<NavMain :nav-main="sidebarData.navMain" />
|
||||||
</UiSidebarContent>
|
</UiSidebarContent>
|
||||||
|
|
||||||
<UiSidebarFooter>
|
<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>
|
<script lang="ts" setup>
|
||||||
import {
|
|
||||||
ChevronsUpDown,
|
|
||||||
Plus,
|
|
||||||
} from 'lucide-vue-next'
|
|
||||||
|
|
||||||
import { useSidebar } from '@/components/ui/sidebar'
|
import { useSidebar } from '@/components/ui/sidebar'
|
||||||
|
|
||||||
import type { Team } from './types'
|
const { isMobile } = useSidebar()
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UiSidebarMenu>
|
<UiSidebarMenu>
|
||||||
<UiSidebarMenuItem>
|
<UiSidebarMenuItem>
|
||||||
<UiDialog v-model:open="isOpen">
|
<UiSidebarMenuButton size="lg">
|
||||||
<UiDropdownMenu>
|
<div class="flex items-center justify-center rounded-lg aspect-square size-8 bg-sidebar-primary text-sidebar-primary-foreground">
|
||||||
<UiDropdownMenuTrigger as-child>
|
<img src="/logo.svg" alt="Monisuo" class="size-5">
|
||||||
<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>
|
||||||
<div class="grid flex-1 text-sm leading-tight text-left">
|
<div class="grid flex-1 text-sm leading-tight text-left">
|
||||||
<span class="font-semibold truncate">{{ activeTeam.name }}</span>
|
<span class="font-semibold truncate">Monisuo</span>
|
||||||
<span class="text-xs truncate">{{ activeTeam.plan }}</span>
|
<span class="text-xs truncate">管理后台</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronsUpDown class="ml-auto" />
|
|
||||||
</UiSidebarMenuButton>
|
</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>
|
|
||||||
</UiSidebarMenuItem>
|
</UiSidebarMenuItem>
|
||||||
</UiSidebarMenu>
|
</UiSidebarMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -29,14 +29,7 @@ export interface User {
|
|||||||
email: string
|
email: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Team {
|
|
||||||
name: string
|
|
||||||
logo: NavIcon
|
|
||||||
plan: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SidebarData {
|
export interface SidebarData {
|
||||||
user: User
|
user: User
|
||||||
teams: Team[]
|
|
||||||
navMain: NavGroup[]
|
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 管理',
|
title: 'Monisuo 管理',
|
||||||
items: [
|
items: [
|
||||||
{ title: '数据看板', url: '/monisuo/dashboard', icon: DollarSign },
|
{ title: '数据看板', url: '/monisuo/dashboard', icon: DollarSign },
|
||||||
{ title: '业务分析', url: '/monisuo/analytics', icon: TrendingUp },
|
|
||||||
{ title: '用户管理', url: '/monisuo/users', icon: Users },
|
{ title: '用户管理', url: '/monisuo/users', icon: Users },
|
||||||
{ title: '币种管理', url: '/monisuo/coins', icon: Coins },
|
{ title: '币种管理', url: '/monisuo/coins', icon: Coins },
|
||||||
{ title: '订单审批', url: '/monisuo/orders', icon: Receipt },
|
{ title: '订单审批', url: '/monisuo/orders', icon: Receipt },
|
||||||
|
{ title: '业务分析', url: '/monisuo/analytics', icon: TrendingUp },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface AdminInfo {
|
|||||||
username: string
|
username: string
|
||||||
nickname: string
|
nickname: string
|
||||||
role: number
|
role: number
|
||||||
|
avatar?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
|
AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
|
||||||
AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.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']
|
AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
|
||||||
AppSidebarNavTeamAdd: typeof import('./../components/app-sidebar/nav-team-add.vue')['default']
|
AppSidebarNavTeamAdd: typeof import('./../components/app-sidebar/nav-team-add.vue')['default']
|
||||||
AppSidebarTeamSwitcher: typeof import('./../components/app-sidebar/team-switcher.vue')['default']
|
AppSidebarTeamSwitcher: typeof import('./../components/app-sidebar/team-switcher.vue')['default']
|
||||||
@@ -415,6 +416,7 @@ declare module 'vue' {
|
|||||||
declare global {
|
declare global {
|
||||||
const AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
|
const AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
|
||||||
const AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.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 AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
|
||||||
const AppSidebarNavTeamAdd: typeof import('./../components/app-sidebar/nav-team-add.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']
|
const AppSidebarTeamSwitcher: typeof import('./../components/app-sidebar/team-switcher.vue')['default']
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ public class AdminController {
|
|||||||
adminInfo.put("username", username);
|
adminInfo.put("username", username);
|
||||||
adminInfo.put("nickname", "超级管理员");
|
adminInfo.put("nickname", "超级管理员");
|
||||||
adminInfo.put("role", 1);
|
adminInfo.put("role", 1);
|
||||||
|
adminInfo.put("avatar", "/avatars/admin.jpg");
|
||||||
result.put("adminInfo", adminInfo);
|
result.put("adminInfo", adminInfo);
|
||||||
return Result.success("登录成功", result);
|
return Result.success("登录成功", result);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user