init
This commit is contained in:
36
frontend/package-lock.json
generated
36
frontend/package-lock.json
generated
@@ -12,7 +12,8 @@
|
||||
"antd": "^5.12.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^7.6.3"
|
||||
"react-router-dom": "^7.6.3",
|
||||
"zustand": "^5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.43",
|
||||
@@ -1583,14 +1584,14 @@
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.23",
|
||||
"resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.23.tgz",
|
||||
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -4487,6 +4488,35 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.6.tgz",
|
||||
"integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=18.0.0",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=18.0.0",
|
||||
"use-sync-external-store": ">=1.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"use-sync-external-store": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"antd": "^5.12.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^7.6.3"
|
||||
"react-router-dom": "^7.6.3",
|
||||
"zustand": "^5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.43",
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import React, {useEffect, useState, useRef} from 'react'
|
||||
import {Button, Card, Layout, message, Select, Spin, Table} from 'antd'
|
||||
import {DownloadOutlined, PlayCircleOutlined, SettingOutlined, StopOutlined} from '@ant-design/icons'
|
||||
import '../App.css'
|
||||
import {ExportData, GetCapturedData,GetNetworkInterfaces,
|
||||
ParseData,StartCapture,StopCapture, ReadRawJsonFile
|
||||
} from "../../wailsjs/go/service/App";
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {Button, Card, Layout, message, Select, Spin, Table} from 'antd';
|
||||
import {DownloadOutlined, PlayCircleOutlined, SettingOutlined, StopOutlined} from '@ant-design/icons';
|
||||
import '../App.css';
|
||||
import {
|
||||
ExportData,
|
||||
GetCapturedData,
|
||||
GetNetworkInterfaces,
|
||||
ParseData,
|
||||
ReadRawJsonFile,
|
||||
StartCapture,
|
||||
StopCapture
|
||||
} from '../../wailsjs/go/service/App';
|
||||
import {useCaptureStore} from '../store/useCaptureStore';
|
||||
|
||||
const {Header, Content, Sider} = Layout
|
||||
const { Header, Content, Sider } = Layout;
|
||||
|
||||
interface NetworkInterface {
|
||||
name: string
|
||||
@@ -35,15 +42,25 @@ interface CaptureResult {
|
||||
}
|
||||
|
||||
function CapturePage() {
|
||||
const [interfaces, setInterfaces] = useState<NetworkInterface[]>([])
|
||||
const [selectedInterface, setSelectedInterface] = useState<string>('')
|
||||
const [isCapturing, setIsCapturing] = useState(false)
|
||||
const [capturedData, setCapturedData] = useState<string[]>([])
|
||||
const [parsedData, setParsedData] = useState<CaptureResult | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [interfaceLoading, setInterfaceLoading] = useState(false)
|
||||
const [uploadedFileName, setUploadedFileName] = useState<string>('')
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
// 只用全局 parsedData
|
||||
const parsedData = useCaptureStore(state => state.parsedData);
|
||||
const setParsedData = useCaptureStore(state => state.setParsedData);
|
||||
|
||||
// 其余状态全部本地 useState
|
||||
const [interfaces, setInterfaces] = useState<NetworkInterface[]>([]);
|
||||
const [selectedInterface, setSelectedInterface] = useState('');
|
||||
const [isCapturing, setIsCapturing] = useState(false);
|
||||
const [capturedData, setCapturedData] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [interfaceLoading, setInterfaceLoading] = useState(false);
|
||||
const [uploadedFileName, setUploadedFileName] = useState('');
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// 顶部 message 只显示一次
|
||||
const showMessage = (type: 'success' | 'error' | 'warning', content: string) => {
|
||||
message.destroy();
|
||||
message[type](content);
|
||||
};
|
||||
|
||||
const safeApiCall = async <T,>(
|
||||
apiCall: () => Promise<T>,
|
||||
@@ -51,20 +68,20 @@ function CapturePage() {
|
||||
fallbackValue: T
|
||||
): Promise<T> => {
|
||||
try {
|
||||
return await apiCall()
|
||||
return await apiCall();
|
||||
} catch (error) {
|
||||
console.error(`${errorMessage}:`, error)
|
||||
message.error(errorMessage)
|
||||
return fallbackValue
|
||||
}
|
||||
console.error(`${errorMessage}:`, error);
|
||||
showMessage('error', errorMessage);
|
||||
return fallbackValue;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchInterfaces = async () => {
|
||||
setIsCapturing(false)
|
||||
setCapturedData([])
|
||||
setParsedData(null)
|
||||
setLoading(false)
|
||||
setInterfaceLoading(true)
|
||||
setIsCapturing(false);
|
||||
setCapturedData([]);
|
||||
setParsedData(null);
|
||||
setLoading(false);
|
||||
setInterfaceLoading(true);
|
||||
try {
|
||||
const response = await safeApiCall(
|
||||
() => GetNetworkInterfaces(),
|
||||
@@ -74,85 +91,85 @@ function CapturePage() {
|
||||
{ name: 'wlan0', description: 'Wireless', addresses: ['192.168.1.101'], is_loopback: false },
|
||||
{ name: 'lo', description: 'Loopback', addresses: ['127.0.0.1'], is_loopback: true }
|
||||
]
|
||||
)
|
||||
setInterfaces(response)
|
||||
let defaultSelected = ''
|
||||
);
|
||||
setInterfaces(response);
|
||||
let defaultSelected = '';
|
||||
for (const iface of response) {
|
||||
if (iface.addresses && iface.addresses.some(ip => ip.includes('192.168'))) {
|
||||
defaultSelected = iface.name
|
||||
break
|
||||
defaultSelected = iface.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!defaultSelected && response.length > 0) {
|
||||
defaultSelected = response[0].name
|
||||
defaultSelected = response[0].name;
|
||||
}
|
||||
setSelectedInterface(defaultSelected)
|
||||
setSelectedInterface(defaultSelected);
|
||||
} catch (error) {
|
||||
console.error('获取网络接口时发生未知错误:', error)
|
||||
console.error('获取网络接口时发生未知错误:', error);
|
||||
const defaultInterfaces = [
|
||||
{ name: 'eth0', description: 'Ethernet', addresses: ['192.168.1.100'], is_loopback: false },
|
||||
{ name: 'wlan0', description: 'Wireless', addresses: ['192.168.1.101'], is_loopback: false },
|
||||
{ name: 'lo', description: 'Loopback', addresses: ['127.0.0.1'], is_loopback: true }
|
||||
]
|
||||
setInterfaces(defaultInterfaces)
|
||||
setSelectedInterface(defaultInterfaces[0].name)
|
||||
];
|
||||
setInterfaces(defaultInterfaces);
|
||||
setSelectedInterface(defaultInterfaces[0].name);
|
||||
} finally {
|
||||
setInterfaceLoading(false)
|
||||
}
|
||||
setInterfaceLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const startCapture = async () => {
|
||||
if (!selectedInterface) {
|
||||
message.warning('请选择网络接口')
|
||||
return
|
||||
showMessage('warning', '请选择网络接口');
|
||||
return;
|
||||
}
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
try {
|
||||
await safeApiCall(
|
||||
() => StartCapture(selectedInterface),
|
||||
'开始抓包失败,但界面将继续工作',
|
||||
undefined
|
||||
)
|
||||
setIsCapturing(true)
|
||||
message.success('开始抓包')
|
||||
);
|
||||
setIsCapturing(true);
|
||||
showMessage('success', '开始抓包');
|
||||
} catch (error) {
|
||||
console.error('开始抓包时发生未知错误:', error)
|
||||
setIsCapturing(true)
|
||||
message.success('开始抓包(模拟模式)')
|
||||
console.error('开始抓包时发生未知错误:', error);
|
||||
setIsCapturing(true);
|
||||
showMessage('success', '开始抓包(模拟模式)');
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const stopCapture = async () => {
|
||||
setLoading(false)
|
||||
setIsCapturing(false)
|
||||
setCapturedData([])
|
||||
setParsedData(null)
|
||||
setLoading(false);
|
||||
setIsCapturing(false);
|
||||
setCapturedData([]);
|
||||
setParsedData(null);
|
||||
try {
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
await safeApiCall(
|
||||
() => StopCapture(),
|
||||
'停止抓包失败,但界面将继续工作',
|
||||
undefined
|
||||
)
|
||||
setIsCapturing(false)
|
||||
);
|
||||
setIsCapturing(false);
|
||||
const data = await safeApiCall(
|
||||
() => GetCapturedData(),
|
||||
'获取抓包数据失败,使用模拟数据',
|
||||
['mock_data_1', 'mock_data_2', 'mock_data_3']
|
||||
)
|
||||
setCapturedData(data)
|
||||
);
|
||||
setCapturedData(data);
|
||||
if (data && data.length > 0) {
|
||||
data.forEach((item, idx) => {
|
||||
let hexStr = ''
|
||||
let hexStr = '';
|
||||
if (/^[0-9a-fA-F\s]+$/.test(item)) {
|
||||
hexStr = item
|
||||
hexStr = item;
|
||||
} else {
|
||||
hexStr = Array.from(item).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ')
|
||||
hexStr = Array.from(item).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ');
|
||||
}
|
||||
console.log(`抓包数据[${idx}]:`, hexStr)
|
||||
})
|
||||
console.log(`抓包数据[${idx}]:`, hexStr);
|
||||
});
|
||||
}
|
||||
if (data.length > 0) {
|
||||
const parsed = await safeApiCall(
|
||||
@@ -166,13 +183,13 @@ function CapturePage() {
|
||||
],
|
||||
heroes: []
|
||||
})
|
||||
)
|
||||
);
|
||||
try {
|
||||
const parsedData = JSON.parse(parsed)
|
||||
setParsedData(parsedData)
|
||||
message.success('数据处理完成')
|
||||
const parsedData = JSON.parse(parsed);
|
||||
setParsedData(parsedData);
|
||||
showMessage('success', '数据处理完成');
|
||||
} catch (parseError) {
|
||||
console.error('解析JSON失败:', parseError)
|
||||
console.error('解析JSON失败:', parseError);
|
||||
setParsedData({
|
||||
items: [
|
||||
{ id: '1', code: 'SWORD001', ct: 100, e: 1500, g: 5, l: false, mg: 10, op: [], p: 25, s: 'Legendary', sk: 3 },
|
||||
@@ -180,110 +197,109 @@ function CapturePage() {
|
||||
{ id: '3', code: 'HELMET003', ct: 60, e: 800, g: 3, l: false, mg: 8, op: [], p: 12, s: 'Common', sk: 1 }
|
||||
],
|
||||
heroes: []
|
||||
})
|
||||
message.success('数据处理完成(使用模拟数据)')
|
||||
});
|
||||
showMessage('success', '数据处理完成(使用模拟数据)');
|
||||
}
|
||||
} else {
|
||||
message.warning('未捕获到数据')
|
||||
showMessage('warning', '未捕获到数据');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('停止抓包时发生未知错误:', error)
|
||||
setIsCapturing(false)
|
||||
setCapturedData([])
|
||||
setParsedData(null)
|
||||
setLoading(false)
|
||||
message.error('抓包失败,已重置状态')
|
||||
return
|
||||
console.error('停止抓包时发生未知错误:', error);
|
||||
setIsCapturing(false);
|
||||
setCapturedData([]);
|
||||
setParsedData(null);
|
||||
setLoading(false);
|
||||
showMessage('error', '抓包失败,已重置状态');
|
||||
return;
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const exportData = async () => {
|
||||
if (!capturedData.length) {
|
||||
message.warning('没有数据可导出')
|
||||
return
|
||||
showMessage('warning', '没有数据可导出');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const filename = `equipment_data_${Date.now()}.json`
|
||||
const filename = `equipment_data_${Date.now()}.json`;
|
||||
await safeApiCall(
|
||||
() => ExportData(capturedData, filename),
|
||||
'导出数据失败',
|
||||
undefined
|
||||
)
|
||||
message.success('数据导出成功')
|
||||
);
|
||||
showMessage('success', '数据导出成功');
|
||||
} catch (error) {
|
||||
console.error('导出数据时发生未知错误:', error)
|
||||
message.success('数据导出成功(模拟模式)')
|
||||
}
|
||||
console.error('导出数据时发生未知错误:', error);
|
||||
showMessage('success', '数据导出成功(模拟模式)');
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
setUploadedFileName(file.name)
|
||||
const reader = new FileReader()
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
setUploadedFileName(file.name);
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const text = e.target?.result as string
|
||||
const json = JSON.parse(text)
|
||||
const text = e.target?.result as string;
|
||||
const json = JSON.parse(text);
|
||||
const safeData = {
|
||||
items: Array.isArray(json.items) ? json.items : [],
|
||||
heroes: Array.isArray(json.heroes) ? json.heroes : []
|
||||
}
|
||||
setParsedData(safeData)
|
||||
};
|
||||
setParsedData(safeData);
|
||||
if (safeData.items.length === 0 && safeData.heroes.length === 0) {
|
||||
message.warning('上传文件数据为空,请检查文件内容')
|
||||
showMessage('warning', '上传文件数据为空,请检查文件内容');
|
||||
} else {
|
||||
message.success(`文件解析成功:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`)
|
||||
showMessage('success', `文件解析成功:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('文件格式错误,无法解析:', err)
|
||||
message.error('文件格式错误,无法解析')
|
||||
setParsedData({ items: [], heroes: [] })
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
console.error('文件格式错误,无法解析:', err);
|
||||
showMessage('error', '文件格式错误,无法解析');
|
||||
setParsedData({ items: [], heroes: [] });
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
const fetchParsedDataFromBackend = async () => {
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
try {
|
||||
const raw = await ReadRawJsonFile()
|
||||
const json = JSON.parse(raw)
|
||||
console.log('已加载本地解析数据:', json)
|
||||
const raw = await ReadRawJsonFile();
|
||||
const json = JSON.parse(raw);
|
||||
console.log('已加载本地解析数据:', json);
|
||||
const safeData = {
|
||||
items: Array.isArray(json.items) ? json.items : [],
|
||||
heroes: Array.isArray(json.heroes) ? json.heroes : []
|
||||
}
|
||||
setParsedData(safeData)
|
||||
};
|
||||
setParsedData(safeData);
|
||||
if (safeData.items.length === 0 && safeData.heroes.length === 0) {
|
||||
message.warning('解析数据为空,请检查数据源')
|
||||
showMessage('warning', '解析数据为空,请检查数据源');
|
||||
} else {
|
||||
message.success(`已加载本地解析数据:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`)
|
||||
showMessage('success', `已加载本地解析数据:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('读取本地解析数据失败:', err)
|
||||
message.error('读取本地解析数据失败')
|
||||
setParsedData({ items: [], heroes: [] })
|
||||
console.error('读取本地解析数据失败:', err);
|
||||
showMessage('error', '读取本地解析数据失败');
|
||||
setParsedData({ items: [], heroes: [] });
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefreshParsedData = () => {
|
||||
setParsedData(null)
|
||||
setUploadedFileName('')
|
||||
fetchParsedDataFromBackend()
|
||||
}
|
||||
setParsedData(null);
|
||||
setUploadedFileName('');
|
||||
fetchParsedDataFromBackend();
|
||||
};
|
||||
|
||||
const handleUploadButtonClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchInterfaces();
|
||||
fetchParsedDataFromBackend();
|
||||
}, []);
|
||||
|
||||
const equipmentColumns = [
|
||||
@@ -328,22 +344,22 @@ function CapturePage() {
|
||||
dataIndex: 'sk',
|
||||
key: 'sk',
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Header style={{background: '#fff', padding: '0 20px'}}>
|
||||
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
|
||||
<h1 style={{margin: 0}}>装备数据导出工具</h1>
|
||||
<Header style={{ background: '#fff', padding: '0 16px', height: 48, lineHeight: '48px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', height: 48 }}>
|
||||
<h1 style={{ margin: 0, fontSize: 20 }}>装备数据导出工具</h1>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={handleRefreshParsedData}
|
||||
loading={loading}
|
||||
style={{flex: 1}}
|
||||
style={{ flex: 1, height: 32 }}
|
||||
>刷新数据</Button>
|
||||
<Button style={{flex: 1}} onClick={handleUploadButtonClick}>
|
||||
<Button style={{ flex: 1, height: 32 }} onClick={handleUploadButtonClick}>
|
||||
上传JSON
|
||||
</Button>
|
||||
<input
|
||||
@@ -361,21 +377,21 @@ function CapturePage() {
|
||||
</Header>
|
||||
|
||||
<Layout>
|
||||
<Sider width={300} style={{background: '#fff'}}>
|
||||
<div style={{padding: '20px'}}>
|
||||
<Sider width={220} style={{ background: '#fff' }}>
|
||||
<div style={{ padding: '12px' }}>
|
||||
<Card title="抓包控制" size="small">
|
||||
<div style={{marginBottom: 16}}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<label>网络接口:</label>
|
||||
<Select
|
||||
style={{width: '100%', marginTop: 8}}
|
||||
style={{ width: '100%', marginTop: 6 }}
|
||||
value={selectedInterface}
|
||||
onChange={setSelectedInterface}
|
||||
placeholder="选择网络接口"
|
||||
loading={interfaceLoading}
|
||||
>
|
||||
{interfaces.map(iface => (
|
||||
{interfaces.map((iface) => (
|
||||
<Select.Option key={iface.name} value={iface.name}>
|
||||
{iface.addresses}
|
||||
{iface.addresses.join(', ')}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
@@ -388,7 +404,7 @@ function CapturePage() {
|
||||
onClick={startCapture}
|
||||
disabled={isCapturing || !selectedInterface}
|
||||
loading={loading}
|
||||
style={{flex: 1}}
|
||||
style={{ flex: 1, height: 32 }}
|
||||
>
|
||||
开始抓包
|
||||
</Button>
|
||||
@@ -399,36 +415,36 @@ function CapturePage() {
|
||||
onClick={stopCapture}
|
||||
disabled={!isCapturing}
|
||||
loading={loading}
|
||||
style={{flex: 1}}
|
||||
style={{ flex: 1, height: 32 }}
|
||||
>
|
||||
停止抓包
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{marginTop: 16}}>
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<Button
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={exportData}
|
||||
disabled={!capturedData.length}
|
||||
style={{width: '100%'}}>
|
||||
style={{ width: '100%', height: 32 }}>
|
||||
导出数据
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="抓包状态" size="small" style={{marginTop: 16}}>
|
||||
<Card title="抓包状态" size="small" style={{ marginTop: 12 }}>
|
||||
<div>
|
||||
<p>状态: {isCapturing ? '正在抓包...' : '准备就绪'}</p>
|
||||
<p>捕获数据: {capturedData.length} 条</p>
|
||||
<p style={{ marginBottom: 4 }}>状态: {isCapturing ? '正在抓包...' : '准备就绪'}</p>
|
||||
<p style={{ marginBottom: 4 }}>捕获数据: {capturedData.length} 条</p>
|
||||
{parsedData && (
|
||||
<p>解析装备: {parsedData.items.length} 件</p>
|
||||
<p style={{ marginBottom: 0 }}>解析装备: {parsedData.items.length} 件</p>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Sider>
|
||||
|
||||
<Content style={{padding: '20px'}}>
|
||||
<Content style={{ padding: '16px' }}>
|
||||
<Spin spinning={loading}>
|
||||
{parsedData && parsedData.items.length > 0 ? (
|
||||
<Card title="装备数据">
|
||||
@@ -454,7 +470,7 @@ function CapturePage() {
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default CapturePage
|
||||
export default CapturePage;
|
||||
Reference in New Issue
Block a user