Browse Source

调整页面布局,增加PageTitle显示

main
dark 4 months ago
parent
commit
2b470f64e2
  1. 4
      src/App.css
  2. 14
      src/components/avatar/index.tsx
  3. 2
      src/components/table/style.ts
  4. 2
      src/global.d.ts
  5. 65
      src/hooks/useInlineStyle.ts
  6. 63
      src/hooks/useResizeObserver.ts
  7. 20
      src/layout/ListPageLayout.tsx
  8. 25
      src/layout/RootLayout.tsx
  9. 21
      src/layout/TwoColPageLayout.tsx
  10. 17
      src/layout/style.ts
  11. 8
      src/pages/system/menus/index.tsx
  12. 5
      src/pages/websites/domain/index.tsx
  13. 5
      src/store/system.ts
  14. 11
      src/store/system/user.ts
  15. 3
      src/utils/index.ts

4
src/App.css

@ -18,3 +18,7 @@
gap: 5px; gap: 5px;
} }
} }
.ant-drawer .ant-drawer-footer{
background-color: #fcfcfc;
}

14
src/components/avatar/index.tsx

@ -1,19 +1,27 @@
import Icon from '@/components/icon' import Icon from '@/components/icon'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { currentUserAtom, logoutAtom } from '@/store/system/user.ts'
import { currentStaticUserAtom, currentUserAtom, logoutAtom } from '@/store/system/user.ts'
import { Avatar as AntAvatar, Dropdown, Spin } from 'antd' import { Avatar as AntAvatar, Dropdown, Spin } from 'antd'
import { useAtomValue } from 'jotai'
import { useAtomValue, useSetAtom } from 'jotai'
import { useNavigate } from '@tanstack/react-router' import { useNavigate } from '@tanstack/react-router'
import { useStyle } from './style' import { useStyle } from './style'
import { useEffect } from 'react'
const Avatar = () => { const Avatar = () => {
const { styles } = useStyle() const { styles } = useStyle()
const { t } = useTranslation() const { t } = useTranslation()
const { data, isLoading } = useAtomValue(currentUserAtom)
const { data, isLoading, isSuccess } = useAtomValue(currentUserAtom)
const updateUser = useSetAtom(currentStaticUserAtom)
const { mutate: logout } = useAtomValue(logoutAtom) const { mutate: logout } = useAtomValue(logoutAtom)
const navigate = useNavigate() const navigate = useNavigate()
useEffect(() => {
if (isSuccess) {
updateUser(data)
}
}, [ isSuccess ])
return ( return (
<div className={styles.container}> <div className={styles.container}>
<Dropdown <Dropdown

2
src/components/table/style.ts

@ -10,7 +10,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
--toolbar-height: 65px; --toolbar-height: 65px;
--alter-height: 0px; --alter-height: 0px;
--padding: 33px; --padding: 33px;
--table-body-height: calc(var(--toolbar-height, 65px) + var(--alter-height, 0px) + var(--header-height, 56px) + var(--padding, 20px) * 4);
--table-body-height: calc( var(--pageHeader, 0px) + var(--toolbar-height, 65px) + var(--alter-height, 0px) + var(--header-height, 56px) + var(--padding, 20px) * 4);
.ant-table-body { .ant-table-body {
overflow: auto scroll; overflow: auto scroll;

2
src/global.d.ts

@ -3,6 +3,7 @@ import { QueryClient } from '@tanstack/react-query'
import { Router } from '@tanstack/react-router' import { Router } from '@tanstack/react-router'
import { RouteOptions } from '@tanstack/react-router/src/route.ts' import { RouteOptions } from '@tanstack/react-router/src/route.ts'
import { Attributes, ReactNode } from 'react' import { Attributes, ReactNode } from 'react'
import { System } from '@/types'
export type LayoutType = 'list' | 'form' | 'tree' | 'normal' export type LayoutType = 'list' | 'form' | 'tree' | 'normal'
@ -13,6 +14,7 @@ export type IAppData = {
baseUrl: string; baseUrl: string;
token: string; token: string;
device: string; device: string;
currentUser: System.IUser
} }
export type TRouter = { export type TRouter = {

65
src/hooks/useInlineStyle.ts

@ -0,0 +1,65 @@
import React, { useEffect, useRef } from 'react'
interface UseStyleOptions {
selector?: string;
styles: React.CSSProperties & { [key: `--${string}`]: string };
}
const applyStyles = (element: HTMLElement, styles: React.CSSProperties) => {
Object.entries(styles).forEach(([ key, value ]) => {
element.style.setProperty(key, value as string)
})
}
const removeStyles = (element: HTMLElement, styles: React.CSSProperties) => {
Object.keys(styles).forEach(key => {
element.style.removeProperty(key)
})
}
function useInlineStyle({ selector, styles }: UseStyleOptions) {
const elementRef = useRef<HTMLElement | null>(null)
useEffect(() => {
let targetElement: HTMLElement | null = null
const manageStyles = () => {
targetElement = selector ? document.querySelector<HTMLElement>(selector) : elementRef.current
if (!targetElement) return
applyStyles(targetElement, styles)
return () => {
if (targetElement) {
removeStyles(targetElement, styles)
}
}
}
if (selector) {
if (document.readyState === 'complete' || document.readyState === 'interactive') {
manageStyles()
} else {
document.addEventListener('DOMContentLoaded', manageStyles)
return () => {
document.removeEventListener('DOMContentLoaded', manageStyles)
}
}
} else {
manageStyles()
}
return () => {
if (targetElement) {
removeStyles(targetElement, styles)
}
}
}, [ selector, styles ])
return elementRef
}
export default useInlineStyle

63
src/hooks/useResizeObserver.ts

@ -0,0 +1,63 @@
import { useState, useEffect, useRef } from 'react'
interface UseResizeObserverOptions {
selector?: string;
}
interface Size {
width: number;
height: number;
inlineSize: number;
blockSize: number;
}
function useResizeObserver({ selector }: UseResizeObserverOptions = {}) {
const [ size, setSize ] = useState<Size>({ width: 0, height: 0, inlineSize: 0, blockSize: 0 })
const elementRef = useRef<HTMLElement | null>(null)
useEffect(() => {
const handleResize = (entries: ResizeObserverEntry[]) => {
if (entries[0]) {
const [ { inlineSize, blockSize } ] = entries[0].borderBoxSize
const { width, height } = entries[0].contentRect
setSize({ width, height, inlineSize, blockSize })
}
}
let resizeObserver: ResizeObserver | null = null
let targetElement: HTMLElement | null = null
const observeElement = () => {
targetElement = selector ? document.querySelector<HTMLElement>(selector) : elementRef.current
if (!targetElement) return
resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(targetElement)
}
if (selector) {
if (document.readyState === 'complete' || document.readyState === 'interactive') {
observeElement()
} else {
document.addEventListener('DOMContentLoaded', observeElement)
return () => {
document.removeEventListener('DOMContentLoaded', observeElement)
}
}
} else {
observeElement()
}
return () => {
if (resizeObserver && targetElement) {
resizeObserver.unobserve(targetElement)
resizeObserver.disconnect()
}
}
}, [ selector, elementRef.current ])
return [ elementRef, size ] as const
}
export default useResizeObserver

20
src/layout/ListPageLayout.tsx

@ -1,6 +1,10 @@
import React from 'react' import React from 'react'
import { useStyle } from '@/layout/style.ts' import { useStyle } from '@/layout/style.ts'
import { PageContainer, PageContainerProps } from '@ant-design/pro-components' import { PageContainer, PageContainerProps } from '@ant-design/pro-components'
import { useAtomValue } from 'jotai'
import { currentMenuAtom } from '@/store/system.ts'
import useResizeObserver from '@/hooks/useResizeObserver.ts'
import useInlineStyle from '@/hooks/useInlineStyle.ts'
interface IListPageLayoutProps extends PageContainerProps { interface IListPageLayoutProps extends PageContainerProps {
children: React.ReactNode children: React.ReactNode
@ -11,13 +15,25 @@ const ListPageLayout: React.FC<IListPageLayoutProps> = (
{ {
className, children, authHeight = true, ...props className, children, authHeight = true, ...props
}) => { }) => {
const { styles, cx } = useStyle({ className: 'two-col' })
const { styles, cx } = useStyle({ className: 'one-col' })
const currentMenu = useAtomValue(currentMenuAtom)
const [ , headerSize ] = useResizeObserver({
selector: '.ant-page-header',
})
useInlineStyle({
styles: {
'--pageHeader': `${headerSize.blockSize}px`,
},
selector: `.one-col`,
})
return ( return (
<> <>
<PageContainer <PageContainer
breadcrumbRender={false} title={false}
ghost={false}
// breadcrumbRender={false}
title={ currentMenu?.title }
className={cx(styles.container, styles.pageCard, styles.layoutTable, className)} className={cx(styles.container, styles.pageCard, styles.layoutTable, className)}
{...props} {...props}

25
src/layout/RootLayout.tsx

@ -2,21 +2,21 @@ import Avatar from '@/components/avatar'
import PageBreadcrumb from '@/components/breadcrumb' import PageBreadcrumb from '@/components/breadcrumb'
import ErrorPage from '@/components/error/error.tsx' import ErrorPage from '@/components/error/error.tsx'
import SelectLang from '@/components/select-lang' import SelectLang from '@/components/select-lang'
import { appAtom } from '@/store/system.ts'
import { userMenuDataAtom } from '@/store/system/user.ts'
import { appAtom, currentMenuAtom } from '@/store/system.ts'
import { currentStaticUserAtom, userMenuDataAtom } from '@/store/system/user.ts'
import { MenuItem } from '@/global' import { MenuItem } from '@/global'
import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components' import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components'
import { zhCNIntl, enUSIntl } from '@ant-design/pro-provider/es/intl' import { zhCNIntl, enUSIntl } from '@ant-design/pro-provider/es/intl'
import { CatchBoundary, Link, Outlet, useNavigate } from '@tanstack/react-router' import { CatchBoundary, Link, Outlet, useNavigate } from '@tanstack/react-router'
import { ConfigProvider } from '@/components/config-provider' import { ConfigProvider } from '@/components/config-provider'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useAtomValue } from 'jotai'
import { useAtomValue, useSetAtom } from 'jotai'
import { useStyle } from '@/layout/style.ts' import { useStyle } from '@/layout/style.ts'
import zh from 'antd/locale/zh_CN' import zh from 'antd/locale/zh_CN'
import en from 'antd/locale/en_US' import en from 'antd/locale/en_US'
import type { MenuDataItem } from '@ant-design/pro-layout/es/typing' import type { MenuDataItem } from '@ant-design/pro-layout/es/typing'
import { convertToMenu, flattenTree } from '@/utils' import { convertToMenu, flattenTree } from '@/utils'
import { Flex, Menu, Space } from 'antd'
import { Flex, Menu, Space, Watermark } from 'antd'
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons' import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'
import { If, Then } from 'react-if' import { If, Then } from 'react-if'
@ -47,8 +47,10 @@ export default () => {
const navigate = useNavigate() const navigate = useNavigate()
const { styles } = useStyle() const { styles } = useStyle()
const currentUser = useAtomValue(currentStaticUserAtom)
const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom) const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom)
const { language } = useAtomValue(appAtom) const { language } = useAtomValue(appAtom)
const setCurrentMenu = useSetAtom(currentMenuAtom)
const items = getBreadcrumbData(menuData, location.pathname) const items = getBreadcrumbData(menuData, location.pathname)
const [ pathname, setPathname ] = useState(location.pathname) const [ pathname, setPathname ] = useState(location.pathname)
const [ openMenuKeys, setOpenKeys ] = useState<string[]>([]) const [ openMenuKeys, setOpenKeys ] = useState<string[]>([])
@ -80,6 +82,10 @@ export default () => {
} }
}, [ location.pathname ]) }, [ location.pathname ])
useEffect(() => {
}, [location.pathname])
return ( return (
<div <div
className={styles.container} className={styles.container}
@ -100,6 +106,15 @@ export default () => {
return document.getElementById('crazy-pro-layout') || document.body return document.getElementById('crazy-pro-layout') || document.body
}} }}
> >
<Watermark {
...{
rotate: -31,
content: currentUser?.nickname,
fontColor: 'rgba(0,0,0,.15)',
fontSize: 17,
zIndex: 1009,
}
} style={{ width: '100vw', height: '100vh'}}>
<ProLayout <ProLayout
token={{ token={{
header: { header: {
@ -209,6 +224,7 @@ export default () => {
onClick={(menu) => { onClick={(menu) => {
const info = menusFlatten.current?.find(item => item.path === menu.key) const info = menusFlatten.current?.find(item => item.path === menu.key)
if (info) { if (info) {
setCurrentMenu(info)
// setOpenKeys([ info.path as string ]) // setOpenKeys([ info.path as string ])
navigate({ navigate({
to: info.path, to: info.path,
@ -304,6 +320,7 @@ export default () => {
> >
</ProLayout>*/} </ProLayout>*/}
</ProLayout> </ProLayout>
</Watermark>
</ConfigProvider> </ConfigProvider>
</ProConfigProvider> </ProConfigProvider>
</CatchBoundary> </CatchBoundary>

21
src/layout/TwoColPageLayout.tsx

@ -3,6 +3,10 @@ import { PageContainer, PageContainerProps } from '@ant-design/pro-components'
import { Flexbox } from 'react-layout-kit' import { Flexbox } from 'react-layout-kit'
import { DraggablePanel, DraggablePanelProps } from '@/components/draggable-panel' import { DraggablePanel, DraggablePanelProps } from '@/components/draggable-panel'
import { useStyle } from './style' import { useStyle } from './style'
import { useAtomValue } from 'jotai/index'
import { currentMenuAtom } from '@/store/system.ts'
import useResizeObserver from '@/hooks/useResizeObserver.ts'
import useInlineStyle from '@/hooks/useInlineStyle.ts'
interface ITreePageLayoutProps { interface ITreePageLayoutProps {
children?: React.ReactNode children?: React.ReactNode
@ -14,10 +18,23 @@ interface ITreePageLayoutProps {
export const TwoColPageLayout: React.FC<ITreePageLayoutProps> = ({ className, ...props }) => { export const TwoColPageLayout: React.FC<ITreePageLayoutProps> = ({ className, ...props }) => {
const { styles, cx } = useStyle({ className: 'two-col' }) const { styles, cx } = useStyle({ className: 'two-col' })
const currentMenu = useAtomValue(currentMenuAtom)
const [ , headerSize ] = useResizeObserver({
selector: '.ant-page-header',
})
useInlineStyle({
styles: {
'--pageHeader': `${headerSize.blockSize}px`,
},
selector: `.two-col`,
})
return ( return (
<PageContainer <PageContainer
breadcrumbRender={false} title={false}
className={cx(styles.pageCard, styles.layoutTable, className)}
breadcrumbRender={false}
title={currentMenu?.title}
className={cx(styles.container, styles.pageCard, styles.layoutTable, className)}
{...props.pageProps} {...props.pageProps}
> >
<Flexbox horizontal className={styles.authHeight}> <Flexbox horizontal className={styles.authHeight}>

17
src/layout/style.ts

@ -40,6 +40,9 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
padding-block: 20px; padding-block: 20px;
} }
.ant-page-header{
background-color: white;
}
.ant-page-header-no-children { .ant-page-header-no-children {
height: 0px; height: 0px;
} }
@ -52,8 +55,8 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
.ant-pro-card-body{ .ant-pro-card-body{
padding-block-start: 0; padding-block-start: 0;
overflow: auto; overflow: auto;
height: calc(100vh - 160px);
min-height: calc(100vh - 160px);
height: calc(100vh - 160px - var(--pageHeader, 0px));
min-height: calc(100vh - 160px - var(--pageHeader, 0px));
${scrollbarBackground} ${scrollbarBackground}
} }
@ -77,6 +80,10 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
height: 100%; height: 100%;
} }
.layoutkit-flexbox{
height: calc(100% - var(--pageHeader, 0px));
}
` `
const pageContext = css` const pageContext = css`
@ -157,6 +164,8 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
.ant-menu-inline-collapsed >.ant-menu-item{ .ant-menu-inline-collapsed >.ant-menu-item{
padding-inline: calc(50% - 8px); padding-inline: calc(50% - 8px);
height: 52px;
line-height: 52px;
} }
.ant-menu-inline >.ant-menu-submenu>.ant-menu-submenu-title, .ant-menu-inline >.ant-menu-submenu>.ant-menu-submenu-title,
@ -182,6 +191,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
.ant-menu-light>.ant-menu .ant-menu-item-selected{ .ant-menu-light>.ant-menu .ant-menu-item-selected{
background-color: #3f9eff; background-color: #3f9eff;
color: white; color: white;
} }
` `
@ -282,12 +292,13 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
const body = css` const body = css`
overflow: hidden; overflow: hidden;
--bodyHeader: 50px; --bodyHeader: 50px;
--pageHeader: 0px;
.ant-pro-page-container { .ant-pro-page-container {
width: 100%; width: 100%;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
height: calc(100vh - var(--bodyHeader, 50px));
height: calc(100vh - var(--bodyHeader, 50px) - var(--pageHeader, 0px));
} }
` `

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

@ -179,6 +179,12 @@ const Menus = () => {
<Form.Item label={t('system.menus.form.path', '路由')} name={'path'}> <Form.Item label={t('system.menus.form.path', '路由')} name={'path'}>
<Input/> <Input/>
</Form.Item> </Form.Item>
<Form.Item label={t('system.menus.form.active', '菜单高亮')}
name={'active'}
help={t('system.menus.form.activeHelp','子节点或详情页需要高亮的上级菜单路由地址')}
>
<Input/>
</Form.Item>
<Form.Item label={t('system.menus.form.component', '视图')} <Form.Item label={t('system.menus.form.component', '视图')}
name={'component'} name={'component'}
@ -202,7 +208,7 @@ const Menus = () => {
loading={isPending} loading={isPending}
onClick={() => { onClick={() => {
form.validateFields().then((values) => { form.validateFields().then((values) => {
console.log(values)
// console.log(values)
mutate(values) mutate(values)
}) })
}} }}

5
src/pages/websites/domain/index.tsx

@ -17,6 +17,7 @@ import { useStyle } from './style'
import { FilterOutlined } from '@ant-design/icons' import { FilterOutlined } from '@ant-design/icons'
import { getValueCount } from '@/utils' import { getValueCount } from '@/utils'
import { Table as ProTable } from '@/components/table' import { Table as ProTable } from '@/components/table'
import { Link } from '@tanstack/react-router'
const i18nPrefix = 'websiteDomains.list' const i18nPrefix = 'websiteDomains.list'
@ -48,8 +49,10 @@ const WebsiteDomain = () => {
{ {
title: t(`${i18nPrefix}.columns.name`, '域名'), title: t(`${i18nPrefix}.columns.name`, '域名'),
dataIndex: 'name', dataIndex: 'name',
render(_text, record){
return <Link to={'/websites/record' }>{record.name}</Link>
}
}, },
{ {
title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'), title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'),
dataIndex: 'dns_account_id', dataIndex: 'dns_account_id',

5
src/store/system.ts

@ -2,6 +2,8 @@ import { IAppData } from '@/global'
import { createStore } from 'jotai' import { createStore } from 'jotai'
import { atomWithStorage } from 'jotai/utils' import { atomWithStorage } from 'jotai/utils'
import { changeLanguage as setLang } from 'i18next' import { changeLanguage as setLang } from 'i18next'
import { atom } from 'jotai/index'
import { System } from '@/types'
/** /**
* app全局状态 * app全局状态
@ -24,6 +26,9 @@ export const getAppData = () => {
return appStore.get(appAtom) return appStore.get(appAtom)
} }
export const currentMenuAtom = atom<System.IMenu>(null)
export const changeLanguage = (lang: string, reload?: boolean) => { export const changeLanguage = (lang: string, reload?: boolean) => {
setLang(lang) setLang(lang)
updateAppData({ language: lang }) updateAppData({ language: lang })

11
src/store/system/user.ts

@ -1,5 +1,5 @@
import { appAtom, setToken } from '@/store/system.ts' import { appAtom, setToken } from '@/store/system.ts'
import { atom } from 'jotai/index'
import { atom } from 'jotai'
import { IApiResult, IAuth, IPage, IPageResult, MenuItem } from '@/global' import { IApiResult, IAuth, IPage, IPageResult, MenuItem } from '@/global'
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'
import systemServ from '@/service/system.ts' import systemServ from '@/service/system.ts'
@ -7,6 +7,8 @@ import { formatMenuData, isDev } from '@/utils'
import { message } from 'antd' import { message } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { System } from '@/types' import { System } from '@/types'
import { atomWithStorage } from 'jotai/utils'
import { IUserInfo } from '@/types/system/user'
export const authAtom = atom<IAuth>({ export const authAtom = atom<IAuth>({
isLogin: false, isLogin: false,
@ -44,6 +46,9 @@ export const logoutAtom = atomWithMutation(() => ({
}, },
})) }))
export const currentStaticUserAtom = atomWithStorage<IUserInfo | null>('user', null)
export const currentUserAtom = atomWithQuery<IApiResult<System.IUserInfo>, any, System.IUserInfo>((get) => { export const currentUserAtom = atomWithQuery<IApiResult<System.IUserInfo>, any, System.IUserInfo>((get) => {
return { return {
queryKey: [ 'user_info', get(appAtom).token ], queryKey: [ 'user_info', get(appAtom).token ],
@ -51,11 +56,13 @@ export const currentUserAtom = atomWithQuery<IApiResult<System.IUserInfo>, any,
return await systemServ.user.current() return await systemServ.user.current()
}, },
select: (data) => { select: (data) => {
// store.set(currentStaticUserAtom, data.data)
return data.data return data.data
}
},
} }
}) })
export const userMenuDataAtom = atomWithQuery<IApiResult<IPageResult<System.IMenu[]>>, any, MenuItem[]>((get) => ({ export const userMenuDataAtom = atomWithQuery<IApiResult<IPageResult<System.IMenu[]>>, any, MenuItem[]>((get) => ({
enabled: false, enabled: false,
queryKey: [ 'user_menus', get(appAtom).token ], queryKey: [ 'user_menus', get(appAtom).token ],

3
src/utils/index.ts

@ -64,6 +64,9 @@ export const convertToMenu = (data: any[], format?: (item: any) => any) => {
const result: MenuItemProps[] = [] const result: MenuItemProps[] = []
format = format ?? ((item: any) => item) format = format ?? ((item: any) => item)
for (const item of data) { for (const item of data) {
if (item.hidden) {
continue
}
const _item = format(item) const _item = format(item)
if (_item.children && _item.children.length) { if (_item.children && _item.children.length) {
result.push({ result.push({

Loading…
Cancel
Save