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' } } }}
>