feat(搜索): 添加防抖功能优化搜索性能
This commit is contained in:
@@ -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);
|
||||||
|
*
|
||||||
|
* <Input onChange={e => 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;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useDebounce } from '../hooks/useDebounce';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
Button,
|
Button,
|
||||||
@@ -31,13 +32,14 @@ function CategoryManagement() {
|
|||||||
showTotal: total => `共 ${total} 条记录`,
|
showTotal: total => `共 ${total} 条记录`,
|
||||||
});
|
});
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
|
const debouncedKeyword = useDebounce(keyword, 300);
|
||||||
const [status, setStatus] = useState('all');
|
const [status, setStatus] = useState('all');
|
||||||
|
|
||||||
const fetchCategories = async (page = 1, pageSize = 10) => {
|
const fetchCategories = async (page = 1, pageSize = 10) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await axios.get('/api/consumable-categories', {
|
const response = await axios.get('/api/consumable-categories', {
|
||||||
params: { page, pageSize, keyword, status },
|
params: { page, pageSize, keyword: debouncedKeyword, status },
|
||||||
});
|
});
|
||||||
setCategories(response.data.categories);
|
setCategories(response.data.categories);
|
||||||
setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total }));
|
setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total }));
|
||||||
@@ -51,7 +53,7 @@ function CategoryManagement() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
}, [keyword, status]);
|
}, [debouncedKeyword, status]);
|
||||||
|
|
||||||
const showModal = (category = null) => {
|
const showModal = (category = null) => {
|
||||||
setEditingCategory(category);
|
setEditingCategory(category);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||||
|
import { useDebounce } from '../hooks/useDebounce';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
Button,
|
Button,
|
||||||
@@ -45,6 +46,7 @@ function ConsumableManagement() {
|
|||||||
showTotal: total => `共 ${total} 条记录`,
|
showTotal: total => `共 ${total} 条记录`,
|
||||||
});
|
});
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
|
const debouncedKeyword = useDebounce(keyword, 300);
|
||||||
const [category, setCategory] = useState('all');
|
const [category, setCategory] = useState('all');
|
||||||
const [status, setStatus] = useState('all');
|
const [status, setStatus] = useState('all');
|
||||||
const [importModalVisible, setImportModalVisible] = useState(false);
|
const [importModalVisible, setImportModalVisible] = useState(false);
|
||||||
@@ -65,7 +67,7 @@ function ConsumableManagement() {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await axios.get('/api/consumables', {
|
const response = await axios.get('/api/consumables', {
|
||||||
params: { page, pageSize, keyword, category, status },
|
params: { page, pageSize, keyword: debouncedKeyword, category, status },
|
||||||
});
|
});
|
||||||
setConsumables(response.data.consumables);
|
setConsumables(response.data.consumables);
|
||||||
setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total }));
|
setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total }));
|
||||||
@@ -76,7 +78,7 @@ function ConsumableManagement() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[keyword, category, status]
|
[debouncedKeyword, category, status]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchCategories = useCallback(async () => {
|
const fetchCategories = useCallback(async () => {
|
||||||
|
|||||||
@@ -1433,7 +1433,7 @@ function DeviceManagement() {
|
|||||||
value={status}
|
value={status}
|
||||||
onChange={setStatus}
|
onChange={setStatus}
|
||||||
style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }}
|
style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }}
|
||||||
dropdownStyle={{ borderRadius: designTokens.borderRadius.medium }}
|
styles={{ popup: { root: { borderRadius: designTokens.borderRadius.medium } } }}
|
||||||
>
|
>
|
||||||
<Option value="all">所有状态</Option>
|
<Option value="all">所有状态</Option>
|
||||||
<Option value="running">运行中</Option>
|
<Option value="running">运行中</Option>
|
||||||
@@ -1448,7 +1448,7 @@ function DeviceManagement() {
|
|||||||
value={type}
|
value={type}
|
||||||
onChange={setType}
|
onChange={setType}
|
||||||
style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }}
|
style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }}
|
||||||
dropdownStyle={{ borderRadius: designTokens.borderRadius.medium }}
|
styles={{ popup: { root: { borderRadius: designTokens.borderRadius.medium } } }}
|
||||||
>
|
>
|
||||||
<Option value="all">所有类型</Option>
|
<Option value="all">所有类型</Option>
|
||||||
<Option value="server">服务器</Option>
|
<Option value="server">服务器</Option>
|
||||||
@@ -1547,7 +1547,7 @@ function DeviceManagement() {
|
|||||||
style: { marginTop: '16px' },
|
style: { marginTop: '16px' },
|
||||||
}}
|
}}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
scroll={{ y: 'calc(100vh - 380px)', scrollToFirstRowOnChange: true }}
|
scroll={{ y: 500, scrollToFirstRowOnChange: true }}
|
||||||
virtual
|
virtual
|
||||||
components={{
|
components={{
|
||||||
header: {
|
header: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||||
|
import { useDebounce } from '../hooks/useDebounce';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
Button,
|
Button,
|
||||||
@@ -340,6 +341,7 @@ function RackManagement() {
|
|||||||
const [selectedRackIds, setSelectedRackIds] = useState([]);
|
const [selectedRackIds, setSelectedRackIds] = useState([]);
|
||||||
const [viewMode, setViewMode] = useState('table');
|
const [viewMode, setViewMode] = useState('table');
|
||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
|
const debouncedSearchKeyword = useDebounce(searchKeyword, 300);
|
||||||
const [statusFilter, setStatusFilter] = useState('all');
|
const [statusFilter, setStatusFilter] = useState('all');
|
||||||
const [roomFilter, setRoomFilter] = useState('all');
|
const [roomFilter, setRoomFilter] = useState('all');
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
@@ -366,7 +368,7 @@ function RackManagement() {
|
|||||||
pageSize,
|
pageSize,
|
||||||
roomId: roomFilter,
|
roomId: roomFilter,
|
||||||
status: statusFilter,
|
status: statusFilter,
|
||||||
keyword: searchKeyword || undefined,
|
keyword: debouncedSearchKeyword || undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { racks: data, total } = response.data;
|
const { racks: data, total } = response.data;
|
||||||
@@ -379,7 +381,7 @@ function RackManagement() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[roomFilter, statusFilter, searchKeyword]
|
[roomFilter, statusFilter, debouncedSearchKeyword]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchRooms = useCallback(async () => {
|
const fetchRooms = useCallback(async () => {
|
||||||
@@ -401,7 +403,7 @@ function RackManagement() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPagination(prev => ({ ...prev, current: 1 }));
|
setPagination(prev => ({ ...prev, current: 1 }));
|
||||||
fetchRacks(1, pagination.pageSize);
|
fetchRacks(1, pagination.pageSize);
|
||||||
}, [roomFilter, statusFilter, searchKeyword]);
|
}, [roomFilter, statusFilter, debouncedSearchKeyword]);
|
||||||
|
|
||||||
const handleTableChange = useCallback(
|
const handleTableChange = useCallback(
|
||||||
pagination => {
|
pagination => {
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ function RoomManagement() {
|
|||||||
value={statusFilter}
|
value={statusFilter}
|
||||||
onChange={setStatusFilter}
|
onChange={setStatusFilter}
|
||||||
style={{ width: '140px', height: '42px' }}
|
style={{ width: '140px', height: '42px' }}
|
||||||
dropdownStyle={{ borderRadius: '10px' }}
|
styles={{ popup: { root: { borderRadius: '10px' } } }}
|
||||||
>
|
>
|
||||||
<Option value="all">全部状态</Option>
|
<Option value="all">全部状态</Option>
|
||||||
<Option value="active">在用</Option>
|
<Option value="active">在用</Option>
|
||||||
|
|||||||
Reference in New Issue
Block a user