feat(database): add gearTxt field to parsed results and update related functions
This commit is contained in:
@@ -3,6 +3,7 @@ import {Button, Card, Layout, message, Spin, Table} from 'antd';
|
||||
import {DownloadOutlined, PlayCircleOutlined, StopOutlined, UploadOutlined} from '@ant-design/icons';
|
||||
import '../App.css';
|
||||
import {
|
||||
ParseAndSaveRawJson,
|
||||
ReadRawJsonFile,
|
||||
SaveParsedDataToDatabase,
|
||||
StartCaptureWithFilter,
|
||||
@@ -239,40 +240,22 @@ function CapturePage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json.heroes || !Array.isArray(json.heroes)) {
|
||||
showMessage('error', '数据格式错误:缺少heroes数组');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json.items || !Array.isArray(json.items)) {
|
||||
showMessage('error', '数据格式错误:缺少items数组');
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.heroes.length === 0) {
|
||||
showMessage('error', '数据格式错误:heroes数组不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.items.length === 0) {
|
||||
showMessage('error', '数据格式错误:items数组不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionName = `import_${Date.now()}`;
|
||||
const equipmentJSON = JSON.stringify(json.items);
|
||||
const heroesJSON = JSON.stringify(json.heroes);
|
||||
|
||||
await SaveParsedDataToDatabase(sessionName, equipmentJSON, heroesJSON);
|
||||
const gearTxt = typeof text === 'string' ? text : JSON.stringify(json);
|
||||
const parsed = await ParseAndSaveRawJson(sessionName, gearTxt);
|
||||
|
||||
const safeData = {
|
||||
items: json.items,
|
||||
heroes: json.heroes
|
||||
items: parsed?.items || [],
|
||||
heroes: parsed?.heroes || []
|
||||
};
|
||||
setParsedData(safeData);
|
||||
setUploadedFileName('');
|
||||
|
||||
showMessage('success', `导入成功:${json.items.length}件装备,${json.heroes.length}个英雄`);
|
||||
if (safeData.items.length === 0 || safeData.heroes.length === 0) {
|
||||
showMessage('warning', `导入完成,但解析为空:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`);
|
||||
} else {
|
||||
showMessage('success', `导入成功:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('文件处理错误:', err);
|
||||
if (err instanceof SyntaxError) {
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Card, Col, Input, Layout, Modal, Row, Select, Space, Statistic, Table, Tag} from 'antd';
|
||||
import {BarChartOutlined, DatabaseOutlined, ReloadOutlined, SettingOutlined,} from '@ant-design/icons';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Card, Col, Input, Layout, Modal, Row, Select, Space, Statistic, Table } from 'antd';
|
||||
import { BarChartOutlined, DatabaseOutlined, DownloadOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import * as App from '../../wailsjs/go/service/App';
|
||||
import {model} from '../../wailsjs/go/models';
|
||||
import {useMessage} from '../utils/useMessage';
|
||||
import { model } from '../../wailsjs/go/models';
|
||||
import { useMessage } from '../utils/useMessage';
|
||||
|
||||
const { Content } = Layout;
|
||||
|
||||
// 定义Equipment接口
|
||||
// 定义 Equipment 接口
|
||||
interface Equipment {
|
||||
id: string | number;
|
||||
code: string;
|
||||
ct: number;
|
||||
e: number;
|
||||
g: number;
|
||||
l: boolean;
|
||||
mg: number;
|
||||
op: any[];
|
||||
p: number;
|
||||
s: string;
|
||||
sk: number;
|
||||
id?: string | number;
|
||||
code?: string;
|
||||
set?: string;
|
||||
level?: number;
|
||||
enhance?: number;
|
||||
gear?: string;
|
||||
main?: { type?: string; value?: number };
|
||||
substats?: Array<{ type?: string; value?: number }>;
|
||||
ingameEquippedId?: string;
|
||||
p?: string | number;
|
||||
}
|
||||
|
||||
const DatabasePage: React.FC = () => {
|
||||
@@ -29,8 +28,123 @@ const DatabasePage: React.FC = () => {
|
||||
const [selectedSessionId, setSelectedSessionId] = useState<number | null>(null);
|
||||
const [renameOpen, setRenameOpen] = useState(false);
|
||||
const [renameValue, setRenameValue] = useState('');
|
||||
const [setFilter, setSetFilter] = useState<string | null>(null);
|
||||
const [gearFilter, setGearFilter] = useState<string | null>(null);
|
||||
const [mainStatFilter, setMainStatFilter] = useState<string | null>(null);
|
||||
const { success, error, info } = useMessage();
|
||||
|
||||
const SET_LABELS: Record<string, string> = {
|
||||
AttackSet: '攻击套装',
|
||||
DefenseSet: '防御套装',
|
||||
HealthSet: '生命值套装',
|
||||
SpeedSet: '速度套装',
|
||||
CriticalSet: '暴击套装',
|
||||
DestructionSet: '破灭套装',
|
||||
HitSet: '命中套装',
|
||||
ResistSet: '抵抗套装',
|
||||
LifestealSet: '吸血套装',
|
||||
CounterSet: '反击套装',
|
||||
ImmunitySet: '免疫套装',
|
||||
PenetrationSet: '穿透套装',
|
||||
InjurySet: '伤口套装',
|
||||
ProtectionSet: '守护套装',
|
||||
TorrentSet: '激流套装',
|
||||
ReversalSet: '逆袭套装',
|
||||
RiposteSet: '回击套装',
|
||||
WarfareSet: '开战套装',
|
||||
PursuitSet: '追击套装',
|
||||
RageSet: '愤怒套装',
|
||||
RevengeSet: '憎恨套装',
|
||||
UnitySet: '夹攻套装',
|
||||
};
|
||||
|
||||
const GEAR_LABELS: Record<string, string> = {
|
||||
Weapon: '武器',
|
||||
Helmet: '头盔',
|
||||
Armor: '铠甲',
|
||||
Necklace: '项链',
|
||||
Ring: '戒指',
|
||||
Boots: '鞋子',
|
||||
};
|
||||
|
||||
const STAT_LABELS: Record<string, string> = {
|
||||
Attack: '攻击力',
|
||||
Defense: '防御力',
|
||||
Health: '生命值',
|
||||
Speed: '速度',
|
||||
AttackPercent: '攻击力%',
|
||||
DefensePercent: '防御力%',
|
||||
HealthPercent: '生命值%',
|
||||
CriticalHitChancePercent: '暴击率',
|
||||
CriticalHitDamagePercent: '暴击伤害',
|
||||
EffectivenessPercent: '效果命中',
|
||||
EffectResistancePercent: '效果抗性',
|
||||
};
|
||||
|
||||
const PERCENT_STATS = new Set([
|
||||
'AttackPercent',
|
||||
'DefensePercent',
|
||||
'HealthPercent',
|
||||
'CriticalHitChancePercent',
|
||||
'CriticalHitDamagePercent',
|
||||
'EffectivenessPercent',
|
||||
'EffectResistancePercent',
|
||||
]);
|
||||
|
||||
const formatStat = (stat?: { type?: string; value?: number }) => {
|
||||
if (!stat || !stat.type) return '-';
|
||||
const rawLabel = STAT_LABELS[stat.type] || stat.type;
|
||||
const value = stat.value ?? 0;
|
||||
const rawValue = Number.isFinite(value) ? value : 0;
|
||||
const roundedValue = Math.round(rawValue * 10) / 10;
|
||||
const formattedValue = Number.isInteger(roundedValue) ? roundedValue.toString() : roundedValue.toString();
|
||||
if (PERCENT_STATS.has(stat.type)) {
|
||||
const label = rawLabel.endsWith('%') ? rawLabel.slice(0, -1) : rawLabel;
|
||||
return `${label}${formattedValue}%`;
|
||||
}
|
||||
return `${rawLabel} ${formattedValue}`;
|
||||
};
|
||||
|
||||
const formatSubstats = (subs?: Array<{ type?: string; value?: number }>) => {
|
||||
if (!subs || subs.length === 0) return '-';
|
||||
return subs.slice(0, 4).map(s => formatStat(s)).join(' / ');
|
||||
};
|
||||
|
||||
const heroNameById = React.useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
const heroes = latestData?.heroes || [];
|
||||
heroes.forEach((h: any) => {
|
||||
const id = h?.id ?? h?.ingameId ?? h?.code;
|
||||
if (id !== undefined && id !== null) {
|
||||
const key = String(id);
|
||||
map.set(key, h?.name || h?.code || key);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}, [latestData?.heroes]);
|
||||
|
||||
const getEquippedHeroName = (item: Equipment) => {
|
||||
const key = item.ingameEquippedId ?? item.p;
|
||||
if (key === undefined || key === null || key === '') return '-';
|
||||
return heroNameById.get(String(key)) || String(key);
|
||||
};
|
||||
|
||||
const filteredItems = React.useMemo(() => {
|
||||
const items = latestData?.items || [];
|
||||
return items.filter((item: Equipment) => {
|
||||
if (setFilter) {
|
||||
if ((item.set || '') !== setFilter) return false;
|
||||
}
|
||||
if (gearFilter) {
|
||||
if ((item.gear || '') !== gearFilter) return false;
|
||||
}
|
||||
if (mainStatFilter) {
|
||||
if ((item.main?.type || '') !== mainStatFilter) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [latestData?.items, setFilter, gearFilter, mainStatFilter]);
|
||||
|
||||
const formatSessionLabel = (session: model.ParsedSession) => {
|
||||
const date = session.created_at ? new Date(session.created_at * 1000) : null;
|
||||
const timeText = date ? date.toLocaleString() : '';
|
||||
@@ -88,9 +202,33 @@ const DatabasePage: React.FC = () => {
|
||||
loadSessions();
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
if (!latestData?.geartxt) {
|
||||
info('没有数据可导出');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const exportContent = latestData.geartxt;
|
||||
const blob = new Blob([exportContent], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'gear.txt';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
success('数据导出成功');
|
||||
} catch (err) {
|
||||
console.error('导出数据出错:', err);
|
||||
error('数据导出失败');
|
||||
}
|
||||
};
|
||||
|
||||
const openRename = () => {
|
||||
if (!selectedSessionId) {
|
||||
info('请先选择一条解析数据');
|
||||
info('请先选择一条装备记录');
|
||||
return;
|
||||
}
|
||||
const current = sessions.find(s => s.id === selectedSessionId);
|
||||
@@ -124,7 +262,7 @@ const DatabasePage: React.FC = () => {
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!selectedSessionId) {
|
||||
info('请先选择一条解析数据');
|
||||
info('请先选择一条装备记录');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
@@ -147,7 +285,7 @@ const DatabasePage: React.FC = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -158,67 +296,46 @@ const DatabasePage: React.FC = () => {
|
||||
// 装备表格列定义
|
||||
const equipmentColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
render: (id: any) => {
|
||||
const idStr = String(id || '');
|
||||
return <span style={{ fontSize: '12px' }}>{idStr.length > 8 ? `${idStr.slice(0, 8)}...` : idStr}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '代码',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
title: '套装类型',
|
||||
dataIndex: 'set',
|
||||
key: 'set',
|
||||
width: 120,
|
||||
render: (_: any, record: Equipment) => SET_LABELS[record.set || ''] || record.set || '-',
|
||||
},
|
||||
{
|
||||
title: '等级',
|
||||
dataIndex: 'g',
|
||||
key: 'g',
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
width: 80,
|
||||
render: (grade: number) => (
|
||||
<Tag color={grade >= 5 ? 'red' : grade >= 3 ? 'orange' : 'green'}>
|
||||
{grade}
|
||||
</Tag>
|
||||
),
|
||||
render: (value: number) => (value ?? '-'),
|
||||
},
|
||||
{
|
||||
title: '经验',
|
||||
dataIndex: 'e',
|
||||
key: 'e',
|
||||
title: '强化等级',
|
||||
dataIndex: 'enhance',
|
||||
key: 'enhance',
|
||||
width: 90,
|
||||
render: (value: number) => (value === undefined || value === null ? '-' : `+${value}`),
|
||||
},
|
||||
{
|
||||
title: '部位',
|
||||
dataIndex: 'gear',
|
||||
key: 'gear',
|
||||
width: 100,
|
||||
render: (exp: number) => exp.toLocaleString(),
|
||||
render: (gear: string) => GEAR_LABELS[gear] || gear || '-',
|
||||
},
|
||||
{
|
||||
title: '力量',
|
||||
dataIndex: 'p',
|
||||
key: 'p',
|
||||
width: 80,
|
||||
title: '主属性',
|
||||
dataIndex: 'main',
|
||||
key: 'main',
|
||||
width: 180,
|
||||
render: (_: any, record: Equipment) => formatStat(record.main),
|
||||
},
|
||||
{
|
||||
title: '魔法',
|
||||
dataIndex: 'mg',
|
||||
key: 'mg',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '技能',
|
||||
dataIndex: 'sk',
|
||||
key: 'sk',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'l',
|
||||
key: 'l',
|
||||
width: 80,
|
||||
render: (locked: boolean) => (
|
||||
<Tag color={locked ? 'red' : 'green'}>
|
||||
{locked ? '锁定' : '正常'}
|
||||
</Tag>
|
||||
),
|
||||
title: '副属性',
|
||||
dataIndex: 'substats',
|
||||
key: 'substats',
|
||||
width: 320,
|
||||
render: (_: any, record: Equipment) => formatSubstats(record.substats),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -268,6 +385,7 @@ const DatabasePage: React.FC = () => {
|
||||
>
|
||||
刷新数据
|
||||
</Button>
|
||||
<Button icon={<DownloadOutlined />} onClick={exportData} disabled={!latestData?.geartxt}>导出数据</Button>
|
||||
<Select
|
||||
style={{ minWidth: 320 }}
|
||||
placeholder="请选择解析数据"
|
||||
@@ -287,11 +405,57 @@ const DatabasePage: React.FC = () => {
|
||||
</Card>
|
||||
|
||||
{/* 装备表格 */}
|
||||
<Card title="解析的装备数据">
|
||||
<Card
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<span>解析的装备数据</span>
|
||||
<Space>
|
||||
<span>套装类型</span>
|
||||
<Select
|
||||
style={{ minWidth: 180 }}
|
||||
placeholder="全部"
|
||||
value={setFilter ?? undefined}
|
||||
allowClear
|
||||
onChange={value => setSetFilter(value ?? null)}
|
||||
options={Object.keys(SET_LABELS).map(key => ({
|
||||
value: key,
|
||||
label: SET_LABELS[key],
|
||||
}))}
|
||||
/>
|
||||
<span>部位</span>
|
||||
<Select
|
||||
style={{ minWidth: 140 }}
|
||||
placeholder="全部"
|
||||
value={gearFilter ?? undefined}
|
||||
allowClear
|
||||
onChange={value => setGearFilter(value ?? null)}
|
||||
options={Object.keys(GEAR_LABELS).map(key => ({
|
||||
value: key,
|
||||
label: GEAR_LABELS[key],
|
||||
}))}
|
||||
/>
|
||||
<span>主属性</span>
|
||||
<Select
|
||||
style={{ minWidth: 160 }}
|
||||
placeholder="全部"
|
||||
value={mainStatFilter ?? undefined}
|
||||
allowClear
|
||||
onChange={value => setMainStatFilter(value ?? null)}
|
||||
options={Object.keys(STAT_LABELS)
|
||||
.filter(key => !['Attack', 'Defense', 'Health'].includes(key))
|
||||
.map(key => ({
|
||||
value: key,
|
||||
label: STAT_LABELS[key],
|
||||
}))}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{latestData?.items && latestData.items.length > 0 ? (
|
||||
<Table
|
||||
columns={equipmentColumns}
|
||||
dataSource={latestData.items}
|
||||
dataSource={filteredItems}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
@@ -331,4 +495,5 @@ const DatabasePage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default DatabasePage;
|
||||
export default DatabasePage;
|
||||
|
||||
|
||||
@@ -321,6 +321,7 @@ export namespace model {
|
||||
export class ParsedResult {
|
||||
items: any[];
|
||||
heroes: any[];
|
||||
geartxt: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ParsedResult(source);
|
||||
@@ -330,6 +331,7 @@ export namespace model {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.items = source["items"];
|
||||
this.heroes = source["heroes"];
|
||||
this.geartxt = source["geartxt"];
|
||||
}
|
||||
}
|
||||
export class ParsedSession {
|
||||
|
||||
4
frontend/wailsjs/go/service/App.d.ts
vendored
4
frontend/wailsjs/go/service/App.d.ts
vendored
@@ -30,13 +30,15 @@ export function GetParsedSessions():Promise<Array<model.ParsedSession>>;
|
||||
|
||||
export function OptimizeBuilds(arg1:model.OptimizeRequest):Promise<model.OptimizeResponse>;
|
||||
|
||||
export function ParseAndSaveRawJson(arg1:string,arg2:string):Promise<model.ParsedResult>;
|
||||
|
||||
export function ParseData(arg1:Array<string>):Promise<string>;
|
||||
|
||||
export function ReadRawJsonFile():Promise<model.ParsedResult>;
|
||||
|
||||
export function SaveAppSetting(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function SaveParsedDataToDatabase(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
export function SaveParsedDataToDatabase(arg1:string,arg2:string,arg3:string,arg4:string):Promise<void>;
|
||||
|
||||
export function StartCapture(arg1:string):Promise<void>;
|
||||
|
||||
|
||||
@@ -58,6 +58,10 @@ export function OptimizeBuilds(arg1) {
|
||||
return window['go']['service']['App']['OptimizeBuilds'](arg1);
|
||||
}
|
||||
|
||||
export function ParseAndSaveRawJson(arg1, arg2) {
|
||||
return window['go']['service']['App']['ParseAndSaveRawJson'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ParseData(arg1) {
|
||||
return window['go']['service']['App']['ParseData'](arg1);
|
||||
}
|
||||
@@ -70,8 +74,8 @@ export function SaveAppSetting(arg1, arg2) {
|
||||
return window['go']['service']['App']['SaveAppSetting'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SaveParsedDataToDatabase(arg1, arg2, arg3) {
|
||||
return window['go']['service']['App']['SaveParsedDataToDatabase'](arg1, arg2, arg3);
|
||||
export function SaveParsedDataToDatabase(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['service']['App']['SaveParsedDataToDatabase'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function StartCapture(arg1) {
|
||||
|
||||
Reference in New Issue
Block a user