From ef8f3ab5653b8581f079681f5e8733a6423b5ea7 Mon Sep 17 00:00:00 2001 From: zhang1106 <849185023@qq.com> Date: Tue, 3 Mar 2026 10:51:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=AE=BE=E5=A4=87=E6=90=9C=E7=B4=A2):=20?= =?UTF-8?q?=E4=B8=BA=E8=AE=BE=E5=A4=87=E9=80=89=E6=8B=A9=E5=99=A8=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=98=B2=E6=8A=96=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/CableCreateModal.jsx | 77 ++++++++++++++------ frontend/src/pages/CableManagement.jsx | 73 ++++++++++++++----- frontend/src/pages/PortManagement.jsx | 59 +++++++++++---- frontend/src/pages/TicketManagement.jsx | 71 ++++++++++++++++-- 4 files changed, 215 insertions(+), 65 deletions(-) diff --git a/frontend/src/components/CableCreateModal.jsx b/frontend/src/components/CableCreateModal.jsx index 35680e1..055b330 100644 --- a/frontend/src/components/CableCreateModal.jsx +++ b/frontend/src/components/CableCreateModal.jsx @@ -5,6 +5,18 @@ import axios from 'axios'; const { Option } = Select; +const debounce = (func, wait) => { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +}; + const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); @@ -14,6 +26,30 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => { const [fetchingDevices, setFetchingDevices] = useState(false); const prevVisibleRef = useRef(false); + const fetchDevices = useCallback(async (keyword = '') => { + try { + setFetchingDevices(true); + const params = { pageSize: 50 }; + if (keyword && keyword.trim()) { + params.keyword = keyword.trim(); + } + const response = await axios.get('/api/devices', { params }); + setDevices(response.data.devices || []); + } catch (error) { + console.error('Failed to fetch devices:', error); + message.error('获取设备列表失败'); + } finally { + setFetchingDevices(false); + } + }, []); + + const handleDeviceSearch = useCallback( + debounce(value => { + fetchDevices(value); + }, 300), + [fetchDevices] + ); + // Initialize when visible changes from false to true useEffect(() => { if (visible && !prevVisibleRef.current) { @@ -27,20 +63,7 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => { fetchDevices(); } prevVisibleRef.current = visible; - }, [visible, sourceDevice, form]); - - const fetchDevices = async () => { - try { - setFetchingDevices(true); - const response = await axios.get('/api/devices', { params: { pageSize: 1000 } }); - setDevices(response.data.devices || []); - } catch (error) { - console.error('Failed to fetch devices:', error); - message.error('获取设备列表失败'); - } finally { - setFetchingDevices(false); - } - }; + }, [visible, sourceDevice, form, fetchDevices]); const fetchDevicePorts = async (deviceId, type) => { if (!deviceId) { @@ -136,14 +159,18 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => { rules={[{ required: true, message: '请选择源设备' }]} > - (option?.children ?? '').toLowerCase().includes(input.toLowerCase()) - } + filterOption={false} + onSearch={handleDeviceSearch} + onDropdownVisibleChange={open => { + if (open && devices.length === 0) { + fetchDevices(); + } + }} onChange={handleTargetDeviceChange} loading={fetchingDevices} > diff --git a/frontend/src/pages/CableManagement.jsx b/frontend/src/pages/CableManagement.jsx index b646f03..5fa54be 100644 --- a/frontend/src/pages/CableManagement.jsx +++ b/frontend/src/pages/CableManagement.jsx @@ -57,6 +57,18 @@ const { Panel } = Collapse; const { Text, Title } = Typography; const { TextArea } = Input; +const debounce = (func, wait) => { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +}; + // 设计令牌 - 现代化配色方案 const designTokens = { colors: { @@ -171,6 +183,7 @@ const animations = { function CableManagement() { const [cables, setCables] = useState([]); const [devices, setDevices] = useState([]); + const [deviceSearching, setDeviceSearching] = useState(false); const [switchDevices, setSwitchDevices] = useState([]); const [groupedCables, setGroupedCables] = useState({}); const [devicePorts, setDevicePorts] = useState({}); @@ -262,9 +275,14 @@ function CableManagement() { } }, [filters, devicePorts]); - const fetchDevices = useCallback(async () => { + const fetchDevices = useCallback(async (keyword = '') => { try { - const response = await axios.get('/api/devices', { params: { pageSize: 100 } }); + setDeviceSearching(true); + const params = { pageSize: 50 }; + if (keyword && keyword.trim()) { + params.keyword = keyword.trim(); + } + const response = await axios.get('/api/devices', { params }); const allDevices = response.data.devices || []; const switches = allDevices.filter(device => device.type === 'switch'); setDevices(allDevices); @@ -272,9 +290,18 @@ function CableManagement() { } catch (error) { message.error('获取设备列表失败'); console.error('获取设备列表失败:', error); + } finally { + setDeviceSearching(false); } }, []); + const handleDeviceSearch = useCallback( + debounce(value => { + fetchDevices(value); + }, 300), + [fetchDevices] + ); + const fetchDevicePorts = useCallback(async deviceId => { if (!deviceId) { setDevicePorts(prev => ({ ...prev, [deviceId]: [] })); @@ -886,17 +913,19 @@ function CableManagement() { 交换机 { - const device = switchDevices.find(d => d.deviceId === option.value); - if (!device) return false; - const searchText = `${device.name} ${device.deviceId}`.toLowerCase(); - return searchText.indexOf(input.toLowerCase()) >= 0; + loading={deviceSearching} + filterOption={false} + onSearch={handleDeviceSearch} + onDropdownVisibleChange={open => { + if (open && switchDevices.length === 0) { + fetchDevices(); + } }} onChange={value => { fetchDevicePorts(value); @@ -1353,13 +1384,15 @@ function CableManagement() { rules={[{ required: true, message: '请选择目标设备' }]} > setFilters(prev => ({ ...prev, deviceId: value }))} allowClear showSearch - filterOption={(input, option) => { - const device = devices.find(d => d.deviceId === option.value); - if (!device) return false; - const searchText = `${device.name} ${device.deviceId}`.toLowerCase(); - return searchText.indexOf(input.toLowerCase()) >= 0; + loading={deviceSearching} + filterOption={false} + onSearch={handleDeviceSearch} + onDropdownVisibleChange={open => { + if (open && devices.length === 0) { + fetchDevices(); + } }} > {devices.map(device => ( @@ -1222,13 +1251,15 @@ function PortManagement() { rules={[{ required: true, message: '请选择设备' }]} > + @@ -467,7 +509,7 @@ function TicketManagement() { ); }, - [devices] + [devices, deviceSearching, handleDeviceSearch, fetchDevices] ); const tableColumns = useMemo(() => { @@ -766,10 +808,23 @@ function TicketManagement() { if (deviceSource === 'select') { return ( - { + if (open && devices.length === 0) { + fetchDevices(); + } + }} + > {devices.map(device => ( ))} @@ -794,7 +849,7 @@ function TicketManagement() { ); - }, [deviceSource, devices]); + }, [deviceSource, devices, deviceSearching, handleDeviceSearch, fetchDevices]); const renderFormItems = useCallback(() => { const items = [];