Browse Source

调整整体布局

main
dark 3 months ago
parent
commit
d20e845a8d
  1. 38
      src/App.tsx
  2. 98
      src/components/avatar/index.tsx
  3. 13
      src/components/avatar/style.ts
  4. 1
      src/components/config-provider/index.tsx
  5. 3
      src/components/icon/picker/IconRender.tsx
  6. 79
      src/components/select-lang/index.tsx
  7. 1
      src/components/table/Table.tsx
  8. 21
      src/components/table/style.ts
  9. 2
      src/context.ts
  10. 49
      src/hooks/useScrollStyle.ts
  11. 6
      src/index.css
  12. 4
      src/layout/ListPageLayout.tsx
  13. 152
      src/layout/RootLayout.tsx
  14. 2
      src/layout/TwoColPageLayout.tsx
  15. 235
      src/layout/style.ts
  16. 4
      src/pages/cms/category/index.tsx
  17. 11
      src/pages/cms/category/style.ts
  18. 5
      src/routes.tsx
  19. 9
      src/store/cms/video.ts
  20. 42
      src/store/system.ts
  21. 4
      src/theme/themes/darkAlgorithm.ts
  22. 20
      src/utils/index.ts

38
src/App.tsx

@ -3,7 +3,7 @@ import { initI18n } from '@/i18n.ts'
import { appAtom, appStore, changeLanguage } from '@/store/system.ts' import { appAtom, appStore, changeLanguage } from '@/store/system.ts'
import { IAppData } from '@/global' import { IAppData } from '@/global'
import { ConfigProvider } from '@/components/config-provider' import { ConfigProvider } from '@/components/config-provider'
import { Provider, useAtom } from 'jotai'
import { Provider } from 'jotai'
import './App.css' import './App.css'
import { useEffect } from 'react' import { useEffect } from 'react'
import { RootProvider } from './routes.tsx' import { RootProvider } from './routes.tsx'
@ -11,27 +11,25 @@ import { RootProvider } from './routes.tsx'
function App() { function App() {
const [ appData, ] = useAtom(appAtom)
useEffect(() => {
initI18n()
}, [])
useEffect(() => {
initI18n()
}, [])
return (
<ConfigProvider>
<AppContextProvider value={{
get appData() {
return appData as IAppData
},
changeLanguage
}}>
<Provider store={appStore}>
<RootProvider context={{}}/>
</Provider>
</AppContextProvider>
</ConfigProvider>
)
return (
<ConfigProvider>
<AppContextProvider value={{
appData: () => {
return appStore.get(appAtom) as IAppData
},
changeLanguage
}}>
<Provider store={appStore}>
<RootProvider context={{}}/>
</Provider>
</AppContextProvider>
</ConfigProvider>
)
} }
export default App export default App

98
src/components/avatar/index.tsx

@ -4,60 +4,62 @@ import { 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 } from 'jotai'
import { useNavigate } from '@tanstack/react-router' import { useNavigate } from '@tanstack/react-router'
import { useStyle } from './style'
const Avatar = () => { const Avatar = () => {
const { t } = useTranslation()
const { data, isLoading } = useAtomValue(currentUserAtom)
const { mutate: logout } = useAtomValue(logoutAtom)
const navigate = useNavigate()
const { styles } = useStyle()
const { t } = useTranslation()
const { data, isLoading } = useAtomValue(currentUserAtom)
const { mutate: logout } = useAtomValue(logoutAtom)
const navigate = useNavigate()
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>,
},
],
onClick: (e) => {
if (e.key === 'logout') {
logout()
navigate({
to: '/login', search: {
redirect: window.location.pathname
}
})
}
},
}}
>
<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'
}}>
return (
<div className={styles.container}>
<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>,
},
],
onClick: (e) => {
if (e.key === 'logout') {
logout()
navigate({
to: '/login', search: {
redirect: window.location.pathname
}
})
}
},
}}
>
<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} {data?.nickname}
</span> </span>
</Spin>
</Dropdown>
</div>
)
</Spin>
</Dropdown>
</div>
)
} }
export default Avatar export default Avatar

13
src/components/avatar/style.ts

@ -0,0 +1,13 @@
import { createStyles } from '@/theme'
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
const prefix = `${prefixCls}-${token?.proPrefix}-avatar-component`
const container = css`
padding-inline: 10px;
`
return {
container: cx(prefix, props?.className, container),
}
})

1
src/components/config-provider/index.tsx

