Browse Source

显示当前用户信息

main
李金 5 months ago
parent
commit
363eec0b27
  1. 50
      src/components/avatar/index.tsx
  2. 7
      src/components/breadcrumb/index.tsx
  3. 18
      src/components/icon/index.tsx
  4. 3
      src/components/icon/picker/IconRender.tsx
  5. 2
      src/components/icon/types.ts
  6. 47
      src/layout/RootLayout.tsx
  7. 12
      src/layout/style.ts
  8. 6
      src/pages/system/menus/index.tsx
  9. 4
      src/service/system.ts
  10. 15
      src/store/user.ts

50
src/components/avatar/index.tsx

@ -0,0 +1,50 @@
import Icon from '@/components/icon'
import { useTranslation } from '@/i18n.ts'
import { currentUserAtom } from '@/store/user.ts'
import { Avatar as AntAvatar, Dropdown, Spin } from 'antd'
import { useAtomValue } from 'jotai'
const Avatar = () => {
const { t } = useTranslation()
const { data, isLoading } = useAtomValue(currentUserAtom)
return (
<div>
<Dropdown
key={'user'}
placement="bottomRight"
menu={{
items: [
{
key: 'logout',
icon: <Icon type={'park:Logout'}/>,
label: <span style={{
marginInlineStart: 8,
userSelect: 'none'
}}>{t('app.header.logout')}</span>,
},
],
}}
>
<Spin spinning={isLoading}>
<AntAvatar
key="avatar"
size={'small'}
src={data?.avatar || data?.nickname?.substring(0, 1)}>
{!data?.avatar && data?.nickname?.substring(0, 1)}
</AntAvatar>
<span key="name"
style={{
marginInlineStart: 8,
userSelect: 'none'
}}>
{data?.nickname}
</span>
</Spin>
</Dropdown>
</div>
)
}
export default Avatar

7
src/components/breadcrumb/index.tsx

@ -40,7 +40,8 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
}}
trigger={[ 'hover' ]}>
{
(!route.component || !route.path)? <a className={'item'}>{renderIcon(route.icon)}<span>{route.name}</span>
(!route.component || !route.path) ?
<a className={'item'}>{renderIcon(route.icon)}<span>{route.name}</span>
<DownOutlined/></a>
: <Link to={`/${route.path}`}
preload={false}
@ -65,12 +66,12 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
return (
<>
<div style={{ userSelect: 'none' }}>
<Breadcrumb {...other}
items={items}
itemRender={itemRender}
/>
</>
</div>
)
})

18
src/components/icon/index.tsx

