1.2.3-hotfix: Fixed data center not showing graphs.
This commit is contained in:
@@ -248,14 +248,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Data Center (Placeholder) -->
|
||||
<div x-show="activeTab === 'data'" class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<div class="text-4xl mb-4">📊</div>
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">数据对比中心 (Construction)</h3>
|
||||
<p class="text-gray-500">此模块正在开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 2. Data Center -->
|
||||
{% include 'tactics/data.html' %}
|
||||
|
||||
<!-- 3. Strategy Board -->
|
||||
<div x-show="activeTab === 'board'" class="h-full flex flex-col">
|
||||
@@ -344,6 +338,15 @@ function tacticsApp() {
|
||||
analysisResult: null,
|
||||
debounceTimer: null,
|
||||
|
||||
// Data Center State
|
||||
dataLineup: [],
|
||||
dataResult: [],
|
||||
searchQuery: '',
|
||||
radarChart: null,
|
||||
allMaps: ['de_mirage', 'de_inferno', 'de_dust2', 'de_nuke', 'de_ancient', 'de_anubis', 'de_vertigo'],
|
||||
mapStatsCache: {},
|
||||
isDraggingOverData: false,
|
||||
|
||||
// Board State
|
||||
currentMap: 'de_mirage',
|
||||
map: null,
|
||||
@@ -372,6 +375,11 @@ function tacticsApp() {
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Watch Data Lineup
|
||||
this.$watch('dataLineup', () => {
|
||||
this.comparePlayers();
|
||||
});
|
||||
|
||||
// Init map on first board view, or delay
|
||||
this.$watch('activeTab', value => {
|
||||
if (value === 'board') {
|
||||
@@ -397,10 +405,226 @@ function tacticsApp() {
|
||||
|
||||
// --- Drag & Drop Generic ---
|
||||
dragStart(event, player) {
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(player));
|
||||
// Only send essential data to avoid circular references with Alpine proxies
|
||||
const payload = {
|
||||
steam_id_64: player.steam_id_64,
|
||||
username: player.username || player.name,
|
||||
name: player.name || player.username,
|
||||
avatar_url: player.avatar_url
|
||||
};
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(payload));
|
||||
event.dataTransfer.effectAllowed = 'copy';
|
||||
},
|
||||
|
||||
// --- Data Center Logic ---
|
||||
searchPlayer() {
|
||||
if (!this.searchQuery) return;
|
||||
const q = this.searchQuery.toLowerCase();
|
||||
const found = this.roster.find(p =>
|
||||
(p.username && p.username.toLowerCase().includes(q)) ||
|
||||
(p.steam_id_64 && p.steam_id_64.includes(q))
|
||||
);
|
||||
if (found) {
|
||||
this.addToDataLineup(found);
|
||||
this.searchQuery = '';
|
||||
} else {
|
||||
alert('未找到玩家 (Locally)');
|
||||
}
|
||||
},
|
||||
|
||||
addToDataLineup(player) {
|
||||
if (this.dataLineup.some(p => p.steam_id_64 === player.steam_id_64)) {
|
||||
alert('该选手已在对比列表中');
|
||||
return;
|
||||
}
|
||||
if (this.dataLineup.length >= 5) {
|
||||
alert('对比列表已满 (最多5人)');
|
||||
return;
|
||||
}
|
||||
this.dataLineup.push(player);
|
||||
},
|
||||
|
||||
removeFromDataLineup(index) {
|
||||
this.dataLineup.splice(index, 1);
|
||||
},
|
||||
|
||||
clearDataLineup() {
|
||||
this.dataLineup = [];
|
||||
},
|
||||
|
||||
dropData(event) {
|
||||
this.isDraggingOverData = false;
|
||||
const data = event.dataTransfer.getData('text/plain');
|
||||
if (!data) return;
|
||||
try {
|
||||
const player = JSON.parse(data);
|
||||
this.addToDataLineup(player);
|
||||
} catch (e) {
|
||||
console.error("Drop Error:", e);
|
||||
alert("无法解析拖拽数据");
|
||||
}
|
||||
},
|
||||
|
||||
comparePlayers() {
|
||||
if (this.dataLineup.length === 0) {
|
||||
this.dataResult = [];
|
||||
if (this.radarChart) {
|
||||
this.radarChart.data.datasets = [];
|
||||
this.radarChart.update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = this.dataLineup.map(p => p.steam_id_64).join(',');
|
||||
|
||||
// 1. Fetch Basic & Radar Stats
|
||||
fetch('/players/api/batch_stats?ids=' + ids)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
this.dataResult = data;
|
||||
// Use $nextTick to ensure DOM update if needed, but for Chart.js usually direct call is fine.
|
||||
// However, dataResult is reactive. Let's call update explicitly.
|
||||
this.$nextTick(() => {
|
||||
this.updateRadarChart();
|
||||
});
|
||||
});
|
||||
|
||||
// 2. Fetch Map Stats
|
||||
fetch('/players/api/batch_map_stats?ids=' + ids)
|
||||
.then(res => res.json())
|
||||
.then(mapData => {
|
||||
this.mapStatsCache = mapData;
|
||||
});
|
||||
},
|
||||
|
||||
getMapStat(sid, mapName) {
|
||||
if (!this.mapStatsCache[sid]) return null;
|
||||
return this.mapStatsCache[sid].find(m => m.map_name === mapName);
|
||||
},
|
||||
|
||||
getPlayerColor(idx) {
|
||||
const colors = ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6'];
|
||||
return colors[idx % colors.length];
|
||||
},
|
||||
|
||||
getRatingColor(rating) {
|
||||
if (rating >= 1.2) return 'text-red-500';
|
||||
if (rating >= 1.05) return 'text-green-600';
|
||||
return 'text-gray-500';
|
||||
},
|
||||
|
||||
updateRadarChart() {
|
||||
// Force destroy to avoid state issues (fullSize error)
|
||||
if (this.radarChart) {
|
||||
this.radarChart.destroy();
|
||||
this.radarChart = null;
|
||||
}
|
||||
|
||||
const canvas = document.getElementById('dataRadarChart');
|
||||
if (!canvas) return; // Tab might not be visible yet
|
||||
|
||||
// Unwrap proxy if needed
|
||||
const rawData = JSON.parse(JSON.stringify(this.dataResult));
|
||||
|
||||
const datasets = rawData.map((p, idx) => {
|
||||
const color = this.getPlayerColor(idx);
|
||||
const d = [
|
||||
p.radar.BAT || 0, p.radar.PTL || 0, p.radar.HPS || 0,
|
||||
p.radar.SIDE || 0, p.radar.UTIL || 0, p.radar.STA || 0
|
||||
];
|
||||
|
||||
return {
|
||||
label: p.username,
|
||||
data: d,
|
||||
borderColor: color,
|
||||
backgroundColor: color + '20',
|
||||
borderWidth: 2,
|
||||
pointRadius: 3
|
||||
};
|
||||
});
|
||||
|
||||
// Recreate Chart with Profile-aligned config
|
||||
const ctx = canvas.getContext('2d');
|
||||
this.radarChart = new Chart(ctx, {
|
||||
type: 'radar',
|
||||
data: {
|
||||
labels: ['BAT (火力)', 'PTL (手枪)', 'HPS (抗压)', 'SIDE (阵营)', 'UTIL (道具)', 'STA (稳定)'],
|
||||
datasets: datasets
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
r: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
ticks: {
|
||||
display: false, // Cleaner look like profile
|
||||
stepSize: 20
|
||||
},
|
||||
pointLabels: {
|
||||
font: { size: 12, weight: 'bold' },
|
||||
color: (ctx) => document.documentElement.classList.contains('dark') ? '#cbd5e1' : '#374151'
|
||||
},
|
||||
grid: {
|
||||
color: (ctx) => document.documentElement.classList.contains('dark') ? 'rgba(51, 65, 85, 0.5)' : 'rgba(229, 231, 235, 0.8)'
|
||||
},
|
||||
angleLines: {
|
||||
color: (ctx) => document.documentElement.classList.contains('dark') ? 'rgba(51, 65, 85, 0.5)' : 'rgba(229, 231, 235, 0.8)'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
color: (ctx) => document.documentElement.classList.contains('dark') ? '#fff' : '#000',
|
||||
usePointStyle: true,
|
||||
padding: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
initRadarChart() {
|
||||
const canvas = document.getElementById('dataRadarChart');
|
||||
if (!canvas) return; // Tab might not be visible yet
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
this.radarChart = new Chart(ctx, {
|
||||
type: 'radar',
|
||||
data: {
|
||||
labels: ['BAT (火力)', 'PTL (手枪)', 'HPS (抗压)', 'SIDE (阵营)', 'UTIL (道具)', 'STA (稳定)'],
|
||||
datasets: []
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
r: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
ticks: { display: false, stepSize: 20 },
|
||||
pointLabels: {
|
||||
font: { size: 12, weight: 'bold' },
|
||||
color: (ctx) => document.documentElement.classList.contains('dark') ? '#cbd5e1' : '#374151'
|
||||
},
|
||||
grid: {
|
||||
color: (ctx) => document.documentElement.classList.contains('dark') ? '#334155' : '#e5e7eb'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: (ctx) => document.documentElement.classList.contains('dark') ? '#fff' : '#000'
|
||||
}
|
||||
}
|
||||
},
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// --- Analysis Logic ---
|
||||
dropAnalysis(event) {
|
||||
const data = event.dataTransfer.getData('text/plain');
|
||||
|
||||
Reference in New Issue
Block a user