@ -47,6 +47,7 @@ export const ConfigProvider: FC<ConfigProviderProps> = ({ children, componentTok
// 以下都是自定义主题 // 以下都是自定义主题
theme={createProAntdTheme} theme={createProAntdTheme}
customToken={getProToken} customToken={getProToken}
> >
{children} {children}
</ThemeProvider> </ThemeProvider>

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

@ -2,6 +2,7 @@ import type { FC } from 'react'
import { memo } from 'react' import { memo } from 'react'
import { AntdIcon, ParkIcon, ALL_ICON_KEYS } from './icons.ts' import { AntdIcon, ParkIcon, ALL_ICON_KEYS } from './icons.ts'
import { IconType } from '@icon-park/react/es/all' import { IconType } from '@icon-park/react/es/all'
import { cx } from 'antd-style'
export interface IconRenderProps { export interface IconRenderProps {
type: 'antd' | 'park'; type: 'antd' | 'park';
@ -32,7 +33,7 @@ const Render: FC<IconRenderProps> = memo(
if (ALL_ICON_KEYS.indexOf(componentName as IconType) < 0) { if (ALL_ICON_KEYS.indexOf(componentName as IconType) < 0) {
return null return null
} }
return <ParkIcon type={componentName} {...props} />
return <ParkIcon type={componentName} {...props} className={cx(props?.className, 'anticon')} />
default: { default: {
return null return null
} }

79
src/components/select-lang/index.tsx

@ -1,6 +1,6 @@
import { useAppContext } from '@/context' import { useAppContext } from '@/context'
import { Dropdown, DropDownProps } from 'antd' import { Dropdown, DropDownProps } from 'antd'
import React, { memo, useState } from 'react'
import React, { memo } from 'react'
interface LocalData { interface LocalData {
@ -43,22 +43,22 @@ const defaultLangUConfigMap = {
export interface HeaderDropdownProps extends DropDownProps { export interface HeaderDropdownProps extends DropDownProps {
overlayClassName?: string; overlayClassName?: string;
placement?: placement?:
| 'bottomLeft'
| 'bottomRight'
| 'topLeft'
| 'topCenter'
| 'topRight'
| 'bottomCenter';
| 'bottomLeft'
| 'bottomRight'
| 'topLeft'
| 'topCenter'
| 'topRight'
| 'bottomCenter';
} }
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ const HeaderDropdown: React.FC<HeaderDropdownProps> = ({
overlayClassName: cls, overlayClassName: cls,
...restProps ...restProps
}) => ( }) => (
<Dropdown
overlayClassName={cls}
{...restProps}
/>
<Dropdown
overlayClassName={cls}
{...restProps}
/>
) )
export const SelectLang = memo((props: SelectLangProps) => { export const SelectLang = memo((props: SelectLangProps) => {
@ -74,37 +74,36 @@ export const SelectLang = memo((props: SelectLangProps) => {
reload, reload,
...restProps ...restProps
} = props } = props
const [ selectedLang, setSelectedLang ] = useState(() => ctx.appData.language)
const changeLang = ({ key }: ClickParam): void => { const changeLang = ({ key }: ClickParam): void => {
ctx.changeLanguage(key, reload) ctx.changeLanguage(key, reload)
setSelectedLang(key)
} }
const defaultLangUConfig = Object.values(defaultLangUConfigMap) const defaultLangUConfig = Object.values(defaultLangUConfigMap)
const allLangUIConfig = const allLangUIConfig =
postLocalesData?.(defaultLangUConfig) || defaultLangUConfig
postLocalesData?.(defaultLangUConfig) || defaultLangUConfig
const handleClick = onItemClick const handleClick = onItemClick
? (params: ClickParam) => onItemClick(params)
: changeLang
? (params: ClickParam) => onItemClick(params)
: changeLang
const menuItemStyle = { minWidth: '160px' } const menuItemStyle = { minWidth: '160px' }
const menuItemIconStyle = { marginRight: '8px' } const menuItemIconStyle = { marginRight: '8px' }
const language = ctx.appData().language
const langMenu = { const langMenu = {
selectedKeys: [ selectedLang ],
selectedKeys: [ language ],
onClick: handleClick, onClick: handleClick,
items: allLangUIConfig.map((localeObj) => ({ items: allLangUIConfig.map((localeObj) => ({
key: localeObj.lang || localeObj.key, key: localeObj.lang || localeObj.key,
style: menuItemStyle, style: menuItemStyle,
label: ( label: (
<>
<>
<span role="img" aria-label={localeObj?.label || 'zh-CN'} style={menuItemIconStyle}> <span role="img" aria-label={localeObj?.label || 'zh-CN'} style={menuItemIconStyle}>
{localeObj?.icon || '🌐'} {localeObj?.icon || '🌐'}
</span> </span>
{localeObj?.label || 'zh-CN'}
</>
{localeObj?.label || 'zh-CN'}
</>
), ),
})), })),
} }
@ -123,29 +122,29 @@ export const SelectLang = memo((props: SelectLangProps) => {
} }
return ( return (
<HeaderDropdown {...dropdownProps} placement="bottomRight" {...restProps}>
<HeaderDropdown {...dropdownProps} placement="bottomRight" {...restProps}>
<span className={globalIconClassName} style={inlineStyle}> <span className={globalIconClassName} style={inlineStyle}>
<i className="anticon" title={allLangUIConfig[selectedLang]?.title}>
<i className="anticon" title={allLangUIConfig[language]?.title}>
{icon ? {icon ?
icon : (
<svg
viewBox="0 0 24 24"
focusable="false"
width="1em"
height="1em"
fill="currentColor"
aria-hidden="true"
>
<path d="M0 0h24v24H0z" fill="none"/>
<path
d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z "
className="css-c4d79v"
/>
</svg>
)}
icon : (
<svg
viewBox="0 0 24 24"
focusable="false"
width="1em"
height="1em"
fill="currentColor"
aria-hidden="true"
>
<path d="M0 0h24v24H0z" fill="none"/>
<path
d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z "
className="css-c4d79v"
/>
</svg>
)}
</i> </i>
</span> </span>
</HeaderDropdown>
</HeaderDropdown>
) )
}) })

1
src/components/table/Table.tsx

@ -81,6 +81,7 @@ export const Table = <T extends Record<string, any> = any, D = any>(props: Table
{...props.cardProps} {...props.cardProps}
bodyStyle={{ bodyStyle={{
paddingBlockStart: 0, paddingBlockStart: 0,
paddingBlockEnd: 0,
}} }}
> >
<div ref={toolbarRef as any}>{domList.toolbar}</div> <div ref={toolbarRef as any}>{domList.toolbar}</div>

21
src/components/table/style.ts

@ -1,19 +1,38 @@
import { createStyles } from '@/theme' import { createStyles } from '@/theme'
import { useScrollStyle } from '@/hooks/useScrollStyle.ts'
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
const prefix = `${prefixCls}-${token?.proPrefix}-my-table` const prefix = `${prefixCls}-${token?.proPrefix}-my-table`
const { scrollbar } = useScrollStyle()
const container = css` const container = css`
--toolbar-height: 65px; --toolbar-height: 65px;
--alter-height: 0px; --alter-height: 0px;
--padding: 37px;
--padding: 23px;
--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(--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;
max-height: calc(100vh - var(--table-body-height)) !important; max-height: calc(100vh - var(--table-body-height)) !important;
height: calc(100vh - var(--table-body-height)) !important; height: calc(100vh - var(--table-body-height)) !important;
}
.ant-table-wrapper .ant-table{
scrollbar-color: unset;
}
.ant-table-body{
${scrollbar}
}
.ant-table-pagination.ant-pagination {
border-top: 1px solid #ebeef5;
height: 51px;
margin: 0;
align-content: center;
} }
` `

2
src/context.ts

@ -3,7 +3,7 @@ import React, { createContext, ProviderProps, useContext } from 'react'
import { t } from 'i18next' import { t } from 'i18next'
export interface IAppContextValue { export interface IAppContextValue {
get appData(): IAppData
appData: () => IAppData
changeLanguage: (lang: string, reload?: boolean) => void changeLanguage: (lang: string, reload?: boolean) => void

49
src/hooks/useScrollStyle.ts

@ -67,8 +67,57 @@ export const useScrollStyle = () => {
${scrollbar} ${scrollbar}
` `
const darkScrollbar = css`
&::-webkit-scrollbar {
width: 9px;
height: 9px;
}
&::-webkit-scrollbar-track {
border-radius: 8px;
background-color: transparent;
&:hover {
background-color: rgba(255, 255, 255, 0.06);
-webkit-box-shadow: -1px 0 0 #000 inset, 1px 0 0 hsla(0, 0%, 0%, 0.9) inset, 0 -1px 0 hsla(0, 0%, 0%, 0.9) inset, 0 1px 0 hsla(0, 0%, 0%, 0.9) inset;
}
&:active {
background-color: rgba(255, 255, 255, 0.1);
}
}
&::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.1);
-webkit-box-shadow: -2px 0 0 #000 inset, 1px 0 0 #000 inset, 0 -1px 0 hsl(0deg 0% 0% / 90%) inset, 0 1px 0 hsl(0deg 0% 0% / 90%) inset;
&:hover {
background-color: rgba(255, 255, 255, 0.4);
}
&:active {
background: rgba(255, 255, 255, 0.6);
}
}
`;
const darkScrollbarBackground = css`
background: linear-gradient(#000 30%, hsla(0, 0%, 0%, 0)),
linear-gradient(hsla(0, 0%, 0%, 0), #000 70%) 0 100%,
radial-gradient(farthest-side at 50% 0, rgba(255, 255, 255, 0.1), transparent),
radial-gradient(farthest-side at 50% 100%, rgba(255, 255, 255, 0.1), transparent) 0 100%;
background-repeat: no-repeat;
background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
background-attachment: local, local, scroll, scroll;
${darkScrollbar}
`;
return { return {
scrollbarBackground, scrollbarBackground,
scrollbar, scrollbar,
darkScrollbarBackground,
darkScrollbar,
} }
} }

6
src/index.css

@ -1,8 +1,14 @@
html,
body{ body{
height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
} }
body.login { body.login {
overflow: hidden; overflow: hidden;
}
#root{
height: 100%;
} }

4
src/layout/ListPageLayout.tsx

@ -9,7 +9,7 @@ interface IListPageLayoutProps extends PageContainerProps {
const ListPageLayout: React.FC<IListPageLayoutProps> = ( const ListPageLayout: React.FC<IListPageLayoutProps> = (
{ {
children, authHeight = true, ...props
className, children, authHeight = true, ...props
}) => { }) => {
const { styles, cx } = useStyle({ className: 'two-col' }) const { styles, cx } = useStyle({ className: 'two-col' })
@ -18,7 +18,7 @@ const ListPageLayout: React.FC<IListPageLayoutProps> = (
<> <>
<PageContainer <PageContainer
breadcrumbRender={false} title={false} breadcrumbRender={false} title={false}
className={cx(styles.container, styles.pageCard)}
className={cx(styles.container, styles.pageCard, styles.layoutTable, className)}
{...props} {...props}
> >

152
src/layout/RootLayout.tsx

@ -7,7 +7,7 @@ import { 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 } 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 } from 'jotai'
@ -15,7 +15,9 @@ 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 { flattenTree } from '@/utils'
import { convertToMenu, flattenTree } from '@/utils'
import { Flex, Menu, Space } from 'antd'
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'
//根据menuData生成Breadcrumb所需的数据 //根据menuData生成Breadcrumb所需的数据
const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => { const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => {
@ -41,11 +43,14 @@ const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => {
export default () => { export default () => {
const navigate = useNavigate()
const { styles } = useStyle() const { styles } = useStyle()
const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom) const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom)
const { language } = useAtomValue(appAtom) const { language } = useAtomValue(appAtom)
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 [ collapsed, setCollapsed ] = useState(false)
const menusFlatten = useRef<MenuItem[]>() const menusFlatten = useRef<MenuItem[]>()
if (!menusFlatten.current) { if (!menusFlatten.current) {
@ -59,10 +64,12 @@ export default () => {
return item ? item.parentName : [] return item ? item.parentName : []
}) })
const childMenuRef = useRef<MenuItem[]>([]) const childMenuRef = useRef<MenuItem[]>([])
childMenuRef.current = menuData.find(item => {
const currentMenu = menuData.find(item => {
return item.key === rootMenuKeys?.[0] return item.key === rootMenuKeys?.[0]
})?.children || []
})
childMenuRef.current = currentMenu?.children || []
useEffect(() => { useEffect(() => {
const item = menusFlatten.current?.find(item => item.path === location.pathname) const item = menusFlatten.current?.find(item => item.path === location.pathname)
@ -71,13 +78,12 @@ export default () => {
} }
}, [ location.pathname ]) }, [ location.pathname ])
return ( return (
<div <div
className={styles.container} className={styles.container}
id="crazy-pro-layout" id="crazy-pro-layout"
style={{ style={{
height: '100vh',
// height: '100vh',
// overflow: 'auto', // overflow: 'auto',
}} }}
> >
@ -97,16 +103,25 @@ export default () => {
header: { header: {
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)', colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
}, },
sider: {
colorMenuBackground: '#222b45',
}
}} }}
fixedHeader={true}
headerContentRender={() => <PageBreadcrumb
className={'top-breadcrumb'}
showIcon={false}
items={items}/>}
className={styles.myLayout}
// fixedHeader={true}
headerContentRender={false}
headerTitleRender={false}
menuHeaderRender={() => {
return <>
<img height={40}
src={'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'}/>
</>
}}
headerRender={false}
title="Crazy Pro" title="Crazy Pro"
layout={'mix'}
// layout={'mix'}
fixSiderbar={true} fixSiderbar={true}
siderWidth={100}
siderWidth={65}
collapsedButtonRender={false} collapsedButtonRender={false}
// collapsed={false} // collapsed={false}
postMenuData={() => { postMenuData={() => {
@ -137,40 +152,100 @@ export default () => {
}} }}
menuItemRender={(item: MenuDataItem) => { menuItemRender={(item: MenuDataItem) => {
return <span style={{ userSelect: 'none' }} onClick={() => {
return (<span style={{ userSelect: 'none' }} onClick={() => {
setRootMenuKeys([ (item as any).key || 'dashboard' ]) setRootMenuKeys([ (item as any).key || 'dashboard' ])
setPathname(item.path || '/dashboard') setPathname(item.path || '/dashboard')
}} }}
> >
<Link to={item.path} className={'menu-link'} target={item.type === 'url' ? '_blank' : '_self'}>
<span>{item.icon}</span>
<span>{item.name}</span>
</Link>
</span>
}}
avatarProps={{
// src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
render: () => {
return (
<Avatar/>
)
},
}}
actionsRender={(props) => {
if (props.isMobile) return []
if (typeof window === 'undefined') return []
return [
<SelectLang/>,
]
<Link to={item.path} className={'menu-link'}
target={item.type === 'url' ? '_blank' : '_self'}>
<span>{item.icon}</span>
<span>{item.name}</span>
</Link>
</span>)
}} }}
avatarProps={false}
actionsRender={false}
menuProps={{ menuProps={{
className: styles.mySiderMenu, className: styles.mySiderMenu,
selectedKeys: rootMenuKeys, selectedKeys: rootMenuKeys,
theme: 'dark',
}} }}
// navTheme={'light'}
loading={isLoading}
contentStyle={{ paddingBlock: 0, paddingInline: 0 }} contentStyle={{ paddingBlock: 0, paddingInline: 0 }}
{
...{
// "fixSiderbar": true,
// "layout": "side",
'splitMenus': false,
'navTheme': 'realDark',
'contentWidth': 'Fluid',
'colorPrimary': '#1677FF',
// "menuHeaderRender": false
}
}
> >
<ProLayout
<Flex className={styles.childMenus}>
{
!collapsed && <div className={styles.childMenuTop}>
<h2>{currentMenu?.title}</h2>
</div>
}
<Menu
mode={'inline'}
inlineCollapsed={collapsed}
selectedKeys={[ location.pathname ]}
openKeys={openMenuKeys}
onOpenChange={(keys) => {
setOpenKeys(keys)
}}
onClick={(menu) => {
const info = menusFlatten.current?.find(item => item.path === menu.key)
if (info) {
setOpenKeys([ info.path as string ])
navigate({
to: info.path,
})
}
}}
items={convertToMenu((childMenuRef.current || []), (item => {
return {
...item,
key: item.path,
label: item.title,
}
})) as any}
style={!collapsed ? { width: 210 } : {}}
/>
<div className={styles.childMenuBottom}
onClick={() => {
setCollapsed(!collapsed)
}}>
{
collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>
}
</div>
</Flex>
<Flex flex={1} className={styles.body} aria-description={'main-body'} vertical={true}>
<div className={styles.bodyHeader}>
<PageBreadcrumb
className={'top-breadcrumb'}
showIcon={false}
items={items}/>
<Flex flex={1}>
<></>
</Flex>
<Space className={styles.headerRight}>
<SelectLang/>
<Avatar/>
</Space>
</div>
<Outlet/>
</Flex>
{/*<ProLayout
className={styles.mySider} className={styles.mySider}
headerRender={false} headerRender={false}
hasSiderMenu={false} hasSiderMenu={false}
@ -218,8 +293,7 @@ export default () => {
// layout: 'side', // layout: 'side',
}} }}
> >
<Outlet/>
</ProLayout>
</ProLayout>*/}
</ProLayout> </ProLayout>
</ConfigProvider> </ConfigProvider>
</ProConfigProvider> </ProConfigProvider>

2
src/layout/TwoColPageLayout.tsx

@ -16,7 +16,7 @@ export const TwoColPageLayout: React.FC<ITreePageLayoutProps> = (props) => {
return ( return (
<PageContainer <PageContainer
breadcrumbRender={false} title={false} breadcrumbRender={false} title={false}
className={cx(styles.container, styles.pageCard)}
className={cx( styles.pageCard)}
{...props.pageProps} {...props.pageProps}
> >
<Flexbox horizontal className={styles.authHeight}> <Flexbox horizontal className={styles.authHeight}>

235
src/layout/style.ts

@ -5,11 +5,12 @@ import { useScrollStyle } from '@/hooks/useScrollStyle'
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
const prefix = `${prefixCls}-${token?.proPrefix}-layout` const prefix = `${prefixCls}-${token?.proPrefix}-layout`
const { scrollbar } = useScrollStyle()
const { scrollbar,darkScrollbar,scrollbarBackground } = useScrollStyle()
const container = { const container = {
[prefix]: css` [prefix]: css`
height: 100%;
.ant-pro-global-header-logo, .ant-pro-global-header-logo,
.ant-pro-layout-bg-list { .ant-pro-layout-bg-list {
@ -20,17 +21,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
//padding-inline-start: 0; //padding-inline-start: 0;
} }
.ant-table-scroll-bar {
}
.ant-table-wrapper .ant-table{
scrollbar-color: unset;
}
.ant-table-body{
${scrollbar}
}
.ant-pro-layout-content{ .ant-pro-layout-content{
padding: 20px; padding: 20px;
@ -41,24 +32,41 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
} }
.ant-pro-page-container-children-container{ .ant-pro-page-container-children-container{
padding-inline: 20px;
padding-block-end: 20px;
padding-inline: 0px;
padding-block: 0px;
} }
.ant-page-header-no-children { .ant-page-header-no-children {
height: 20px;
}
height: 0px;
}
.ant-pro-card{ .ant-pro-card{
height: calc(100vh - 100px)!important;
min-height: calc(100vh - 100px)!important;
height: 100%;
.ant-pro-card-header{
padding-block: 16px;
}
} }
.ant-pro-card-body{
padding-block-start: 0;
overflow: auto;
height: calc(100vh - 100px);
min-height: calc(100vh - 100px);
${scrollbarBackground}
}
`, `,
} }
const pageCard = css` const pageCard = css`
.ant-pro-grid-content,
.ant-pro-grid-content-children,
.ant-pro-page-container-children-container,
.layoutkit-flexbox
{
height: 100%;
}
` `
const pageContext = css` const pageContext = css`
@ -71,6 +79,107 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
} }
` `
const myLayout = css`
height: 100%;
.ant-pro-layout-content{
flex-direction: row;
}
.ant-pro-sider-light{
//background-color: #222b45!important;
}
.ant-pro-sider .ant-layout-sider-children{
padding-inline: 0;
}
.ant-layout-sider-children > div:nth-child(2){
${darkScrollbar}
}
.ant-pro-sider-logo{
padding-block-start: 10px;
padding-block-end: 0;
max-height: 50px;
overflow: hidden;
}
.ant-pro-layout-content,
.ant-pro-layout-container{
width: calc(100vw - 65px);
overflow: hidden;
height: 100vh;
}
`
const childMenus = css`
background: #fff;
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, .05);
border-right: 1px solid #e6e6e6;
// transition: width .3s;
overflow: hidden;
height: 100vh;
display: flex;
flex-direction: column;
.i-icon{
display: inline-flex;
}
.ant-menu{
flex: 1;
border-inline-end: 0!important;
overflow: auto;
${scrollbar}
.ant-menu-item{
border-radius: 0;
margin: 0;
width: 100%;
}
.ant-menu-item-selected{
}
}
.ant-menu-inline-collapsed >.ant-menu-item{
padding-inline: calc(50% - 8px);
}
.ant-menu-inline .ant-menu-submenu-title{
margin-inline: 0;
margin-block: 0;
width: 100%;
border-radius: 0;
}
`
const childMenuTop = css`
overflow: hidden;
border-bottom: 1px solid #ebeef5;
height: var(--bodyHeader, 50px);
line-height: var(--bodyHeader, 50px);
h2{
padding: 0 20px;
font-size: 17px;
color: #3c4a54;
margin: 0;
}
`
const childMenuBottom = css `
border-top: 1px solid #ebeef5;
height: 51px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
`
const mySider = css` const mySider = css`
.ant-layout-sider-children{ .ant-layout-sider-children{
@ -82,7 +191,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
} }
` `
const mySiderMenu = css` const mySiderMenu = css`
padding-top: 10px; padding-top: 10px;
.ant-menu-item{ .ant-menu-item{
@ -109,6 +218,67 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
align-items: center; align-items: center;
} }
} }
.ant-pro-layout .ant-layout-sider.ant-pro-sider{
background: none;
}
&.ant-menu-inline .ant-menu-item{
margin-inline: 0;
margin-block: 0;
width: 65px;
border-radius: 0;
color: white!important;
}
.ant-menu-item:not(.ant-menu-item-selected):hover{
background-color: hsla(0,0%,100%,.1) !important;
color: white!important;
}
.ant-menu-item-selected{
background-color: #409eff;
color: white;
}
`
const body = css`
overflow: hidden;
--bodyHeader: 50px;
.ant-pro-page-container {
width: 100%;
flex: 1;
overflow: hidden;
height: calc(100vh - var(--bodyHeader, 50px));
}
`
const bodyHeader = css`
padding-block-end: 0;
padding-block-start: 0;
border-bottom: 1px solid #ebeef5;
background-color: white;
padding-inline: 15px;
height: var(--bodyHeader, 50px);
line-height: var(--bodyHeader, 50px);
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
`
const headerRight = css`
.ant-space-item{
cursor: pointer;
&:hover{
background-color: ${token.colorBgTextHover};
}
}
` `
const box = css` const box = css`
@ -119,8 +289,19 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
background-color: ${token.colorBgContainer}; background-color: ${token.colorBgContainer};
.ant-pro-draggable-panel-fixed{ .ant-pro-draggable-panel-fixed{
height: calc(100vh - 100px)!important;
min-height: calc(100vh - 100px)!important;
//height: calc(100vh - 100px)!important;
//min-height: calc(100vh - 100px)!important;
}
`
const layoutTable = css`
.ant-pro-table{
.ant-pro-card-body{
height: calc(100vh - 50px);
min-height: calc(100vh - 50px);
}
} }
` `
@ -133,6 +314,14 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
mySider, mySider,
mySiderMenu, mySiderMenu,
pageCard, pageCard,
myLayout,
childMenuTop,
childMenus,
childMenuBottom,
body,
bodyHeader,
headerRight,
layoutTable,
} }
}) })

4
src/pages/cms/category/index.tsx

@ -48,7 +48,9 @@ const Category = () => {
return ( return (
<TwoColPageLayout <TwoColPageLayout
leftPanel={<> leftPanel={<>
<ProCard title={t('videos.category.title', '分类')}>
<ProCard
className={styles.card}
title={t('videos.category.title', '分类')}>
<CategoryTree form={form}/> <CategoryTree form={form}/>
</ProCard> </ProCard>

11
src/pages/cms/category/style.ts

@ -68,6 +68,14 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }) => {
min-height: calc(100vh - 122px); min-height: calc(100vh - 122px);
background-color: ${token.colorBgContainer}; background-color: ${token.colorBgContainer};
` `
const card = css`
.ant-pro-card-body{
height: calc(100% - 100px)!important;
min-height: calc(100% - 100px)!important;
}
`
return { return {
container: cx(prefix), container: cx(prefix),
authHeight, authHeight,
@ -76,6 +84,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }) => {
emptyForm, emptyForm,
tree, tree,
treeNode, treeNode,
treeActions
treeActions,
card,
} }
}) })

5
src/routes.tsx

@ -26,7 +26,7 @@ import {
import { memo, useEffect, useRef } from 'react' import { memo, useEffect, useRef } from 'react'
import RootLayout from './layout/RootLayout' import RootLayout from './layout/RootLayout'
import { IRootContext, MenuItem } from './global' import { IRootContext, MenuItem } from './global'
import { DevTools } from 'jotai-devtools'
// import { DevTools } from 'jotai-devtools'
import { useAtomValue } from 'jotai' import { useAtomValue } from 'jotai'
import { userMenuDataAtom } from '@/store/system/user.ts' import { userMenuDataAtom } from '@/store/system/user.ts'
@ -51,7 +51,7 @@ const rootRoute = createRootRouteWithContext<IRootContext>()({
<> <>
<FetchLoading/> <FetchLoading/>
<Outlet/> <Outlet/>
<DevTools/>
{/*<DevTools/>*/}
{/*<TanStackRouterDevtools position={'bottom-right'}/>*/} {/*<TanStackRouterDevtools position={'bottom-right'}/>*/}
</> </>
), ),
@ -209,6 +209,7 @@ const generateDynamicRoutes = (menuData: MenuItem[], parentRoute: AnyRoute) => {
} else { } else {
if (!path) { if (!path) {
console.log(`${menu.name}没有设置视图`) console.log(`${menu.name}没有设置视图`)
options.id = menu.meta.name
} else { } else {
options.path = path options.path = path
} }

9
src/store/cms/video.ts

@ -25,13 +25,10 @@ export const videoIdsAtom = atom<number[]>([])
export const videoAtom = atom<Cms.IVideo>(undefined as unknown as Cms.IVideo) export const videoAtom = atom<Cms.IVideo>(undefined as unknown as Cms.IVideo)
export const videoSearchAtom = atom<SearchParams>({ export const videoSearchAtom = atom<SearchParams>({
key: ''
} as SearchParams)
export const videoPageAtom = atom<IPage>({
pageSize: 10,
key: '',
pageSize: 20,
page: 1, page: 1,
})
} as SearchParams)
export const videosAtom = atomWithQuery((get) => { export const videosAtom = atomWithQuery((get) => {
return { return {

42
src/store/system.ts

@ -9,45 +9,43 @@ import { changeLanguage as setLang } from 'i18next'
export const appStore = createStore() export const appStore = createStore()
export const appAtom = atomWithStorage<Partial<IAppData>>('app', { export const appAtom = atomWithStorage<Partial<IAppData>>('app', {
name: 'Crazy Pro',
version: '1.0.0',
language: 'zh-CN',
name: 'Crazy Pro',
version: '1.0.0',
language: 'zh-CN',
}) })
appStore.sub(appAtom, () => { appStore.sub(appAtom, () => {
// const token = appStore.get(appAtom).token
// const token = appStore.get(appAtom).token
// console.log('切换 ',appStore.get(appAtom).language)
}) })
export const getAppData = () => { export const getAppData = () => {
return appStore.get(appAtom)
return appStore.get(appAtom)
} }
export const changeLanguage = (lang: string, reload?: boolean) => { export const changeLanguage = (lang: string, reload?: boolean) => {
if (appStore.get(appAtom).language !== lang) {
setLang(lang)
updateAppData({ language: lang })
if (reload) {
window.location.reload()
}
}
setLang(lang)
updateAppData({ language: lang })
if (reload) {
window.location.reload()
}
} }
export const updateAppData = (app: Partial<IAppData>) => { export const updateAppData = (app: Partial<IAppData>) => {
appStore.set(appAtom, (prev) => {
return {
...prev,
...app,
}
})
appStore.set(appAtom, (prev) => {
return {
...prev,
...app,
}
})
} }
export const getToken = () => { export const getToken = () => {
return appStore.get(appAtom).token
return appStore.get(appAtom).token
} }
export const setToken = (token: string) => { export const setToken = (token: string) => {
console.log('settoken', token)
updateAppData({ token })
console.log('settoken', token)
updateAppData({ token })
} }

4
src/theme/themes/darkAlgorithm.ts

@ -12,7 +12,9 @@ export const proDarkAlgorithm: MappingAlgorithm = (seedToken, mapToken) => {
return { return {
...mergeToken, ...mergeToken,
colorBgLayout: '#20252b',
// colorBgLayout: '#20252b',
colorBgLayout: '#222b45',
colorBgContainer: '#282c34', colorBgContainer: '#282c34',
colorBgElevated: '#32363e', colorBgElevated: '#32363e',
} }

20
src/utils/index.ts

@ -1,7 +1,7 @@
import { IMenu } from '@/types/system/menus' import { IMenu } from '@/types/system/menus'
import { FiledNames, FlattenData, MenuItem } from '@/global' import { FiledNames, FlattenData, MenuItem } from '@/global'
import { getIcon } from '@/components/icon' import { getIcon } from '@/components/icon'
import { TreeDataNode } from 'antd'
import { TreeDataNode, MenuItemProps } from 'antd'
//vite环境变量, 判断是否是开发环境 //vite环境变量, 判断是否是开发环境
export const isDev = import.meta.env.MODE === 'development' export const isDev = import.meta.env.MODE === 'development'
@ -59,6 +59,24 @@ export const formatterMenuData = (data: MenuItem[]): TreeDataNode[] => {
return result return result
} }
//将tree转成Menu结构
export const convertToMenu = (data: any[], format?: (item: any) => any) => {
const result: MenuItemProps[] = []
format = format ?? ((item: any) => item)
for (const item of data) {
const _item = format(item)
if (_item.children && _item.children.length) {
result.push( {
..._item,
children: convertToMenu(_item.children, format),
})
}else {
result.push(_item)
}
}
return result
}
//把tree转成平铺数组 //把tree转成平铺数组
const defaultTreeFieldNames: FiledNames = { const defaultTreeFieldNames: FiledNames = {
key: 'id', key: 'id',

Loading…
Cancel
Save