diff --git a/web/src/components/auth/OAuth2Callback.js b/web/src/components/auth/OAuth2Callback.js index 6d0bbe70..7d435574 100644 --- a/web/src/components/auth/OAuth2Callback.js +++ b/web/src/components/auth/OAuth2Callback.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { API, showError, showSuccess, updateAPI, setUserData } from '../../helpers'; @@ -7,22 +7,28 @@ import Loading from '../common/Loading'; const OAuth2Callback = (props) => { const { t } = useTranslation(); - const [searchParams, setSearchParams] = useSearchParams(); + const [searchParams] = useSearchParams(); + const [, userDispatch] = useContext(UserContext); + const navigate = useNavigate(); - const [userState, userDispatch] = useContext(UserContext); - const [prompt, setPrompt] = useState(t('处理中...')); + // 最大重试次数 + const MAX_RETRIES = 3; - let navigate = useNavigate(); + const sendCode = async (code, state, retry = 0) => { + try { + const { data: resData } = await API.get( + `/api/oauth/${props.type}?code=${code}&state=${state}`, + ); + + const { success, message, data } = resData; + + if (!success) { + throw new Error(message || 'OAuth2 callback error'); + } - const sendCode = async (code, state, count) => { - const res = await API.get( - `/api/oauth/${props.type}?code=${code}&state=${state}`, - ); - const { success, message, data } = res.data; - if (success) { if (message === 'bind') { showSuccess(t('绑定成功!')); - navigate('/console/setting'); + navigate('/console/personal'); } else { userDispatch({ type: 'login', payload: data }); localStorage.setItem('user', JSON.stringify(data)); @@ -31,27 +37,34 @@ const OAuth2Callback = (props) => { showSuccess(t('登录成功!')); navigate('/console/token'); } - } else { - showError(message); - if (count === 0) { - setPrompt(t('操作失败,重定向至登录界面中...')); - navigate('/console/setting'); // in case this is failed to bind GitHub - return; + } catch (error) { + if (retry < MAX_RETRIES) { + // 递增的退避等待 + await new Promise((resolve) => setTimeout(resolve, (retry + 1) * 2000)); + return sendCode(code, state, retry + 1); } - count++; - setPrompt(t('出现错误,第 ${count} 次重试中...', { count })); - await new Promise((resolve) => setTimeout(resolve, count * 2000)); - await sendCode(code, state, count); + + // 重试次数耗尽,提示错误并返回设置页面 + showError(error.message || t('授权失败')); + navigate('/console/personal'); } }; useEffect(() => { - let code = searchParams.get('code'); - let state = searchParams.get('state'); - sendCode(code, state, 0).then(); + const code = searchParams.get('code'); + const state = searchParams.get('state'); + + // 参数缺失直接返回 + if (!code) { + showError(t('未获取到授权码')); + navigate('/console/personal'); + return; + } + + sendCode(code, state); }, []); - return ; + return ; }; export default OAuth2Callback; diff --git a/web/src/components/common/Loading.js b/web/src/components/common/Loading.js index a12be053..73822755 100644 --- a/web/src/components/common/Loading.js +++ b/web/src/components/common/Loading.js @@ -1,22 +1,14 @@ import React from 'react'; import { Spin } from '@douyinfe/semi-ui'; -import { useTranslation } from 'react-i18next'; -const Loading = ({ prompt: name = '', size = 'large' }) => { - const { t } = useTranslation(); +const Loading = ({ size = 'small' }) => { return ( -
-
- - - {name ? t('{{name}}', { name }) : t('加载中...')} - -
+
+
); }; diff --git a/web/src/components/layout/HeaderBar.js b/web/src/components/layout/HeaderBar.js index 98a7e17b..4d83d48b 100644 --- a/web/src/components/layout/HeaderBar.js +++ b/web/src/components/layout/HeaderBar.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState, useRef } from 'react'; import { Link, useNavigate, useLocation } from 'react-router-dom'; import { UserContext } from '../../context/User/index.js'; import { useSetTheme, useTheme } from '../../context/Theme/index.js'; @@ -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); @@ -45,6 +47,7 @@ const HeaderBar = () => { const location = useLocation(); const [noticeVisible, setNoticeVisible] = useState(false); const [unreadCount, setUnreadCount] = useState(0); + const loadingStartRef = useRef(Date.now()); const systemName = getSystemName(); const logo = getLogo(); @@ -194,11 +197,15 @@ const HeaderBar = () => { }, [i18n]); useEffect(() => { - const timer = setTimeout(() => { - setIsLoading(false); - }, 500); - return () => clearTimeout(timer); - }, []); + if (statusState?.status !== undefined) { + const elapsed = Date.now() - loadingStartRef.current; + const remaining = Math.max(0, 500 - elapsed); + const timer = setTimeout(() => { + setIsLoading(false); + }, remaining); + return () => clearTimeout(timer); + } + }, [statusState?.status]); const handleLanguageChange = (lang) => { i18n.changeLanguage(lang); @@ -207,7 +214,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 +300,7 @@ const HeaderBar = () => { placeholder={ } /> @@ -388,7 +395,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 +443,7 @@ const HeaderBar = () => { 0 ? 'system' : 'inApp'} unreadKeys={getUnreadKeys()} /> @@ -447,18 +454,18 @@ const HeaderBar = () => {
); diff --git a/web/src/components/settings/ChannelSelectorModal.js b/web/src/components/settings/ChannelSelectorModal.js index 4a3a0d18..998c2bf3 100644 --- a/web/src/components/settings/ChannelSelectorModal.js +++ b/web/src/components/settings/ChannelSelectorModal.js @@ -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={{t('选择同步渠道')}} - size={isMobile() ? 'full-width' : 'large'} + size={isMobile ? 'full-width' : 'large'} keepDOM lazyRender={false} > diff --git a/web/src/components/table/ChannelsTable.js b/web/src/components/table/ChannelsTable.js index 3299f61f..fba5db79 100644 --- a/web/src/components/table/ChannelsTable.js +++ b/web/src/components/table/ChannelsTable.js @@ -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'} >
{currentTestChannel && ( diff --git a/web/src/context/Style/index.js b/web/src/context/Style/index.js deleted file mode 100644 index 7bfe0ef7..00000000 --- a/web/src/context/Style/index.js +++ /dev/null @@ -1,227 +0,0 @@ -// contexts/Style/index.js - -import React, { useReducer, useEffect, useMemo, createContext } from 'react'; -import { useLocation } from 'react-router-dom'; -import { isMobile as getIsMobile } from '../../helpers'; - -// Action Types -const ACTION_TYPES = { - TOGGLE_SIDER: 'TOGGLE_SIDER', - SET_SIDER: 'SET_SIDER', - SET_MOBILE: 'SET_MOBILE', - SET_SIDER_COLLAPSED: 'SET_SIDER_COLLAPSED', - BATCH_UPDATE: 'BATCH_UPDATE', -}; - -// Constants -const STORAGE_KEYS = { - SIDEBAR_COLLAPSED: 'default_collapse_sidebar', -}; - -const ROUTE_PATTERNS = { - CONSOLE: '/console', -}; - -/** - * 判断路径是否为控制台路由 - * @param {string} pathname - 路由路径 - * @returns {boolean} 是否为控制台路由 - */ -const isConsoleRoute = (pathname) => { - return pathname === ROUTE_PATTERNS.CONSOLE || - pathname.startsWith(ROUTE_PATTERNS.CONSOLE + '/'); -}; - -/** - * 获取初始状态 - * @param {string} pathname - 当前路由路径 - * @returns {Object} 初始状态对象 - */ -const getInitialState = (pathname) => { - const isMobile = getIsMobile(); - const isConsole = isConsoleRoute(pathname); - const isCollapsed = localStorage.getItem(STORAGE_KEYS.SIDEBAR_COLLAPSED) === 'true'; - - return { - isMobile, - showSider: isConsole && !isMobile, - siderCollapsed: isCollapsed, - isManualSiderControl: false, - }; -}; - -/** - * Style reducer - * @param {Object} state - 当前状态 - * @param {Object} action - action 对象 - * @returns {Object} 新状态 - */ -const styleReducer = (state, action) => { - switch (action.type) { - case ACTION_TYPES.TOGGLE_SIDER: - return { - ...state, - showSider: !state.showSider, - isManualSiderControl: true, - }; - - case ACTION_TYPES.SET_SIDER: - return { - ...state, - showSider: action.payload, - isManualSiderControl: action.isManualControl ?? false, - }; - - case ACTION_TYPES.SET_MOBILE: - return { - ...state, - isMobile: action.payload, - }; - - case ACTION_TYPES.SET_SIDER_COLLAPSED: - // 自动保存到 localStorage - localStorage.setItem(STORAGE_KEYS.SIDEBAR_COLLAPSED, action.payload.toString()); - return { - ...state, - siderCollapsed: action.payload, - }; - - case ACTION_TYPES.BATCH_UPDATE: - return { - ...state, - ...action.payload, - }; - - default: - return state; - } -}; - -// Context (内部使用,不导出) -const StyleContext = createContext(null); - -/** - * 自定义 Hook - 处理窗口大小变化 - * @param {Function} dispatch - dispatch 函数 - * @param {Object} state - 当前状态 - * @param {string} pathname - 当前路径 - */ -const useWindowResize = (dispatch, state, pathname) => { - useEffect(() => { - const handleResize = () => { - const isMobile = getIsMobile(); - dispatch({ type: ACTION_TYPES.SET_MOBILE, payload: isMobile }); - - // 只有在非手动控制的情况下,才根据屏幕大小自动调整侧边栏 - if (!state.isManualSiderControl && isConsoleRoute(pathname)) { - dispatch({ - type: ACTION_TYPES.SET_SIDER, - payload: !isMobile, - isManualControl: false - }); - } - }; - - let timeoutId; - const debouncedResize = () => { - clearTimeout(timeoutId); - timeoutId = setTimeout(handleResize, 150); - }; - - window.addEventListener('resize', debouncedResize); - return () => { - window.removeEventListener('resize', debouncedResize); - clearTimeout(timeoutId); - }; - }, [dispatch, state.isManualSiderControl, pathname]); -}; - -/** - * 自定义 Hook - 处理路由变化 - * @param {Function} dispatch - dispatch 函数 - * @param {string} pathname - 当前路径 - */ -const useRouteChange = (dispatch, pathname) => { - useEffect(() => { - const isMobile = getIsMobile(); - const isConsole = isConsoleRoute(pathname); - - dispatch({ - type: ACTION_TYPES.BATCH_UPDATE, - payload: { - showSider: isConsole && !isMobile, - isManualSiderControl: false, - }, - }); - }, [pathname, dispatch]); -}; - -/** - * 自定义 Hook - 处理移动设备侧边栏自动收起 - * @param {Object} state - 当前状态 - * @param {Function} dispatch - dispatch 函数 - */ -const useMobileSiderAutoHide = (state, dispatch) => { - useEffect(() => { - // 移动设备上,如果不是手动控制且侧边栏是打开的,则自动关闭 - if (state.isMobile && state.showSider && !state.isManualSiderControl) { - dispatch({ type: ACTION_TYPES.SET_SIDER, payload: false }); - } - }, [state.isMobile, state.showSider, state.isManualSiderControl, dispatch]); -}; - -/** - * Style Provider 组件 - */ -export const StyleProvider = ({ children }) => { - const location = useLocation(); - const pathname = location.pathname; - - const [state, dispatch] = useReducer( - styleReducer, - pathname, - getInitialState - ); - - useWindowResize(dispatch, state, pathname); - useRouteChange(dispatch, pathname); - useMobileSiderAutoHide(state, dispatch); - - const contextValue = useMemo( - () => ({ state, dispatch }), - [state] - ); - - return ( - - {children} - - ); -}; - -/** - * 自定义 Hook - 使用 StyleContext - * @returns {{state: Object, dispatch: Function}} context value - */ -export const useStyle = () => { - const context = React.useContext(StyleContext); - if (!context) { - throw new Error('useStyle must be used within StyleProvider'); - } - return context; -}; - -// 导出 action creators 以便外部使用 -export const styleActions = { - toggleSider: () => ({ type: ACTION_TYPES.TOGGLE_SIDER }), - setSider: (show, isManualControl = false) => ({ - type: ACTION_TYPES.SET_SIDER, - payload: show, - isManualControl - }), - setMobile: (isMobile) => ({ type: ACTION_TYPES.SET_MOBILE, payload: isMobile }), - setSiderCollapsed: (collapsed) => ({ - type: ACTION_TYPES.SET_SIDER_COLLAPSED, - payload: collapsed - }), -}; diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 12aa01b3..34ba78d7 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -1,6 +1,7 @@ import i18next from 'i18next'; import { Modal, Tag, Typography } from '@douyinfe/semi-ui'; -import { copy, isMobile, showSuccess } from './utils'; +import { copy, showSuccess } from './utils'; +import { MOBILE_BREAKPOINT } from '../hooks/useIsMobile.js'; import { visit } from 'unist-util-visit'; import { OpenAI, @@ -669,7 +670,8 @@ const measureTextWidth = ( }; export function truncateText(text, maxWidth = 200) { - if (!isMobile()) { + const isMobileScreen = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`).matches; + if (!isMobileScreen) { return text; } if (!text) return text; diff --git a/web/src/helpers/utils.js b/web/src/helpers/utils.js index 68a05846..6c4f1275 100644 --- a/web/src/helpers/utils.js +++ b/web/src/helpers/utils.js @@ -4,6 +4,7 @@ import React from 'react'; import { toast } from 'react-toastify'; import { THINK_TAG_REGEX, MESSAGE_ROLES } from '../constants/playground.constants'; import { TABLE_COMPACT_MODES_KEY } from '../constants'; +import { MOBILE_BREAKPOINT } from '../hooks/useIsMobile.js'; const HTMLToastContent = ({ htmlContent }) => { return
; @@ -67,9 +68,7 @@ export async function copy(text) { return okay; } -export function isMobile() { - return window.innerWidth <= 600; -} +// isMobile 函数已移除,请改用 useIsMobile Hook let showErrorOptions = { autoClose: toastConstants.ERROR_TIMEOUT }; let showWarningOptions = { autoClose: toastConstants.WARNING_TIMEOUT }; @@ -77,7 +76,8 @@ let showSuccessOptions = { autoClose: toastConstants.SUCCESS_TIMEOUT }; let showInfoOptions = { autoClose: toastConstants.INFO_TIMEOUT }; let showNoticeOptions = { autoClose: false }; -if (isMobile()) { +const isMobileScreen = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`).matches; +if (isMobileScreen) { showErrorOptions.position = 'top-center'; // showErrorOptions.transition = 'flip'; diff --git a/web/src/hooks/useIsMobile.js b/web/src/hooks/useIsMobile.js new file mode 100644 index 00000000..08f9c5e2 --- /dev/null +++ b/web/src/hooks/useIsMobile.js @@ -0,0 +1,16 @@ +export const MOBILE_BREAKPOINT = 768; + +import { useSyncExternalStore } from 'react'; + +export const useIsMobile = () => { + const query = `(max-width: ${MOBILE_BREAKPOINT - 1}px)`; + return useSyncExternalStore( + (callback) => { + const mql = window.matchMedia(query); + mql.addEventListener('change', callback); + return () => mql.removeEventListener('change', callback); + }, + () => window.matchMedia(query).matches, + () => false, + ); +}; \ No newline at end of file diff --git a/web/src/hooks/useSidebarCollapsed.js b/web/src/hooks/useSidebarCollapsed.js new file mode 100644 index 00000000..2982ff9b --- /dev/null +++ b/web/src/hooks/useSidebarCollapsed.js @@ -0,0 +1,22 @@ +import { useState, useCallback } from 'react'; + +const KEY = 'default_collapse_sidebar'; + +export const useSidebarCollapsed = () => { + const [collapsed, setCollapsed] = useState(() => localStorage.getItem(KEY) === 'true'); + + const toggle = useCallback(() => { + setCollapsed(prev => { + const next = !prev; + localStorage.setItem(KEY, next.toString()); + return next; + }); + }, []); + + const set = useCallback((value) => { + setCollapsed(value); + localStorage.setItem(KEY, value.toString()); + }, []); + + return [collapsed, toggle, set]; +}; \ No newline at end of file diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index e1837de9..7babcad6 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -179,7 +179,6 @@ "注销": "Logout", "登录": "Sign in", "注册": "Sign up", - "加载{name}中...": "Loading {name}...", "未登录或登录已过期,请重新登录!": "Not logged in or session expired. Please login again!", "用户登录": "User Login", "密码": "Password", @@ -933,7 +932,6 @@ "更新令牌后需等待几分钟生效": "It will take a few minutes to take effect after updating the token.", "一小时": "One hour", "新建数量": "New quantity", - "加载失败,请稍后重试": "Loading failed, please try again later", "未设置": "Not set", "API文档": "API documentation", "不是合法的 JSON 字符串": "Not a valid JSON string", diff --git a/web/src/index.css b/web/src/index.css index 66bc64d3..791b853e 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -14,6 +14,22 @@ } /* ==================== 全局基础样式 ==================== */ +/* 侧边栏宽度相关的 CSS 变量,配合 .sidebar-collapsed 类和媒体查询实现响应式布局 */ +:root { + --sidebar-width: 180px; + /* 展开时宽度 */ + --sidebar-width-collapsed: 60px; /* 折叠后宽度,显示图标栏 */ + /* 折叠后宽度 */ + --sidebar-current-width: var(--sidebar-width); +} + +/* 当 body 上存在 .sidebar-collapsed 类时,使用折叠宽度 */ +body.sidebar-collapsed { + --sidebar-current-width: var(--sidebar-width-collapsed); +} + +/* 移除了在移动端强制设为 0 的限制,改由 React 控制是否渲染侧边栏以实现显示/隐藏 */ + body { font-family: Lato, 'Helvetica Neue', Arial, Helvetica, 'Microsoft YaHei', sans-serif; color: var(--semi-color-text-0); diff --git a/web/src/index.js b/web/src/index.js index ef299ea2..2a097023 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -6,11 +6,18 @@ import { UserProvider } from './context/User'; import 'react-toastify/dist/ReactToastify.css'; import { StatusProvider } from './context/Status'; import { ThemeProvider } from './context/Theme'; -import { StyleProvider } from './context/Style/index.js'; import PageLayout from './components/layout/PageLayout.js'; import './i18n/i18n.js'; import './index.css'; +// 欢迎信息(二次开发者不准将此移除) +// Welcome message (Secondary developers are not allowed to remove this) +if (typeof window !== 'undefined') { + console.log('%cWe ❤ NewAPI%c Github: https://github.com/QuantumNous/new-api', + 'color: #10b981; font-weight: bold; font-size: 24px;', + 'color: inherit; font-size: 14px;'); +} + // initialization const root = ReactDOM.createRoot(document.getElementById('root')); @@ -18,11 +25,14 @@ root.render( - + - - - + diff --git a/web/src/pages/About/index.js b/web/src/pages/About/index.js index 032562ca..5ca96d97 100644 --- a/web/src/pages/About/index.js +++ b/web/src/pages/About/index.js @@ -105,7 +105,7 @@ const About = () => { ); return ( -
+
{aboutLoaded && about === '' ? (
{ const channelId = props.editingChannel.id; const isEdit = channelId !== undefined; const [loading, setLoading] = useState(isEdit); + const isMobile = useIsMobile(); const handleCancel = () => { props.handleClose(); }; @@ -693,7 +694,7 @@ const EditChannel = (props) => { } bodyStyle={{ padding: '0' }} visible={props.visible} - width={isMobile() ? '100%' : 600} + width={isMobile ? '100%' : 600} footer={
diff --git a/web/src/pages/Channel/index.js b/web/src/pages/Channel/index.js index 9e43f318..d8425bd3 100644 --- a/web/src/pages/Channel/index.js +++ b/web/src/pages/Channel/index.js @@ -3,7 +3,7 @@ import ChannelsTable from '../../components/table/ChannelsTable'; const File = () => { return ( -
+
); diff --git a/web/src/pages/Chat2Link/index.js b/web/src/pages/Chat2Link/index.js index 28a48535..30ec785a 100644 --- a/web/src/pages/Chat2Link/index.js +++ b/web/src/pages/Chat2Link/index.js @@ -17,7 +17,7 @@ const chat2page = () => { } return ( -
+

正在加载,请稍候...

); diff --git a/web/src/pages/Detail/index.js b/web/src/pages/Detail/index.js index d560177c..b5553cbf 100644 --- a/web/src/pages/Detail/index.js +++ b/web/src/pages/Detail/index.js @@ -41,8 +41,9 @@ import { VChart } from '@visactor/react-vchart'; import { API, isAdmin, - isMobile, showError, + showSuccess, + showWarning, timestamp2string, timestamp2string1, getQuotaWithUnit, @@ -51,9 +52,9 @@ import { renderQuota, modelToColor, copy, - showSuccess, getRelativeTime } from '../../helpers'; +import { useIsMobile } from '../../hooks/useIsMobile.js'; import { UserContext } from '../../context/User/index.js'; import { StatusContext } from '../../context/Status/index.js'; import { useTranslation } from 'react-i18next'; @@ -66,6 +67,7 @@ const Detail = (props) => { // ========== Hooks - Navigation & Translation ========== const { t } = useTranslation(); const navigate = useNavigate(); + const isMobile = useIsMobile(); // ========== Hooks - Refs ========== const formRef = useRef(); @@ -1118,7 +1120,7 @@ const Detail = (props) => { }, []); return ( -
+

{ onOk={handleSearchConfirm} onCancel={handleCloseModal} closeOnEsc={true} - size={isMobile() ? 'full-width' : 'small'} + size={isMobile ? 'full-width' : 'small'} centered >
diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js index f2db769c..0cf8dea1 100644 --- a/web/src/pages/Home/index.js +++ b/web/src/pages/Home/index.js @@ -1,6 +1,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { Button, Typography, Tag, Input, ScrollList, ScrollItem } from '@douyinfe/semi-ui'; -import { API, showError, isMobile, copy, showSuccess } from '../../helpers'; +import { API, showError, copy, showSuccess } from '../../helpers'; +import { useIsMobile } from '../../hooks/useIsMobile.js'; import { API_ENDPOINTS } from '../../constants/common.constant'; import { StatusContext } from '../../context/Status'; import { marked } from 'marked'; @@ -18,6 +19,7 @@ const Home = () => { const [homePageContentLoaded, setHomePageContentLoaded] = useState(false); const [homePageContent, setHomePageContent] = useState(''); const [noticeVisible, setNoticeVisible] = useState(false); + const isMobile = useIsMobile(); const isDemoSiteMode = statusState?.status?.demo_site_enabled || false; const docsLink = statusState?.status?.docs_link || ''; const serverAddress = statusState?.status?.server_address || window.location.origin; @@ -98,7 +100,7 @@ const Home = () => { setNoticeVisible(false)} - isMobile={isMobile()} + isMobile={isMobile} /> {homePageContentLoaded && homePageContent === '' ? (
@@ -133,7 +135,7 @@ const Home = () => { readonly value={serverAddress} className="flex-1 !rounded-full" - size={isMobile() ? 'default' : 'large'} + size={isMobile ? 'default' : 'large'} suffix={
@@ -160,13 +162,13 @@ const Home = () => { {/* 操作按钮 */}
- {isDemoSiteMode && statusState?.status?.version ? (