📱 refactor(web): remove legacy isMobile util and migrate to useIsMobile hook
BREAKING CHANGE: helpers/utils.js no longer exports `isMobile()`. Any external code that relied on this function must switch to the `useIsMobile` React hook. Summary ------- 1. Deleted the obsolete `isMobile()` function from helpers/utils.js. 2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts. 3. Reworked toast positioning logic in utils.js to rely on `matchMedia`. 4. Updated render.js: • Removed isMobile import. • Added MOBILE_BREAKPOINT detection in `truncateText`. 5. Migrated every page/component to the `useIsMobile` hook: • Layout: HeaderBar, PageLayout, SiderBar • Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync • Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal 6. Purged all remaining `isMobile()` calls and legacy imports. 7. Added missing `const isMobile = useIsMobile()` declarations where required. Benefits -------- • Unifies mobile detection with a React-friendly hook. • Eliminates duplicated logic and improves maintainability. • Keeps non-React helpers lightweight by using `matchMedia` directly.
This commit is contained in:
@@ -31,13 +31,15 @@ import {
|
||||
Badge,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { StatusContext } from '../../context/Status/index.js';
|
||||
import { useStyle, styleActions } from '../../context/Style/index.js';
|
||||
import { useIsMobile } from '../../hooks/useIsMobile.js';
|
||||
import { useSidebarCollapsed } from '../../hooks/useSidebarCollapsed.js';
|
||||
|
||||
const HeaderBar = () => {
|
||||
const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [userState, userDispatch] = useContext(UserContext);
|
||||
const [statusState, statusDispatch] = useContext(StatusContext);
|
||||
const { state: styleState, dispatch: styleDispatch } = useStyle();
|
||||
const isMobile = useIsMobile();
|
||||
const [collapsed, toggleCollapsed] = useSidebarCollapsed();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
let navigate = useNavigate();
|
||||
const [currentLang, setCurrentLang] = useState(i18n.language);
|
||||
@@ -207,7 +209,7 @@ const HeaderBar = () => {
|
||||
|
||||
const handleNavLinkClick = (itemKey) => {
|
||||
if (itemKey === 'home') {
|
||||
styleDispatch(styleActions.setSider(false));
|
||||
// styleDispatch(styleActions.setSider(false)); // This line is removed
|
||||
}
|
||||
setMobileMenuOpen(false);
|
||||
};
|
||||
@@ -293,7 +295,7 @@ const HeaderBar = () => {
|
||||
placeholder={
|
||||
<Skeleton.Title
|
||||
active
|
||||
style={{ width: styleState.isMobile ? 15 : 50, height: 12 }}
|
||||
style={{ width: isMobile ? 15 : 50, height: 12 }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -388,7 +390,7 @@ const HeaderBar = () => {
|
||||
const registerButtonTextSpanClass = "!text-xs !text-white !p-1.5";
|
||||
|
||||
if (showRegisterButton) {
|
||||
if (styleState.isMobile) {
|
||||
if (isMobile) {
|
||||
loginButtonClasses += " !rounded-full";
|
||||
} else {
|
||||
loginButtonClasses += " !rounded-l-full !rounded-r-none";
|
||||
@@ -436,7 +438,7 @@ const HeaderBar = () => {
|
||||
<NoticeModal
|
||||
visible={noticeVisible}
|
||||
onClose={handleNoticeClose}
|
||||
isMobile={styleState.isMobile}
|
||||
isMobile={isMobile}
|
||||
defaultTab={unreadCount > 0 ? 'system' : 'inApp'}
|
||||
unreadKeys={getUnreadKeys()}
|
||||
/>
|
||||
@@ -447,18 +449,18 @@ const HeaderBar = () => {
|
||||
<Button
|
||||
icon={
|
||||
isConsoleRoute
|
||||
? (styleState.showSider ? <IconClose className="text-lg" /> : <IconMenu className="text-lg" />)
|
||||
? ((isMobile ? drawerOpen : collapsed) ? <IconClose className="text-lg" /> : <IconMenu className="text-lg" />)
|
||||
: (mobileMenuOpen ? <IconClose className="text-lg" /> : <IconMenu className="text-lg" />)
|
||||
}
|
||||
aria-label={
|
||||
isConsoleRoute
|
||||
? (styleState.showSider ? t('关闭侧边栏') : t('打开侧边栏'))
|
||||
? ((isMobile ? drawerOpen : collapsed) ? t('关闭侧边栏') : t('打开侧边栏'))
|
||||
: (mobileMenuOpen ? t('关闭菜单') : t('打开菜单'))
|
||||
}
|
||||
onClick={() => {
|
||||
if (isConsoleRoute) {
|
||||
// 控制侧边栏的显示/隐藏,无论是否移动设备
|
||||
styleDispatch(styleActions.toggleSider());
|
||||
isMobile ? onMobileMenuToggle() : toggleCollapsed();
|
||||
} else {
|
||||
// 控制HeaderBar自己的移动菜单
|
||||
setMobileMenuOpen(!mobileMenuOpen);
|
||||
|
||||
@@ -4,8 +4,9 @@ import SiderBar from './SiderBar.js';
|
||||
import App from '../../App.js';
|
||||
import FooterBar from './Footer.js';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { useStyle } from '../../context/Style/index.js';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useIsMobile } from '../../hooks/useIsMobile.js';
|
||||
import { useSidebarCollapsed } from '../../hooks/useSidebarCollapsed.js';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { API, getLogo, getSystemName, showError, setStatusData } from '../../helpers/index.js';
|
||||
import { UserContext } from '../../context/User/index.js';
|
||||
@@ -16,7 +17,9 @@ const { Sider, Content, Header } = Layout;
|
||||
const PageLayout = () => {
|
||||
const [userState, userDispatch] = useContext(UserContext);
|
||||
const [statusState, statusDispatch] = useContext(StatusContext);
|
||||
const { state: styleState } = useStyle();
|
||||
const isMobile = useIsMobile();
|
||||
const [collapsed, , setCollapsed] = useSidebarCollapsed();
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const { i18n } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
@@ -26,6 +29,16 @@ const PageLayout = () => {
|
||||
!location.pathname.startsWith('/console/chat') &&
|
||||
location.pathname !== '/console/playground';
|
||||
|
||||
const isConsoleRoute = location.pathname.startsWith('/console');
|
||||
const showSider = isConsoleRoute && (!isMobile || drawerOpen);
|
||||
|
||||
// Ensure sidebar not collapsed when opening drawer on mobile
|
||||
useEffect(() => {
|
||||
if (isMobile && drawerOpen && collapsed) {
|
||||
setCollapsed(false);
|
||||
}
|
||||
}, [isMobile, drawerOpen, collapsed, setCollapsed]);
|
||||
|
||||
const loadUser = () => {
|
||||
let user = localStorage.getItem('user');
|
||||
if (user) {
|
||||
@@ -76,7 +89,7 @@ const PageLayout = () => {
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: styleState.isMobile ? 'visible' : 'hidden',
|
||||
overflow: isMobile ? 'visible' : 'hidden',
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
@@ -90,25 +103,26 @@ const PageLayout = () => {
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<HeaderBar />
|
||||
<HeaderBar onMobileMenuToggle={() => setDrawerOpen(prev => !prev)} drawerOpen={drawerOpen} />
|
||||
</Header>
|
||||
<Layout
|
||||
style={{
|
||||
overflow: styleState.isMobile ? 'visible' : 'auto',
|
||||
overflow: isMobile ? 'visible' : 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{styleState.showSider && (
|
||||
{showSider && (
|
||||
<Sider
|
||||
style={{
|
||||
position: 'fixed',
|
||||
position: isMobile ? 'fixed' : 'fixed',
|
||||
left: 0,
|
||||
top: '64px',
|
||||
zIndex: 99,
|
||||
border: 'none',
|
||||
paddingRight: '0',
|
||||
height: 'calc(100vh - 64px)',
|
||||
width: 'var(--sidebar-current-width)',
|
||||
}}
|
||||
>
|
||||
<SiderBar />
|
||||
@@ -116,14 +130,7 @@ const PageLayout = () => {
|
||||
)}
|
||||
<Layout
|
||||
style={{
|
||||
marginLeft: styleState.isMobile
|
||||
? '0'
|
||||
: styleState.showSider
|
||||
? styleState.siderCollapsed
|
||||
? '60px'
|
||||
: '180px'
|
||||
: '0',
|
||||
transition: 'margin-left 0.3s ease',
|
||||
marginLeft: isMobile ? '0' : showSider ? 'var(--sidebar-current-width)' : '0',
|
||||
flex: '1 1 auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -132,9 +139,9 @@ const PageLayout = () => {
|
||||
<Content
|
||||
style={{
|
||||
flex: '1 0 auto',
|
||||
overflowY: styleState.isMobile ? 'visible' : 'hidden',
|
||||
overflowY: isMobile ? 'visible' : 'hidden',
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
padding: shouldInnerPadding ? (styleState.isMobile ? '5px' : '24px') : '0',
|
||||
padding: shouldInnerPadding ? (isMobile ? '5px' : '24px') : '0',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Link, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getLucideIcon, sidebarIconColors } from '../../helpers/render.js';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { useStyle, styleActions } from '../../context/Style/index.js';
|
||||
import { useIsMobile } from '../../hooks/useIsMobile.js';
|
||||
import { useSidebarCollapsed } from '../../hooks/useSidebarCollapsed.js';
|
||||
import {
|
||||
isAdmin,
|
||||
isRoot,
|
||||
@@ -36,10 +37,10 @@ const routerMap = {
|
||||
|
||||
const SiderBar = () => {
|
||||
const { t } = useTranslation();
|
||||
const { state: styleState, dispatch: styleDispatch } = useStyle();
|
||||
const isMobile = useIsMobile();
|
||||
const [collapsed, toggleCollapsed] = useSidebarCollapsed();
|
||||
|
||||
const [selectedKeys, setSelectedKeys] = useState(['home']);
|
||||
const [isCollapsed, setIsCollapsed] = useState(styleState.siderCollapsed);
|
||||
const [chatItems, setChatItems] = useState([]);
|
||||
const [openedKeys, setOpenedKeys] = useState([]);
|
||||
const location = useLocation();
|
||||
@@ -217,10 +218,14 @@ const SiderBar = () => {
|
||||
}
|
||||
}, [location.pathname, routerMapState]);
|
||||
|
||||
// 同步折叠状态
|
||||
// 监控折叠状态变化以更新 body class
|
||||
useEffect(() => {
|
||||
setIsCollapsed(styleState.siderCollapsed);
|
||||
}, [styleState.siderCollapsed]);
|
||||
if (collapsed) {
|
||||
document.body.classList.add('sidebar-collapsed');
|
||||
} else {
|
||||
document.body.classList.remove('sidebar-collapsed');
|
||||
}
|
||||
}, [collapsed]);
|
||||
|
||||
// 获取菜单项对应的颜色
|
||||
const getItemColor = (itemKey) => {
|
||||
@@ -323,32 +328,13 @@ const SiderBar = () => {
|
||||
return (
|
||||
<div
|
||||
className="sidebar-container"
|
||||
style={{ width: isCollapsed ? '60px' : '180px' }}
|
||||
style={{ width: 'var(--sidebar-current-width)' }}
|
||||
>
|
||||
<Nav
|
||||
className="sidebar-nav"
|
||||
defaultIsCollapsed={styleState.siderCollapsed}
|
||||
isCollapsed={isCollapsed}
|
||||
onCollapseChange={(collapsed) => {
|
||||
setIsCollapsed(collapsed);
|
||||
styleDispatch(styleActions.setSiderCollapsed(collapsed));
|
||||
|
||||
// 确保在收起侧边栏时有选中的项目
|
||||
if (selectedKeys.length === 0) {
|
||||
const currentPath = location.pathname;
|
||||
const matchingKey = Object.keys(routerMapState).find(
|
||||
(key) => routerMapState[key] === currentPath,
|
||||
);
|
||||
|
||||
if (matchingKey) {
|
||||
setSelectedKeys([matchingKey]);
|
||||
} else if (currentPath.startsWith('/console/chat/')) {
|
||||
setSelectedKeys(['chat']);
|
||||
} else {
|
||||
setSelectedKeys(['detail']); // 默认选中首页
|
||||
}
|
||||
}
|
||||
}}
|
||||
defaultIsCollapsed={collapsed}
|
||||
isCollapsed={collapsed}
|
||||
onCollapseChange={toggleCollapsed}
|
||||
selectedKeys={selectedKeys}
|
||||
itemStyle="sidebar-nav-item"
|
||||
hoverStyle="sidebar-nav-item:hover"
|
||||
@@ -383,7 +369,7 @@ const SiderBar = () => {
|
||||
>
|
||||
{/* 聊天区域 */}
|
||||
<div className="sidebar-section">
|
||||
{!isCollapsed && (
|
||||
{!collapsed && (
|
||||
<div className="sidebar-group-label">{t('聊天')}</div>
|
||||
)}
|
||||
{chatMenuItems.map((item) => renderSubItem(item))}
|
||||
@@ -392,7 +378,7 @@ const SiderBar = () => {
|
||||
{/* 控制台区域 */}
|
||||
<Divider className="sidebar-divider" />
|
||||
<div>
|
||||
{!isCollapsed && (
|
||||
{!collapsed && (
|
||||
<div className="sidebar-group-label">{t('控制台')}</div>
|
||||
)}
|
||||
{workspaceItems.map((item) => renderNavItem(item))}
|
||||
@@ -403,7 +389,7 @@ const SiderBar = () => {
|
||||
<>
|
||||
<Divider className="sidebar-divider" />
|
||||
<div>
|
||||
{!isCollapsed && (
|
||||
{!collapsed && (
|
||||
<div className="sidebar-group-label">{t('管理员')}</div>
|
||||
)}
|
||||
{adminItems.map((item) => renderNavItem(item))}
|
||||
@@ -414,7 +400,7 @@ const SiderBar = () => {
|
||||
{/* 个人中心区域 */}
|
||||
<Divider className="sidebar-divider" />
|
||||
<div>
|
||||
{!isCollapsed && (
|
||||
{!collapsed && (
|
||||
<div className="sidebar-group-label">{t('个人中心')}</div>
|
||||
)}
|
||||
{financeItems.map((item) => renderNavItem(item))}
|
||||
@@ -425,16 +411,14 @@ const SiderBar = () => {
|
||||
<div
|
||||
className="sidebar-collapse-button"
|
||||
onClick={() => {
|
||||
const newCollapsed = !isCollapsed;
|
||||
setIsCollapsed(newCollapsed);
|
||||
styleDispatch(styleActions.setSiderCollapsed(newCollapsed));
|
||||
toggleCollapsed();
|
||||
}}
|
||||
>
|
||||
<Tooltip content={isCollapsed ? t('展开侧边栏') : t('收起侧边栏')} position="right">
|
||||
<Tooltip content={collapsed ? t('展开侧边栏') : t('收起侧边栏')} position="right">
|
||||
<div className="sidebar-collapse-button-inner">
|
||||
<span
|
||||
className="sidebar-collapse-icon-container"
|
||||
style={{ transform: isCollapsed ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||||
style={{ transform: collapsed ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||||
>
|
||||
<ChevronLeft size={16} strokeWidth={2.5} color="var(--semi-color-text-2)" />
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { isMobile } from '../../helpers';
|
||||
import { useIsMobile } from '../../hooks/useIsMobile.js';
|
||||
import {
|
||||
Modal,
|
||||
Table,
|
||||
@@ -26,6 +26,7 @@ const ChannelSelectorModal = forwardRef(({
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [filteredData, setFilteredData] = useState([]);
|
||||
|
||||
@@ -186,7 +187,7 @@ const ChannelSelectorModal = forwardRef(({
|
||||
onCancel={onCancel}
|
||||
onOk={onOk}
|
||||
title={<span className="text-lg font-semibold">{t('选择同步渠道')}</span>}
|
||||
size={isMobile() ? 'full-width' : 'large'}
|
||||
size={isMobile ? 'full-width' : 'large'}
|
||||
keepDOM
|
||||
lazyRender={false}
|
||||
>
|
||||
|
||||
@@ -44,7 +44,8 @@ import {
|
||||
IconMore,
|
||||
IconDescend2
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { loadChannelModels, isMobile, copy } from '../../helpers';
|
||||
import { loadChannelModels, copy } from '../../helpers';
|
||||
import { useIsMobile } from '../../hooks/useIsMobile.js';
|
||||
import EditTagModal from '../../pages/Channel/EditTagModal.js';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTableCompactMode } from '../../hooks/useTableCompactMode';
|
||||
@@ -52,6 +53,7 @@ import { FaRandom } from 'react-icons/fa';
|
||||
|
||||
const ChannelsTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
let type2label = undefined;
|
||||
|
||||
@@ -2031,7 +2033,7 @@ const ChannelsTable = () => {
|
||||
}
|
||||
maskClosable={!isBatchTesting}
|
||||
className="!rounded-lg"
|
||||
size={isMobile() ? 'full-width' : 'large'}
|
||||
size={isMobile ? 'full-width' : 'large'}
|
||||
>
|
||||
<div className="model-test-scroll">
|
||||
{currentTestChannel && (
|
||||
|
||||
Reference in New Issue
Block a user