init: TEALab 2025 实验室网站项目
从百度网盘 /apps/bypy/dofile/ 下载的完整项目结构,包含: - dofile/: Flask 后端(端口 5000)+ 静态前端(端口 6002) - officefile/: 项目相关文档 - .gitignore: Python venv / 日志 / 缓存 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
// Admin Panel JavaScript
|
||||
|
||||
let currentArticles = [];
|
||||
let currentResources = [];
|
||||
let currentTeamMembers = [];
|
||||
|
||||
// 检查管理员权限
|
||||
async function checkAdminAccess() {
|
||||
try {
|
||||
const response = await window.DAELAPI.auth.checkAuth();
|
||||
if (!response.authenticated || !response.user.is_admin) {
|
||||
alert('需要管理员权限才能访问此页面');
|
||||
window.location.href = 'website.html';
|
||||
return false;
|
||||
}
|
||||
document.getElementById('admin-user').textContent = `管理员: ${response.user.username}`;
|
||||
return true;
|
||||
} catch (error) {
|
||||
alert('无法验证权限,请先登录');
|
||||
window.location.href = 'login.html';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Tab切换
|
||||
document.querySelectorAll('.admin-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const tabName = tab.dataset.tab;
|
||||
|
||||
// 更新tab样式
|
||||
document.querySelectorAll('.admin-tab').forEach(t => {
|
||||
t.classList.remove('active', 'border-black', 'text-black');
|
||||
t.classList.add('text-gray-500');
|
||||
});
|
||||
tab.classList.add('active', 'border-black', 'text-black');
|
||||
tab.classList.remove('text-gray-500');
|
||||
|
||||
// 更新内容
|
||||
document.querySelectorAll('.admin-tab-content').forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
});
|
||||
document.getElementById(`tab-${tabName}`).classList.remove('hidden');
|
||||
|
||||
// 加载对应内容
|
||||
if (tabName === 'articles') loadArticles();
|
||||
else if (tabName === 'resources') loadResources();
|
||||
else if (tabName === 'team') loadTeamMembers();
|
||||
});
|
||||
});
|
||||
|
||||
// 加载文章列表
|
||||
async function loadArticles() {
|
||||
try {
|
||||
const response = await window.DAELAPI.content.getArticles('news', false);
|
||||
currentArticles = response.articles;
|
||||
displayArticles(currentArticles);
|
||||
} catch (error) {
|
||||
console.error('Failed to load articles:', error);
|
||||
document.getElementById('articles-list').innerHTML = '<p class="text-red-600">加载失败</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示文章列表
|
||||
function displayArticles(articles) {
|
||||
const container = document.getElementById('articles-list');
|
||||
if (articles.length === 0) {
|
||||
container.innerHTML = '<p class="text-gray-500">暂无文章</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = articles.map(article => `
|
||||
<div class="bg-white border border-gray-200 p-6 rounded">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-bold mb-2">${article.title}</h3>
|
||||
<p class="text-sm text-gray-500 mb-2">${article.summary || article.content.substring(0, 100)}...</p>
|
||||
<div class="flex gap-4 text-xs text-gray-400">
|
||||
<span>分类: ${article.category}</span>
|
||||
<span>${article.is_protected ? '🔒 受保护' : '公开'}</span>
|
||||
<span>${article.published ? '已发布' : '草稿'}</span>
|
||||
<span>${new Date(article.created_at).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 ml-4">
|
||||
<button onclick="editArticle(${article.id})" class="text-sm text-blue-600 hover:text-blue-800">编辑</button>
|
||||
<button onclick="deleteArticle(${article.id})" class="text-sm text-red-600 hover:text-red-800">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 显示文章表单
|
||||
function showArticleForm(articleId = null) {
|
||||
const modal = document.getElementById('article-modal');
|
||||
const form = document.getElementById('article-form');
|
||||
const title = document.getElementById('article-modal-title');
|
||||
|
||||
if (articleId) {
|
||||
const article = currentArticles.find(a => a.id === articleId);
|
||||
if (article) {
|
||||
title.textContent = '编辑文章';
|
||||
document.getElementById('article-id').value = article.id;
|
||||
document.getElementById('article-title').value = article.title;
|
||||
document.getElementById('article-summary').value = article.summary || '';
|
||||
document.getElementById('article-content').value = article.content;
|
||||
document.getElementById('article-category').value = article.category;
|
||||
document.getElementById('article-published').value = article.published.toString();
|
||||
document.getElementById('article-protected').checked = article.is_protected;
|
||||
}
|
||||
} else {
|
||||
title.textContent = '新建文章';
|
||||
form.reset();
|
||||
document.getElementById('article-id').value = '';
|
||||
}
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 关闭文章表单
|
||||
function closeArticleForm() {
|
||||
document.getElementById('article-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 保存文章
|
||||
async function saveArticle(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const articleId = document.getElementById('article-id').value;
|
||||
const articleData = {
|
||||
title: document.getElementById('article-title').value,
|
||||
summary: document.getElementById('article-summary').value,
|
||||
content: document.getElementById('article-content').value,
|
||||
category: document.getElementById('article-category').value,
|
||||
published: document.getElementById('article-published').value === 'true',
|
||||
is_protected: document.getElementById('article-protected').checked
|
||||
};
|
||||
|
||||
try {
|
||||
if (articleId) {
|
||||
await window.DAELAPI.content.updateArticle(articleId, articleData);
|
||||
} else {
|
||||
await window.DAELAPI.content.createArticle(articleData);
|
||||
}
|
||||
closeArticleForm();
|
||||
loadArticles();
|
||||
} catch (error) {
|
||||
alert('保存失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑文章
|
||||
function editArticle(articleId) {
|
||||
showArticleForm(articleId);
|
||||
}
|
||||
|
||||
// 删除文章
|
||||
async function deleteArticle(articleId) {
|
||||
if (!confirm('确定要删除这篇文章吗?')) return;
|
||||
|
||||
try {
|
||||
await window.DAELAPI.content.deleteArticle(articleId);
|
||||
loadArticles();
|
||||
} catch (error) {
|
||||
alert('删除失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载资源列表
|
||||
async function loadResources() {
|
||||
try {
|
||||
const response = await window.DAELAPI.content.getResources();
|
||||
currentResources = response.resources;
|
||||
displayResources(currentResources);
|
||||
} catch (error) {
|
||||
console.error('Failed to load resources:', error);
|
||||
document.getElementById('resources-list').innerHTML = '<p class="text-red-600">加载失败</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示资源列表
|
||||
function displayResources(resources) {
|
||||
const container = document.getElementById('resources-list');
|
||||
if (resources.length === 0) {
|
||||
container.innerHTML = '<p class="text-gray-500">暂无资源</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = resources.map(resource => `
|
||||
<div class="bg-white border border-gray-200 p-6 rounded">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-bold mb-2">${resource.name}</h3>
|
||||
<p class="text-sm text-gray-500 mb-2">${resource.description || ''}</p>
|
||||
<div class="flex gap-4 text-xs text-gray-400">
|
||||
<span>分类: ${resource.category}</span>
|
||||
<span>${resource.is_protected ? '🔒 受保护' : '公开'}</span>
|
||||
<span>大小: ${(resource.file_size / 1024 / 1024).toFixed(2)} MB</span>
|
||||
<span>下载: ${resource.download_count} 次</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 ml-4">
|
||||
<button onclick="window.DAELAPI.api.downloadResource(${resource.id})" class="text-sm text-blue-600 hover:text-blue-800">下载</button>
|
||||
<button onclick="deleteResource(${resource.id})" class="text-sm text-red-600 hover:text-red-800">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 显示资源表单
|
||||
function showResourceForm() {
|
||||
document.getElementById('resource-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 关闭资源表单
|
||||
function closeResourceForm() {
|
||||
document.getElementById('resource-modal').classList.add('hidden');
|
||||
document.getElementById('resource-form').reset();
|
||||
}
|
||||
|
||||
// 保存资源
|
||||
async function saveResource(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const fileInput = document.getElementById('resource-file');
|
||||
if (!fileInput.files[0]) {
|
||||
alert('请选择文件');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.DAELAPI.content.uploadResource(
|
||||
fileInput.files[0],
|
||||
document.getElementById('resource-name').value,
|
||||
document.getElementById('resource-description').value,
|
||||
document.getElementById('resource-category').value,
|
||||
document.getElementById('resource-protected').checked
|
||||
);
|
||||
closeResourceForm();
|
||||
loadResources();
|
||||
} catch (error) {
|
||||
alert('上传失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除资源
|
||||
async function deleteResource(resourceId) {
|
||||
if (!confirm('确定要删除这个资源吗?')) return;
|
||||
// 注意:需要添加删除资源的API端点
|
||||
alert('删除功能需要后端支持');
|
||||
}
|
||||
|
||||
// 加载团队成员
|
||||
async function loadTeamMembers() {
|
||||
try {
|
||||
const response = await window.DAELAPI.content.getTeam();
|
||||
currentTeamMembers = response.members;
|
||||
displayTeamMembers(currentTeamMembers);
|
||||
} catch (error) {
|
||||
console.error('Failed to load team members:', error);
|
||||
document.getElementById('team-list').innerHTML = '<p class="text-red-600">加载失败</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示团队成员
|
||||
function displayTeamMembers(members) {
|
||||
const container = document.getElementById('team-list');
|
||||
if (members.length === 0) {
|
||||
container.innerHTML = '<p class="text-gray-500">暂无成员</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = members.map(member => `
|
||||
<div class="bg-white border border-gray-200 p-6 rounded">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-bold mb-2">${member.name}</h3>
|
||||
<p class="text-sm text-gray-600 mb-2">${member.title || ''}</p>
|
||||
<p class="text-sm text-gray-500 mb-2">${member.bio || ''}</p>
|
||||
<div class="flex gap-4 text-xs text-gray-400">
|
||||
<span>角色: ${member.role}</span>
|
||||
<span>排序: ${member.order}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 ml-4">
|
||||
<button onclick="editTeamMember(${member.id})" class="text-sm text-blue-600 hover:text-blue-800">编辑</button>
|
||||
<button onclick="deleteTeamMember(${member.id})" class="text-sm text-red-600 hover:text-red-800">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 显示团队成员表单
|
||||
function showTeamForm(memberId = null) {
|
||||
const modal = document.getElementById('team-modal');
|
||||
const form = document.getElementById('team-form');
|
||||
|
||||
if (memberId) {
|
||||
const member = currentTeamMembers.find(m => m.id === memberId);
|
||||
if (member) {
|
||||
document.getElementById('team-id').value = member.id;
|
||||
document.getElementById('team-name').value = member.name;
|
||||
document.getElementById('team-title').value = member.title || '';
|
||||
document.getElementById('team-title-en').value = member.title_en || '';
|
||||
document.getElementById('team-bio').value = member.bio || '';
|
||||
document.getElementById('team-bio-en').value = member.bio_en || '';
|
||||
document.getElementById('team-role').value = member.role;
|
||||
document.getElementById('team-order').value = member.order;
|
||||
document.getElementById('team-email').value = member.email || '';
|
||||
document.getElementById('team-website').value = member.website || '';
|
||||
}
|
||||
} else {
|
||||
form.reset();
|
||||
document.getElementById('team-id').value = '';
|
||||
}
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 关闭团队成员表单
|
||||
function closeTeamForm() {
|
||||
document.getElementById('team-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 保存团队成员
|
||||
async function saveTeamMember(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const memberId = document.getElementById('team-id').value;
|
||||
const memberData = {
|
||||
name: document.getElementById('team-name').value,
|
||||
title: document.getElementById('team-title').value,
|
||||
title_en: document.getElementById('team-title-en').value,
|
||||
bio: document.getElementById('team-bio').value,
|
||||
bio_en: document.getElementById('team-bio-en').value,
|
||||
role: document.getElementById('team-role').value,
|
||||
order: parseInt(document.getElementById('team-order').value) || 0,
|
||||
email: document.getElementById('team-email').value,
|
||||
website: document.getElementById('team-website').value
|
||||
};
|
||||
|
||||
try {
|
||||
if (memberId) {
|
||||
// 更新功能需要后端支持
|
||||
alert('更新功能需要后端支持');
|
||||
} else {
|
||||
await window.DAELAPI.content.createTeamMember(memberData);
|
||||
}
|
||||
closeTeamForm();
|
||||
loadTeamMembers();
|
||||
} catch (error) {
|
||||
alert('保存失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑团队成员
|
||||
function editTeamMember(memberId) {
|
||||
showTeamForm(memberId);
|
||||
}
|
||||
|
||||
// 删除团队成员
|
||||
async function deleteTeamMember(memberId) {
|
||||
if (!confirm('确定要删除这个成员吗?')) return;
|
||||
// 注意:需要添加删除成员的API端点
|
||||
alert('删除功能需要后端支持');
|
||||
}
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
if (await checkAdminAccess()) {
|
||||
loadArticles();
|
||||
|
||||
// 退出按钮
|
||||
document.getElementById('logout-btn').addEventListener('click', async () => {
|
||||
await logout();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user