|
@@ -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];
|
|
|
|
|
- if (localKey === '') {
|
|
|
|
|
- localKey = 'home';
|
|
|
|
|
|
|
+ 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']);
|
|
|
}
|
|
}
|
|
|
- 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,22 +423,7 @@ const SiderBar = () => {
|
|
|
key={item.itemKey}
|
|
key={item.itemKey}
|
|
|
itemKey={item.itemKey}
|
|
itemKey={item.itemKey}
|
|
|
text={item.text}
|
|
text={item.text}
|
|
|
- icon={item.icon}
|
|
|
|
|
- className={item.className}
|
|
|
|
|
- />
|
|
|
|
|
- ))}
|
|
|
|
|
-
|
|
|
|
|
- {/* Divider */}
|
|
|
|
|
- <Divider style={dividerStyle} />
|
|
|
|
|
-
|
|
|
|
|
- {/* Finance Management Section */}
|
|
|
|
|
- {!isCollapsed && <div style={groupLabelStyle}>{t('个人中心')}</div>}
|
|
|
|
|
- {financeItems.map((item) => (
|
|
|
|
|
- <Nav.Item
|
|
|
|
|
- key={item.itemKey}
|
|
|
|
|
- itemKey={item.itemKey}
|
|
|
|
|
- text={item.text}
|
|
|
|
|
- icon={item.icon}
|
|
|
|
|
|
|
+ icon={React.cloneElement(item.icon, { style: iconStyles[item.itemKey] })}
|
|
|
className={item.className}
|
|
className={item.className}
|
|
|
/>
|
|
/>
|
|
|
))}
|
|
))}
|
|
@@ -355,20 +434,41 @@ const SiderBar = () => {
|
|
|
<Divider style={dividerStyle} />
|
|
<Divider style={dividerStyle} />
|
|
|
|
|
|
|
|
{/* Admin Section */}
|
|
{/* Admin Section */}
|
|
|
|
|
+ {!isCollapsed && <div style={groupLabelStyle}>{t('管理员')}</div>}
|
|
|
{adminItems.map((item) => (
|
|
{adminItems.map((item) => (
|
|
|
<Nav.Item
|
|
<Nav.Item
|
|
|
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}
|
|
|
/>
|
|
/>
|
|
|
))}
|
|
))}
|
|
|
</>
|
|
</>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
|
|
+ {/* Divider */}
|
|
|
|
|
+ <Divider style={dividerStyle} />
|
|
|
|
|
+
|
|
|
|
|
+ {/* Finance Management Section */}
|
|
|
|
|
+ {!isCollapsed && <div style={groupLabelStyle}>{t('个人中心')}</div>}
|
|
|
|
|
+ {financeItems.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}
|
|
|
|
|
+ />
|
|
|
|
|
+ ))}
|
|
|
|
|
+
|
|
|
<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){
|