diff --git a/frontend/src/hooks/useDebounce.js b/frontend/src/hooks/useDebounce.js new file mode 100644 index 0000000..74e201b --- /dev/null +++ b/frontend/src/hooks/useDebounce.js @@ -0,0 +1,74 @@ +import { useState, useEffect } from 'react'; + +/** + * 防抖 Hook + * @param {any} value - 需要防抖的值 + * @param {number} delay - 延迟时间(毫秒) + * @returns {any} 防抖后的值 + * + * @example + * const [keyword, setKeyword] = useState(''); + * const debouncedKeyword = useDebounce(keyword, 300); + * + * useEffect(() => { + * // 只有在停止输入 300ms 后才会执行 + * fetchData(debouncedKeyword); + * }, [debouncedKeyword]); + */ +export function useDebounce(value, delay = 300) { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +} + +/** + * 防抖函数 Hook(用于回调函数) + * @param {Function} callback - 需要防抖的回调函数 + * @param {number} delay - 延迟时间(毫秒) + * @returns {Function} 防抖后的函数 + * + * @example + * const debouncedSearch = useDebouncedCallback((value) => { + * fetchData(value); + * }, 300); + * + * debouncedSearch(e.target.value)} /> + */ +export function useDebouncedCallback(callback, delay = 300) { + const [timeoutId, setTimeoutId] = useState(null); + + const debouncedFn = (...args) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + + const newTimeoutId = setTimeout(() => { + callback(...args); + }, delay); + + setTimeoutId(newTimeoutId); + }; + + // 清理函数 + useEffect(() => { + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [timeoutId]); + + return debouncedFn; +} + +export default useDebounce; diff --git a/frontend/src/pages/CategoryManagement.jsx b/frontend/src/pages/CategoryManagement.jsx index 811eff5..7530411 100644 --- a/frontend/src/pages/CategoryManagement.jsx +++ b/frontend/src/pages/CategoryManagement.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useDebounce } from '../hooks/useDebounce'; import { Table, Button, @@ -31,13 +32,14 @@ function CategoryManagement() { showTotal: total => `共 ${total} 条记录`, }); const [keyword, setKeyword] = useState(''); + const debouncedKeyword = useDebounce(keyword, 300); const [status, setStatus] = useState('all'); const fetchCategories = async (page = 1, pageSize = 10) => { try { setLoading(true); const response = await axios.get('/api/consumable-categories', { - params: { page, pageSize, keyword, status }, + params: { page, pageSize, keyword: debouncedKeyword, status }, }); setCategories(response.data.categories); setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total })); @@ -51,7 +53,7 @@ function CategoryManagement() { useEffect(() => { fetchCategories(); - }, [keyword, status]); + }, [debouncedKeyword, status]); const showModal = (category = null) => { setEditingCategory(category); diff --git a/frontend/src/pages/ConsumableManagement.jsx b/frontend/src/pages/ConsumableManagement.jsx index 0d04b83..62781dc 100644 --- a/frontend/src/pages/ConsumableManagement.jsx +++ b/frontend/src/pages/ConsumableManagement.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import { useDebounce } from '../hooks/useDebounce'; import { Table, Button, @@ -45,6 +46,7 @@ function ConsumableManagement() { showTotal: total => `共 ${total} 条记录`, }); const [keyword, setKeyword] = useState(''); + const debouncedKeyword = useDebounce(keyword, 300); const [category, setCategory] = useState('all'); const [status, setStatus] = useState('all'); const [importModalVisible, setImportModalVisible] = useState(false); @@ -65,7 +67,7 @@ function ConsumableManagement() { try { setLoading(true); const response = await axios.get('/api/consumables', { - params: { page, pageSize, keyword, category, status }, + params: { page, pageSize, keyword: debouncedKeyword, category, status }, }); setConsumables(response.data.consumables); setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total })); @@ -76,7 +78,7 @@ function ConsumableManagement() { setLoading(false); } }, - [keyword, category, status] + [debouncedKeyword, category, status] ); const fetchCategories = useCallback(async () => { diff --git a/frontend/src/pages/DeviceManagement.jsx b/frontend/src/pages/DeviceManagement.jsx index fc096e4..2f7e778 100644 --- a/frontend/src/pages/DeviceManagement.jsx +++ b/frontend/src/pages/DeviceManagement.jsx @@ -1433,7 +1433,7 @@ function DeviceManagement() { value={status} onChange={setStatus} style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }} - dropdownStyle={{ borderRadius: designTokens.borderRadius.medium }} + styles={{ popup: { root: { borderRadius: designTokens.borderRadius.medium } } }} > @@ -1448,7 +1448,7 @@ function DeviceManagement() { value={type} onChange={setType} style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }} - dropdownStyle={{ borderRadius: designTokens.borderRadius.medium }} + styles={{ popup: { root: { borderRadius: designTokens.borderRadius.medium } } }} > @@ -1547,7 +1547,7 @@ function DeviceManagement() { style: { marginTop: '16px' }, }} onChange={handleTableChange} - scroll={{ y: 'calc(100vh - 380px)', scrollToFirstRowOnChange: true }} + scroll={{ y: 500, scrollToFirstRowOnChange: true }} virtual components={{ header: { diff --git a/frontend/src/pages/RackManagement.jsx b/frontend/src/pages/RackManagement.jsx index d102bc6..528db84 100644 --- a/frontend/src/pages/RackManagement.jsx +++ b/frontend/src/pages/RackManagement.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; +import { useDebounce } from '../hooks/useDebounce'; import { Table, Button, @@ -340,6 +341,7 @@ function RackManagement() { const [selectedRackIds, setSelectedRackIds] = useState([]); const [viewMode, setViewMode] = useState('table'); const [searchKeyword, setSearchKeyword] = useState(''); + const debouncedSearchKeyword = useDebounce(searchKeyword, 300); const [statusFilter, setStatusFilter] = useState('all'); const [roomFilter, setRoomFilter] = useState('all'); const [pagination, setPagination] = useState({ @@ -366,7 +368,7 @@ function RackManagement() { pageSize, roomId: roomFilter, status: statusFilter, - keyword: searchKeyword || undefined, + keyword: debouncedSearchKeyword || undefined, }, }); const { racks: data, total } = response.data; @@ -379,7 +381,7 @@ function RackManagement() { setLoading(false); } }, - [roomFilter, statusFilter, searchKeyword] + [roomFilter, statusFilter, debouncedSearchKeyword] ); const fetchRooms = useCallback(async () => { @@ -401,7 +403,7 @@ function RackManagement() { useEffect(() => { setPagination(prev => ({ ...prev, current: 1 })); fetchRacks(1, pagination.pageSize); - }, [roomFilter, statusFilter, searchKeyword]); + }, [roomFilter, statusFilter, debouncedSearchKeyword]); const handleTableChange = useCallback( pagination => { diff --git a/frontend/src/pages/RoomManagement.jsx b/frontend/src/pages/RoomManagement.jsx index 5eea3b1..036b06b 100644 --- a/frontend/src/pages/RoomManagement.jsx +++ b/frontend/src/pages/RoomManagement.jsx @@ -660,7 +660,7 @@ function RoomManagement() { value={statusFilter} onChange={setStatusFilter} style={{ width: '140px', height: '42px' }} - dropdownStyle={{ borderRadius: '10px' }} + styles={{ popup: { root: { borderRadius: '10px' } } }} >