DEV-004: M-DEVBOARD环节5 真实API对接+离线降级
This commit is contained in:
parent
93fc07c4c3
commit
b450cd3a3d
|
|
@ -0,0 +1,81 @@
|
||||||
|
// =====================================
|
||||||
|
// HoloLake DevBoard · API Init v1.0
|
||||||
|
// 负责:异步加载 + 加载动画 + 自动刷新
|
||||||
|
// 必须在 api.js 和 main.js 之后加载
|
||||||
|
// =====================================
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
// -------------- 加载动画 --------------
|
||||||
|
function showLoader(show) {
|
||||||
|
var el = document.getElementById('devboard-loader');
|
||||||
|
if (!el && show) {
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.id = 'devboard-loader';
|
||||||
|
el.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(10,14,23,0.92);display:flex;align-items:center;justify-content:center;z-index:10000';
|
||||||
|
el.innerHTML = '<div style="display:flex;flex-direction:column;align-items:center;gap:16px;"><div style="width:40px;height:40px;border:3px solid rgba(96,165,250,0.3);border-top-color:#60a5fa;border-radius:50%;animation:dbspin 0.8s linear infinite;"></div><div style="font-size:14px;color:#60a5fa;">数据加载中...</div></div>';
|
||||||
|
var style = document.createElement('style');
|
||||||
|
style.textContent = '@keyframes dbspin{to{transform:rotate(360deg)}}';
|
||||||
|
document.head.appendChild(style);
|
||||||
|
document.body.appendChild(el);
|
||||||
|
}
|
||||||
|
if (el) el.style.display = show ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- 数据加载+渲染 --------------
|
||||||
|
async function loadAndRender() {
|
||||||
|
try {
|
||||||
|
var developers = await apiGetDevelopers();
|
||||||
|
var stats = await apiGetStats();
|
||||||
|
var leaderboard = await apiGetLeaderboard();
|
||||||
|
|
||||||
|
// 使用 components.js 里实际存在的函数
|
||||||
|
if (typeof renderDeveloperCards === 'function') {
|
||||||
|
renderDeveloperCards(developers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof renderRanking === 'function') {
|
||||||
|
renderRanking(leaderboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stats 暂时用控制台输出,等找到实际渲染函数再改
|
||||||
|
console.log('[DevBoard] 统计数据:', stats);
|
||||||
|
|
||||||
|
console.log('[DevBoard] 数据加载完成,共 ' + developers.length + ' 位开发者');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[DevBoard] 数据加载异常: ', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- 初始化入口 --------------
|
||||||
|
async function init() {
|
||||||
|
showLoader(true);
|
||||||
|
|
||||||
|
// 检测API状态
|
||||||
|
var online = await apiHealthCheck();
|
||||||
|
showApiStatus(online);
|
||||||
|
console.log('[DevBoard] API状态: ' + (online ? '在线(实时)' : '离线(降级)'));
|
||||||
|
|
||||||
|
// 加载+渲染
|
||||||
|
await loadAndRender();
|
||||||
|
showLoader(false);
|
||||||
|
|
||||||
|
// 自动刷新(仅API在线时)
|
||||||
|
if (online && API.REFRESH_MS > 0) {
|
||||||
|
console.log('[DevBoard] 自动刷新已启动,间隔 ' + (API.REFRESH_MS/1000) + ' 秒');
|
||||||
|
setInterval(async function() {
|
||||||
|
try {
|
||||||
|
await loadAndRender();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[DevBoard] 自动刷新失败: ', e.message);
|
||||||
|
}
|
||||||
|
}, API.REFRESH_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待DOM和所有脚本加载完成后启动
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
// =====================================
|
||||||
|
// HoloLake DevBoard · API Init v1.0
|
||||||
|
// 负责:异步加载 + 加载动画 + 自动刷新
|
||||||
|
// 必须在 api.js 和 main.js 之后加载
|
||||||
|
// =====================================
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
// -------------- 加载动画 --------------
|
||||||
|
function showLoader(show) {
|
||||||
|
var el = document.getElementById('devboard-loader');
|
||||||
|
if (!el && show) {
|
||||||
|
el = document.createElement('div');
|
||||||
|
el.id = 'devboard-loader';
|
||||||
|
el.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(10,14,23,0.92);display:flex;align-items:center;justify-content:center;z-index:10000';
|
||||||
|
el.innerHTML = '<div style="display:flex;flex-direction:column;align-items:center;gap:16px;"><div style="width:40px;height:40px;border:3px solid rgba(96,165,250,0.3);border-top-color:#60a5fa;border-radius:50%;animation:dbspin 0.8s linear infinite;"></div><div style="font-size:14px;color:#60a5fa;">数据加载中...</div></div>';
|
||||||
|
var style = document.createElement('style');
|
||||||
|
style.textContent = '@keyframes dbspin{to{transform:rotate(360deg)}}';
|
||||||
|
document.head.appendChild(style);
|
||||||
|
document.body.appendChild(el);
|
||||||
|
}
|
||||||
|
if (el) el.style.display = show ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- 数据加载+渲染 --------------
|
||||||
|
async function loadAndRender() {
|
||||||
|
try {
|
||||||
|
var developers = await apiGetDevelopers();
|
||||||
|
var stats = await apiGetStats();
|
||||||
|
var leaderboard = await apiGetLeaderboard();
|
||||||
|
|
||||||
|
// 用现有渲染函数(main.js / components.js 定义的)
|
||||||
|
if (typeof renderDeveloperCards === 'function') renderDeveloperCards(developers);
|
||||||
|
if (typeof renderStats === 'function') renderStats(stats);
|
||||||
|
if (typeof renderLeaderboard === 'function') renderLeaderboard(leaderboard);
|
||||||
|
if (typeof renderRankings === 'function') renderRankings(leaderboard);
|
||||||
|
|
||||||
|
console.log('[DevBoard] 数据加载完成,共 ' + developers.length + ' 位开发者');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[DevBoard] 数据加载异常: ', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------- 初始化入口 --------------
|
||||||
|
async function init() {
|
||||||
|
showLoader(true);
|
||||||
|
|
||||||
|
// 检测API状态
|
||||||
|
var online = await apiHealthCheck();
|
||||||
|
showApiStatus(online);
|
||||||
|
console.log('[DevBoard] API状态: ' + (online ? '在线(实时)' : '离线(降级)'));
|
||||||
|
|
||||||
|
// 加载+渲染
|
||||||
|
await loadAndRender();
|
||||||
|
showLoader(false);
|
||||||
|
|
||||||
|
// 自动刷新(仅API在线时)
|
||||||
|
if (online && API.REFRESH_MS > 0) {
|
||||||
|
console.log('[DevBoard] 自动刷新已启动,间隔 ' + (API.REFRESH_MS/1000) + ' 秒');
|
||||||
|
setInterval(async function() {
|
||||||
|
try {
|
||||||
|
await loadAndRender();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[DevBoard] 自动刷新失败: ', e.message);
|
||||||
|
}
|
||||||
|
}, API.REFRESH_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待DOM和所有脚本加载完成后启动
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -1,79 +1,122 @@
|
||||||
// api.js - API配置和模拟数据切换
|
// ======================
|
||||||
|
// HoloLake DevBoard · API Client v2.0
|
||||||
|
// DEV-004 之之 · 环节5 · 真实API对接
|
||||||
|
// 协议:SYSLOG-v4.0
|
||||||
|
// ======================
|
||||||
|
|
||||||
const CONFIG = {
|
const API = {
|
||||||
// API基础地址(页页的接口)
|
// ====== 配置区 ======
|
||||||
BASE_URL: 'https://guanghulab.com/api',
|
// 页页的后端API地址(如果地址不对,问知秋确认)
|
||||||
|
BASE_URL: 'https://guanghulab.com/api/devboard',
|
||||||
// 是否使用真实API(false = 使用模拟数据)
|
TIMEOUT: 8000,
|
||||||
USE_REAL_API: false,
|
FALLBACK: true, // API挂了自动切换模拟数据
|
||||||
|
REFRESH_MS: 30000, // 30秒自动刷新
|
||||||
// API端点
|
_cache: {}
|
||||||
endpoints: {
|
|
||||||
teamStatus: '/dashboard/team-status',
|
|
||||||
developerDetail: '/dashboard/dev/', // + id
|
|
||||||
modules: '/dashboard/modules'
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查API是否可用
|
// ====== 通用请求(带超时+缓存+降级) =====================
|
||||||
async function checkApiAvailability() {
|
async function apiFetch(path) {
|
||||||
if (!CONFIG.USE_REAL_API) return false;
|
const url = API.BASE_URL + path;
|
||||||
|
const ctrl = new AbortController();
|
||||||
|
const timer = setTimeout(() => ctrl.abort(), API.TIMEOUT);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${CONFIG.BASE_URL}/health`, {
|
const res = await fetch(url, {
|
||||||
method: 'HEAD',
|
signal: ctrl.signal,
|
||||||
mode: 'cors',
|
headers: { 'Accept': 'application/json' }
|
||||||
cache: 'no-cache'
|
|
||||||
});
|
});
|
||||||
return response.ok;
|
clearTimeout(timer);
|
||||||
} catch (error) {
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||||
console.log('API不可用,使用模拟数据');
|
const data = await res.json();
|
||||||
|
API._cache[path] = { data: data, time: Date.now() };
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
console.warn(' 🚨 [DevBoard API] ' + path + ' 失败: ', err.message);
|
||||||
|
if (API._cache[path]) {
|
||||||
|
console.info(' 💾 [DevBoard API] 使用缓存');
|
||||||
|
return API._cache[path].data;
|
||||||
|
}
|
||||||
|
if (API.FALLBACK) {
|
||||||
|
console.info(' 🛡️ [DevBoard API] 降级到模拟数据');
|
||||||
|
return getMockData(path);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 公开接口 ==========
|
||||||
|
async function apiGetDevelopers() {
|
||||||
|
return apiFetch('/developers');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apiGetDeveloperDetail(devId) {
|
||||||
|
return apiFetch('/developers/' + devId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apiGetStats() {
|
||||||
|
return apiFetch('/stats');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apiGetLeaderboard() {
|
||||||
|
return apiFetch('/leaderboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 健康检查 ==========
|
||||||
|
async function apiHealthCheck() {
|
||||||
|
try {
|
||||||
|
const ctrl = new AbortController();
|
||||||
|
const t = setTimeout(() => ctrl.abort(), 3000);
|
||||||
|
const res = await fetch(API.BASE_URL + '/health', { signal: ctrl.signal });
|
||||||
|
clearTimeout(t);
|
||||||
|
return res.ok;
|
||||||
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取团队状态
|
// ========== 模拟数据(降级保护) ==========
|
||||||
async function getTeamStatus() {
|
function getMockData(path) {
|
||||||
if (CONFIG.USE_REAL_API) {
|
var devs = [
|
||||||
try {
|
{ dev_id:'DEV-001', name:'页页', module:'BC-集成', status:'进行中', streak:5, el:'EL-6', pca:{EXE:75,TEC:60,SYS:70,COL:80,INI:65} },
|
||||||
const response = await fetch(`${CONFIG.BASE_URL}${CONFIG.endpoints.teamStatus}`);
|
{ dev_id:'DEV-002', name:'肥猫', module:'M-STATUS', status:'进行中', streak:9, el:'EL-5', pca:{EXE:72,TEC:40,SYS:68,COL:82,INI:70} },
|
||||||
if (response.ok) {
|
{ dev_id:'DEV-003', name:'燕樊', module:'M-MEMORY', status:'进行中', streak:7, el:'EL-5', pca:{EXE:70,TEC:55,SYS:65,COL:75,INI:68} },
|
||||||
const data = await response.json();
|
{ dev_id:'DEV-004', name:'之之', module:'M-DEVBOARD', status:'进行中', streak:14, el:'EL-8', pca:{EXE:90,TEC:55,SYS:80,COL:85,INI:88} },
|
||||||
return data;
|
{ dev_id:'DEV-009', name:'花尔', module:'M20', status:'进行中', streak:4, el:'EL-8', pca:{EXE:65,TEC:45,SYS:55,COL:70,INI:60} },
|
||||||
}
|
{ dev_id:'DEV-010', name:'桔子', module:'M-CHANNEL', status:'进行中', streak:14, el:'EL-6', pca:{EXE:85,TEC:50,SYS:75,COL:80,INI:82} },
|
||||||
} catch (error) {
|
{ dev_id:'DEV-011', name:'匆匆那年',module:'M16', status:'等待中', streak:1, el:'EL-3', pca:{EXE:50,TEC:30,SYS:40,COL:55,INI:45} },
|
||||||
console.warn('API请求失败,使用模拟数据');
|
{ dev_id:'DEV-012', name:'Awen', module:'M22', status:'进行中', streak:13, el:'EL-6', pca:{EXE:88,TEC:45,SYS:78,COL:90,INI:80} },
|
||||||
}
|
{ dev_id:'DEV-013', name:'小兴', module:'M-AUTH', status:'进行中', streak:1, el:'EL-3', pca:{EXE:55,TEC:25,SYS:35,COL:60,INI:50} }
|
||||||
|
];
|
||||||
|
var stats = { total_developers:10, active:8, modules:12, code_lines:32000, longest_streak:14, avg_streak:7.5 };
|
||||||
|
|
||||||
|
if (path === '/developers') return devs;
|
||||||
|
if (path.indexOf('/developers/') === 0) {
|
||||||
|
var id = path.split('/').pop();
|
||||||
|
return devs.find(function(d){ return d.dev_id === id; }) || devs[0];
|
||||||
}
|
}
|
||||||
|
if (path === '/stats') return stats;
|
||||||
// 返回模拟数据
|
if (path === '/leaderboard') return devs.slice().sort(function(a,b){ return b.streak - a.streak; });
|
||||||
return {
|
return [];
|
||||||
developers: window.getDevelopers ? window.getDevelopers() : []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取开发者详情
|
// ========== API状态指示器 ==========
|
||||||
async getDeveloperDetail(devId) {
|
function showApiStatus(online) {
|
||||||
if (CONFIG.USE_REAL_API) {
|
var el = document.getElementById('api-status');
|
||||||
try {
|
if (!el) {
|
||||||
const response = await fetch(`${CONFIG.BASE_URL}${CONFIG.endpoints.developerDetail}${devId}`);
|
el = document.createElement('div');
|
||||||
if (response.ok) {
|
el.id = 'api-status';
|
||||||
const data = await response.json();
|
el.style.cssText = 'position:fixed;bottom:12px;right:12px;padding:6px 14px;border-radius:20px;font-size:12px;font-weight:600;z-index:9999;transition:all 0.3s;';
|
||||||
return data;
|
document.body.appendChild(el);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
if (online) {
|
||||||
console.warn('API请求失败,使用模拟数据');
|
el.textContent = '🟢 实时数据';
|
||||||
}
|
el.style.backgroundColor = 'rgba(52,211,153,0.15)';
|
||||||
|
el.style.color = '#34d399';
|
||||||
|
el.style.border = '1px solid rgba(52,211,153,0.3)';
|
||||||
|
} else {
|
||||||
|
el.textContent = '🟡 模拟数据';
|
||||||
|
el.style.backgroundColor = 'rgba(251,191,36,0.15)';
|
||||||
|
el.style.color = '#fbbf24';
|
||||||
|
el.style.border = '1px solid rgba(251,191,36,0.3)';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回模拟数据
|
|
||||||
return window.getDeveloperById ? window.getDeveloperById(devId) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出配置
|
|
||||||
window.API = {
|
|
||||||
CONFIG,
|
|
||||||
checkApiAvailability,
|
|
||||||
getTeamStatus,
|
|
||||||
getDeveloperDetail
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,35 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.5, user-scalable=yes">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>开发者实时看板 · 环节0~4 · 之之</title>
|
<title>开发者实时看板 · HoloLake</title>
|
||||||
<link rel="stylesheet" href="style.css">
|
<style>
|
||||||
<link rel="stylesheet" href="components.css">
|
/* 这里保留你原有的样式,如果样式比较多,建议去复制原有的内容 */
|
||||||
<link rel="stylesheet" href="detail.css">
|
/* 为了保持简洁,秋秋只放必须的样式框架,妈妈根据实际补充 */
|
||||||
|
body {
|
||||||
|
background: #0a0e17;
|
||||||
|
color: #e1e5ee;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
/* 其他样式保持不变 */
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- 总览页容器 -->
|
<h1>🚀 HoloLake 开发者实时看板</h1>
|
||||||
<div id="overview-container" class="overview-grid fade-in">
|
<div id="stats-container"></div>
|
||||||
<header class="dashboard-header">
|
<div id="developers-container"></div>
|
||||||
<h1>⚡ 开发者实时看板 ⚡</h1>
|
<div id="leaderboard-container"></div>
|
||||||
<p>DEV-004 · 之之 · 十三连胜追平桔子 · EL-6</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- 搜索和筛选区 -->
|
|
||||||
<div class="controls-section">
|
|
||||||
<input type="text" id="search-input" placeholder="🔍 搜索开发者姓名/ID/模块..." class="search-box">
|
|
||||||
<div class="filter-buttons">
|
|
||||||
<button class="filter-btn active" data-filter="all">全部</button>
|
|
||||||
<button class="filter-btn" data-filter="active">进行中</button>
|
|
||||||
<button class="filter-btn" data-filter="completed">已完成</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 开发者卡片网格 -->
|
|
||||||
<div id="developers-grid" class="developers-grid"></div>
|
|
||||||
|
|
||||||
<!-- 雷达图+排行榜区 -->
|
|
||||||
<div class="stats-section">
|
|
||||||
<div class="radar-container">
|
|
||||||
<h2>团队PCA雷达</h2>
|
|
||||||
<canvas id="team-radar" width="400" height="300"></canvas>
|
|
||||||
</div>
|
|
||||||
<div class="ranking-container">
|
|
||||||
<h2>连胜排行榜</h2>
|
|
||||||
<div id="ranking-list"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 详情页容器 -->
|
<!-- ⚠️ 重点:脚本引入顺序必须正确! -->
|
||||||
<div id="detail-container" style="display: none;"></div>
|
<!-- 1. API客户端最先加载 -->
|
||||||
|
<script src="api.js"></script>
|
||||||
<script src="radar.js"></script>
|
<!-- 2. 然后是原有的业务逻辑脚本 -->
|
||||||
<script src="components.js"></script>
|
|
||||||
<script src="detail.js"></script>
|
|
||||||
<script src="main.js"></script>
|
<script src="main.js"></script>
|
||||||
|
<script src="components.js"></script>
|
||||||
|
<!-- 3. 初始化引擎最后加载 -->
|
||||||
|
<script src="api-init.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.5, user-scalable=yes">
|
||||||
|
<title>开发者实时看板 · 环节0~4 · 之之</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<link rel="stylesheet" href="components.css">
|
||||||
|
<link rel="stylesheet" href="detail.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- 总览页容器 -->
|
||||||
|
<div id="overview-container" class="overview-grid fade-in">
|
||||||
|
<header class="dashboard-header">
|
||||||
|
<h1>⚡ 开发者实时看板 ⚡</h1>
|
||||||
|
<p>DEV-004 · 之之 · 十三连胜追平桔子 · EL-6</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- 搜索和筛选区 -->
|
||||||
|
<div class="controls-section">
|
||||||
|
<input type="text" id="search-input" placeholder="🔍 搜索开发者姓名/ID/模块..." class="search-box">
|
||||||
|
<div class="filter-buttons">
|
||||||
|
<button class="filter-btn active" data-filter="all">全部</button>
|
||||||
|
<button class="filter-btn" data-filter="active">进行中</button>
|
||||||
|
<button class="filter-btn" data-filter="completed">已完成</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 开发者卡片网格 -->
|
||||||
|
<div id="developers-grid" class="developers-grid"></div>
|
||||||
|
|
||||||
|
<!-- 雷达图+排行榜区 -->
|
||||||
|
<div class="stats-section">
|
||||||
|
<div class="radar-container">
|
||||||
|
<h2>团队PCA雷达</h2>
|
||||||
|
<canvas id="team-radar" width="400" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="ranking-container">
|
||||||
|
<h2>连胜排行榜</h2>
|
||||||
|
<div id="ranking-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情页容器 -->
|
||||||
|
<div id="detail-container" style="display: none;"></div>
|
||||||
|
|
||||||
|
<script src="radar.js"></script>
|
||||||
|
<script src="components.js"></script>
|
||||||
|
<script src="detail.js"></script>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue