diff --git a/modules/devboard/api.js b/modules/devboard/api.js
new file mode 100644
index 00000000..87481710
--- /dev/null
+++ b/modules/devboard/api.js
@@ -0,0 +1,79 @@
+// api.js - API配置和模拟数据切换
+
+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'
+ }
+};
+
+// 检查API是否可用
+async function checkApiAvailability() {
+ if (!CONFIG.USE_REAL_API) return false;
+
+ try {
+ const response = await fetch(`${CONFIG.BASE_URL}/health`, {
+ method: 'HEAD',
+ mode: 'cors',
+ cache: 'no-cache'
+ });
+ return response.ok;
+ } catch (error) {
+ console.log('API不可用,使用模拟数据');
+ 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请求失败,使用模拟数据');
+ }
+ }
+
+ // 返回模拟数据
+ return {
+ developers: window.getDevelopers ? window.getDevelopers() : []
+ };
+}
+
+// 获取开发者详情
+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请求失败,使用模拟数据');
+ }
+ }
+
+ // 返回模拟数据
+ return window.getDeveloperById ? window.getDeveloperById(devId) : null;
+}
+
+// 导出配置
+window.API = {
+ CONFIG,
+ checkApiAvailability,
+ getTeamStatus,
+ getDeveloperDetail
+};
diff --git a/modules/devboard/components.css b/modules/devboard/components.css
new file mode 100644
index 00000000..fd16ad31
--- /dev/null
+++ b/modules/devboard/components.css
@@ -0,0 +1,156 @@
+/* components.css - 组件样式 */
+
+/* 开发者卡片 */
+.dev-card {
+ background: linear-gradient(145deg, #1e2436, #161b28);
+ border-radius: 20px;
+ padding: 20px;
+ border: 1px solid #2a3040;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ cursor: pointer;
+ position: relative;
+ overflow: hidden;
+}
+
+.dev-card:hover {
+ transform: translateY(-8px) scale(1.02);
+ border-color: #4a9eff;
+ box-shadow: 0 20px 30px -10px rgba(74, 158, 255, 0.3);
+}
+
+.dev-card:focus-visible {
+ outline: 3px solid #ffd700;
+ outline-offset: 2px;
+}
+
+.dev-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
+ transition: left 0.5s;
+}
+
+.dev-card:hover::before {
+ left: 100%;
+}
+
+.dev-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 15px;
+ font-size: 14px;
+}
+
+.dev-id {
+ color: #4a9eff;
+ font-weight: bold;
+ font-family: monospace;
+}
+
+.dev-wins {
+ background: rgba(255, 215, 0, 0.1);
+ padding: 4px 8px;
+ border-radius: 20px;
+ color: #ffd700;
+}
+
+.dev-name {
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 10px;
+ color: #fff;
+}
+
+.dev-details {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 15px;
+ font-size: 14px;
+}
+
+.dev-el {
+ background: #2a3040;
+ padding: 4px 8px;
+ border-radius: 12px;
+ color: #4a9eff;
+}
+
+.dev-module {
+ background: #2a3040;
+ padding: 4px 8px;
+ border-radius: 12px;
+ color: #a0a0a0;
+}
+
+.dev-pca {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 5px;
+ font-size: 11px;
+}
+
+.pca-mini {
+ text-align: center;
+ padding: 4px 0;
+ border-radius: 8px;
+ background: rgba(0,0,0,0.3);
+}
+
+.pca-mini.EXE { color: #4CAF50; }
+.pca-mini.TEC { color: #2196F3; }
+.pca-mini.SYS { color: #9C27B0; }
+.pca-mini.COL { color: #FF9800; }
+.pca-mini.INI { color: #f44336; }
+
+/* 排行榜项 */
+.ranking-item {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 15px;
+ border-bottom: 1px solid #2a3040;
+ transition: background 0.2s;
+}
+
+.ranking-item:hover {
+ background: #1e2436;
+}
+
+.rank-number {
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #2a3040;
+ border-radius: 50%;
+ font-weight: bold;
+ color: #4a9eff;
+}
+
+.rank-1 .rank-number { background: #ffd700; color: #000; }
+.rank-2 .rank-number { background: #c0c0c0; color: #000; }
+.rank-3 .rank-number { background: #cd7f32; color: #000; }
+
+.rank-info {
+ flex: 1;
+}
+
+.rank-name {
+ font-weight: bold;
+ color: #fff;
+}
+
+.rank-id {
+ font-size: 12px;
+ color: #888;
+}
+
+.rank-wins {
+ font-weight: bold;
+ color: #ffd700;
+}
diff --git a/modules/devboard/components.js b/modules/devboard/components.js
new file mode 100644
index 00000000..5f033e92
--- /dev/null
+++ b/modules/devboard/components.js
@@ -0,0 +1,146 @@
+// components.js - 组件渲染函数
+
+// 模拟数据
+const mockDevelopers = [
+ { id: 'DEV-001', name: '小明', pca: { EXE: 85, TEC: 72, SYS: 68, COL: 90, INI: 78 }, wins: 5, el: 'EL-6', module: 'M-DEVBOARD' },
+ { id: 'DEV-002', name: '小红', pca: { EXE: 78, TEC: 88, SYS: 82, COL: 75, INI: 92 }, wins: 8, el: 'EL-7', module: 'M-FRONTEND' },
+ { id: 'DEV-003', name: '小刚', pca: { EXE: 92, TEC: 65, SYS: 70, COL: 68, INI: 85 }, wins: 3, el: 'EL-5', module: 'M-BACKEND' },
+ { id: 'DEV-004', name: '之之', pca: { EXE: 75, TEC: 58, SYS: 82, COL: 82, INI: 85 }, wins: 13, el: 'EL-6', module: 'M-DEVBOARD' }
+];
+
+// 获取所有开发者
+function getDevelopers() {
+ return mockDevelopers;
+}
+
+// 根据ID获取开发者
+function getDeveloperById(id) {
+ return mockDevelopers.find(d => d.id === id) || mockDevelopers[0];
+}
+
+// 渲染开发者卡片
+function renderDeveloperCards(developers) {
+ return developers.map(dev => `
+
+
+
${dev.name}
+
+ ${dev.el}
+ ${dev.module}
+
+
+ ${Object.entries(dev.pca).map(([key, val]) =>
+ `${key}:${val}`
+ ).join('')}
+
+
+ `).join('');
+}
+
+// 渲染排行榜
+function renderRanking(developers) {
+ // 按连胜数排序
+ const sorted = [...developers].sort((a, b) => b.wins - a.wins);
+
+ return sorted.map((dev, index) => {
+ const rankClass = index === 0 ? 'rank-1' : index === 1 ? 'rank-2' : index === 2 ? 'rank-3' : '';
+ return `
+
+
${index + 1}
+
+
${dev.name}
+
${dev.id}
+
+
${dev.wins}连胜
+
+ `;
+ }).join('');
+}
+
+// 刷新看板
+function refreshDashboard() {
+ const developers = getDevelopers();
+
+ // 渲染卡片
+ const grid = document.getElementById('developers-grid');
+ if (grid) {
+ grid.innerHTML = renderDeveloperCards(developers);
+ }
+
+ // 渲染排行榜
+ const ranking = document.getElementById('ranking-list');
+ if (ranking) {
+ ranking.innerHTML = renderRanking(developers);
+ }
+
+ // 绘制团队雷达图(平均分)
+ const avgPca = {
+ EXE: Math.round(developers.reduce((sum, d) => sum + d.pca.EXE, 0) / developers.length),
+ TEC: Math.round(developers.reduce((sum, d) => sum + d.pca.TEC, 0) / developers.length),
+ SYS: Math.round(developers.reduce((sum, d) => sum + d.pca.SYS, 0) / developers.length),
+ COL: Math.round(developers.reduce((sum, d) => sum + d.pca.COL, 0) / developers.length),
+ INI: Math.round(developers.reduce((sum, d) => sum + d.pca.INI, 0) / developers.length)
+ };
+
+ drawRadarChart('team-radar', avgPca);
+}
+
+// 搜索筛选
+function filterDevelopers(searchTerm, filterStatus) {
+ let developers = getDevelopers();
+
+ // 搜索过滤
+ if (searchTerm) {
+ const term = searchTerm.toLowerCase();
+ developers = developers.filter(dev =>
+ dev.name.toLowerCase().includes(term) ||
+ dev.id.toLowerCase().includes(term) ||
+ dev.module.toLowerCase().includes(term)
+ );
+ }
+
+ // 状态过滤(模拟,实际需要真实数据)
+ if (filterStatus !== 'all') {
+ // 这里简化处理,实际应该根据模块状态筛选
+ developers = developers.filter(dev => {
+ if (filterStatus === 'active') return dev.wins < 10;
+ if (filterStatus === 'completed') return dev.wins >= 10;
+ return true;
+ });
+ }
+
+ return developers;
+}
+
+// 导出函数
+window.getDevelopers = getDevelopers;
+window.getDeveloperById = getDeveloperById;
+window.renderDeveloperCards = renderDeveloperCards;
+window.renderRanking = renderRanking;
+window.refreshDashboard = refreshDashboard;
+window.filterDevelopers = filterDevelopers;
+
+// 增强版排行榜渲染(带数字滚动支持)
+function renderRankingWithAnimation(developers) {
+ const sorted = [...developers].sort((a, b) => b.wins - a.wins);
+
+ return sorted.map((dev, index) => {
+ const rankClass = index === 0 ? 'rank-1' : index === 1 ? 'rank-2' : index === 2 ? 'rank-3' : '';
+ return `
+
+
${index + 1}
+
+
${dev.name}
+
${dev.id}
+
+
${dev.wins}连胜
+
+ `;
+ }).join('');
+}
+
+// 覆盖原函数
+window.renderRanking = renderRankingWithAnimation;
diff --git a/modules/devboard/detail.css b/modules/devboard/detail.css
new file mode 100644
index 00000000..385c4846
--- /dev/null
+++ b/modules/devboard/detail.css
@@ -0,0 +1,358 @@
+/* detail.css - 详情页专用样式 */
+
+/* 详情页容器 */
+#detail-container {
+ display: none;
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+ min-height: 100vh;
+ padding: 20px;
+ animation: fadeIn 0.3s ease;
+}
+
+/* 详情页头部 */
+.detail-header {
+ margin-bottom: 30px;
+ max-width: 1200px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.back-button {
+ background: rgba(255, 255, 255, 0.1);
+ border: 2px solid #4a9eff;
+ color: #fff;
+ padding: 12px 24px;
+ border-radius: 30px;
+ font-size: 16px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(5px);
+}
+
+.back-button:hover {
+ background: #4a9eff;
+ transform: translateX(-5px);
+ box-shadow: 0 0 20px rgba(74, 158, 255, 0.5);
+}
+
+.back-button:focus-visible {
+ outline: 3px solid #ffd700;
+ outline-offset: 2px;
+}
+
+/* 详情页内容区 */
+.detail-content {
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.detail-section {
+ background: rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(10px);
+ border-radius: 20px;
+ padding: 25px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
+}
+
+.detail-section h2 {
+ color: #4a9eff;
+ font-size: 20px;
+ margin-bottom: 20px;
+ border-bottom: 2px solid rgba(74, 158, 255, 0.3);
+ padding-bottom: 10px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+/* 开发者大头卡片(大尺寸) */
+.developer-card-large .dev-card {
+ transform: scale(1);
+ background: linear-gradient(145deg, #1e2436, #161b28);
+ border: 2px solid #4a9eff;
+ margin-bottom: 0;
+}
+
+.developer-card-large .dev-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 20px 30px -10px rgba(74, 158, 255, 0.5);
+}
+
+/* PCA雷达图容器 */
+.radar-large-container {
+ height: 300px;
+ margin-bottom: 20px;
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 12px;
+ padding: 10px;
+}
+
+.radar-large-container canvas {
+ width: 100%;
+ height: 100%;
+}
+
+/* PCA分数标签 */
+.pca-scores {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 15px;
+ justify-content: center;
+ margin-top: 20px;
+}
+
+.score-tag {
+ padding: 8px 16px;
+ border-radius: 20px;
+ font-weight: bold;
+ font-size: 14px;
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid;
+ transition: transform 0.2s;
+}
+
+.score-tag:hover {
+ transform: scale(1.05);
+}
+
+.score-tag.EXE { border-color: #4CAF50; color: #4CAF50; }
+.score-tag.TEC { border-color: #2196F3; color: #2196F3; }
+.score-tag.SYS { border-color: #9C27B0; color: #9C27B0; }
+.score-tag.COL { border-color: #FF9800; color: #FF9800; }
+.score-tag.INI { border-color: #f44336; color: #f44336; }
+
+/* 五维进度条 */
+.progress-bars-container {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.progress-item {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ padding: 10px;
+ background: rgba(0, 0, 0, 0.2);
+ border-radius: 12px;
+ transition: transform 0.2s ease, background 0.2s;
+}
+
+.progress-item:hover {
+ transform: translateX(10px);
+ background: rgba(0, 0, 0, 0.3);
+}
+
+.progress-label {
+ width: 100px;
+ display: flex;
+ justify-content: space-between;
+ font-weight: bold;
+}
+
+.dim-name {
+ color: #fff;
+}
+
+.dim-score {
+ color: #ffd700;
+ font-family: monospace;
+}
+
+.progress-bar-bg {
+ flex: 1;
+ height: 24px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 12px;
+ overflow: hidden;
+ position: relative;
+}
+
+.progress-bar-fill {
+ height: 100%;
+ border-radius: 12px;
+ transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
+ position: relative;
+}
+
+.progress-bar-fill::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
+ animation: shimmer 2s infinite;
+}
+
+.progress-dim-code {
+ width: 50px;
+ text-align: right;
+ font-family: monospace;
+ color: #888;
+ font-size: 14px;
+}
+
+/* 模块历史列表 */
+.module-history-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.module-item {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ padding: 15px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ transition: background 0.2s ease;
+}
+
+.module-item:hover {
+ background: rgba(255, 255, 255, 0.05);
+ transform: scale(1.01);
+}
+
+.module-code {
+ font-family: monospace;
+ color: #4a9eff;
+ font-weight: bold;
+ width: 150px;
+}
+
+.module-name {
+ flex: 1;
+ color: #fff;
+}
+
+.module-status {
+ padding: 4px 12px;
+ border-radius: 20px;
+ font-size: 12px;
+ font-weight: bold;
+ width: 80px;
+ text-align: center;
+}
+
+.status-已完成 { background: rgba(76, 175, 80, 0.2); color: #4CAF50; border: 1px solid #4CAF50; }
+.status-进行中 { background: rgba(255, 152, 0, 0.2); color: #FF9800; border: 1px solid #FF9800; }
+.status-待开始 { background: rgba(158, 158, 158, 0.2); color: #9E9E9E; border: 1px solid #9E9E9E; }
+
+.module-date {
+ color: #888;
+ font-size: 14px;
+ width: 100px;
+ font-family: monospace;
+}
+
+/* 趋势图容器 */
+.trend-chart-container {
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 12px;
+ padding: 20px;
+ position: relative;
+}
+
+/* 无数据提示 */
+.no-data {
+ text-align: center;
+ color: #888;
+ padding: 40px;
+ font-style: italic;
+}
+
+/* 动画定义 */
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(20px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes fadeOut {
+ from { opacity: 1; transform: translateY(0); }
+ to { opacity: 0; transform: translateY(20px); }
+}
+
+@keyframes shimmer {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(100%); }
+}
+
+.fade-in {
+ animation: fadeIn 0.3s ease forwards;
+}
+
+.fade-out {
+ animation: fadeOut 0.3s ease forwards;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .detail-content {
+ gap: 15px;
+ }
+
+ .detail-section {
+ padding: 15px;
+ }
+
+ .progress-item {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .progress-label {
+ width: 100%;
+ }
+
+ .progress-bar-bg {
+ width: 100%;
+ }
+
+ .progress-dim-code {
+ width: 100%;
+ text-align: left;
+ }
+
+ .module-item {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ }
+
+ .module-code {
+ width: auto;
+ }
+
+ .module-name {
+ width: 100%;
+ }
+
+ .module-status {
+ width: auto;
+ }
+
+ .module-date {
+ width: auto;
+ }
+
+ .pca-scores {
+ gap: 8px;
+ }
+
+ .score-tag {
+ padding: 4px 12px;
+ font-size: 12px;
+ }
+
+ .radar-large-container {
+ height: 250px;
+ }
+}
diff --git a/modules/devboard/detail.js b/modules/devboard/detail.js
new file mode 100644
index 00000000..7996cbb6
--- /dev/null
+++ b/modules/devboard/detail.js
@@ -0,0 +1,354 @@
+// detail.js - 开发者详情页渲染器
+
+let currentDevId = null;
+
+// 显示详情页
+function showDetailPage(devId) {
+ currentDevId = devId;
+
+ const overview = document.getElementById('overview-container');
+ const detail = document.getElementById('detail-container');
+
+ if (!overview || !detail) {
+ console.error('找不到容器元素');
+ return;
+ }
+
+ // 淡出总览页
+ overview.classList.add('fade-out');
+
+ setTimeout(() => {
+ overview.style.display = 'none';
+ overview.classList.remove('fade-out');
+
+ // 渲染详情页数据
+ renderDetailPage(devId);
+
+ detail.style.display = 'block';
+ detail.classList.add('fade-in');
+
+ // 滚动到顶部
+ window.scrollTo(0, 0);
+ }, 300);
+}
+
+// 返回总览页
+function hideDetailPage() {
+ const overview = document.getElementById('overview-container');
+ const detail = document.getElementById('detail-container');
+
+ detail.classList.add('fade-out');
+
+ setTimeout(() => {
+ detail.style.display = 'none';
+ detail.classList.remove('fade-out');
+
+ overview.style.display = 'block';
+ overview.classList.add('fade-in');
+
+ // 刷新总览页数据
+ refreshDashboard();
+ }, 300);
+}
+
+// 渲染详情页
+function renderDetailPage(devId) {
+ const dev = getDeveloperById(devId);
+ if (!dev) return;
+
+ const container = document.getElementById('detail-container');
+
+ // 生成五维进度条
+ const progressBars = renderPCAProgressBars(dev.pca);
+
+ // 获取模块历史
+ const modules = getModuleHistory(devId);
+ const moduleList = renderModuleHistory(modules);
+
+ // 获取连胜趋势数据
+ const trendData = getWinTrendData(devId);
+
+ container.innerHTML = `
+
+
+
+
+
+
+
${dev.name}
+
+ ${dev.el}
+ ${dev.module}
+
+
+ ${Object.entries(dev.pca).map(([key, val]) =>
+ `${key}:${val}`
+ ).join('')}
+
+
+
+
+
+
+
📊 完整PCA雷达图
+
+ ${renderRadarChart(dev.pca, 'large')}
+
+
+ ${Object.entries(dev.pca).map(([dim, score]) =>
+ `${dim}: ${score}`
+ ).join('')}
+
+
+
+
+
+
📈 五维进度条
+
+ ${progressBars}
+
+
+
+
+
+
📋 模块历史
+
+ ${moduleList}
+
+
+
+
+
+
+ `;
+
+ // 绘制趋势图
+ setTimeout(() => drawTrendChart('trend-canvas', trendData), 100);
+}
+
+// 渲染PCA五维进度条
+function renderPCAProgressBars(pca) {
+ const dimensions = [
+ { key: 'EXE', label: '执行力', color: '#4CAF50' },
+ { key: 'TEC', label: '技术深度', color: '#2196F3' },
+ { key: 'SYS', label: '系统思维', color: '#9C27B0' },
+ { key: 'COL', label: '协作力', color: '#FF9800' },
+ { key: 'INI', label: '主动性', color: '#f44336' }
+ ];
+
+ return dimensions.map(dim => {
+ const score = pca[dim.key] || 0;
+
+ return `
+
+
+ ${dim.label}
+ ${score}
+
+
+
${dim.key}
+
+ `;
+ }).join('');
+}
+
+// 渲染模块历史列表
+function renderModuleHistory(modules) {
+ if (!modules || modules.length === 0) {
+ return '暂无模块历史
';
+ }
+
+ return `
+
+ ${modules.map(m => `
+ -
+ ${m.code}
+ ${m.name}
+ ${m.status}
+ ${m.completedDate || m.startDate || '-'}
+
+ `).join('')}
+
+ `;
+}
+
+// 获取模块历史(模拟数据)
+function getModuleHistory(devId) {
+ const histories = {
+ 'DEV-001': [
+ { code: 'M-DEVBOARD-001', name: '开发者看板·环节0~2', status: '已完成', completedDate: '2026-03-10' },
+ { code: 'M-FRONTEND-003', name: '前端基础组件', status: '已完成', completedDate: '2026-03-08' },
+ { code: 'M-API-002', name: 'API对接练习', status: '进行中', completedDate: null }
+ ],
+ 'DEV-002': [
+ { code: 'M-FRONTEND-001', name: '前端架构设计', status: '已完成', completedDate: '2026-03-09' },
+ { code: 'M-UI-005', name: '组件库开发', status: '已完成', completedDate: '2026-03-07' },
+ { code: 'M-TEST-003', name: '单元测试编写', status: '待开始', completedDate: null }
+ ],
+ 'DEV-003': [
+ { code: 'M-BACKEND-002', name: 'API服务搭建', status: '已完成', completedDate: '2026-03-11' },
+ { code: 'M-DB-001', name: '数据库设计', status: '已完成', completedDate: '2026-03-09' },
+ { code: 'M-DEPLOY-002', name: '容器化部署', status: '进行中', completedDate: null }
+ ],
+ 'DEV-004': [
+ { code: 'M-DEVBOARD-001', name: '开发者看板·环节0~2', status: '已完成', completedDate: '2026-03-11' },
+ { code: 'M-CANVAS-005', name: 'Canvas雷达图绘制', status: '已完成', completedDate: '2026-03-09' },
+ { code: 'M-COMPONENTS-003', name: '组件化思维训练', status: '已完成', completedDate: '2026-03-07' },
+ { code: 'M-DEPLOY-001', name: '静态部署练习', status: '待开始', completedDate: null }
+ ]
+ };
+ return histories[devId] || histories['DEV-001'];
+}
+
+// 获取连胜趋势数据
+function getWinTrendData(devId) {
+ const trends = {
+ 'DEV-001': [
+ { date: '03-06', value: 3 },
+ { date: '03-07', value: 3 },
+ { date: '03-08', value: 4 },
+ { date: '03-09', value: 4 },
+ { date: '03-10', value: 5 },
+ { date: '03-11', value: 5 },
+ { date: '03-12', value: 5 }
+ ],
+ 'DEV-002': [
+ { date: '03-06', value: 5 },
+ { date: '03-07', value: 6 },
+ { date: '03-08', value: 6 },
+ { date: '03-09', value: 7 },
+ { date: '03-10', value: 7 },
+ { date: '03-11', value: 8 },
+ { date: '03-12', value: 8 }
+ ],
+ 'DEV-003': [
+ { date: '03-06', value: 1 },
+ { date: '03-07', value: 1 },
+ { date: '03-08', value: 2 },
+ { date: '03-09', value: 2 },
+ { date: '03-10', value: 3 },
+ { date: '03-11', value: 3 },
+ { date: '03-12', value: 3 }
+ ],
+ 'DEV-004': [
+ { date: '03-06', value: 8 },
+ { date: '03-07', value: 9 },
+ { date: '03-08', value: 9 },
+ { date: '03-09', value: 10 },
+ { date: '03-10', value: 11 },
+ { date: '03-11', value: 12 },
+ { date: '03-12', value: 13 }
+ ]
+ };
+ return trends[devId] || trends['DEV-001'];
+}
+
+// 绘制趋势图
+function drawTrendChart(canvasId, data) {
+ const canvas = document.getElementById(canvasId);
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ const width = canvas.width;
+ const height = canvas.height;
+ const padding = 40;
+
+ ctx.clearRect(0, 0, width, height);
+
+ if (!data || data.length < 2) {
+ ctx.fillStyle = '#888';
+ ctx.font = '14px monospace';
+ ctx.fillText('暂无连胜数据', padding, height/2);
+ return;
+ }
+
+ // 计算坐标范围
+ const values = data.map(d => d.value);
+ const maxValue = Math.max(...values) + 1;
+ const minValue = Math.max(0, Math.min(...values) - 1);
+ const xStep = (width - 2 * padding) / (data.length - 1);
+
+ // 绘制网格线
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ for (let i = 0; i <= 5; i++) {
+ const y = padding + (i * (height - 2 * padding) / 5);
+ ctx.moveTo(padding, y);
+ ctx.lineTo(width - padding, y);
+ }
+ ctx.strokeStyle = '#444';
+ ctx.stroke();
+
+ // 绘制轴线
+ ctx.beginPath();
+ ctx.strokeStyle = '#666';
+ ctx.lineWidth = 2;
+ ctx.moveTo(padding, padding);
+ ctx.lineTo(padding, height - padding);
+ ctx.lineTo(width - padding, height - padding);
+ ctx.stroke();
+
+ // 绘制折线
+ ctx.beginPath();
+ ctx.strokeStyle = '#4CAF50';
+ ctx.lineWidth = 3;
+
+ data.forEach((point, i) => {
+ const x = padding + i * xStep;
+ const y = padding + (height - 2 * padding) * (1 - (point.value - minValue) / (maxValue - minValue));
+
+ if (i === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ });
+ ctx.stroke();
+
+ // 绘制数据点
+ data.forEach((point, i) => {
+ const x = padding + i * xStep;
+ const y = padding + (height - 2 * padding) * (1 - (point.value - minValue) / (maxValue - minValue));
+
+ ctx.beginPath();
+ ctx.arc(x, y, 6, 0, Math.PI * 2);
+ ctx.fillStyle = '#FFD700';
+ ctx.fill();
+ ctx.strokeStyle = '#fff';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ // 显示数值
+ ctx.fillStyle = '#fff';
+ ctx.font = 'bold 12px monospace';
+ ctx.textAlign = 'center';
+ ctx.fillText(point.value, x, y - 15);
+
+ // 显示日期
+ ctx.fillStyle = '#aaa';
+ ctx.font = '10px monospace';
+ ctx.fillText(point.date, x, height - padding + 20);
+ });
+}
+
+// 导出函数
+window.showDetailPage = showDetailPage;
+window.hideDetailPage = hideDetailPage;
+window.getModuleHistory = getModuleHistory;
+window.getWinTrendData = getWinTrendData;
diff --git a/modules/devboard/index.html b/modules/devboard/index.html
index e9deb195..f671be0a 100644
--- a/modules/devboard/index.html
+++ b/modules/devboard/index.html
@@ -1,49 +1,53 @@
-
+
-
- HoloLake 开发者实时看板
-
-
-
+
+ 开发者实时看板 · 环节0~4 · 之之
+
+
+
-
-
-
-