refactor: Enhance UI layout and styling with responsive design improvements
This commit is contained in:
@@ -19,7 +19,10 @@ import {
|
|||||||
IconNoteMoneyStroked,
|
IconNoteMoneyStroked,
|
||||||
IconPriceTag,
|
IconPriceTag,
|
||||||
IconUser,
|
IconUser,
|
||||||
IconLanguage
|
IconLanguage,
|
||||||
|
IconInfoCircle,
|
||||||
|
IconCreditCard,
|
||||||
|
IconTerminal
|
||||||
} from '@douyinfe/semi-icons';
|
} from '@douyinfe/semi-icons';
|
||||||
import { Avatar, Button, Dropdown, Layout, Nav, Switch, Tag } from '@douyinfe/semi-ui';
|
import { Avatar, Button, Dropdown, Layout, Nav, Switch, Tag } from '@douyinfe/semi-ui';
|
||||||
import { stringToColor } from '../helpers/render';
|
import { stringToColor } from '../helpers/render';
|
||||||
@@ -27,6 +30,73 @@ import Text from '@douyinfe/semi-ui/lib/es/typography/text';
|
|||||||
import { StyleContext } from '../context/Style/index.js';
|
import { StyleContext } from '../context/Style/index.js';
|
||||||
import { StatusContext } from '../context/Status/index.js';
|
import { StatusContext } from '../context/Status/index.js';
|
||||||
|
|
||||||
|
// 自定义顶部栏样式
|
||||||
|
const headerStyle = {
|
||||||
|
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)',
|
||||||
|
borderBottom: '1px solid var(--semi-color-border)',
|
||||||
|
background: 'var(--semi-color-bg-0)',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
width: '100%'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义顶部栏按钮样式
|
||||||
|
const headerItemStyle = {
|
||||||
|
borderRadius: '4px',
|
||||||
|
margin: '0 4px',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义顶部栏按钮悬停样式
|
||||||
|
const headerItemHoverStyle = {
|
||||||
|
backgroundColor: 'var(--semi-color-primary-light-default)',
|
||||||
|
color: 'var(--semi-color-primary)'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义顶部栏Logo样式
|
||||||
|
const logoStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '10px',
|
||||||
|
padding: '0 10px',
|
||||||
|
height: '100%'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义顶部栏系统名称样式
|
||||||
|
const systemNameStyle = {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '18px',
|
||||||
|
background: 'linear-gradient(45deg, var(--semi-color-primary), var(--semi-color-secondary))',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
padding: '0 5px'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义顶部栏按钮图标样式
|
||||||
|
const headerIconStyle = {
|
||||||
|
fontSize: '18px',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义头像样式
|
||||||
|
const avatarStyle = {
|
||||||
|
margin: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义下拉菜单样式
|
||||||
|
const dropdownStyle = {
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||||
|
overflow: 'hidden'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义主题切换开关样式
|
||||||
|
const switchStyle = {
|
||||||
|
margin: '0 8px'
|
||||||
|
};
|
||||||
|
|
||||||
const HeaderBar = () => {
|
const HeaderBar = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const [userState, userDispatch] = useContext(UserContext);
|
const [userState, userDispatch] = useContext(UserContext);
|
||||||
@@ -52,16 +122,19 @@ const HeaderBar = () => {
|
|||||||
text: t('首页'),
|
text: t('首页'),
|
||||||
itemKey: 'home',
|
itemKey: 'home',
|
||||||
to: '/',
|
to: '/',
|
||||||
|
icon: <IconHome style={headerIconStyle} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: t('控制台'),
|
text: t('控制台'),
|
||||||
itemKey: 'detail',
|
itemKey: 'detail',
|
||||||
to: '/',
|
to: '/',
|
||||||
|
icon: <IconTerminal style={headerIconStyle} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: t('定价'),
|
text: t('定价'),
|
||||||
itemKey: 'pricing',
|
itemKey: 'pricing',
|
||||||
to: '/pricing',
|
to: '/pricing',
|
||||||
|
icon: <IconPriceTag style={headerIconStyle} />,
|
||||||
},
|
},
|
||||||
// Only include the docs button if docsLink exists
|
// Only include the docs button if docsLink exists
|
||||||
...(docsLink ? [{
|
...(docsLink ? [{
|
||||||
@@ -69,11 +142,13 @@ const HeaderBar = () => {
|
|||||||
itemKey: 'docs',
|
itemKey: 'docs',
|
||||||
isExternal: true,
|
isExternal: true,
|
||||||
externalLink: docsLink,
|
externalLink: docsLink,
|
||||||
|
icon: <IconHelpCircle style={headerIconStyle} />,
|
||||||
}] : []),
|
}] : []),
|
||||||
{
|
{
|
||||||
text: t('关于'),
|
text: t('关于'),
|
||||||
itemKey: 'about',
|
itemKey: 'about',
|
||||||
to: '/about',
|
to: '/about',
|
||||||
|
icon: <IconInfoCircle style={headerIconStyle} />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -143,6 +218,9 @@ const HeaderBar = () => {
|
|||||||
<Nav
|
<Nav
|
||||||
className={'topnav'}
|
className={'topnav'}
|
||||||
mode={'horizontal'}
|
mode={'horizontal'}
|
||||||
|
style={headerStyle}
|
||||||
|
itemStyle={headerItemStyle}
|
||||||
|
hoverStyle={headerItemHoverStyle}
|
||||||
renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
|
renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
|
||||||
const routerMap = {
|
const routerMap = {
|
||||||
about: '/about',
|
about: '/about',
|
||||||
@@ -224,11 +302,13 @@ const HeaderBar = () => {
|
|||||||
),
|
),
|
||||||
}:{
|
}:{
|
||||||
logo: (
|
logo: (
|
||||||
<img src={logo} alt='logo' />
|
<div style={logoStyle}>
|
||||||
|
<img src={logo} alt='logo' style={{ height: '28px' }} />
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
text: (
|
text: (
|
||||||
<div style={{ position: 'relative', display: 'inline-block' }}>
|
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||||
{systemName}
|
<span style={systemNameStyle}>{systemName}</span>
|
||||||
{(isSelfUseMode || isDemoSiteMode) && (
|
{(isSelfUseMode || isDemoSiteMode) && (
|
||||||
<Tag
|
<Tag
|
||||||
color={isSelfUseMode ? 'purple' : 'blue'}
|
color={isSelfUseMode ? 'purple' : 'blue'}
|
||||||
@@ -257,7 +337,7 @@ const HeaderBar = () => {
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
position='bottomRight'
|
position='bottomRight'
|
||||||
render={
|
render={
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu style={dropdownStyle}>
|
||||||
<Dropdown.Item onClick={handleNewYearClick}>
|
<Dropdown.Item onClick={handleNewYearClick}>
|
||||||
Happy New Year!!!
|
Happy New Year!!!
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
@@ -274,6 +354,7 @@ const HeaderBar = () => {
|
|||||||
size={styleState.isMobile?'default':'large'}
|
size={styleState.isMobile?'default':'large'}
|
||||||
checked={theme === 'dark'}
|
checked={theme === 'dark'}
|
||||||
uncheckedText='🌙'
|
uncheckedText='🌙'
|
||||||
|
style={switchStyle}
|
||||||
onChange={(checked) => {
|
onChange={(checked) => {
|
||||||
setTheme(checked);
|
setTheme(checked);
|
||||||
}}
|
}}
|
||||||
@@ -282,7 +363,7 @@ const HeaderBar = () => {
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
position='bottomRight'
|
position='bottomRight'
|
||||||
render={
|
render={
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu style={dropdownStyle}>
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
onClick={() => handleLanguageChange('zh')}
|
onClick={() => handleLanguageChange('zh')}
|
||||||
type={currentLang === 'zh' ? 'primary' : 'tertiary'}
|
type={currentLang === 'zh' ? 'primary' : 'tertiary'}
|
||||||
@@ -300,7 +381,7 @@ const HeaderBar = () => {
|
|||||||
>
|
>
|
||||||
<Nav.Item
|
<Nav.Item
|
||||||
itemKey={'language'}
|
itemKey={'language'}
|
||||||
icon={<IconLanguage />}
|
icon={<IconLanguage style={headerIconStyle} />}
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
{userState.user ? (
|
{userState.user ? (
|
||||||
@@ -308,7 +389,7 @@ const HeaderBar = () => {
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
position='bottomRight'
|
position='bottomRight'
|
||||||
render={
|
render={
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu style={dropdownStyle}>
|
||||||
<Dropdown.Item onClick={logout}>{t('退出')}</Dropdown.Item>
|
<Dropdown.Item onClick={logout}>{t('退出')}</Dropdown.Item>
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
}
|
}
|
||||||
@@ -316,11 +397,11 @@ const HeaderBar = () => {
|
|||||||
<Avatar
|
<Avatar
|
||||||
size='small'
|
size='small'
|
||||||
color={stringToColor(userState.user.username)}
|
color={stringToColor(userState.user.username)}
|
||||||
style={{ margin: 4 }}
|
style={avatarStyle}
|
||||||
>
|
>
|
||||||
{userState.user.username[0]}
|
{userState.user.username[0]}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
{styleState.isMobile?null:<Text>{userState.user.username}</Text>}
|
{styleState.isMobile?null:<Text style={{ marginLeft: '4px', fontWeight: '500' }}>{userState.user.username}</Text>}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -328,7 +409,7 @@ const HeaderBar = () => {
|
|||||||
<Nav.Item
|
<Nav.Item
|
||||||
itemKey={'login'}
|
itemKey={'login'}
|
||||||
text={!styleState.isMobile?t('登录'):null}
|
text={!styleState.isMobile?t('登录'):null}
|
||||||
icon={<IconUser />}
|
icon={<IconUser style={headerIconStyle} />}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
// Hide register option in self-use mode
|
// Hide register option in self-use mode
|
||||||
@@ -336,7 +417,7 @@ const HeaderBar = () => {
|
|||||||
<Nav.Item
|
<Nav.Item
|
||||||
itemKey={'register'}
|
itemKey={'register'}
|
||||||
text={t('注册')}
|
text={t('注册')}
|
||||||
icon={<IconKey />}
|
icon={<IconKey style={headerIconStyle} />}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,20 +62,57 @@ const PageLayout = () => {
|
|||||||
if (savedLang) {
|
if (savedLang) {
|
||||||
i18n.changeLanguage(savedLang);
|
i18n.changeLanguage(savedLang);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 默认显示侧边栏
|
||||||
|
styleDispatch({ type: 'SET_SIDER', payload: true });
|
||||||
}, [i18n]);
|
}, [i18n]);
|
||||||
|
|
||||||
|
// 获取侧边栏折叠状态
|
||||||
|
const isSidebarCollapsed = localStorage.getItem('default_collapse_sidebar') === 'true';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
<Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
||||||
<Header>
|
<Header style={{
|
||||||
|
padding: 0,
|
||||||
|
height: 'auto',
|
||||||
|
lineHeight: 'normal',
|
||||||
|
position: 'fixed',
|
||||||
|
width: '100%',
|
||||||
|
top: 0,
|
||||||
|
zIndex: 100,
|
||||||
|
boxShadow: '0 1px 6px rgba(0, 0, 0, 0.08)'
|
||||||
|
}}>
|
||||||
<HeaderBar />
|
<HeaderBar />
|
||||||
</Header>
|
</Header>
|
||||||
<Layout style={{ flex: 1, overflow: 'hidden' }}>
|
<Layout style={{ marginTop: '56px', height: 'calc(100vh - 56px)', overflow: 'hidden' }}>
|
||||||
<Sider>
|
{styleState.showSider && (
|
||||||
{styleState.showSider ? <SiderBar /> : null}
|
<Sider style={{
|
||||||
</Sider>
|
height: 'calc(100vh - 56px)',
|
||||||
<Layout>
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
top: '56px',
|
||||||
|
zIndex: 90,
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
width: 'auto',
|
||||||
|
background: 'transparent',
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: 'none',
|
||||||
|
paddingRight: '5px'
|
||||||
|
}}>
|
||||||
|
<SiderBar />
|
||||||
|
</Sider>
|
||||||
|
)}
|
||||||
|
<Layout style={{
|
||||||
|
marginLeft: styleState.showSider ? (isSidebarCollapsed ? '60px' : '200px') : '0',
|
||||||
|
transition: 'margin-left 0.3s ease'
|
||||||
|
}}>
|
||||||
<Content
|
<Content
|
||||||
style={{ overflowY: 'auto', padding: styleState.shouldInnerPadding? '24px': '0' }}
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
overflowY: 'auto',
|
||||||
|
padding: styleState.shouldInnerPadding? '24px': '0'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<App />
|
<App />
|
||||||
</Content>
|
</Content>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { UserContext } from '../context/User';
|
import { UserContext } from '../context/User';
|
||||||
import { StatusContext } from '../context/Status';
|
import { StatusContext } from '../context/Status';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -34,7 +34,34 @@ import { stringToColor } from '../helpers/render.js';
|
|||||||
import { useSetTheme, useTheme } from '../context/Theme/index.js';
|
import { useSetTheme, useTheme } from '../context/Theme/index.js';
|
||||||
import { StyleContext } from '../context/Style/index.js';
|
import { StyleContext } from '../context/Style/index.js';
|
||||||
|
|
||||||
// HeaderBar Buttons
|
// 自定义侧边栏按钮样式
|
||||||
|
const navItemStyle = {
|
||||||
|
borderRadius: '6px',
|
||||||
|
margin: '4px 8px',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义侧边栏按钮悬停样式
|
||||||
|
const navItemHoverStyle = {
|
||||||
|
backgroundColor: 'var(--semi-color-primary-light-default)',
|
||||||
|
color: 'var(--semi-color-primary)'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义侧边栏按钮选中样式
|
||||||
|
const navItemSelectedStyle = {
|
||||||
|
backgroundColor: 'var(--semi-color-primary-light-default)',
|
||||||
|
color: 'var(--semi-color-primary)',
|
||||||
|
fontWeight: '600'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义图标样式
|
||||||
|
const iconStyle = (itemKey, selectedKeys) => {
|
||||||
|
return {
|
||||||
|
fontSize: '18px',
|
||||||
|
color: selectedKeys.includes(itemKey) ? 'var(--semi-color-primary)' : 'var(--semi-color-text-2)',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const SiderBar = () => {
|
const SiderBar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -46,8 +73,30 @@ const SiderBar = () => {
|
|||||||
const [selectedKeys, setSelectedKeys] = useState(['home']);
|
const [selectedKeys, setSelectedKeys] = useState(['home']);
|
||||||
const [isCollapsed, setIsCollapsed] = useState(defaultIsCollapsed);
|
const [isCollapsed, setIsCollapsed] = useState(defaultIsCollapsed);
|
||||||
const [chatItems, setChatItems] = useState([]);
|
const [chatItems, setChatItems] = useState([]);
|
||||||
|
const [openedKeys, setOpenedKeys] = useState([]);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setTheme = useSetTheme();
|
const setTheme = useSetTheme();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
// 预先计算所有可能的图标样式
|
||||||
|
const allItemKeys = useMemo(() => {
|
||||||
|
const keys = ['home', 'channel', 'token', 'redemption', 'topup', 'user', 'log', 'midjourney',
|
||||||
|
'setting', 'about', 'chat', 'detail', 'pricing', 'task', 'playground', 'personal'];
|
||||||
|
// 添加聊天项的keys
|
||||||
|
for (let i = 0; i < chatItems.length; i++) {
|
||||||
|
keys.push('chat' + i);
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}, [chatItems]);
|
||||||
|
|
||||||
|
// 使用useMemo一次性计算所有图标样式
|
||||||
|
const iconStyles = useMemo(() => {
|
||||||
|
const styles = {};
|
||||||
|
allItemKeys.forEach(key => {
|
||||||
|
styles[key] = iconStyle(key, selectedKeys);
|
||||||
|
});
|
||||||
|
return styles;
|
||||||
|
}, [allItemKeys, selectedKeys]);
|
||||||
|
|
||||||
const routerMap = {
|
const routerMap = {
|
||||||
home: '/',
|
home: '/',
|
||||||
@@ -190,11 +239,14 @@ const SiderBar = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let localKey = window.location.pathname.split('/')[1];
|
const currentPath = location.pathname;
|
||||||
if (localKey === '') {
|
const matchingKey = Object.keys(routerMap).find(key => routerMap[key] === currentPath);
|
||||||
localKey = 'home';
|
|
||||||
|
if (matchingKey) {
|
||||||
|
setSelectedKeys([matchingKey]);
|
||||||
|
} else if (currentPath.startsWith('/chat/')) {
|
||||||
|
setSelectedKeys(['chat']);
|
||||||
}
|
}
|
||||||
setSelectedKeys([localKey]);
|
|
||||||
|
|
||||||
let chats = localStorage.getItem('chats');
|
let chats = localStorage.getItem('chats');
|
||||||
if (chats) {
|
if (chats) {
|
||||||
@@ -222,7 +274,7 @@ const SiderBar = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsCollapsed(localStorage.getItem('default_collapse_sidebar') === 'true');
|
setIsCollapsed(localStorage.getItem('default_collapse_sidebar') === 'true');
|
||||||
}, []);
|
}, [location.pathname]);
|
||||||
|
|
||||||
// Custom divider style
|
// Custom divider style
|
||||||
const dividerStyle = {
|
const dividerStyle = {
|
||||||
@@ -235,21 +287,54 @@ const SiderBar = () => {
|
|||||||
padding: '8px 16px',
|
padding: '8px 16px',
|
||||||
color: 'var(--semi-color-text-2)',
|
color: 'var(--semi-color-text-2)',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: 'normal',
|
fontWeight: 'bold',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.5px',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Nav
|
<Nav
|
||||||
style={{ maxWidth: 200, height: '100%' }}
|
className="custom-sidebar-nav"
|
||||||
|
style={{
|
||||||
|
width: isCollapsed ? '60px' : '200px',
|
||||||
|
height: '100%',
|
||||||
|
boxShadow: '0 1px 6px rgba(0, 0, 0, 0.08)',
|
||||||
|
borderRight: '1px solid var(--semi-color-border)',
|
||||||
|
background: 'var(--semi-color-bg-0)',
|
||||||
|
borderRadius: '0 8px 8px 0',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 95
|
||||||
|
}}
|
||||||
defaultIsCollapsed={
|
defaultIsCollapsed={
|
||||||
localStorage.getItem('default_collapse_sidebar') === 'true'
|
localStorage.getItem('default_collapse_sidebar') === 'true'
|
||||||
}
|
}
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
onCollapseChange={(collapsed) => {
|
onCollapseChange={(collapsed) => {
|
||||||
setIsCollapsed(collapsed);
|
setIsCollapsed(collapsed);
|
||||||
|
localStorage.setItem('default_collapse_sidebar', collapsed);
|
||||||
|
// 始终保持侧边栏显示,只是宽度不同
|
||||||
|
styleDispatch({ type: 'SET_SIDER', payload: true });
|
||||||
|
|
||||||
|
// 确保在收起侧边栏时有选中的项目,避免不必要的计算
|
||||||
|
if (selectedKeys.length === 0) {
|
||||||
|
const currentPath = location.pathname;
|
||||||
|
const matchingKey = Object.keys(routerMap).find(key => routerMap[key] === currentPath);
|
||||||
|
|
||||||
|
if (matchingKey) {
|
||||||
|
setSelectedKeys([matchingKey]);
|
||||||
|
} else if (currentPath.startsWith('/chat/')) {
|
||||||
|
setSelectedKeys(['chat']);
|
||||||
|
} else {
|
||||||
|
setSelectedKeys(['home']); // 默认选中首页
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
selectedKeys={selectedKeys}
|
selectedKeys={selectedKeys}
|
||||||
|
itemStyle={navItemStyle}
|
||||||
|
hoverStyle={navItemHoverStyle}
|
||||||
|
selectedStyle={navItemSelectedStyle}
|
||||||
renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
|
renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
|
||||||
let chats = localStorage.getItem('chats');
|
let chats = localStorage.getItem('chats');
|
||||||
if (chats) {
|
if (chats) {
|
||||||
@@ -284,8 +369,18 @@ const SiderBar = () => {
|
|||||||
} else {
|
} else {
|
||||||
styleDispatch({ type: 'SET_INNER_PADDING', payload: true });
|
styleDispatch({ type: 'SET_INNER_PADDING', payload: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果点击的是已经展开的子菜单的父项,则收起子菜单
|
||||||
|
if (openedKeys.includes(key.itemKey)) {
|
||||||
|
setOpenedKeys(openedKeys.filter(k => k !== key.itemKey));
|
||||||
|
}
|
||||||
|
|
||||||
setSelectedKeys([key.itemKey]);
|
setSelectedKeys([key.itemKey]);
|
||||||
}}
|
}}
|
||||||
|
openKeys={openedKeys}
|
||||||
|
onOpenChange={(data) => {
|
||||||
|
setOpenedKeys(data.openKeys);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{/* Chat Section - Only show if there are chat items */}
|
{/* Chat Section - Only show if there are chat items */}
|
||||||
{chatMenuItems.map((item) => {
|
{chatMenuItems.map((item) => {
|
||||||
@@ -295,7 +390,7 @@ const SiderBar = () => {
|
|||||||
key={item.itemKey}
|
key={item.itemKey}
|
||||||
itemKey={item.itemKey}
|
itemKey={item.itemKey}
|
||||||
text={item.text}
|
text={item.text}
|
||||||
icon={item.icon}
|
icon={React.cloneElement(item.icon, { style: iconStyles[item.itemKey] })}
|
||||||
>
|
>
|
||||||
{item.items.map((subItem) => (
|
{item.items.map((subItem) => (
|
||||||
<Nav.Item
|
<Nav.Item
|
||||||
@@ -312,13 +407,12 @@ const SiderBar = () => {
|
|||||||
key={item.itemKey}
|
key={item.itemKey}
|
||||||
itemKey={item.itemKey}
|
itemKey={item.itemKey}
|
||||||
text={item.text}
|
text={item.text}
|
||||||
icon={item.icon}
|
icon={React.cloneElement(item.icon, { style: iconStyles[item.itemKey] })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<Divider style={dividerStyle} />
|
<Divider style={dividerStyle} />
|
||||||
|
|
||||||
@@ -329,11 +423,30 @@ const SiderBar = () => {
|
|||||||
key={item.itemKey}
|
key={item.itemKey}
|
||||||
itemKey={item.itemKey}
|
itemKey={item.itemKey}
|
||||||
text={item.text}
|
text={item.text}
|
||||||
icon={item.icon}
|
icon={React.cloneElement(item.icon, { style: iconStyles[item.itemKey] })}
|
||||||
className={item.className}
|
className={item.className}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{isAdmin() && (
|
||||||
|
<>
|
||||||
|
{/* Divider */}
|
||||||
|
<Divider style={dividerStyle} />
|
||||||
|
|
||||||
|
{/* Admin Section */}
|
||||||
|
{!isCollapsed && <div style={groupLabelStyle}>{t('管理员')}</div>}
|
||||||
|
{adminItems.map((item) => (
|
||||||
|
<Nav.Item
|
||||||
|
key={item.itemKey}
|
||||||
|
itemKey={item.itemKey}
|
||||||
|
text={item.text}
|
||||||
|
icon={React.cloneElement(item.icon, { style: iconStyles[item.itemKey] })}
|
||||||
|
className={item.className}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<Divider style={dividerStyle} />
|
<Divider style={dividerStyle} />
|
||||||
|
|
||||||
@@ -344,31 +457,18 @@ const SiderBar = () => {
|
|||||||
key={item.itemKey}
|
key={item.itemKey}
|
||||||
itemKey={item.itemKey}
|
itemKey={item.itemKey}
|
||||||
text={item.text}
|
text={item.text}
|
||||||
icon={item.icon}
|
icon={React.cloneElement(item.icon, { style: iconStyles[item.itemKey] })}
|
||||||
className={item.className}
|
className={item.className}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{isAdmin() && (
|
|
||||||
<>
|
|
||||||
{/* Divider */}
|
|
||||||
<Divider style={dividerStyle} />
|
|
||||||
|
|
||||||
{/* Admin Section */}
|
|
||||||
{adminItems.map((item) => (
|
|
||||||
<Nav.Item
|
|
||||||
key={item.itemKey}
|
|
||||||
itemKey={item.itemKey}
|
|
||||||
text={item.text}
|
|
||||||
icon={item.icon}
|
|
||||||
className={item.className}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Nav.Footer
|
<Nav.Footer
|
||||||
collapseButton={true}
|
collapseButton={true}
|
||||||
|
style={{
|
||||||
|
borderTop: '1px solid var(--semi-color-border)',
|
||||||
|
padding: '12px 0',
|
||||||
|
marginTop: 'auto'
|
||||||
|
}}
|
||||||
collapseText={(collapsed)=>
|
collapseText={(collapsed)=>
|
||||||
{
|
{
|
||||||
if(collapsed){
|
if(collapsed){
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 55px;
|
padding-top: 0;
|
||||||
overflow-y: scroll;
|
overflow: hidden;
|
||||||
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, 'Microsoft YaHei',
|
font-family: Lato, 'Helvetica Neue', Arial, Helvetica, 'Microsoft YaHei',
|
||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
@@ -15,6 +15,7 @@ body {
|
|||||||
#root {
|
#root {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root > section > header > section > div > div > div > div.semi-navigation-header-list-outer > div.semi-navigation-list-wrapper > ul > div > a > li > span{
|
#root > section > header > section > div > div > div > div.semi-navigation-header-list-outer > div.semi-navigation-list-wrapper > ul > div > a > li > span{
|
||||||
@@ -29,6 +30,15 @@ body {
|
|||||||
/*.semi-navigation-sub-wrap .semi-navigation-sub-title, .semi-navigation-item {*/
|
/*.semi-navigation-sub-wrap .semi-navigation-sub-title, .semi-navigation-item {*/
|
||||||
/* padding: 0 0;*/
|
/* padding: 0 0;*/
|
||||||
/*}*/
|
/*}*/
|
||||||
|
.topnav {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .semi-navigation-item {
|
||||||
|
margin: 0 1px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.topnav .semi-navigation-list-wrapper {
|
.topnav .semi-navigation-list-wrapper {
|
||||||
max-width: calc(55vw - 20px);
|
max-width: calc(55vw - 20px);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -120,6 +130,38 @@ code {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义侧边栏按钮悬停效果 */
|
||||||
|
.semi-navigation-item:hover {
|
||||||
|
transform: translateX(2px);
|
||||||
|
box-shadow: 0 2px 8px rgba(var(--semi-color-primary-rgb), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义侧边栏按钮选中效果 */
|
||||||
|
.semi-navigation-item-selected {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.semi-navigation-item-selected::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 4px;
|
||||||
|
background-color: var(--semi-color-primary);
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.semi-navigation-vertical {
|
.semi-navigation-vertical {
|
||||||
/*flex: 0 0 auto;*/
|
/*flex: 0 0 auto;*/
|
||||||
/*display: flex;*/
|
/*display: flex;*/
|
||||||
@@ -147,3 +189,67 @@ code {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 顶部栏样式 */
|
||||||
|
.topnav {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .semi-navigation-item {
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 0 2px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .semi-navigation-item:hover {
|
||||||
|
background-color: var(--semi-color-primary-light-default);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 2px 8px rgba(var(--semi-color-primary-rgb), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topnav .semi-navigation-item-selected {
|
||||||
|
background-color: var(--semi-color-primary-light-default);
|
||||||
|
color: var(--semi-color-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部栏文本样式 */
|
||||||
|
.header-bar-text {
|
||||||
|
color: var(--semi-color-text-0);
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-bar-text:hover {
|
||||||
|
color: var(--semi-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滚动条样式 */
|
||||||
|
.semi-layout-content::-webkit-scrollbar,
|
||||||
|
.semi-sider::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.semi-layout-content::-webkit-scrollbar-thumb,
|
||||||
|
.semi-sider::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--semi-color-tertiary-light-default);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.semi-layout-content::-webkit-scrollbar-thumb:hover,
|
||||||
|
.semi-sider::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--semi-color-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.semi-layout-content::-webkit-scrollbar-track,
|
||||||
|
.semi-sider::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom sidebar shadow */
|
||||||
|
.custom-sidebar-nav {
|
||||||
|
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;
|
||||||
|
-webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;
|
||||||
|
-moz-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user