1.0.0 : Web Implemented.
This commit is contained in:
471
web/templates/tactics/index.html
Normal file
471
web/templates/tactics/index.html
Normal file
@@ -0,0 +1,471 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tactics Center{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<!-- Leaflet CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
||||
<style>
|
||||
.player-token { cursor: grab; transition: transform 0.1s; }
|
||||
.player-token:active { cursor: grabbing; transform: scale(1.05); }
|
||||
#map-container { background-color: #1a1a1a; z-index: 1; }
|
||||
.leaflet-container { background: #1a1a1a; }
|
||||
.custom-scroll::-webkit-scrollbar { width: 6px; }
|
||||
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
|
||||
.custom-scroll::-webkit-scrollbar-thumb { background-color: rgba(156, 163, 175, 0.5); border-radius: 20px; }
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex h-[calc(100vh-4rem)] overflow-hidden" x-data="tacticsApp()" x-cloak>
|
||||
|
||||
<!-- Left Sidebar: Roster (Permanent) -->
|
||||
<div class="w-72 flex flex-col bg-white dark:bg-slate-800 border-r border-gray-200 dark:border-slate-700 shadow-xl z-20 shrink-0">
|
||||
<div class="p-4 border-b border-gray-200 dark:border-slate-700">
|
||||
<h2 class="text-lg font-bold text-gray-900 dark:text-white">队员列表 (Roster)</h2>
|
||||
<p class="text-xs text-gray-500">拖拽队员至右侧功能区</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto custom-scroll p-4 space-y-2">
|
||||
<template x-for="player in roster" :key="player.steam_id_64">
|
||||
<div class="player-token group flex items-center p-2 rounded-lg border border-transparent hover:bg-gray-50 dark:hover:bg-slate-700 hover:border-gray-200 dark:hover:border-slate-600 transition select-none cursor-grab active:cursor-grabbing"
|
||||
:data-id="player.steam_id_64"
|
||||
draggable="true"
|
||||
@dragstart="dragStart($event, player)">
|
||||
|
||||
<img :src="player.avatar_url || 'https://avatars.steamstatic.com/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg'"
|
||||
class="w-10 h-10 rounded-full border border-gray-200 dark:border-slate-600 object-cover pointer-events-none">
|
||||
|
||||
<div class="ml-3 flex-1 min-w-0 pointer-events-none">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-white truncate" x-text="player.username || player.name || player.steam_id_64"></div>
|
||||
<!-- Tag Display -->
|
||||
<div class="flex flex-wrap gap-1 mt-0.5">
|
||||
<template x-for="tag in player.tags">
|
||||
<span class="text-[10px] bg-gray-100 dark:bg-slate-600 text-gray-600 dark:text-gray-300 px-1 rounded" x-text="tag"></span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="roster.length === 0">
|
||||
<div class="text-sm text-gray-500 text-center py-8">
|
||||
暂无队员,请去 <a href="/teams" class="text-yrtv-600 hover:underline">Team</a> 页面添加。
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Content Area -->
|
||||
<div class="flex-1 flex flex-col min-w-0 bg-gray-50 dark:bg-gray-900">
|
||||
|
||||
<!-- Top Navigation Tabs -->
|
||||
<div class="bg-white dark:bg-slate-800 border-b border-gray-200 dark:border-slate-700 px-4">
|
||||
<nav class="-mb-px flex space-x-8">
|
||||
<button @click="switchTab('analysis')" :class="{'border-yrtv-500 text-yrtv-600 dark:text-yrtv-400': activeTab === 'analysis', 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400': activeTab !== 'analysis'}" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition">
|
||||
深度分析 (Deep Analysis)
|
||||
</button>
|
||||
<button @click="switchTab('data')" :class="{'border-yrtv-500 text-yrtv-600 dark:text-yrtv-400': activeTab === 'data', 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400': activeTab !== 'data'}" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition">
|
||||
数据中心 (Data Center)
|
||||
</button>
|
||||
<button @click="switchTab('board')" :class="{'border-yrtv-500 text-yrtv-600 dark:text-yrtv-400': activeTab === 'board', 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400': activeTab !== 'board'}" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition">
|
||||
战术白板 (Strategy Board)
|
||||
</button>
|
||||
<button @click="switchTab('economy')" :class="{'border-yrtv-500 text-yrtv-600 dark:text-yrtv-400': activeTab === 'economy', 'border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400': activeTab !== 'economy'}" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition">
|
||||
经济计算 (Economy)
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Tab Contents -->
|
||||
<div class="flex-1 overflow-y-auto p-6 relative">
|
||||
|
||||
<!-- 1. Deep Analysis -->
|
||||
<div x-show="activeTab === 'analysis'" class="space-y-6">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">阵容化学反应分析</h3>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Drop Zone -->
|
||||
<div class="bg-white dark:bg-slate-800 p-6 rounded-lg shadow min-h-[300px]"
|
||||
@dragover.prevent @drop="dropAnalysis($event)">
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-4 flex justify-between">
|
||||
<span x-text="'阵容构建 (' + analysisLineup.length + '/5)'">阵容构建 (0/5)</span>
|
||||
<button @click="clearAnalysis()" class="text-xs text-red-500 hover:underline">清空</button>
|
||||
</h4>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-5 gap-4">
|
||||
<template x-for="(p, idx) in analysisLineup" :key="p.steam_id_64">
|
||||
<div class="relative bg-gray-50 dark:bg-slate-700 p-2 rounded border border-gray-200 dark:border-slate-600 flex flex-col items-center">
|
||||
<button @click="removeFromAnalysis(idx)" class="absolute top-1 right-1 text-red-400 hover:text-red-600">×</button>
|
||||
<img :src="p.avatar_url" class="w-12 h-12 rounded-full mb-2">
|
||||
<span class="text-xs font-bold truncate w-full text-center dark:text-white" x-text="p.username || p.name"></span>
|
||||
<span class="text-[10px] text-gray-500" x-text="'R: ' + (p.stats?.basic_avg_rating || 0).toFixed(2)"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty Slots -->
|
||||
<template x-for="i in (5 - analysisLineup.length)">
|
||||
<div class="border-2 border-dashed border-gray-300 dark:border-slate-600 rounded flex items-center justify-center h-24 text-gray-400 text-xs">
|
||||
拖拽至此
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<button @click="analyzeLineup()" :disabled="analysisLineup.length < 1" class="w-full py-2 bg-yrtv-600 text-white rounded hover:bg-yrtv-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
开始分析 (Analyze)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Area -->
|
||||
<div class="bg-white dark:bg-slate-800 p-6 rounded-lg shadow">
|
||||
<template x-if="!analysisResult">
|
||||
<div class="h-full flex items-center justify-center text-gray-400 text-sm">
|
||||
请先选择阵容并点击分析
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="analysisResult">
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h4 class="font-bold text-gray-900 dark:text-white">综合评分</h4>
|
||||
<span class="text-2xl font-bold text-yrtv-600" x-text="analysisResult.avg_stats.rating.toFixed(2)"></span>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2 text-center text-sm">
|
||||
<div class="bg-gray-50 dark:bg-slate-700 p-2 rounded">
|
||||
<div class="text-gray-500">Avg K/D</div>
|
||||
<div class="font-bold dark:text-white" x-text="analysisResult.avg_stats.kd.toFixed(2)"></div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-slate-700 p-2 rounded">
|
||||
<div class="text-gray-500">Avg ADR</div>
|
||||
<div class="font-bold dark:text-white" x-text="analysisResult.avg_stats.adr.toFixed(1)"></div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-slate-700 p-2 rounded">
|
||||
<div class="text-gray-500">共同场次</div>
|
||||
<div class="font-bold dark:text-white" x-text="analysisResult.shared_matches.length"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">近期共同比赛 (Recent Shared Matches)</h5>
|
||||
<div class="max-h-40 overflow-y-auto text-xs">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-slate-600">
|
||||
<thead class="bg-gray-50 dark:bg-slate-700">
|
||||
<tr>
|
||||
<th class="px-2 py-1 text-left">Map</th>
|
||||
<th class="px-2 py-1 text-right">Score</th>
|
||||
<th class="px-2 py-1 text-right">Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
|
||||
<template x-for="m in analysisResult.shared_matches" :key="m.match_id">
|
||||
<tr>
|
||||
<td class="px-2 py-1 dark:text-gray-300" x-text="m.map_name"></td>
|
||||
<td class="px-2 py-1 text-right dark:text-gray-300" x-text="m.score_team1 + ':' + m.score_team2"></td>
|
||||
<td class="px-2 py-1 text-right font-bold" :class="m.winner_team ? 'text-green-600' : 'text-gray-500'" x-text="m.winner_team ? 'Win' : 'Draw/Loss'"></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<template x-if="analysisResult.shared_matches.length === 0">
|
||||
<p class="text-center text-gray-400 py-2">无共同比赛记录</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<!-- 3. Strategy Board -->
|
||||
<div x-show="activeTab === 'board'" class="h-full flex flex-col">
|
||||
<!-- Map Controls -->
|
||||
<div class="mb-4 flex justify-between items-center bg-white dark:bg-slate-800 p-3 rounded shadow">
|
||||
<div class="flex space-x-2">
|
||||
<select x-model="currentMap" @change="changeMap()" class="rounded border-gray-300 dark:bg-slate-700 dark:border-slate-600 dark:text-white text-sm">
|
||||
<option value="de_mirage">Mirage</option>
|
||||
<option value="de_inferno">Inferno</option>
|
||||
<option value="de_dust2">Dust 2</option>
|
||||
<option value="de_nuke">Nuke</option>
|
||||
<option value="de_ancient">Ancient</option>
|
||||
<option value="de_anubis">Anubis</option>
|
||||
<option value="de_vertigo">Vertigo</option>
|
||||
</select>
|
||||
<button @click="clearBoard()" class="px-3 py-1 bg-red-100 text-red-700 rounded hover:bg-red-200 text-sm">清空 (Clear)</button>
|
||||
<button @click="saveBoard()" class="px-3 py-1 bg-green-100 text-green-700 rounded hover:bg-green-200 text-sm">保存快照 (Save)</button>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
在场人数: <span x-text="boardPlayers.length" class="font-bold text-yrtv-600"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Area -->
|
||||
<div class="flex-1 relative bg-gray-900 rounded-lg overflow-hidden border border-gray-700"
|
||||
id="board-dropzone"
|
||||
@dragover.prevent
|
||||
@drop="dropBoard($event)">
|
||||
<div id="map-container" class="w-full h-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. Economy -->
|
||||
<div x-show="activeTab === 'economy'" 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-6">经济计算器 (Economy Calculator)</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">本回合结果</label>
|
||||
<select x-model="econ.result" class="mt-1 block w-full rounded-md border-gray-300 dark:bg-slate-700 dark:text-white">
|
||||
<option value="win">胜利 (Won)</option>
|
||||
<option value="loss">失败 (Lost)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">连败加成等级 (Loss Bonus)</label>
|
||||
<select x-model="econ.lossBonus" class="mt-1 block w-full rounded-md border-gray-300 dark:bg-slate-700 dark:text-white">
|
||||
<option value="0">$1400 (0)</option>
|
||||
<option value="1">$1900 (1)</option>
|
||||
<option value="2">$2400 (2)</option>
|
||||
<option value="3">$2900 (3)</option>
|
||||
<option value="4">$3400 (4+)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">存活人数</label>
|
||||
<input type="number" x-model="econ.surviving" min="0" max="5" class="mt-1 block w-full rounded-md border-gray-300 dark:bg-slate-700 dark:text-white">
|
||||
</div>
|
||||
|
||||
<div class="pt-4">
|
||||
<div class="p-4 bg-gray-100 dark:bg-slate-700 rounded-lg">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">下回合收入预测</div>
|
||||
<div class="text-3xl font-bold text-green-600 dark:text-green-400" x-text="'$' + calculateIncome()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- External Libs -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
function tacticsApp() {
|
||||
return {
|
||||
activeTab: 'analysis',
|
||||
roster: [],
|
||||
|
||||
// Analysis State
|
||||
analysisLineup: [],
|
||||
analysisResult: null,
|
||||
|
||||
// Board State
|
||||
currentMap: 'de_mirage',
|
||||
map: null,
|
||||
markers: {},
|
||||
boardPlayers: [],
|
||||
|
||||
// Economy State
|
||||
econ: {
|
||||
result: 'loss',
|
||||
lossBonus: '0',
|
||||
surviving: 0
|
||||
},
|
||||
|
||||
init() {
|
||||
this.fetchRoster();
|
||||
// Init map on first board view, or delay
|
||||
this.$watch('activeTab', value => {
|
||||
if (value === 'board') {
|
||||
this.$nextTick(() => {
|
||||
if (!this.map) this.initMap();
|
||||
else this.map.invalidateSize();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
fetchRoster() {
|
||||
fetch('/teams/api/roster')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
this.roster = data.roster || [];
|
||||
});
|
||||
},
|
||||
|
||||
switchTab(tab) {
|
||||
this.activeTab = tab;
|
||||
},
|
||||
|
||||
// --- Drag & Drop Generic ---
|
||||
dragStart(event, player) {
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(player));
|
||||
event.dataTransfer.effectAllowed = 'copy';
|
||||
},
|
||||
|
||||
// --- Analysis Logic ---
|
||||
dropAnalysis(event) {
|
||||
const data = event.dataTransfer.getData('text/plain');
|
||||
if (!data) return;
|
||||
const player = JSON.parse(data);
|
||||
|
||||
// Check duplicates
|
||||
if (this.analysisLineup.some(p => p.steam_id_64 === player.steam_id_64)) return;
|
||||
|
||||
// Limit 5
|
||||
if (this.analysisLineup.length >= 5) return;
|
||||
|
||||
this.analysisLineup.push(player);
|
||||
},
|
||||
|
||||
removeFromAnalysis(index) {
|
||||
this.analysisLineup.splice(index, 1);
|
||||
},
|
||||
|
||||
clearAnalysis() {
|
||||
this.analysisLineup = [];
|
||||
this.analysisResult = null;
|
||||
},
|
||||
|
||||
analyzeLineup() {
|
||||
const ids = this.analysisLineup.map(p => p.steam_id_64);
|
||||
fetch('/tactics/api/analyze', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({steam_ids: ids})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
this.analysisResult = data;
|
||||
});
|
||||
},
|
||||
|
||||
// --- Board Logic ---
|
||||
initMap() {
|
||||
this.map = L.map('map-container', {
|
||||
crs: L.CRS.Simple,
|
||||
minZoom: -2,
|
||||
maxZoom: 2,
|
||||
zoomControl: true,
|
||||
attributionControl: false
|
||||
});
|
||||
this.loadMapImage();
|
||||
},
|
||||
|
||||
loadMapImage() {
|
||||
const mapUrls = {
|
||||
'de_mirage': 'https://static.wikia.nocookie.net/cswikia/images/e/e3/Mirage_CS2_Radar.png',
|
||||
'de_inferno': 'https://static.wikia.nocookie.net/cswikia/images/7/77/Inferno_CS2_Radar.png',
|
||||
'de_dust2': 'https://static.wikia.nocookie.net/cswikia/images/0/03/Dust2_CS2_Radar.png',
|
||||
'de_nuke': 'https://static.wikia.nocookie.net/cswikia/images/1/14/Nuke_CS2_Radar.png',
|
||||
'de_ancient': 'https://static.wikia.nocookie.net/cswikia/images/1/16/Ancient_CS2_Radar.png',
|
||||
'de_anubis': 'https://static.wikia.nocookie.net/cswikia/images/2/22/Anubis_CS2_Radar.png',
|
||||
'de_vertigo': 'https://static.wikia.nocookie.net/cswikia/images/2/23/Vertigo_CS2_Radar.png'
|
||||
};
|
||||
const url = mapUrls[this.currentMap] || mapUrls['de_mirage'];
|
||||
const bounds = [[0,0], [1024,1024]];
|
||||
|
||||
this.map.eachLayer((layer) => { this.map.removeLayer(layer); });
|
||||
L.imageOverlay(url, bounds).addTo(this.map);
|
||||
this.map.fitBounds(bounds);
|
||||
},
|
||||
|
||||
changeMap() {
|
||||
this.loadMapImage();
|
||||
this.clearBoard();
|
||||
},
|
||||
|
||||
dropBoard(event) {
|
||||
const data = event.dataTransfer.getData('text/plain');
|
||||
if (!data) return;
|
||||
const player = JSON.parse(data);
|
||||
|
||||
const container = document.getElementById('map-container');
|
||||
const rect = container.getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
const point = this.map.containerPointToLatLng([x, y]);
|
||||
|
||||
this.addMarker(player, point);
|
||||
},
|
||||
|
||||
addMarker(player, latlng) {
|
||||
if (this.markers[player.steam_id_64]) {
|
||||
this.markers[player.steam_id_64].setLatLng(latlng);
|
||||
} else {
|
||||
const displayName = player.username || player.name || player.steam_id_64;
|
||||
const iconHtml = `
|
||||
<div class="flex flex-col items-center justify-center transform hover:scale-110 transition duration-200">
|
||||
<img src="${player.avatar_url || 'https://avatars.steamstatic.com/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg'}"
|
||||
class="w-8 h-8 rounded-full border-2 border-white shadow-lg box-content">
|
||||
<span class="mt-1 text-[10px] font-bold text-white bg-black/60 px-1.5 py-0.5 rounded backdrop-blur-sm whitespace-nowrap overflow-hidden max-w-[80px] text-ellipsis">
|
||||
${displayName}
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
const icon = L.divIcon({ className: 'bg-transparent', html: iconHtml, iconSize: [60, 60], iconAnchor: [30, 30] });
|
||||
|
||||
const marker = L.marker(latlng, { icon: icon, draggable: true }).addTo(this.map);
|
||||
this.markers[player.steam_id_64] = marker;
|
||||
this.boardPlayers.push(player);
|
||||
}
|
||||
},
|
||||
|
||||
clearBoard() {
|
||||
for (let id in this.markers) { this.map.removeLayer(this.markers[id]); }
|
||||
this.markers = {};
|
||||
this.boardPlayers = [];
|
||||
},
|
||||
|
||||
saveBoard() {
|
||||
const title = prompt("请输入战术标题:", "New Strat " + new Date().toLocaleTimeString());
|
||||
if (!title) return;
|
||||
|
||||
const markerData = [];
|
||||
for (let id in this.markers) {
|
||||
const m = this.markers[id];
|
||||
markerData.push({ id: id, lat: m.getLatLng().lat, lng: m.getLatLng().lng });
|
||||
}
|
||||
|
||||
fetch('/tactics/save_board', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ title: title, map_name: this.currentMap, markers: markerData })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => alert(data.success ? "保存成功" : "保存失败"));
|
||||
},
|
||||
|
||||
// --- Economy Logic ---
|
||||
calculateIncome() {
|
||||
let base = 0;
|
||||
const lbLevel = parseInt(this.econ.lossBonus);
|
||||
|
||||
if (this.econ.result === 'win') {
|
||||
base = 3250 + (300 * this.econ.surviving); // Simplified estimate
|
||||
} else {
|
||||
// Loss base
|
||||
const lossAmounts = [1400, 1900, 2400, 2900, 3400];
|
||||
base = lossAmounts[Math.min(lbLevel, 4)];
|
||||
}
|
||||
return base;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user