feat: add layout and org

This commit is contained in:
2024-10-08 02:44:11 +08:00
parent fd99875461
commit ec5c64d03a
21 changed files with 962 additions and 130 deletions

View File

@@ -0,0 +1,99 @@
import { useShallow } from 'zustand/react/shallow';
import { useLayoutStore } from './store';
import clsx from 'clsx';
import { Button, Dropdown, message } from 'antd';
import {
CloseOutlined,
CodeOutlined,
DashboardOutlined,
HomeOutlined,
MessageOutlined,
ReadOutlined,
RocketOutlined,
SmileOutlined,
SwapOutlined,
SwitcherOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router';
import { useMemo } from 'react';
const meun = [
{
title: 'Your profile',
icon: <HomeOutlined />,
link: '/map',
},
{
title: 'Your orgs',
icon: <DashboardOutlined />,
link: '/panel/edit/list',
},
];
export const LayoutUser = () => {
const { open, setOpen, ...store } = useLayoutStore(
useShallow((state) => ({
open: state.openUser, //
setOpen: state.setOpenUser,
me: state.me,
switchOrg: state.switchOrg,
})),
);
const navigate = useNavigate();
const items = useMemo(() => {
const orgs = store.me?.orgs || [];
return orgs.map((item) => {
return {
label: item,
key: item,
icon: <SmileOutlined />,
};
});
}, [store.me]);
return (
<div className={clsx('w-full h-full absolute z-20', !open && 'hidden')}>
<div
className='bg-white w-full absolute h-full opacity-60 z-0'
onClick={() => {
setOpen(false);
}}></div>
<div className='w-[400px] h-full absolute top-0 right-0 bg-white rounded-l-lg'>
<div className='flex justify-between p-6 font-bold items-center border-b'>
User: {store.me?.username}
<div className='flex gap-4'>
{items.length > 0 && (
<Dropdown
placement='bottomRight'
menu={{
items: items,
onClick: (item) => {
store.switchOrg(item.key, 'org');
},
}}>
<Button icon={<SwapOutlined />} onClick={() => {}}></Button>
</Dropdown>
)}
<Button icon={<CloseOutlined />} onClick={() => setOpen(false)}></Button>
</div>
</div>
<div className='mt-3 font-medium'>
{meun.map((item, index) => {
return (
<div
key={index}
className='flex items-center p-4 hover:bg-gray-100 cursor-pointer'
onClick={() => {
if (item.link) {
navigate(item.link);
} else {
message.info('Coming soon');
}
}}>
<div className='mr-4'>{item.icon}</div>
<div>{item.title}</div>
</div>
);
})}
</div>
</div>
</div>
);
};

View File

@@ -1,8 +1,20 @@
import { useShallow } from 'zustand/react/shallow';
import { useMenuStore } from './store';
import { useLayoutStore } from './store';
import clsx from 'clsx';
import { Button, message } from 'antd';
import { CloseOutlined, CodeOutlined, DashboardOutlined, HomeOutlined, MessageOutlined, ReadOutlined, RocketOutlined, SmileOutlined } from '@ant-design/icons';
import {
AppstoreOutlined,
CloseOutlined,
CodeOutlined,
DashboardOutlined,
FolderOutlined,
HomeOutlined,
MessageOutlined,
ReadOutlined,
RocketOutlined,
SmileOutlined,
SwitcherOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router';
const meun = [
{
@@ -15,6 +27,16 @@ const meun = [
icon: <DashboardOutlined />,
link: '/panel/edit/list',
},
{
title: 'User App',
icon: <AppstoreOutlined />,
link: '/app/edit/list',
},
{
title: 'File App',
icon: <FolderOutlined />,
link: '/file/edit/list',
},
{
title: 'Prompt',
icon: <MessageOutlined />,
@@ -35,13 +57,18 @@ const meun = [
icon: <ReadOutlined />,
link: '/chat/chat-prompt/list',
},
{
title: 'Org',
icon: <SwitcherOutlined />,
link: '/org/edit/list',
},
{
title: 'About',
icon: <SmileOutlined />,
},
];
export const LayoutMenu = () => {
const { open, setOpen } = useMenuStore(useShallow((state) => ({ open: state.open, setOpen: state.setOpen })));
const { open, setOpen } = useLayoutStore(useShallow((state) => ({ open: state.open, setOpen: state.setOpen })));
const navigate = useNavigate();
return (
<div className={clsx('w-full h-full absolute z-20', !open && 'hidden')}>

View File

@@ -1,21 +1,33 @@
import { AiMoudle } from '@/pages/ai-chat';
import { MenuOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import { MenuOutlined, SwapOutlined } from '@ant-design/icons';
import { Button, Tooltip } from 'antd';
import { Outlet } from 'react-router-dom';
import { LayoutMenu } from './Menu';
import { useMenuStore } from './store';
import { useLayoutStore } from './store';
import { useShallow } from 'zustand/react/shallow';
import { useEffect } from 'react';
import { LayoutUser } from './LayoutUser';
type LayoutMainProps = {
title?: React.ReactNode;
children?: React.ReactNode;
};
export const LayoutMain = (props: LayoutMainProps) => {
const menuStore = useMenuStore(
const menuStore = useLayoutStore(
useShallow((state) => {
return { open: state.open, setOpen: state.setOpen };
return {
open: state.open,
setOpen: state.setOpen, //
getMe: state.getMe,
me: state.me,
setOpenUser: state.setOpenUser,
switchOrg: state.switchOrg,
};
}),
);
useEffect(() => {
menuStore.getMe();
}, []);
return (
<div className='flex w-full h-full flex-col relative'>
<LayoutMenu />
@@ -26,7 +38,26 @@ export const LayoutMain = (props: LayoutMainProps) => {
menuStore.setOpen(true);
}}
icon={<MenuOutlined />}></Button>
<div className='flex flex-grow justify-between'>{props.title}</div>
<div className='flex flex-grow justify-between'>
{props.title}
<div className='mr-4 flex gap-4 items-center'>
{menuStore.me?.type === 'org' && (
<div>
<Tooltip title='Switch To User'>
<Button
icon={<SwapOutlined />}
onClick={() => {
menuStore.switchOrg('', 'user');
}}></Button>
</Tooltip>
</div>
)}
<div className='w-8 h-8 rounded-full bg-blue-200 avatar cursor-pointer' onClick={() => menuStore.setOpenUser(true)}></div>
<div className='cursor-pointer' onClick={() => menuStore.setOpenUser(true)}>
{menuStore.me?.username}
</div>
</div>
</div>
</div>
<div
className='flex'
@@ -40,6 +71,7 @@ export const LayoutMain = (props: LayoutMainProps) => {
</div>
<AiMoudle />
</div>
<LayoutUser />
</div>
);
};

View File

@@ -1,10 +1,58 @@
import { query } from '@/modules/query';
import { message } from 'antd';
import { create } from 'zustand';
export type MenuStore = {
type Me = {
id?: string;
username?: string;
needChangePassword?: boolean;
role?: string;
description?: string;
type?: 'user' | 'org';
orgs?: string[];
};
export type LayoutStore = {
open: boolean;
setOpen: (open: boolean) => void;
me: Me;
setMe: (me: Me) => void;
getMe: () => Promise<void>;
openUser: boolean;
setOpenUser: (openUser: boolean) => void;
switchOrg: (username?: string, type?: 'user' | 'org') => Promise<void>;
};
export const useMenuStore = create<MenuStore>((set) => ({
export const useLayoutStore = create<LayoutStore>((set) => ({
open: false,
setOpen: (open) => set({ open }),
me: {},
setMe: (me) => set({ me }),
getMe: async () => {
const res = await query.post({
path: 'user',
key: 'me',
});
if (res.code === 200) {
set({ me: res.data });
}
},
openUser: false,
setOpenUser: (openUser) => set({ openUser }),
switchOrg: async (username?: string, type?: string) => {
const res = await query.post({
path: 'user',
key: 'switchOrg',
data: {
username,
type,
},
});
if (res.code === 200) {
const { token } = res.data;
query.saveToken(token);
message.success('Switch success');
window.location.reload();
} else {
message.error(res.message || 'Request failed');
}
},
}));