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 = {
|
||||
// API基础地址(页页的接口)
|
||||
BASE_URL: 'https://guanghulab.com/api',
|
||||
|
||||
// 是否使用真实API(false = 使用模拟数据)
|
||||
USE_REAL_API: false,
|
||||
|
||||
// API端点
|
||||
endpoints: {
|
||||
teamStatus: '/dashboard/team-status',
|
||||
developerDetail: '/dashboard/dev/', // + id
|
||||
modules: '/dashboard/modules'
|
||||
}
|
||||
const API = {
|
||||
// ====== 配置区 ======
|
||||
// 页页的后端API地址(如果地址不对,问知秋确认)
|
||||
BASE_URL: 'https://guanghulab.com/api/devboard',
|
||||
TIMEOUT: 8000,
|
||||
FALLBACK: true, // API挂了自动切换模拟数据
|
||||
REFRESH_MS: 30000, // 30秒自动刷新
|
||||
_cache: {}
|
||||
};
|
||||
|
||||
// 检查API是否可用
|
||||
async function checkApiAvailability() {
|
||||
if (!CONFIG.USE_REAL_API) return false;
|
||||
|
||||
// ====== 通用请求(带超时+缓存+降级) =====================
|
||||
async function apiFetch(path) {
|
||||
const url = API.BASE_URL + path;
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), API.TIMEOUT);
|
||||
try {
|
||||
const response = await fetch(`${CONFIG.BASE_URL}/health`, {
|
||||
method: 'HEAD',
|
||||
mode: 'cors',
|
||||
cache: 'no-cache'
|
||||
const res = await fetch(url, {
|
||||
signal: ctrl.signal,
|
||||
headers: { 'Accept': 'application/json' }
|
||||
});
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
console.log('API不可用,使用模拟数据');
|
||||
clearTimeout(timer);
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取团队状态
|
||||
async function getTeamStatus() {
|
||||
if (CONFIG.USE_REAL_API) {
|
||||
try {
|
||||
const response = await fetch(`${CONFIG.BASE_URL}${CONFIG.endpoints.teamStatus}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('API请求失败,使用模拟数据');
|
||||
}
|
||||
// ========== 模拟数据(降级保护) ==========
|
||||
function getMockData(path) {
|
||||
var devs = [
|
||||
{ dev_id:'DEV-001', name:'页页', module:'BC-集成', status:'进行中', streak:5, el:'EL-6', pca:{EXE:75,TEC:60,SYS:70,COL:80,INI:65} },
|
||||
{ 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} },
|
||||
{ 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} },
|
||||
{ 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} },
|
||||
{ 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} },
|
||||
{ dev_id:'DEV-011', name:'匆匆那年',module:'M16', status:'等待中', streak:1, el:'EL-3', pca:{EXE:50,TEC:30,SYS:40,COL:55,INI:45} },
|
||||
{ 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];
|
||||
}
|
||||
|
||||
// 返回模拟数据
|
||||
return {
|
||||
developers: window.getDevelopers ? window.getDevelopers() : []
|
||||
};
|
||||
if (path === '/stats') return stats;
|
||||
if (path === '/leaderboard') return devs.slice().sort(function(a,b){ return b.streak - a.streak; });
|
||||
return [];
|
||||
}
|
||||
|
||||
// 获取开发者详情
|
||||
async getDeveloperDetail(devId) {
|
||||
if (CONFIG.USE_REAL_API) {
|
||||
try {
|
||||
const response = await fetch(`${CONFIG.BASE_URL}${CONFIG.endpoints.developerDetail}${devId}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('API请求失败,使用模拟数据');
|
||||
}
|
||||
// ========== API状态指示器 ==========
|
||||
function showApiStatus(online) {
|
||||
var el = document.getElementById('api-status');
|
||||
if (!el) {
|
||||
el = document.createElement('div');
|
||||
el.id = 'api-status';
|
||||
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;';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
if (online) {
|
||||
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>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<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">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>开发者实时看板 · HoloLake</title>
|
||||
<style>
|
||||
/* 这里保留你原有的样式,如果样式比较多,建议去复制原有的内容 */
|
||||
/* 为了保持简洁,秋秋只放必须的样式框架,妈妈根据实际补充 */
|
||||
body {
|
||||
background: #0a0e17;
|
||||
color: #e1e5ee;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
/* 其他样式保持不变 */
|
||||
</style>
|
||||
</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>
|
||||
<h1>🚀 HoloLake 开发者实时看板</h1>
|
||||
<div id="stats-container"></div>
|
||||
<div id="developers-container"></div>
|
||||
<div id="leaderboard-container"></div>
|
||||
|
||||
<!-- 详情页容器 -->
|
||||
<div id="detail-container" style="display: none;"></div>
|
||||
|
||||
<script src="radar.js"></script>
|
||||
<script src="components.js"></script>
|
||||
<script src="detail.js"></script>
|
||||
<!-- ⚠️ 重点:脚本引入顺序必须正确! -->
|
||||
<!-- 1. API客户端最先加载 -->
|
||||
<script src="api.js"></script>
|
||||
<!-- 2. 然后是原有的业务逻辑脚本 -->
|
||||
<script src="main.js"></script>
|
||||
<script src="components.js"></script>
|
||||
<!-- 3. 初始化引擎最后加载 -->
|
||||
<script src="api-init.js"></script>
|
||||
</body>
|
||||
</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