<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的工作台 - 超协体</title>
<script src="/js/auth.js"></script>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<script src="/js/websocket-client.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.dashboard-container {
max-width: 1400px;
margin: 0 auto;
}
/* 顶部导航栏 */
.topbar {
background: white;
border-radius: 16px;
padding: 16px 24px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.topbar-left {
display: flex;
align-items: center;
gap: 16px;
}
.logo {
font-size: 24px;
font-weight: bold;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 18px;
}
.user-details {
display: flex;
flex-direction: column;
}
.username {
font-weight: 600;
color: #333;
}
.points {
font-size: 13px;
color: #666;
}
.points-value {
color: #ffa726;
font-weight: 600;
}
.btn-logout {
padding: 8px 16px;
background: #f5f5f5;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
color: #666;
transition: all 0.3s;
}
.btn-logout:hover {
background: #e0e0e0;
color: #333;
}
/* 导航菜单 */
.navbar-nav {
display: flex;
gap: 8px;
margin-left: 24px;
}
.nav-link {
padding: 8px 16px;
color: #666;
text-decoration: none;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s;
}
.nav-link:hover {
background: #f0f0f0;
color: #333;
}
.nav-link.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
@media (max-width: 900px) {
.navbar-nav {
display: none;
}
}
/* 网格布局 */
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 1200px) {
.grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
}
/* 卡片样式 */
.card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.card-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: #333;
display: flex;
align-items: center;
gap: 8px;
}
.card-icon {
font-size: 20px;
}
/* 快速操作卡片 */
.quick-actions {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}
@media (max-width: 768px) {
.quick-actions {
grid-template-columns: 1fr 1fr;
}
}
.action-btn {
padding: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 12px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: transform 0.2s, box-shadow 0.2s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
text-decoration: none;
text-align: center;
}
.action-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3);
}
.action-icon {
font-size: 24px;
}
/* 统计卡片 */
.stat-card {
text-align: center;
}
.stat-value {
font-size: 36px;
font-weight: bold;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 8px;
}
.stat-label {
color: #666;
font-size: 14px;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 40px 20px;
color: #999;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.3;
}
/* 加载状态 */
.loading {
text-align: center;
padding: 40px;
color: #999;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* 任务列表 */
.task-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.task-item {
padding: 16px;
background: #f8f9fa;
border-radius: 10px;
border-left: 4px solid #667eea;
cursor: pointer;
transition: all 0.3s;
text-decoration: none;
display: block;
}
.task-item:hover {
background: #f0f4ff;
transform: translateX(4px);
}
.task-title {
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.task-meta {
display: flex;
gap: 12px;
font-size: 12px;
color: #666;
}
.task-status {
padding: 2px 8px;
background: #e3f2fd;
color: #1976d2;
border-radius: 4px;
font-size: 11px;
}
.task-status.completed {
background: #e8f5e9;
color: #388e3c;
}
.task-status.in-progress {
background: #fff3e0;
color: #f57c00;
}
/* 五行能量条 */
.wuxing-bar {
margin-bottom: 12px;
}
.wuxing-label {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
font-size: 13px;
}
.wuxing-progress {
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.wuxing-fill {
height: 100%;
transition: width 0.5s;
}
.wuxing-fire { background: linear-gradient(90deg, #ff6b6b, #ff2e63); }
.wuxing-metal { background: linear-gradient(90deg, #ffd93d, #f9ca24); }
.wuxing-wood { background: linear-gradient(90deg, #6bff92, #06d6a0); }
.wuxing-water { background: linear-gradient(90deg, #00d9ff, #4facfe); }
.wuxing-earth { background: linear-gradient(90deg, #c77dff, #9d4edd); }
/* 候选者列表 */
.candidate-item {
padding: 16px;
background: #f8f9fa;
border-radius: 10px;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.candidate-info {
flex: 1;
}
.candidate-name {
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.candidate-meta {
font-size: 13px;
color: #666;
}
.candidate-actions {
display: flex;
gap: 8px;
}
.btn-approve, .btn-reject {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
transition: opacity 0.2s;
}
.btn-approve {
background: #4caf50;
color: white;
}
.btn-approve:hover {
opacity: 0.9;
}
.btn-reject {
background: #f44336;
color: white;
}
.btn-reject:hover {
opacity: 0.9;
}
/* 推荐任务样式 */
.recommended-tasks-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.recommended-tasks-card .card-title {
color: white;
}
.recommendation-badge {
font-size: 11px;
background: rgba(255,255,255,0.2);
padding: 4px 10px;
border-radius: 12px;
margin-left: auto;
font-weight: normal;
}
.recommended-tasks-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 12px;
}
.recommended-task-card {
background: rgba(255, 255, 255, 0.95);
color: #333;
padding: 16px;
border-radius: 12px;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
text-decoration: none;
display: block;
}
.recommended-task-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.rec-task-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
}
.rec-task-title {
font-weight: 600;
color: #333;
flex: 1;
margin-right: 10px;
line-height: 1.3;
}
.match-score {
display: inline-block;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
white-space: nowrap;
}
.growth-badge {
display: inline-block;
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
margin-left: 8px;
}
.rec-task-meta {
font-size: 12px;
color: #666;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.rec-task-meta span {
background: #f3f4f6;
padding: 2px 8px;
border-radius: 4px;
}
.rec-empty-state {
text-align: center;
padding: 30px;
color: rgba(255,255,255,0.8);
}
.recommended-tasks-card .loading {
color: rgba(255,255,255,0.8);
}
.recommended-tasks-card .spinner {
border-color: rgba(255,255,255,0.3);
border-top-color: white;
}
</style>
</head>
<body>
<div class="dashboard-container">
<!-- 顶部导航栏 -->
<div class="topbar">
<div class="topbar-left">
<div class="logo">超协体</div>
<nav class="navbar-nav">
<a href="/dashboard.html" class="nav-link active">工作台</a>
<a href="/task-create.html" class="nav-link">创建任务</a>
<a href="/my-tasks.html" class="nav-link">我的任务</a>
<a href="/crystal-library.html" class="nav-link">晶体库</a>
<a href="/members.html" class="nav-link">成员</a>
<a href="/profile.html" class="nav-link">画像</a>
</nav>
<div style="display: flex; align-items: center; gap: 6px; margin-left: 16px; padding: 6px 12px; background: #f0f4ff; border-radius: 8px;">
<span style="width: 8px; height: 8px; background: #10b981; border-radius: 50%; animation: pulse 2s infinite;"></span>
<span style="font-size: 13px; color: #666;"><span class="online-count">0</span> 人在线</span>
</div>
</div>
<div class="user-info">
<div class="avatar" id="userAvatar">U</div>
<div class="user-details">
<div class="username" id="username">加载中...</div>
<div class="points" style="cursor: pointer;" onclick="window.location.href='/member-detail.html?id=' + getUser()?.id + '&type=community'" title="点击查看个人详情">
积分:<span class="points-value" id="pointsBalance">--</span>
</div>
</div>
<button class="btn-logout" onclick="logout()">登出</button>
</div>
</div>
<!-- 快速操作区 -->
<div class="grid">
<div class="card">
<div class="card-title">
<span class="card-icon">⚡</span>
快速操作
</div>
<div class="quick-actions">
<a class="action-btn" href="/task-create.html">
<span class="action-icon">📝</span>
<span>创建任务</span>
</a>
<a class="action-btn" href="/my-tasks.html">
<span class="action-icon">📋</span>
<span>我的任务</span>
</a>
<button class="action-btn" onclick="issueTicketDialog()">
<span class="action-icon">🎫</span>
<span>发门票</span>
</button>
<a class="action-btn" href="/members.html">
<span class="action-icon">👥</span>
<span>查看成员</span>
</a>
<button class="action-btn" onclick="checkWuxingBalance()">
<span class="action-icon">☯️</span>
<span>五行平衡</span>
</button>
<a class="action-btn" href="/crystal-library.html">
<span class="action-icon">💡</span>
<span>浏览晶体</span>
</a>
<a class="action-btn" href="/profile.html">
<span class="action-icon">🎨</span>
<span>填写画像</span>
</a>
<a class="action-btn admin-only" href="/admin.html" id="adminPanelBtn" style="display: none; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);">
<span class="action-icon">⚙</span>
<span>管理后台</span>
</a>
</div>
</div>
<!-- 统计数据 -->
<div class="card stat-card">
<div class="card-title">
<span class="card-icon">📋</span>
我的任务
</div>
<div class="stat-value" id="myTaskCount">--</div>
<div class="stat-label">进行中的任务</div>
</div>
<div class="card stat-card">
<div class="card-title">
<span class="card-icon">👥</span>
团队成员
</div>
<div class="stat-value" id="memberCount">--</div>
<div class="stat-label">已注册成员</div>
</div>
<div class="card stat-card" onclick="window.location.href='/crystal-library.html'" style="cursor: pointer;">
<div class="card-title">
<span class="card-icon">💎</span>
我的晶体
</div>
<div class="stat-value" id="myCrystalCount">--</div>
<div class="stat-label">已凝结晶体</div>
</div>
<div class="card stat-card" onclick="window.location.href='/projects.html'" style="cursor: pointer;">
<div class="card-title">
<span class="card-icon">🚀</span>
我的项目
</div>
<div class="stat-value" id="myProjectCount">--</div>
<div class="stat-label">参与的项目</div>
</div>
</div>
<!-- 管理员专用:候选者管理 -->
<div class="grid" id="adminPanel" style="display: none;">
<div class="card" style="grid-column: span 2;">
<div class="card-title">
<span class="card-icon">🛡️</span>
候选者管理(管理员)
</div>
<div id="candidatesList">
<div class="loading">
<div class="spinner"></div>
正在加载候选者...
</div>
</div>
</div>
<!-- AI成员管理 -->
<div class="card" id="aiMembersCard">
<div class="card-title">
<span class="card-icon">🤖</span>
AI成员管理
</div>
<div class="ai-create-buttons" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px;">
<button onclick="createAIMember('code_master')" class="ai-type-btn" style="padding: 12px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 13px; transition: transform 0.2s;">
<div style="font-size: 20px; margin-bottom: 4px;">💻</div>
<div style="font-weight: 600;">CodeMaster</div>
<div style="font-size: 11px; opacity: 0.8;">代码开发</div>
</button>
<button onclick="createAIMember('doc_writer')" class="ai-type-btn" style="padding: 12px; background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 13px; transition: transform 0.2s;">
<div style="font-size: 20px; margin-bottom: 4px;">📝</div>
<div style="font-weight: 600;">DocWriter</div>
<div style="font-size: 11px; opacity: 0.8;">文档撰写</div>
</button>
<button onclick="createAIMember('analyst')" class="ai-type-btn" style="padding: 12px; background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 13px; transition: transform 0.2s;">
<div style="font-size: 20px; margin-bottom: 4px;">📊</div>
<div style="font-weight: 600;">Analyst</div>
<div style="font-size: 11px; opacity: 0.8;">数据分析</div>
</button>
<button onclick="createAIMember('coordinator')" class="ai-type-btn" style="padding: 12px; background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 13px; transition: transform 0.2s;">
<div style="font-size: 20px; margin-bottom: 4px;">🎯</div>
<div style="font-weight: 600;">Coordinator</div>
<div style="font-size: 11px; opacity: 0.8;">项目协调</div>
</button>
</div>
<div id="aiMembersList" style="max-height: 200px; overflow-y: auto;">
<div class="loading" style="padding: 20px;">
<div class="spinner"></div>
加载AI成员...
</div>
</div>
</div>
</div>
<!-- 为你推荐任务 -->
<div class="grid" id="recommendedTasksSection" style="display: none;">
<div class="card recommended-tasks-card" style="grid-column: span 3;">
<div class="card-title">
<span class="card-icon">🎯</span>
为你推荐
<span class="recommendation-badge">AI智能匹配</span>
</div>
<div id="recommendedTasksList" class="recommended-tasks-list">
<div class="loading">
<div class="spinner"></div>
正在分析匹配度...
</div>
</div>
</div>
</div>
<!-- 任务列表 & 五行分析 -->
<div class="grid">
<div class="card" style="grid-column: span 2;">
<div class="card-title">
<span class="card-icon">📌</span>
最近任务
</div>
<div id="taskList" class="task-list">
<div class="loading">
<div class="spinner"></div>
正在加载任务...
</div>
</div>
</div>
<div class="card">
<div class="card-title">
<span class="card-icon">☯️</span>
五行能量
</div>
<div id="wuxingEnergy">
<div class="wuxing-bar">
<div class="wuxing-label">
<span>🔥 火</span>
<span id="firePercent">20%</span>
</div>
<div class="wuxing-progress">
<div class="wuxing-fill wuxing-fire" id="fireFill" style="width: 20%"></div>
</div>
</div>
<div class="wuxing-bar">
<div class="wuxing-label">
<span>⚙️ 金</span>
<span id="metalPercent">20%</span>
</div>
<div class="wuxing-progress">
<div class="wuxing-fill wuxing-metal" id="metalFill" style="width: 20%"></div>
</div>
</div>
<div class="wuxing-bar">
<div class="wuxing-label">
<span>🌳 木</span>
<span id="woodPercent">20%</span>
</div>
<div class="wuxing-progress">
<div class="wuxing-fill wuxing-wood" id="woodFill" style="width: 20%"></div>
</div>
</div>
<div class="wuxing-bar">
<div class="wuxing-label">
<span>💧 水</span>
<span id="waterPercent">20%</span>
</div>
<div class="wuxing-progress">
<div class="wuxing-fill wuxing-water" id="waterFill" style="width: 20%"></div>
</div>
</div>
<div class="wuxing-bar">
<div class="wuxing-label">
<span>🏔️ 土</span>
<span id="earthPercent">20%</span>
</div>
<div class="wuxing-progress">
<div class="wuxing-fill wuxing-earth" id="earthFill" style="width: 20%"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const API_BASE = window.location.origin;
// 检查登录状态
if (!isLoggedIn()) {
window.location.href = '/login.html';
}
// 全局状态
let userStatus = null;
let userRole = null;
let aiEvaluation = null;
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', async () => {
await loadUserInfo();
await checkUserStatus();
await loadDashboardData();
// 如果是正式成员,加载推荐任务
if (userStatus === 'member') {
await loadRecommendedTasks();
}
// 如果是管理员,显示管理后台入口和加载候选者列表
if (userRole === 'admin') {
document.getElementById('adminPanel').style.display = 'grid';
document.getElementById('adminPanelBtn').style.display = 'flex';
await loadCandidates();
}
});
// 检查用户状态(候选者 vs 正式成员)
async function checkUserStatus() {
try {
const response = await authFetch('/api/my/status');
const data = await response.json();
if (data.success) {
userStatus = data.user.status;
userRole = data.user.role || 'member';
aiEvaluation = data.evaluation;
// 如果是候选者,显示提示横幅
if (userStatus === 'candidate') {
showCandidateBanner();
}
}
} catch (error) {
console.error('检查用户状态失败:', error);
}
}
// 显示候选者横幅
function showCandidateBanner() {
const topbar = document.querySelector('.topbar');
const banner = document.createElement('div');
banner.style.cssText = `
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
padding: 12px 24px;
margin-bottom: 20px;
border-radius: 12px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
`;
const aiScore = aiEvaluation?.score || 0;
const progress = Math.min(Math.floor((aiScore / 80) * 100), 100);
banner.innerHTML = `
<div>
<strong style="font-size: 15px; color: #333;">🎫 观察者模式</strong>
<div style="font-size: 13px; color: #666; margin-top: 4px;">
AI评估进度:${aiScore}/80分 (${progress}%) ${progress >= 100 ? '- 即将收到邀请!' : ''}
</div>
</div>
${aiScore >= 80 ? `
<button onclick="acceptInvitation()" style="
padding: 8px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
">接受AI邀请</button>
` : ''}
`;
topbar.parentNode.insertBefore(banner, topbar.nextSibling);
}
// 接受AI邀请
async function acceptInvitation() {
if (!confirm('确认接受AI邀请,正式加入超协体?')) return;
try {
const response = await authFetch('/api/ai/accept-invitation', {
method: 'POST'
});
const data = await response.json();
if (data.success) {
alert('🎉 ' + data.message + '\n\n你的超协体序号:#' + data.member.serialNumber);
location.reload();
} else {
alert('接受邀请失败:' + data.message);
}
} catch (error) {
console.error('接受邀请失败:', error);
alert('操作失败,请重试');
}
}
// 加载用户信息
async function loadUserInfo() {
const user = getUser();
if (user) {
document.getElementById('username').textContent = user.username;
document.getElementById('pointsBalance').textContent = user.pointsBalance;
document.getElementById('userAvatar').textContent = user.username.charAt(0).toUpperCase();
}
}
// 加载仪表盘数据
async function loadDashboardData() {
try {
// 调用MCP API获取任务数据
const tasksResponse = await authFetch('/mcp/tools/call', {
method: 'POST',
body: JSON.stringify({
name: 'list_all_tasks',
arguments: {}
})
});
// 检查是否是候选者权限不足
if (tasksResponse.status === 403) {
showCandidateView();
return;
}
const tasksData = await tasksResponse.json();
// 调用MCP API获取成员数据
const membersResponse = await authFetch('/mcp/tools/call', {
method: 'POST',
body: JSON.stringify({
name: 'list_all_members',
arguments: {}
})
});
const membersData = await membersResponse.json();
// 更新统计数据
const tasks = tasksData.content?.[0]?.text ? JSON.parse(tasksData.content[0].text).tasks || [] : [];
const members = membersData.content?.[0]?.text ? JSON.parse(membersData.content[0].text).members || [] : [];
const myTasks = tasks.filter(t => t.status === 'in_progress' || t.status === 'pending');
document.getElementById('myTaskCount').textContent = myTasks.length;
document.getElementById('memberCount').textContent = members.length;
// 渲染任务列表
renderTaskList(tasks.slice(0, 5)); // 显示最近5个任务
// 更新五行能量
updateWuxingEnergy(tasks, members);
// 加载我的方案数量
loadMyCrystalsCount();
// 加载我的项目数量
loadMyProjectsCount();
} catch (error) {
console.error('加载数据失败:', error);
document.getElementById('myTaskCount').textContent = '0';
document.getElementById('memberCount').textContent = '0';
document.getElementById('mySolutionCount').textContent = '0';
document.getElementById('taskList').innerHTML = `
<div class="empty-state">
<div class="empty-icon">📭</div>
<div>暂无数据</div>
</div>
`;
}
}
// 显示候选者视图(权限受限)
function showCandidateView() {
document.getElementById('myTaskCount').textContent = '--';
document.getElementById('memberCount').textContent = '--';
document.getElementById('taskList').innerHTML = `
<div class="empty-state">
<div class="empty-icon">🔒</div>
<div>观察者模式</div>
<div style="font-size: 13px; margin-top: 8px; color: #888;">
完善你的五行画像,等待AI评估后可解锁全部功能
</div>
<a href="/profile.html" style="display: inline-block; margin-top: 16px; padding: 10px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600;">
填写五行画像
</a>
</div>
`;
// 禁用需要成员权限的快速操作按钮
document.querySelectorAll('.action-btn').forEach(btn => {
const href = btn.getAttribute('href');
// 画像页面保持可用
if (href === '/profile.html') return;
btn.style.opacity = '0.5';
btn.style.cursor = 'not-allowed';
if (btn.tagName === 'A') {
btn.addEventListener('click', (e) => {
e.preventDefault();
alert('此功能仅对正式成员开放\n\n提示:完善五行画像可提高AI评估分数');
});
} else {
const originalOnclick = btn.getAttribute('onclick');
btn.setAttribute('data-original-onclick', originalOnclick);
btn.setAttribute('onclick', 'alert("此功能仅对正式成员开放\\n\\n提示:完善五行画像可提高AI评估分数")');
}
});
}
// ========================================
// 智能推荐任务
// ========================================
// 加载推荐任务
async function loadRecommendedTasks() {
try {
const response = await authFetch('/api/tasks/recommended');
const data = await response.json();
if (data.success) {
document.getElementById('recommendedTasksSection').style.display = 'grid';
renderRecommendedTasks(data.recommendations);
} else {
console.error('获取推荐任务失败:', data.message);
}
} catch (error) {
console.error('Failed to load recommended tasks:', error);
}
}
// 渲染推荐任务
function renderRecommendedTasks(recommendations) {
const container = document.getElementById('recommendedTasksList');
if (!recommendations || recommendations.length === 0) {
container.innerHTML = `
<div class="rec-empty-state">
<div style="font-size: 36px; margin-bottom: 12px;">📭</div>
<div>暂无待分配任务</div>
<div style="font-size: 13px; margin-top: 8px; opacity: 0.7;">
所有任务都已分配,或者还没有创建任务
</div>
</div>
`;
return;
}
container.innerHTML = recommendations.map(rec => {
const { task, score, reasons } = rec;
// 成长任务标识(成长价值 > 50)
let growthBadge = '';
if (reasons.growthValue > 50) {
growthBadge = '<span class="growth-badge">成长任务</span>';
}
// 五行图标映射
const wuxingIcons = { '火': '🔥', '金': '⚙️', '木': '🌳', '水': '💧', '土': '🏔️' };
const wuxingIcon = task.wuxing ? wuxingIcons[task.wuxing] || '☯️' : '';
return `
<a class="recommended-task-card" href="/task-detail.html?id=${task.id}">
<div class="rec-task-header">
<div class="rec-task-title">
${escapeHtml(task.title)}
${growthBadge}
</div>
<span class="match-score">${score}%</span>
</div>
<div class="rec-task-meta">
<span>五行匹配 ${reasons.wuxingMatch}%</span>
<span>技能匹配 ${reasons.skillMatch}%</span>
${task.wuxing ? `<span>${wuxingIcon} ${task.wuxing}</span>` : ''}
<span>🎁 ${task.reward_points || 20}积分</span>
</div>
</a>
`;
}).join('');
}
// HTML转义
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 渲染任务列表
function renderTaskList(tasks) {
const taskListEl = document.getElementById('taskList');
if (!tasks || tasks.length === 0) {
taskListEl.innerHTML = `
<div class="empty-state">
<div class="empty-icon">📭</div>
<div>暂无任务</div>
<div style="font-size: 13px; margin-top: 8px;">点击"创建任务"开始协作</div>
</div>
`;
return;
}
const statusMap = {
'pending': { class: '', label: '待分配' },
'in_progress': { class: 'in-progress', label: '进行中' },
'completed': { class: 'completed', label: '已完成' }
};
taskListEl.innerHTML = tasks.map(task => {
const status = statusMap[task.status] || statusMap['pending'];
return `
<a class="task-item" href="/task-detail.html?id=${task.id}">
<div class="task-title">${escapeHtml(task.title)}</div>
<div class="task-meta">
<span class="task-status ${status.class}">${status.label}</span>
${task.wuxing ? `<span>☯️ ${task.wuxing}</span>` : ''}
${task.assigned_to_name ? `<span>👤 ${escapeHtml(task.assigned_to_name)}</span>` : '<span style="color:#f57c00">未分配</span>'}
<span>🎁 ${task.reward_points || 20}积分</span>
</div>
</a>
`;
}).join('');
// 添加查看更多链接
if (tasks.length >= 5) {
taskListEl.innerHTML += `
<a href="/my-tasks.html" style="text-align: center; padding: 12px; color: #667eea; font-size: 14px;">
查看全部任务 →
</a>
`;
}
}
// 更新五行能量分布
function updateWuxingEnergy(tasks, members) {
// 计算五行能量分布
const wuxingCount = { fire: 0, metal: 0, wood: 0, water: 0, earth: 0 };
const wuxingMap = { '火': 'fire', '金': 'metal', '木': 'wood', '水': 'water', '土': 'earth' };
members.forEach(member => {
const mainWuxing = wuxingMap[member.mainWuxing];
if (mainWuxing) wuxingCount[mainWuxing]++;
});
const total = Math.max(members.length, 1);
const updateBar = (id, count) => {
const percent = Math.round((count / total) * 100);
document.getElementById(`${id}Percent`).textContent = `${percent}%`;
document.getElementById(`${id}Fill`).style.width = `${percent}%`;
};
updateBar('fire', wuxingCount.fire);
updateBar('metal', wuxingCount.metal);
updateBar('wood', wuxingCount.wood);
updateBar('water', wuxingCount.water);
updateBar('earth', wuxingCount.earth);
}
// 快速操作函数
async function checkWuxingBalance() {
try {
const response = await authFetch('/mcp/tools/call', {
method: 'POST',
body: JSON.stringify({
name: 'check_wuxing_balance',
arguments: {}
})
});
const data = await response.json();
if (data.content?.[0]?.text) {
const balance = JSON.parse(data.content[0].text);
let message = '☯️ 五行平衡分析\n\n';
message += `火: ${balance.distribution.fire} (${balance.percentages.fire}%)\n`;
message += `金: ${balance.distribution.metal} (${balance.percentages.metal}%)\n`;
message += `木: ${balance.distribution.wood} (${balance.percentages.wood}%)\n`;
message += `水: ${balance.distribution.water} (${balance.percentages.water}%)\n`;
message += `土: ${balance.distribution.earth} (${balance.percentages.earth}%)\n`;
message += `\n平衡状态: ${balance.isBalanced ? '✅ 平衡' : '⚠️ 不平衡'}\n`;
if (balance.recommendations?.length > 0) {
message += `\n建议:\n${balance.recommendations.join('\n')}`;
}
alert(message);
}
} catch (error) {
console.error('五行平衡检查失败:', error);
alert('五行平衡检查失败,请重试');
}
}
// 发放门票(正式成员才能使用)
async function issueTicketDialog() {
if (userStatus === 'candidate') {
alert('此功能仅对正式成员开放');
return;
}
const recipientEmail = prompt('请输入接收者的邮箱:');
if (!recipientEmail) return;
// 简单的邮箱验证
if (!recipientEmail.includes('@')) {
alert('请输入有效的邮箱地址');
return;
}
try {
const response = await authFetch('/api/ticket/issue', {
method: 'POST',
body: JSON.stringify({ recipientEmail })
});
const data = await response.json();
if (data.success) {
const ticketUrl = data.ticketUrl;
alert(`✅ 门票发放成功!\n\n接收者:${recipientEmail}\n\n门票链接:\n${ticketUrl}\n\n请将此链接发送给对方。`);
// 可选:复制到剪贴板
if (navigator.clipboard) {
navigator.clipboard.writeText(ticketUrl);
console.log('门票链接已复制到剪贴板');
}
} else {
alert('发放失败:' + data.message);
}
} catch (error) {
console.error('发放门票失败:', error);
alert('发放门票失败,请重试');
}
}
// ========================================
// 积分系统
// ========================================
// 查看积分历史
async function viewPointsHistory() {
try {
const response = await authFetch('/api/points/history?limit=10');
const data = await response.json();
if (data.success) {
let message = `💰 积分历史(当前余额:${data.balance})\n\n`;
if (data.transactions.length === 0) {
message += '暂无交易记录';
} else {
data.transactions.forEach(tx => {
const date = new Date(tx.createdAt).toLocaleDateString();
const sign = tx.amount > 0 ? '+' : '';
message += `${date} ${sign}${tx.amount}积分\n`;
message += ` ${tx.description}\n\n`;
});
}
alert(message);
} else {
alert('加载失败:' + data.message);
}
} catch (error) {
console.error('查看积分历史失败:', error);
alert('查看积分历史失败,请重试');
}
}
// ========================================
// 管理员功能
// ========================================
// 加载候选者列表
async function loadCandidates() {
try {
const response = await authFetch('/api/admin/candidates');
const data = await response.json();
if (data.success) {
renderCandidates(data.candidates);
} else {
document.getElementById('candidatesList').innerHTML = `
<div class="empty-state">
<div class="empty-icon">❌</div>
<div>加载失败:${data.message}</div>
</div>
`;
}
} catch (error) {
console.error('加载候选者失败:', error);
document.getElementById('candidatesList').innerHTML = `
<div class="empty-state">
<div class="empty-icon">❌</div>
<div>加载失败,请刷新重试</div>
</div>
`;
}
}
// 渲染候选者列表
function renderCandidates(candidates) {
const listEl = document.getElementById('candidatesList');
if (!candidates || candidates.length === 0) {
listEl.innerHTML = `
<div class="empty-state">
<div class="empty-icon">✅</div>
<div>暂无待审核候选者</div>
</div>
`;
return;
}
listEl.innerHTML = candidates.map(candidate => {
const joinedDate = new Date(candidate.createdAt).toLocaleDateString();
return `
<div class="candidate-item">
<div class="candidate-info">
<div class="candidate-name">${candidate.username}</div>
<div class="candidate-meta">
📧 ${candidate.email} | 📅 ${joinedDate}
</div>
</div>
<div class="candidate-actions">
<button class="btn-approve" onclick="approveCandidate('${candidate.id}', '${candidate.username}')">
✅ 批准
</button>
<button class="btn-reject" onclick="rejectCandidate('${candidate.id}', '${candidate.username}')">
❌ 拒绝
</button>
</div>
</div>
`;
}).join('');
}
// 批准候选者
async function approveCandidate(candidateId, candidateName) {
if (!confirm(`确认批准 ${candidateName} 加入超协体?`)) return;
try {
const response = await authFetch('/api/admin/approve-candidate', {
method: 'POST',
body: JSON.stringify({ candidateId })
});
const data = await response.json();
if (data.success) {
alert(`✅ 已批准 ${candidateName}!\n\n超协体序号:#${data.member.serialNumber}`);
await loadCandidates(); // 刷新列表
} else {
alert('批准失败:' + data.message);
}
} catch (error) {
console.error('批准候选者失败:', error);
alert('批准失败,请重试');
}
}
// 拒绝候选者
async function rejectCandidate(candidateId, candidateName) {
const reason = prompt(`确认拒绝 ${candidateName}?\n\n请输入拒绝理由(可选):`);
if (reason === null) return; // 用户取消
try {
const response = await authFetch('/api/admin/reject-candidate', {
method: 'POST',
body: JSON.stringify({ candidateId, reason })
});
const data = await response.json();
if (data.success) {
alert(`已拒绝 ${candidateName}`);
await loadCandidates(); // 刷新列表
} else {
alert('拒绝失败:' + data.message);
}
} catch (error) {
console.error('拒绝候选者失败:', error);
alert('拒绝失败,请重试');
}
}
// ========================================
// AI成员管理
// ========================================
// 创建AI成员
async function createAIMember(aiType) {
const typeNames = {
'code_master': 'CodeMaster AI',
'doc_writer': 'DocWriter AI',
'analyst': 'Analyst AI',
'coordinator': 'Coordinator AI'
};
if (!confirm(`确认创建 ${typeNames[aiType]} 吗?`)) return;
try {
const response = await authFetch('/api/ai-members/create', {
method: 'POST',
body: JSON.stringify({ aiType })
});
const data = await response.json();
if (data.success) {
alert(`${data.aiMember.username} 创建成功!序号:#${String(data.aiMember.serialNumber).padStart(3, '0')}`);
loadAIMembers();
} else {
alert(data.message);
}
} catch (error) {
console.error('Failed to create AI member:', error);
alert('创建失败,请重试');
}
}
// 加载AI成员列表
async function loadAIMembers() {
try {
const response = await authFetch('/api/ai-members');
const data = await response.json();
if (data.success) {
renderAIMembers(data.aiMembers);
}
} catch (error) {
console.error('Failed to load AI members:', error);
document.getElementById('aiMembersList').innerHTML = `
<div style="text-align: center; color: #999; padding: 20px;">
加载失败
</div>
`;
}
}
// 渲染AI成员列表
function renderAIMembers(aiMembers) {
const container = document.getElementById('aiMembersList');
if (!aiMembers || aiMembers.length === 0) {
container.innerHTML = `
<div style="text-align: center; color: #999; padding: 20px;">
暂无AI成员,点击上方按钮创建
</div>
`;
return;
}
const typeIcons = {
'code_master': '💻',
'doc_writer': '📝',
'analyst': '📊',
'coordinator': '🎯'
};
container.innerHTML = aiMembers.map(ai => `
<div style="display: flex; align-items: center; padding: 12px; background: #f8f9fa; border-radius: 10px; margin-bottom: 8px;">
<div style="font-size: 24px; margin-right: 12px;">${typeIcons[ai.aiType] || '🤖'}</div>
<div style="flex: 1;">
<div style="font-weight: 600; font-size: 14px;">#${String(ai.serialNumber).padStart(3, '0')} ${escapeHtml(ai.username)}</div>
<div style="font-size: 12px; color: #666;">${ai.pwpProfile?.skills?.slice(0, 2).join(', ') || ''}</div>
</div>
<a href="/member-detail.html?id=${ai.id}&type=community" style="padding: 6px 12px; background: #667eea; color: white; text-decoration: none; border-radius: 6px; font-size: 12px;">
详情
</a>
</div>
`).join('');
}
// 加载我的方案数量
async function loadMyCrystalsCount() {
try {
const user = getUser();
const response = await authFetch(`/api/solutions?authorId=${user.id}&status=published`);
const data = await response.json();
if (data.success) {
document.getElementById('mySolutionCount').textContent = data.pagination.total;
} else {
document.getElementById('mySolutionCount').textContent = '0';
}
} catch (error) {
console.error('加载方案数量失败:', error);
document.getElementById('mySolutionCount').textContent = '0';
}
}
// 加载我的项目数量
async function loadMyProjectsCount() {
try {
const response = await authFetch('/api/projects?myProjects=true');
const data = await response.json();
if (data.success) {
document.getElementById('myProjectCount').textContent = data.pagination.total;
} else {
document.getElementById('myProjectCount').textContent = '0';
}
} catch (error) {
console.error('加载项目数量失败:', error);
document.getElementById('myProjectCount').textContent = '0';
}
}
// 页面加载时,如果是管理员则加载AI成员
document.addEventListener('DOMContentLoaded', () => {
// 延迟加载AI成员(等待用户角色确认)
setTimeout(() => {
if (userRole === 'admin') {
loadAIMembers();
}
}, 500);
// WebSocket实时更新集成
setupWebSocketListeners();
});
// ========================================
// WebSocket实时协作
// ========================================
function setupWebSocketListeners() {
if (!window.wsClient) {
console.warn('[Dashboard] WebSocket客户端未就绪');
return;
}
// 监听任务状态变更
wsClient.on('task-status-changed', (data) => {
console.log('[Dashboard] 任务状态更新', data);
// 刷新任务列表
loadDashboardData();
});
// 监听任务分配变更
wsClient.on('task-assignment-changed', (data) => {
console.log('[Dashboard] 任务分配更新', data);
// 刷新任务列表和推荐任务
loadDashboardData();
if (userStatus === 'member') {
loadRecommendedTasks();
}
});
// 监听新任务创建
wsClient.on('task-created', (data) => {
console.log('[Dashboard] 新任务创建', data);
// 刷新任务列表
loadDashboardData();
if (userStatus === 'member') {
loadRecommendedTasks();
}
});
// 监听在线用户变化
wsClient.on('user-online', (data) => {
console.log(`[Dashboard] 用户上线: ${data.username}`);
});
wsClient.on('user-offline', (data) => {
console.log(`[Dashboard] 用户下线: ${data.username}`);
});
wsClient.on('online-users-updated', (users) => {
console.log('[Dashboard] 在线用户列表更新', users);
});
}
</script>
</body>
</html>