1.2.3-hotfix: Fixed data center not showing graphs.

This commit is contained in:
2026-01-27 01:40:56 +08:00
parent b9c1af5d70
commit 2e0bedb5ff
5 changed files with 719 additions and 38 deletions

View File

@@ -1,22 +1,355 @@
{% extends "tactics/layout.html" %}
{% block title %}Data Center - Tactics{% endblock %}
{% block tactics_content %}
<div class="bg-white dark:bg-slate-800 shadow rounded-lg p-6">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">Data Center: Comparison</h2>
<div class="space-y-6">
<!-- Controls -->
<div class="flex space-x-4">
<input type="text" placeholder="Search players to compare..." class="flex-1 rounded-md border-gray-300 dark:bg-slate-700 dark:border-slate-600 dark:text-white">
<button class="px-4 py-2 bg-yrtv-600 text-white rounded-md">Add to Compare</button>
<!-- Data Center Tab Content -->
<div x-show="activeTab === 'data'" class="space-y-6 h-full flex flex-col">
<!-- Header / Controls -->
<div class="flex justify-between items-center bg-white dark:bg-slate-800 p-4 rounded-xl shadow-sm border border-gray-200 dark:border-slate-700">
<div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<span>📊</span> 数据对比中心 (Data Comparison)
</h3>
<p class="text-xs text-gray-500 mt-1">拖拽左侧队员至下方区域,或点击搜索添加</p>
</div>
<!-- Chart Placeholder -->
<div class="h-96 bg-gray-50 dark:bg-slate-700 rounded flex items-center justify-center">
<p class="text-gray-500 dark:text-gray-400">Multi-player Radar Chart / Bar Chart Area</p>
<div class="flex gap-3">
<div class="relative">
<input type="text" x-model="searchQuery" @keydown.enter="searchPlayer()" placeholder="Search Player..." class="pl-3 pr-8 py-2 border border-gray-300 dark:border-slate-600 rounded-lg text-sm bg-gray-50 dark:bg-slate-900 dark:text-white focus:ring-2 focus:ring-yrtv-500">
<button @click="searchPlayer()" class="absolute right-2 top-2 text-gray-400 hover:text-yrtv-600">🔍</button>
</div>
<button @click="clearDataLineup()" class="px-4 py-2 bg-red-50 text-red-600 rounded-lg hover:bg-red-100 text-sm font-bold transition">清空</button>
</div>
</div>
</div>
{% endblock %}
<!-- Main Content Grid -->
<div class="flex-1 grid grid-cols-1 lg:grid-cols-4 gap-6 min-h-0">
<!-- Left: Selected Players (Drop Zone) -->
<div class="lg:col-span-1 bg-white dark:bg-slate-800 rounded-xl shadow-lg border border-gray-100 dark:border-slate-700 flex flex-col overflow-hidden transition-colors duration-200"
:class="{'border-yrtv-400 bg-yrtv-50 dark:bg-slate-700 ring-2 ring-yrtv-200': isDraggingOverData}"
@dragover.prevent="isDraggingOverData = true"
@dragleave="isDraggingOverData = false"
@drop="dropData($event)">
<div class="p-4 border-b border-gray-100 dark:border-slate-700 bg-gray-50 dark:bg-slate-700/50">
<h4 class="font-bold text-gray-700 dark:text-gray-200 flex justify-between">
<span>对比列表</span>
<span class="text-xs bg-yrtv-100 text-yrtv-700 px-2 py-0.5 rounded-full" x-text="dataLineup.length + '/5'">0/5</span>
</h4>
</div>
<div class="flex-1 p-4 space-y-3 overflow-y-auto custom-scroll min-h-[100px]">
<template x-for="(p, idx) in dataLineup" :key="p.steam_id_64">
<div class="flex items-center p-3 bg-white dark:bg-slate-700 border border-gray-200 dark:border-slate-600 rounded-xl shadow-sm group hover:border-yrtv-300 transition relative">
<!-- Color Indicator -->
<div class="w-1.5 h-full absolute left-0 top-0 rounded-l-xl" :style="'background-color: ' + getPlayerColor(idx)"></div>
<div class="ml-3 flex-shrink-0">
<template x-if="p.avatar_url">
<img :src="p.avatar_url" class="w-10 h-10 rounded-full object-cover border border-gray-200 dark:border-slate-500">
</template>
<template x-if="!p.avatar_url">
<div class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-500 font-bold text-xs">
<span x-text="(p.username || p.name).substring(0,2).toUpperCase()"></span>
</div>
</template>
</div>
<div class="ml-3 flex-1 min-w-0">
<div class="text-sm font-bold text-gray-900 dark:text-white truncate" x-text="p.username || p.name"></div>
<div class="text-xs text-gray-500 font-mono truncate" x-text="p.steam_id_64"></div>
</div>
<button @click="removeFromDataLineup(idx)" class="text-gray-400 hover:text-red-500 p-1 opacity-0 group-hover:opacity-100 transition">
&times;
</button>
</div>
</template>
<template x-if="dataLineup.length < 5">
<div class="h-24 border-2 border-dashed border-gray-200 dark:border-slate-600 rounded-xl flex flex-col items-center justify-center text-gray-400 text-sm hover:bg-gray-50 dark:hover:bg-slate-800 transition cursor-default"
:class="{'border-yrtv-400 text-yrtv-600 bg-white': isDraggingOverData}">
<span>+ 拖拽或搜索添加</span>
</div>
</template>
</div>
</div>
<!-- Right: Visualization (Scrollable) -->
<div class="lg:col-span-3 space-y-6 overflow-y-auto custom-scroll pr-2">
<!-- 1. Radar & Key Stats -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Radar Chart -->
<div class="bg-white dark:bg-slate-800 p-6 rounded-xl shadow-lg border border-gray-100 dark:border-slate-700 min-h-[400px] flex flex-col">
<h4 class="font-bold text-gray-800 dark:text-gray-200 mb-4">能力模型对比 (Capability Radar)</h4>
<div class="flex-1 relative">
<canvas id="dataRadarChart"></canvas>
</div>
</div>
<!-- Basic Stats Table -->
<div class="bg-white dark:bg-slate-800 p-6 rounded-xl shadow-lg border border-gray-100 dark:border-slate-700 flex flex-col">
<h4 class="font-bold text-gray-800 dark:text-gray-200 mb-4">基础数据 (Basic Stats)</h4>
<div class="flex-1 overflow-x-auto">
<table class="min-w-full text-sm">
<thead>
<tr class="text-gray-500 border-b border-gray-100 dark:border-slate-700">
<th class="py-2 text-left">Player</th>
<th class="py-2 text-right">Rating</th>
<th class="py-2 text-right">K/D</th>
<th class="py-2 text-right">ADR</th>
<th class="py-2 text-right">KAST</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-slate-700">
<template x-for="(stat, idx) in dataResult" :key="stat.steam_id">
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/50">
<td class="py-3 flex items-center gap-2">
<div class="w-3 h-3 rounded-full" :style="'background-color: ' + getPlayerColor(idx)"></div>
<span class="font-bold dark:text-white truncate max-w-[100px]" x-text="stat.username"></span>
</td>
<td class="py-3 text-right font-mono font-bold" :class="getRatingColor(stat.basic.rating)" x-text="stat.basic.rating.toFixed(2)"></td>
<td class="py-3 text-right font-mono" x-text="stat.basic.kd.toFixed(2)"></td>
<td class="py-3 text-right font-mono" x-text="stat.basic.adr.toFixed(1)"></td>
<td class="py-3 text-right font-mono" x-text="(stat.basic.kast * 100).toFixed(1) + '%'"></td>
</tr>
</template>
<template x-if="!dataResult || dataResult.length === 0">
<tr><td colspan="5" class="py-8 text-center text-gray-400">请选择选手进行对比</td></tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
<!-- 2. Detailed Breakdown (New) -->
<div class="bg-white dark:bg-slate-800 p-6 rounded-xl shadow-lg border border-gray-100 dark:border-slate-700">
<h4 class="font-bold text-gray-800 dark:text-gray-200 mb-6">详细数据对比 (Detailed Stats)</h4>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead>
<tr class="bg-gray-50 dark:bg-slate-700/50 text-gray-500">
<th class="px-4 py-3 text-left rounded-l-lg">Metric</th>
<template x-for="(stat, idx) in dataResult" :key="'dh-'+stat.steam_id">
<th class="px-4 py-3 text-center" :class="{'rounded-r-lg': idx === dataResult.length-1}">
<span class="border-b-2 px-1 font-bold dark:text-gray-300" :style="'border-color: ' + getPlayerColor(idx)" x-text="stat.username"></span>
</th>
</template>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-slate-700">
<!-- Row 1 -->
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">Rating (Rating/KD)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400 font-bold" x-text="stat.detailed.rating_t.toFixed(2)"></span>
<span class="text-blue-600 dark:text-blue-400 font-bold" x-text="stat.detailed.rating_ct.toFixed(2)"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">KD Ratio</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="stat.detailed.kd_t.toFixed(2)"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="stat.detailed.kd_ct.toFixed(2)"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<!-- Row 2 -->
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">Win Rate (胜率)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="(stat.detailed.win_rate_t * 100).toFixed(1) + '%'"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="(stat.detailed.win_rate_ct * 100).toFixed(1) + '%'"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">First Kill Rate (首杀率)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="(stat.detailed.first_kill_t * 100).toFixed(1) + '%'"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="(stat.detailed.first_kill_ct * 100).toFixed(1) + '%'"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<!-- Row 3 -->
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">First Death Rate (首死率)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="(stat.detailed.first_death_t * 100).toFixed(1) + '%'"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="(stat.detailed.first_death_ct * 100).toFixed(1) + '%'"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">KAST (贡献率)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="(stat.detailed.kast_t * 100).toFixed(1) + '%'"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="(stat.detailed.kast_ct * 100).toFixed(1) + '%'"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<!-- Row 4 -->
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">RWS (Round Win Share)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="stat.detailed.rws_t.toFixed(2)"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="stat.detailed.rws_ct.toFixed(2)"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">Multi-Kill Rate (多杀率)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="(stat.detailed.multikill_t * 100).toFixed(1) + '%'"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="(stat.detailed.multikill_ct * 100).toFixed(1) + '%'"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<!-- Row 5 -->
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">Headshot Rate (爆头率)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="(stat.detailed.hs_t * 100).toFixed(1) + '%'"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="(stat.detailed.hs_ct * 100).toFixed(1) + '%'"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-2 font-medium text-gray-600 dark:text-gray-400">Obj (下包 vs 拆包)</td>
<template x-for="stat in dataResult">
<td class="px-4 py-2 text-center font-mono text-xs">
<div class="flex flex-col">
<div class="flex justify-between w-full max-w-[120px] mx-auto">
<span class="text-amber-600 dark:text-amber-400" x-text="stat.detailed.obj_t.toFixed(2)"></span>
<span class="text-blue-600 dark:text-blue-400" x-text="stat.detailed.obj_ct.toFixed(2)"></span>
</div>
<div class="flex justify-between w-full max-w-[120px] mx-auto text-[10px] text-gray-400">
<span>T-Side</span><span>CT-Side</span>
</div>
</div>
</td>
</template>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 3. Map Performance -->
<div class="bg-white dark:bg-slate-800 p-6 rounded-xl shadow-lg border border-gray-100 dark:border-slate-700">
<h4 class="font-bold text-gray-800 dark:text-gray-200 mb-6">地图表现 (Map Performance)</h4>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="bg-gray-50 dark:bg-slate-700/50">
<tr>
<th class="px-4 py-2 text-left rounded-l-lg">Map</th>
<template x-for="(stat, idx) in dataResult" :key="'h-'+stat.steam_id">
<th class="px-4 py-2 text-center" :class="{'rounded-r-lg': idx === dataResult.length-1}">
<span class="border-b-2 px-1" :style="'border-color: ' + getPlayerColor(idx)" x-text="stat.username"></span>
</th>
</template>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-slate-700">
<!-- We need to iterate maps. Assuming mapMap is computed in JS -->
<template x-for="mapName in allMaps" :key="mapName">
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700/30">
<td class="px-4 py-3 font-bold text-gray-600 dark:text-gray-300" x-text="mapName"></td>
<template x-for="stat in dataResult" :key="'d-'+stat.steam_id+mapName">
<td class="px-4 py-3 text-center">
<template x-if="getMapStat(stat.steam_id, mapName)">
<div>
<div class="font-bold font-mono" :class="getRatingColor(getMapStat(stat.steam_id, mapName).rating)" x-text="getMapStat(stat.steam_id, mapName).rating.toFixed(2)"></div>
<div class="text-[10px] text-gray-400" x-text="(getMapStat(stat.steam_id, mapName).win_rate * 100).toFixed(0) + '% (' + getMapStat(stat.steam_id, mapName).matches + ')'"></div>
</div>
</template>
<template x-if="!getMapStat(stat.steam_id, mapName)">
<span class="text-gray-300">-</span>
</template>
</td>
</template>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>