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