@ -5,13 +5,23 @@ import * as AntIcons from '@ant-design/icons/es/icons'
import IconItem from './picker/IconRender.tsx'
import { IconUnit } from './types.ts'
export function Icon(props: IconUnit) {
const { type, ...other } = props
type Prefix = 'antd:' | 'park:';
type IconType = `${Prefix}${string}`;
interface IconProps extends Pick<IconUnit, 'type'> {
type: IconType | IconUnit['type']
}
if (type && [ 'antd:', 'park:' ].includes(type as string)) {
function isAntdOrParkIcon(value: string): value is IconType {
return value.startsWith('antd:') || value.startsWith('park:')
}
export function Icon(props: IconProps) {
const { type, ...other } = props
if (type && isAntdOrParkIcon(type)) {
const [ t, c ] = type.split(':')
return <IconItem {...props as any} type={t} componentName={c}/>
return <IconItem {...other as any} type={t} componentName={c}/>
}
const AntIcon = AntIcons[type as keyof typeof AntIcons]

3
src/components/icon/picker/IconRender.tsx

@ -22,6 +22,9 @@ const Render: FC<IconRenderProps> = memo(
case 'antd':
// eslint-disable-next-line no-case-declarations
const AIcon = AntdIcon[componentName!]
if (!AIcon) {
return null
}
return <AIcon {...props} />
case 'park':

2
src/components/icon/types.ts

@ -22,4 +22,4 @@ export interface IconComponentProps {
/**
*
*/
export type IconUnit = ReactIcon | IconfontIcon | IconComponentProps
export type IconUnit = IconComponentProps | ReactIcon | IconfontIcon

47
src/layout/RootLayout.tsx

@ -1,19 +1,21 @@
import Avatar from '@/components/avatar'
import PageBreadcrumb from '@/components/breadcrumb'
import ErrorPage from '@/components/error/error.tsx'
import SelectLang from '@/components/select-lang'
import { useTranslation } from '@/i18n.ts'
import { appAtom } from '@/store/system.ts'
import { userMenuDataAtom } from '@/store/user.ts'
import { MenuItem } from '@/types'
import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components'
import { zhCNIntl, enUSIntl } from '@ant-design/pro-provider/es/intl'
import { CatchBoundary, Link, Outlet } from '@tanstack/react-router'
import { Dropdown } from 'antd'
import { ConfigProvider} from '@/components/config-provider'
import { ConfigProvider } from '@/components/config-provider'
import { useState } from 'react'
import Icon from '../components/icon'
import defaultProps from './_defaultProps'
import { useAtomValue } from 'jotai'
import { useStyle } from '@/layout/style.ts'
import zh from 'antd/locale/zh_CN'
import en from 'antd/locale/en_US'
//根据menuData生成Breadcrumb所需的数据
const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => {
@ -42,7 +44,7 @@ export default () => {
const { styles } = useStyle()
const { t } = useTranslation()
const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom)
const { language } = useAtomValue(appAtom)
const items = getBreadcrumbData(menuData, location.pathname)
const [ pathname, setPathname ] = useState(location.pathname)
@ -59,8 +61,9 @@ export default () => {
getResetKey={() => 'reset-page'}
errorComponent={ErrorPage}
>
<ProConfigProvider hashed={false}>
<ProConfigProvider hashed={false} intl={language === 'zh-CN' ? zhCNIntl : enUSIntl}>
<ConfigProvider
locale={language === 'zh-CN' ? zh : en}
getTargetContainer={() => {
return document.getElementById('crazy-pro-layout') || document.body
}}
@ -88,25 +91,12 @@ export default () => {
collapsedShowGroupTitle: true,
loading: isLoading,
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '管理员',
render: (_, dom) => {
// src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
render: () => {
return (
<Dropdown
menu={{
items: [
{
key: 'logout',
icon: <Icon type={'Logout'}/>,
label: t('app.header.logout'),
},
],
}}
>
{dom}
</Dropdown>
<Avatar/>
)
},
}}
@ -114,16 +104,19 @@ export default () => {
if (props.isMobile) return []
if (typeof window === 'undefined') return []
return [
<SelectLang/>
<SelectLang/>,
]
}}
menuProps={{
className: styles.sideMenu,
}}
menuRender={(_, defaultDom) => (
<>
<span style={{ userSelect: 'none' }}>
{defaultDom}
</>
</span>
)}
menuItemRender={(item, dom) => {
return <div onClick={() => {
return <div style={{ userSelect: 'none' }} onClick={() => {
setPathname(item.path || '/dashboard')
}}
>

12
src/layout/style.ts

@ -5,7 +5,10 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
const container = {
[prefix]: css`
.ant-pro-global-header-logo,
.ant-pro-layout-bg-list {
user-select: none;
}
`,
}
@ -13,9 +16,16 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
box-shadow: ${token.boxShadowSecondary};
`
const sideMenu = css`
.ant-pro-base-menu-inline-group .ant-menu-item-group-title .anticon {
margin-inline-end: 0;
}
`
return {
container: cx(container[prefix], props?.className),
pageContext,
sideMenu,
}
})

6
src/pages/system/menus/index.tsx

@ -42,7 +42,11 @@ const Menus = () => {
<PageContainer breadcrumbRender={false} title={false} className={styles.container}>
<Flexbox horizontal>
<DraggablePanel expandable={false} placement="left" maxWidth={800} style={{ width: '100%', }}>
<DraggablePanel expandable={false}
placement="left"
defaultSize={{ width: 300 }}
maxWidth={500}
>
<ProCard title={t('system.menus.title', '菜单')}
extra={
<BatchButton/>

4
src/service/system.ts

@ -1,3 +1,4 @@
import { IUserInfo } from '@/types/user'
import request from '../request.ts'
import { LoginRequest, LoginResponse } from '@/types/login'
import { createCURD } from '@/service/base.ts'
@ -15,6 +16,9 @@ const systemServ = {
return request.post<LoginResponse>('/sys/login', data)
},
user: {
current: () => {
return request.get<IUserInfo>('/sys/user/info')
},
menus: () => {
return request.get<IMenu[]>('/sys/user/menus')
}

15
src/store/user.ts

@ -1,7 +1,8 @@
import { appAtom } from '@/store/system.ts'
import { IUserInfo } from '@/types/user'
import { AxiosResponse } from 'axios'
import { atom } from 'jotai/index'
import { IAuth, MenuItem } from '@/types'
import { IApiResult, IAuth, MenuItem } from '@/types'
import { LoginRequest } from '@/types/login'
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'
import systemServ from '@/service/system.ts'
@ -32,6 +33,18 @@ export const loginAtom = atomWithMutation<any, LoginRequest>(() => ({
retry: false,
}))
export const currentUserAtom = atomWithQuery<IApiResult<IUserInfo>, any, IUserInfo>((get) => {
return {
queryKey: [ 'user_info', get(appAtom).token ],
queryFn: async () => {
return await systemServ.user.current()
},
select: (data) => {
return data.data
}
}
})
export const userMenuDataAtom = atomWithQuery<any, MenuItem[]>((get) => ({
enabled: false,
queryKey: [ 'user_menus', get(appAtom).token ],

Loading…
Cancel
Save