mirror of
https://github.com/GSManagerXZ/GameServerManager.git
synced 2026-06-02 11:02:33 +08:00
增加盘符切换
This commit is contained in:
@@ -15,7 +15,8 @@ import {
|
||||
Modal,
|
||||
Progress,
|
||||
Badge,
|
||||
Drawer
|
||||
Drawer,
|
||||
Select
|
||||
} from 'antd'
|
||||
import {
|
||||
HomeOutlined,
|
||||
@@ -40,7 +41,8 @@ import {
|
||||
ClockCircleOutlined,
|
||||
BellOutlined,
|
||||
AppstoreOutlined,
|
||||
UnorderedListOutlined
|
||||
UnorderedListOutlined,
|
||||
HddOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { useFileStore } from '@/stores/fileStore'
|
||||
import { useNotificationStore } from '@/stores/notificationStore'
|
||||
@@ -169,6 +171,88 @@ const FileManagerPage: React.FC = () => {
|
||||
position: { x: number; y: number }
|
||||
} | null>(null);
|
||||
|
||||
// 盘符选择状态
|
||||
const [drives, setDrives] = useState<Array<{ label: string; value: string; type: string }>>([])
|
||||
const [selectedDrive, setSelectedDrive] = useState<string>('')
|
||||
const [drivesLoading, setDrivesLoading] = useState(false)
|
||||
|
||||
// 查找当前路径对应的盘符
|
||||
const findDriveForPath = useCallback((path: string, driveList: Array<{ label: string; value: string; type: string }>) => {
|
||||
if (!path || !driveList.length) return null
|
||||
|
||||
const normalizedPath = normalizePath(path)
|
||||
|
||||
// 对每个盘符进行匹配
|
||||
for (const drive of driveList) {
|
||||
const normalizedDriveValue = normalizePath(drive.value)
|
||||
|
||||
// 确保盘符路径以 / 结尾进行比较
|
||||
const driveRoot = normalizedDriveValue.endsWith('/') ? normalizedDriveValue : normalizedDriveValue + '/'
|
||||
const pathToCheck = normalizedPath.endsWith('/') ? normalizedPath : normalizedPath + '/'
|
||||
|
||||
// 检查路径是否以盘符开头
|
||||
if (pathToCheck.startsWith(driveRoot) || normalizedPath === normalizedDriveValue) {
|
||||
return drive
|
||||
}
|
||||
|
||||
// 特殊处理:如果是 Windows 盘符格式(如 D: 和 D:/),也要匹配
|
||||
if (normalizedDriveValue.match(/^[A-Za-z]:\/?\/?$/)) {
|
||||
const drivePrefix = normalizedDriveValue.charAt(0) + ':'
|
||||
if (normalizedPath.startsWith(drivePrefix)) {
|
||||
return drive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}, [])
|
||||
|
||||
// 加载系统盘符
|
||||
const loadDrives = useCallback(async () => {
|
||||
try {
|
||||
setDrivesLoading(true)
|
||||
const driveList = await fileApiClient.getDrives()
|
||||
setDrives(driveList)
|
||||
|
||||
// 如果当前路径匹配某个盘符,设置为选中状态
|
||||
if (currentPath) {
|
||||
const currentDrive = findDriveForPath(currentPath, driveList)
|
||||
if (currentDrive) {
|
||||
setSelectedDrive(currentDrive.value)
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('加载盘符失败:', error)
|
||||
addNotification({
|
||||
type: 'error',
|
||||
title: '加载盘符失败',
|
||||
message: error.message || '无法获取系统盘符'
|
||||
})
|
||||
} finally {
|
||||
setDrivesLoading(false)
|
||||
}
|
||||
}, [currentPath, addNotification, findDriveForPath])
|
||||
|
||||
// 导航到指定路径
|
||||
const navigateToPath = useCallback((newPath: string) => {
|
||||
const normalizedPath = normalizePath(newPath)
|
||||
|
||||
// 更新历史记录
|
||||
const newHistory = history.slice(0, historyIndex + 1)
|
||||
newHistory.push(normalizedPath)
|
||||
setHistory(newHistory)
|
||||
setHistoryIndex(newHistory.length - 1)
|
||||
|
||||
// 只更新 URL 参数,让 useEffect 监听 URL 变化来更新状态
|
||||
navigate(`/files?path=${encodeURIComponent(normalizedPath)}`, { replace: true })
|
||||
}, [history, historyIndex, navigate])
|
||||
|
||||
// 切换盘符
|
||||
const handleDriveChange = useCallback((driveValue: string) => {
|
||||
setSelectedDrive(driveValue)
|
||||
navigateToPath(driveValue)
|
||||
}, [navigateToPath])
|
||||
|
||||
// 初始化
|
||||
useEffect(() => {
|
||||
// 检查 URL 参数中的路径
|
||||
@@ -183,7 +267,20 @@ const FileManagerPage: React.FC = () => {
|
||||
|
||||
// 初始加载任务列表
|
||||
loadActiveTasks()
|
||||
}, [searchParams, setCurrentPath, loadFiles, loadActiveTasks])
|
||||
|
||||
// 加载系统盘符
|
||||
loadDrives()
|
||||
}, [searchParams, setCurrentPath, loadFiles])
|
||||
|
||||
// 当路径变化时更新选中的盘符
|
||||
useEffect(() => {
|
||||
if (drives.length > 0 && currentPath) {
|
||||
const currentDrive = findDriveForPath(currentPath, drives)
|
||||
if (currentDrive && currentDrive.value !== selectedDrive) {
|
||||
setSelectedDrive(currentDrive.value)
|
||||
}
|
||||
}
|
||||
}, [currentPath, drives, selectedDrive, findDriveForPath])
|
||||
|
||||
// 定期刷新活动任务
|
||||
useEffect(() => {
|
||||
@@ -201,7 +298,7 @@ const FileManagerPage: React.FC = () => {
|
||||
}, 2000) // 每2秒刷新一次
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [activeTasks, loadActiveTasks, loadFiles])
|
||||
}, [activeTasks, loadFiles])
|
||||
|
||||
// 键盘快捷键
|
||||
useEffect(() => {
|
||||
@@ -268,30 +365,40 @@ const FileManagerPage: React.FC = () => {
|
||||
}
|
||||
}, [error, addNotification, setError])
|
||||
|
||||
// 获取显示路径(相对于当前盘符的路径)
|
||||
const getDisplayPath = () => {
|
||||
if (selectedDrive && currentPath) {
|
||||
const normalizedCurrentPath = normalizePath(currentPath)
|
||||
const normalizedDriveValue = normalizePath(selectedDrive)
|
||||
|
||||
// 确保盘符路径以 / 结尾进行比较
|
||||
const driveRoot = normalizedDriveValue.endsWith('/') ? normalizedDriveValue : normalizedDriveValue + '/'
|
||||
|
||||
if (normalizedCurrentPath.startsWith(driveRoot) || normalizedCurrentPath === normalizedDriveValue) {
|
||||
let relativePath = normalizedCurrentPath.slice(normalizedDriveValue.length)
|
||||
// 移除开头的斜杠
|
||||
if (relativePath.startsWith('/')) {
|
||||
relativePath = relativePath.slice(1)
|
||||
}
|
||||
// 如果是根目录,显示盘符
|
||||
return relativePath || normalizedDriveValue
|
||||
}
|
||||
}
|
||||
return currentPath
|
||||
}
|
||||
|
||||
// 更新路径输入
|
||||
useEffect(() => {
|
||||
setPathInput(currentPath)
|
||||
}, [currentPath])
|
||||
|
||||
// 导航到指定路径
|
||||
const navigateToPath = useCallback((newPath: string) => {
|
||||
const normalizedPath = normalizePath(newPath)
|
||||
|
||||
// 更新历史记录
|
||||
const newHistory = history.slice(0, historyIndex + 1)
|
||||
newHistory.push(normalizedPath)
|
||||
setHistory(newHistory)
|
||||
setHistoryIndex(newHistory.length - 1)
|
||||
|
||||
setCurrentPath(normalizedPath)
|
||||
}, [history, historyIndex, setCurrentPath])
|
||||
setPathInput(getDisplayPath())
|
||||
}, [currentPath, selectedDrive])
|
||||
|
||||
// 后退
|
||||
const goBack = () => {
|
||||
if (historyIndex > 0) {
|
||||
const newIndex = historyIndex - 1
|
||||
setHistoryIndex(newIndex)
|
||||
setCurrentPath(history[newIndex])
|
||||
const targetPath = history[newIndex]
|
||||
navigate(`/files?path=${encodeURIComponent(targetPath)}`, { replace: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +407,8 @@ const FileManagerPage: React.FC = () => {
|
||||
if (historyIndex < history.length - 1) {
|
||||
const newIndex = historyIndex + 1
|
||||
setHistoryIndex(newIndex)
|
||||
setCurrentPath(history[newIndex])
|
||||
const targetPath = history[newIndex]
|
||||
navigate(`/files?path=${encodeURIComponent(targetPath)}`, { replace: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +422,19 @@ const FileManagerPage: React.FC = () => {
|
||||
|
||||
// 处理路径输入
|
||||
const handlePathSubmit = () => {
|
||||
const trimmedInput = normalizePath(pathInput.trim())
|
||||
let inputPath = pathInput.trim()
|
||||
|
||||
// 如果输入的是相对路径且有选中的盘符,转换为绝对路径
|
||||
if (selectedDrive && !inputPath.includes(':') && !inputPath.startsWith('/')) {
|
||||
// 确保盘符路径以正确的分隔符结尾
|
||||
let basePath = selectedDrive
|
||||
if (!basePath.endsWith('/') && !basePath.endsWith('\\')) {
|
||||
basePath += '/'
|
||||
}
|
||||
inputPath = basePath + inputPath
|
||||
}
|
||||
|
||||
const trimmedInput = normalizePath(inputPath)
|
||||
const current = normalizePath(currentPath)
|
||||
if (trimmedInput && trimmedInput !== current) {
|
||||
navigateToPath(trimmedInput)
|
||||
@@ -607,21 +727,42 @@ const FileManagerPage: React.FC = () => {
|
||||
|
||||
// 生成面包屑
|
||||
const generateBreadcrumbs = () => {
|
||||
const parts = currentPath.split('/').filter(Boolean)
|
||||
// 获取相对于当前盘符的路径
|
||||
let relativePath = currentPath
|
||||
let rootTitle = '根目录'
|
||||
let rootPath = '/'
|
||||
|
||||
// 如果有选中的盘符,计算相对路径
|
||||
if (selectedDrive && currentPath.startsWith(selectedDrive)) {
|
||||
relativePath = currentPath.slice(selectedDrive.length)
|
||||
rootTitle = selectedDrive.replace(':', '')
|
||||
rootPath = selectedDrive
|
||||
|
||||
// 移除开头的斜杠或反斜杠
|
||||
if (relativePath.startsWith('/') || relativePath.startsWith('\\')) {
|
||||
relativePath = relativePath.slice(1)
|
||||
}
|
||||
}
|
||||
|
||||
const parts = relativePath.split(/[/\\]/).filter(Boolean)
|
||||
const items = [
|
||||
{
|
||||
title: (
|
||||
<span className="flex items-center cursor-pointer" onClick={() => navigateToPath('/')}>
|
||||
<HomeOutlined className="mr-1" />
|
||||
根目录
|
||||
<span className="flex items-center cursor-pointer" onClick={() => navigateToPath(rootPath)}>
|
||||
<HddOutlined className="mr-1" />
|
||||
{rootTitle}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
let currentBreadcrumbPath = ''
|
||||
let currentBreadcrumbPath = rootPath
|
||||
parts.forEach((part, index) => {
|
||||
currentBreadcrumbPath += '/' + part
|
||||
// 确保路径分隔符正确
|
||||
if (!currentBreadcrumbPath.endsWith('/') && !currentBreadcrumbPath.endsWith('\\')) {
|
||||
currentBreadcrumbPath += '/'
|
||||
}
|
||||
currentBreadcrumbPath += part
|
||||
const breadcrumbPath = currentBreadcrumbPath
|
||||
|
||||
items.push({
|
||||
@@ -681,6 +822,20 @@ const FileManagerPage: React.FC = () => {
|
||||
{/* 工具栏 */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* 盘符选择 */}
|
||||
<div className="mr-2">
|
||||
<Select
|
||||
value={selectedDrive}
|
||||
onChange={handleDriveChange}
|
||||
loading={drivesLoading}
|
||||
placeholder="盘符"
|
||||
size="small"
|
||||
style={{ width: 80, height: 25 }}
|
||||
suffixIcon={<HddOutlined />}
|
||||
options={drives}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 导航按钮 */}
|
||||
<Space>
|
||||
<Tooltip title="后退">
|
||||
|
||||
@@ -89,8 +89,12 @@ export const useFileStore = create<FileStore>((set, get) => ({
|
||||
|
||||
// 设置当前路径
|
||||
setCurrentPath: (path: string) => {
|
||||
set({ currentPath: path })
|
||||
get().loadFiles(path)
|
||||
const currentState = get()
|
||||
// 只有当路径真正改变时才更新状态和加载文件
|
||||
if (currentState.currentPath !== path) {
|
||||
set({ currentPath: path })
|
||||
get().loadFiles(path)
|
||||
}
|
||||
},
|
||||
|
||||
// 加载文件列表
|
||||
|
||||
@@ -296,6 +296,12 @@ export class FileApiClient {
|
||||
const response = await this.client.delete(`${API_BASE}/tasks/${taskId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 获取系统盘符
|
||||
async getDrives(): Promise<Array<{ label: string; value: string; type: string }>> {
|
||||
const response = await this.client.get(`${API_BASE}/drives`)
|
||||
return response.data.data
|
||||
}
|
||||
}
|
||||
|
||||
export const fileApiClient = new FileApiClient()
|
||||
@@ -14,8 +14,11 @@ export function normalizePath(pathStr: string): string {
|
||||
// 将反斜杠转换为正斜杠
|
||||
let normalized = pathStr.replace(/\\/g, '/')
|
||||
|
||||
// 确保以/开头
|
||||
if (!normalized.startsWith('/')) {
|
||||
// 检查是否是 Windows 绝对路径(如 C:/ 或 D:/)
|
||||
const isWindowsAbsolute = /^[A-Za-z]:/.test(normalized)
|
||||
|
||||
// 只有在不是 Windows 绝对路径且不以/开头时才添加前缀/
|
||||
if (!isWindowsAbsolute && !normalized.startsWith('/')) {
|
||||
normalized = '/' + normalized
|
||||
}
|
||||
|
||||
@@ -37,8 +40,19 @@ export function normalizePath(pathStr: string): string {
|
||||
}
|
||||
|
||||
// 重新构建路径
|
||||
const result = '/' + stack.join('/')
|
||||
return result === '/' ? '/' : result
|
||||
if (isWindowsAbsolute) {
|
||||
// Windows 绝对路径不需要前缀 /
|
||||
const result = stack.join('/')
|
||||
// 确保 Windows 盘符路径以 / 结尾(如 C:/ 而不是 C:)
|
||||
if (result.match(/^[A-Za-z]:$/) && !result.endsWith('/')) {
|
||||
return result + '/'
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
// Unix 风格路径需要前缀 /
|
||||
const result = '/' + stack.join('/')
|
||||
return result === '/' ? '/' : result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,13 +64,31 @@ export function getDirectoryPath(pathStr: string): string {
|
||||
if (!pathStr || pathStr === '/') return '/'
|
||||
|
||||
const normalized = normalizePath(pathStr)
|
||||
|
||||
// 检查是否是 Windows 绝对路径
|
||||
const isWindowsAbsolute = /^[A-Za-z]:/.test(normalized)
|
||||
|
||||
const lastSlashIndex = normalized.lastIndexOf('/')
|
||||
|
||||
if (lastSlashIndex === 0) {
|
||||
return '/'
|
||||
if (isWindowsAbsolute) {
|
||||
// Windows 路径处理
|
||||
if (lastSlashIndex === -1) {
|
||||
// 如果没有斜杠,返回盘符根目录
|
||||
return normalized.match(/^[A-Za-z]:/)![0] + '/'
|
||||
} else if (lastSlashIndex === 2 && normalized.charAt(1) === ':') {
|
||||
// 如果是盘符根目录(如 C:/),返回自身
|
||||
return normalized
|
||||
} else {
|
||||
// 返回父目录
|
||||
return normalized.substring(0, lastSlashIndex)
|
||||
}
|
||||
} else {
|
||||
// Unix 风格路径处理
|
||||
if (lastSlashIndex === 0) {
|
||||
return '/'
|
||||
}
|
||||
return normalized.substring(0, lastSlashIndex)
|
||||
}
|
||||
|
||||
return normalized.substring(0, lastSlashIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +150,15 @@ export function getExtension(pathStr: string): string {
|
||||
* @returns 是否为绝对路径
|
||||
*/
|
||||
export function isAbsolute(pathStr: string): boolean {
|
||||
return pathStr && pathStr.startsWith('/')
|
||||
if (!pathStr) return false
|
||||
|
||||
// 检查是否是 Windows 绝对路径(如 C:/ 或 D:/)
|
||||
const isWindowsAbsolute = /^[A-Za-z]:/.test(pathStr)
|
||||
|
||||
// 检查是否是 Unix 绝对路径(以 / 开头)
|
||||
const isUnixAbsolute = pathStr.startsWith('/')
|
||||
|
||||
return isWindowsAbsolute || isUnixAbsolute
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,10 +9,14 @@ import unzipper from 'unzipper'
|
||||
import * as tar from 'tar'
|
||||
import * as zlib from 'zlib'
|
||||
import mime from 'mime-types'
|
||||
import { exec } from 'child_process'
|
||||
import { promisify } from 'util'
|
||||
import { authenticateToken } from '../middleware/auth.js'
|
||||
import { taskManager } from '../modules/task/taskManager.js'
|
||||
import { compressionWorker } from '../modules/task/compressionWorker.js'
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
const router = Router()
|
||||
|
||||
// 处理中文文件名编码的工具函数
|
||||
@@ -116,7 +120,10 @@ const isValidPath = (filePath: string): boolean => {
|
||||
// 在Unix系统上,绝对路径以 / 开头
|
||||
const isAbsolute = path.isAbsolute(normalizedPath)
|
||||
|
||||
return isAbsolute
|
||||
// 特殊处理 Windows 盘符路径(如 D: 或 D:/)
|
||||
const isWindowsDrive = process.platform === 'win32' && /^[A-Za-z]:[\\/]?$/.test(normalizedPath)
|
||||
|
||||
return isAbsolute || isWindowsDrive
|
||||
}
|
||||
// 修复Windows路径格式的工具函数
|
||||
const fixWindowsPath = (filePath: string): string => {
|
||||
@@ -130,6 +137,17 @@ const fixWindowsPath = (filePath: string): string => {
|
||||
decodedPath = decodedPath.substring(1)
|
||||
}
|
||||
|
||||
// 在Windows系统中,如果路径是盘符格式(如 D: 或 D:/),转换为根目录格式(如 D:\)
|
||||
if (process.platform === 'win32') {
|
||||
if (/^[A-Za-z]:$/.test(decodedPath)) {
|
||||
// D: -> D:\
|
||||
decodedPath = decodedPath + '\\'
|
||||
} else if (/^[A-Za-z]:\/+$/.test(decodedPath)) {
|
||||
// D:/ 或 D:/// -> D:\
|
||||
decodedPath = decodedPath.charAt(0) + decodedPath.charAt(1) + '\\'
|
||||
}
|
||||
}
|
||||
|
||||
return decodedPath
|
||||
}
|
||||
|
||||
@@ -1827,4 +1845,93 @@ router.delete('/tasks/:taskId', authenticateToken, async (req: Request, res: Res
|
||||
}
|
||||
})
|
||||
|
||||
// 获取系统盘符
|
||||
router.get('/drives', authenticateToken, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const drives: Array<{ label: string; value: string; type: string }> = []
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Windows系统 - 获取所有盘符
|
||||
try {
|
||||
const { stdout } = await execAsync('wmic logicaldisk get size,freespace,caption,description,drivetype')
|
||||
const lines = stdout.split('\n').filter(line => line.trim() && !line.includes('Caption'))
|
||||
|
||||
for (const line of lines) {
|
||||
const parts = line.trim().split(/\s+/)
|
||||
if (parts.length >= 5) {
|
||||
const caption = parts[0] // 盘符 (如 C:)
|
||||
const driveType = parseInt(parts[2]) // 驱动器类型
|
||||
|
||||
if (caption && caption.match(/^[A-Z]:$/)) {
|
||||
let type = 'unknown'
|
||||
switch (driveType) {
|
||||
case 2: type = 'removable'; break // 可移动磁盘
|
||||
case 3: type = 'fixed'; break // 固定磁盘
|
||||
case 4: type = 'network'; break // 网络磁盘
|
||||
case 5: type = 'cdrom'; break // 光盘
|
||||
default: type = 'unknown'; break
|
||||
}
|
||||
|
||||
drives.push({
|
||||
label: `${caption}\\`,
|
||||
value: `${caption}\\`,
|
||||
type: type
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果wmic命令失败,使用备用方法
|
||||
console.warn('wmic命令失败,使用备用方法获取盘符:', error)
|
||||
|
||||
// 尝试常见的盘符
|
||||
const commonDrives = ['C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:', 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:']
|
||||
|
||||
for (const drive of commonDrives) {
|
||||
try {
|
||||
const drivePath = `${drive}\\`
|
||||
await fs.access(drivePath)
|
||||
drives.push({
|
||||
label: drivePath,
|
||||
value: drivePath,
|
||||
type: 'fixed'
|
||||
})
|
||||
} catch (error) {
|
||||
// 盘符不存在,跳过
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Linux/Unix系统 - 只显示根目录
|
||||
drives.push({
|
||||
label: '根目录 (/)',
|
||||
value: '/',
|
||||
type: 'fixed'
|
||||
})
|
||||
}
|
||||
|
||||
// 如果没有找到任何盘符,至少返回当前工作目录
|
||||
if (drives.length === 0) {
|
||||
const cwd = process.cwd()
|
||||
const rootPath = process.platform === 'win32' ? path.parse(cwd).root : '/'
|
||||
drives.push({
|
||||
label: process.platform === 'win32' ? rootPath : '根目录 (/)',
|
||||
value: rootPath,
|
||||
type: 'fixed'
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
data: drives
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('获取盘符失败:', error)
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: error.message || '获取盘符失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
60
报错.txt
60
报错.txt
@@ -1,60 +0,0 @@
|
||||
react-dom.development.js:86
|
||||
Warning: React has detected a change in the order of Hooks called by GlobalMusicPlayer. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
|
||||
|
||||
Previous render Next render
|
||||
------------------------------------------------------
|
||||
1. useRef useRef
|
||||
2. useMemo useMemo
|
||||
3. useSyncExternalStore useSyncExternalStore
|
||||
4. useEffect useEffect
|
||||
5. useDebugValue useDebugValue
|
||||
6. useDebugValue useDebugValue
|
||||
7. useState useState
|
||||
8. useState useState
|
||||
9. useRef useRef
|
||||
10. useContext useContext
|
||||
11. useContext useContext
|
||||
12. useContext useContext
|
||||
13. useContext useContext
|
||||
14. useContext useContext
|
||||
15. useContext useContext
|
||||
16. useContext useContext
|
||||
17. useRef useRef
|
||||
18. useContext useContext
|
||||
19. useLayoutEffect useLayoutEffect
|
||||
20. useCallback useCallback
|
||||
21. undefined useEffect
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
at GlobalMusicPlayer (http://localhost:5173/src/components/GlobalMusicPlayer.tsx:40:7)
|
||||
at div
|
||||
at div
|
||||
at App (http://localhost:5173/node_modules/.vite/deps/antd.js?v=41c5d98f:21124:16)
|
||||
at MotionWrapper (http://localhost:5173/node_modules/.vite/deps/antd.js?v=41c5d98f:6541:32)
|
||||
at ProviderChildren (http://localhost:5173/node_modules/.vite/deps/antd.js?v=41c5d98f:6649:5)
|
||||
at ConfigProvider (http://localhost:5173/node_modules/.vite/deps/antd.js?v=41c5d98f:6938:27)
|
||||
at App (http://localhost:5173/src/App.tsx?t=1752220576201:124:39)
|
||||
at Router (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=41c5d98f:4501:15)
|
||||
at BrowserRouter (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=41c5d98f:5247:5)
|
||||
2
|
||||
react-dom.development.js:15688
|
||||
Uncaught Error: Rendered more hooks than during the previous render.
|
||||
at GlobalMusicPlayer (GlobalMusicPlayer.tsx:44:3)
|
||||
react-dom.development.js:18704
|
||||
The above error occurred in the <GlobalMusicPlayer> component:
|
||||
|
||||
at GlobalMusicPlayer (http://localhost:5173/src/components/GlobalMusicPlayer.tsx:40:7)
|
||||
at div
|
||||
at div
|
||||
at App (http://localhost:5173/node_modules/.vite/deps/antd.js?v=41c5d98f:21124:16)
|
||||
at MotionWrapper (http://localhost:5173/node_modules/.vite/deps/antd.js?v=41c5d98f:6541:32)
|
||||
at ProviderChildren (http://localhost:5173/node_modules/.vite/deps/antd.js?v=41c5d98f:6649:5)
|
||||
at ConfigProvider (http://localhost:5173/node_modules/.vite/deps/antd.js?v=41c5d98f:6938:27)
|
||||
at App (http://localhost:5173/src/App.tsx?t=1752220576201:124:39)
|
||||
at Router (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=41c5d98f:4501:15)
|
||||
at BrowserRouter (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=41c5d98f:5247:5)
|
||||
|
||||
Consider adding an error boundary to your tree to customize error handling behavior.
|
||||
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
|
||||
react-dom.development.js:12056
|
||||
Uncaught Error: Rendered more hooks than during the previous render.
|
||||
Reference in New Issue
Block a user