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:
+32
@@ -0,0 +1,32 @@
|
|||||||
|
# Python
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.egg-info/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Build
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Flask session
|
||||||
|
instance/
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
# 后端系统设置指南
|
||||||
|
|
||||||
|
## 登录前后的功能差别
|
||||||
|
|
||||||
|
### 登录前
|
||||||
|
- 只能查看公开内容
|
||||||
|
- 受保护内容显示"登录后查看"遮罩
|
||||||
|
- 无法下载受保护资源
|
||||||
|
- 无法查看详细统计数据
|
||||||
|
- 导航栏显示"登录"按钮
|
||||||
|
|
||||||
|
### 登录后
|
||||||
|
- 可以查看所有内容,包括受保护内容
|
||||||
|
- 可以下载受保护资源
|
||||||
|
- 可以查看详细统计数据(文章数、资源数、下载量等)
|
||||||
|
- 导航栏显示用户名和"退出"按钮
|
||||||
|
- 可以访问内部文章和资料
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 安装Python依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
或使用虚拟环境:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate # Linux/Mac
|
||||||
|
# 或 venv\Scripts\activate # Windows
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 初始化数据库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
python init_data.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这将创建:
|
||||||
|
- 数据库表
|
||||||
|
- 默认管理员账户:`admin` / `admin123`
|
||||||
|
- 测试用户账户:`user` / `user123`
|
||||||
|
- 示例受保护内容
|
||||||
|
|
||||||
|
### 3. 启动后端服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
或使用启动脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start_backend.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
服务器将在 `http://localhost:5000` 启动。
|
||||||
|
|
||||||
|
### 4. 启动前端服务器
|
||||||
|
|
||||||
|
在另一个终端:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd dofile
|
||||||
|
python3 -m http.server 6002 --bind 0.0.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试登录功能
|
||||||
|
|
||||||
|
1. 访问 `http://localhost:6002/website.html`
|
||||||
|
2. 点击导航栏的"登录"按钮
|
||||||
|
3. 使用以下账户登录:
|
||||||
|
- 管理员:`admin` / `admin123`
|
||||||
|
- 普通用户:`user` / `user123`
|
||||||
|
4. 登录后,页面会显示受保护内容区块
|
||||||
|
|
||||||
|
## API端点说明
|
||||||
|
|
||||||
|
### 认证相关
|
||||||
|
- `POST /api/auth/login` - 用户登录
|
||||||
|
- `POST /api/auth/logout` - 用户登出
|
||||||
|
- `GET /api/auth/check` - 检查登录状态
|
||||||
|
|
||||||
|
### 内容管理
|
||||||
|
- `GET /api/cms/articles` - 获取文章列表
|
||||||
|
- `GET /api/cms/resources` - 获取资源列表
|
||||||
|
- `GET /api/cms/team` - 获取团队成员
|
||||||
|
|
||||||
|
### 受保护内容
|
||||||
|
- `GET /api/protected-content` - 获取受保护内容(需登录)
|
||||||
|
- `GET /api/stats` - 获取统计数据(需登录)
|
||||||
|
- `GET /api/download/<id>` - 下载资源文件
|
||||||
|
|
||||||
|
## 内容管理系统(CMS)
|
||||||
|
|
||||||
|
管理员可以通过API管理内容:
|
||||||
|
|
||||||
|
1. **文章管理**
|
||||||
|
- 创建、编辑、删除文章
|
||||||
|
- 设置文章为受保护内容
|
||||||
|
- 控制文章发布状态
|
||||||
|
|
||||||
|
2. **资源管理**
|
||||||
|
- 上传文件资源
|
||||||
|
- 设置资源为受保护
|
||||||
|
- 跟踪下载次数
|
||||||
|
|
||||||
|
3. **团队管理**
|
||||||
|
- 添加、编辑团队成员信息
|
||||||
|
- 管理成员角色和排序
|
||||||
|
|
||||||
|
## 数据库
|
||||||
|
|
||||||
|
- 开发环境使用SQLite:`data/dael.db`
|
||||||
|
- 生产环境建议使用PostgreSQL
|
||||||
|
- 数据库模型定义在 `backend/models.py`
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/
|
||||||
|
├── app.py # Flask主应用
|
||||||
|
├── models.py # 数据库模型
|
||||||
|
├── init_data.py # 初始化脚本
|
||||||
|
├── requirements.txt # Python依赖
|
||||||
|
├── routes/ # API路由
|
||||||
|
│ ├── auth.py # 认证路由
|
||||||
|
│ ├── cms.py # 内容管理路由
|
||||||
|
│ └── api.py # 其他API路由
|
||||||
|
└── README.md # 详细文档
|
||||||
|
|
||||||
|
data/
|
||||||
|
└── dael.db # SQLite数据库
|
||||||
|
|
||||||
|
uploads/ # 上传文件存储
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **安全性**
|
||||||
|
- 生产环境必须更改默认密码
|
||||||
|
- 使用强SECRET_KEY
|
||||||
|
- 启用HTTPS
|
||||||
|
- 配置CORS策略
|
||||||
|
|
||||||
|
2. **性能**
|
||||||
|
- 生产环境使用PostgreSQL
|
||||||
|
- 配置文件上传大小限制
|
||||||
|
- 使用CDN存储静态文件
|
||||||
|
|
||||||
|
3. **扩展性**
|
||||||
|
- 可以添加更多用户角色
|
||||||
|
- 可以实现更细粒度的权限控制
|
||||||
|
- 可以集成第三方认证服务
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
# 组会管理和研究管理系统使用指南
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
系统已成功实现组会管理和个人研究管理两个核心功能模块,允许每个用户创建和管理自己的内容,同时支持实验室成员之间的内容共享。
|
||||||
|
|
||||||
|
## 组会管理系统
|
||||||
|
|
||||||
|
### 功能特性
|
||||||
|
|
||||||
|
#### 基础功能
|
||||||
|
- ✅ 创建组会(标题、描述、日期、时间、地点)
|
||||||
|
- ✅ 查看所有组会列表(实验室成员可查看)
|
||||||
|
- ✅ 编辑自己的组会
|
||||||
|
- ✅ 删除自己的组会
|
||||||
|
- ✅ 组会状态管理(计划中/进行中/已结束/已取消)
|
||||||
|
|
||||||
|
#### 高级功能
|
||||||
|
- ✅ 组会报名(实验室成员可报名参会)
|
||||||
|
- ✅ 签到功能(参会人员可签到)
|
||||||
|
- ✅ 会议记录(组织者可添加会议记录)
|
||||||
|
- ✅ 文件共享(组织者可上传会议相关文件)
|
||||||
|
- ✅ 参会人员列表查看
|
||||||
|
|
||||||
|
### 访问方式
|
||||||
|
|
||||||
|
1. **登录后访问**
|
||||||
|
- 登录账户后,导航栏会显示"组会管理"链接
|
||||||
|
- 或直接访问:`http://localhost:6002/meetings.html`
|
||||||
|
|
||||||
|
2. **创建组会**
|
||||||
|
- 点击"创建组会"按钮
|
||||||
|
- 填写组会信息(标题、日期、时间、地点等)
|
||||||
|
- 选择组会类型(周会/月会/专题会)
|
||||||
|
- 保存
|
||||||
|
|
||||||
|
3. **管理组会**
|
||||||
|
- 查看:点击"查看"按钮查看详情
|
||||||
|
- 编辑:只有创建者可以编辑(显示"编辑"按钮)
|
||||||
|
- 删除:只有创建者可以删除(显示"删除"按钮)
|
||||||
|
|
||||||
|
4. **报名参会**
|
||||||
|
- 在组会详情页面,点击"报名参会"按钮
|
||||||
|
- 报名后状态显示为"已报名"
|
||||||
|
- 会议开始后可进行签到
|
||||||
|
|
||||||
|
## 个人研究管理系统
|
||||||
|
|
||||||
|
### 功能特性
|
||||||
|
|
||||||
|
#### 基础功能
|
||||||
|
- ✅ 创建研究项目(标题、描述、日期、进度、标签)
|
||||||
|
- ✅ 查看所有研究项目(实验室成员可查看)
|
||||||
|
- ✅ 编辑自己的项目
|
||||||
|
- ✅ 删除自己的项目
|
||||||
|
- ✅ 项目进度跟踪(0-100%)
|
||||||
|
|
||||||
|
#### 高级功能
|
||||||
|
- ✅ 研究笔记管理(创建、编辑、删除笔记)
|
||||||
|
- ✅ 实验记录(笔记类型:笔记/实验记录/文献笔记/想法)
|
||||||
|
- ✅ 任务管理(创建任务、分配任务、跟踪任务状态)
|
||||||
|
- ✅ 文件管理(上传、下载、删除研究文件)
|
||||||
|
- ✅ 项目时间线(开始日期、结束日期)
|
||||||
|
|
||||||
|
### 访问方式
|
||||||
|
|
||||||
|
1. **登录后访问**
|
||||||
|
- 登录账户后,导航栏会显示"研究管理"链接
|
||||||
|
- 或直接访问:`http://localhost:6002/research.html`
|
||||||
|
|
||||||
|
2. **创建研究项目**
|
||||||
|
- 点击"新建项目"按钮
|
||||||
|
- 填写项目信息(名称、描述、日期、进度、标签)
|
||||||
|
- 选择项目状态(进行中/已完成/已暂停/已取消)
|
||||||
|
- 保存
|
||||||
|
|
||||||
|
3. **管理项目内容**
|
||||||
|
- **笔记管理**:
|
||||||
|
- 在项目详情页的"笔记"标签下
|
||||||
|
- 点击"+ 新建笔记"创建笔记
|
||||||
|
- 可以编辑和删除自己的笔记
|
||||||
|
- 支持多种笔记类型(笔记/实验记录/文献笔记/想法)
|
||||||
|
|
||||||
|
- **任务管理**:
|
||||||
|
- 在项目详情页的"任务"标签下
|
||||||
|
- 项目创建者可以创建任务
|
||||||
|
- 可以分配任务给其他成员
|
||||||
|
- 设置任务优先级(高/中/低)和截止日期
|
||||||
|
- 可以勾选完成任务
|
||||||
|
|
||||||
|
- **文件管理**:
|
||||||
|
- 在项目详情页的"文件"标签下
|
||||||
|
- 点击"+ 上传文件"上传研究文件
|
||||||
|
- 支持多种文件格式(PDF、文档、代码、数据文件等)
|
||||||
|
- 可以下载和删除文件(只有上传者可以删除)
|
||||||
|
|
||||||
|
## 权限说明
|
||||||
|
|
||||||
|
### 访问权限
|
||||||
|
- **实验室共享模式**:所有登录的实验室成员可以查看所有内容
|
||||||
|
- **编辑权限**:只有创建者可以编辑和删除自己创建的内容
|
||||||
|
- **查看权限**:所有实验室成员可以查看所有组会和项目
|
||||||
|
|
||||||
|
### 具体权限规则
|
||||||
|
|
||||||
|
#### 组会管理
|
||||||
|
- 创建:所有登录用户
|
||||||
|
- 查看:所有登录用户
|
||||||
|
- 编辑:只有组织者(创建者)
|
||||||
|
- 删除:只有组织者
|
||||||
|
- 报名:所有登录用户
|
||||||
|
- 上传文件:只有组织者
|
||||||
|
|
||||||
|
#### 研究管理
|
||||||
|
- 创建项目:所有登录用户
|
||||||
|
- 查看项目:所有登录用户
|
||||||
|
- 编辑项目:只有项目所有者
|
||||||
|
- 删除项目:只有项目所有者
|
||||||
|
- 创建笔记:所有登录用户(在自己的项目或查看的项目中)
|
||||||
|
- 编辑笔记:只有笔记作者
|
||||||
|
- 创建任务:只有项目所有者
|
||||||
|
- 编辑任务:项目所有者或任务分配者
|
||||||
|
- 上传文件:所有登录用户
|
||||||
|
- 删除文件:只有文件上传者
|
||||||
|
|
||||||
|
## 使用流程示例
|
||||||
|
|
||||||
|
### 示例1:创建和管理组会
|
||||||
|
|
||||||
|
1. 登录系统(使用 `admin` / `admin123` 或 `user` / `user123`)
|
||||||
|
2. 点击导航栏的"组会管理"
|
||||||
|
3. 点击"+ 创建组会"
|
||||||
|
4. 填写组会信息:
|
||||||
|
- 标题:周会 - AI算法讨论
|
||||||
|
- 日期:选择下周的日期
|
||||||
|
- 时间:14:00
|
||||||
|
- 地点:会议室A
|
||||||
|
- 类型:周会
|
||||||
|
5. 保存后,组会出现在列表中
|
||||||
|
6. 其他成员可以查看并报名参会
|
||||||
|
7. 会议开始后,可以添加会议记录和上传相关文件
|
||||||
|
|
||||||
|
### 示例2:创建研究项目并管理
|
||||||
|
|
||||||
|
1. 登录系统
|
||||||
|
2. 点击导航栏的"研究管理"
|
||||||
|
3. 点击"+ 新建项目"
|
||||||
|
4. 填写项目信息:
|
||||||
|
- 项目名称:生成式生态学算法研究
|
||||||
|
- 描述:研究基于GAN的生态系统设计算法
|
||||||
|
- 开始日期:2024-01-01
|
||||||
|
- 进度:30%
|
||||||
|
- 标签:AI, 生态, 算法
|
||||||
|
5. 保存后,点击"查看"进入项目详情
|
||||||
|
6. 在"笔记"标签下创建研究笔记
|
||||||
|
7. 在"任务"标签下创建研究任务
|
||||||
|
8. 在"文件"标签下上传研究资料
|
||||||
|
|
||||||
|
## API端点
|
||||||
|
|
||||||
|
### 组会管理API
|
||||||
|
- `GET /api/meetings/meetings` - 获取组会列表
|
||||||
|
- `GET /api/meetings/meetings/<id>` - 获取组会详情
|
||||||
|
- `POST /api/meetings/meetings` - 创建组会
|
||||||
|
- `PUT /api/meetings/meetings/<id>` - 更新组会
|
||||||
|
- `DELETE /api/meetings/meetings/<id>` - 删除组会
|
||||||
|
- `POST /api/meetings/meetings/<id>/register` - 报名参会
|
||||||
|
- `POST /api/meetings/meetings/<id>/checkin` - 签到
|
||||||
|
- `POST /api/meetings/meetings/<id>/files` - 上传文件
|
||||||
|
|
||||||
|
### 研究管理API
|
||||||
|
- `GET /api/research/projects` - 获取项目列表
|
||||||
|
- `GET /api/research/projects/<id>` - 获取项目详情
|
||||||
|
- `POST /api/research/projects` - 创建项目
|
||||||
|
- `PUT /api/research/projects/<id>` - 更新项目
|
||||||
|
- `DELETE /api/research/projects/<id>` - 删除项目
|
||||||
|
- `GET /api/research/projects/<id>/notes` - 获取笔记列表
|
||||||
|
- `POST /api/research/projects/<id>/notes` - 创建笔记
|
||||||
|
- `PUT /api/research/notes/<id>` - 更新笔记
|
||||||
|
- `DELETE /api/research/notes/<id>` - 删除笔记
|
||||||
|
- `GET /api/research/projects/<id>/tasks` - 获取任务列表
|
||||||
|
- `POST /api/research/projects/<id>/tasks` - 创建任务
|
||||||
|
- `PUT /api/research/tasks/<id>` - 更新任务
|
||||||
|
- `DELETE /api/research/tasks/<id>` - 删除任务
|
||||||
|
- `POST /api/research/projects/<id>/files` - 上传文件
|
||||||
|
- `GET /api/research/projects/<id>/files/<file_id>` - 下载文件
|
||||||
|
- `DELETE /api/research/projects/<id>/files/<file_id>` - 删除文件
|
||||||
|
|
||||||
|
## 数据库表结构
|
||||||
|
|
||||||
|
新增的数据表:
|
||||||
|
- `meetings` - 组会表
|
||||||
|
- `meeting_registrations` - 组会报名表
|
||||||
|
- `meeting_files` - 组会文件表
|
||||||
|
- `research_projects` - 研究项目表
|
||||||
|
- `research_notes` - 研究笔记表
|
||||||
|
- `research_tasks` - 研究任务表
|
||||||
|
- `research_files` - 研究文件表
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **数据库迁移**:首次使用需要运行数据库初始化,新表会自动创建
|
||||||
|
2. **文件存储**:上传的文件存储在 `uploads/meetings/` 和 `uploads/research/` 目录
|
||||||
|
3. **权限控制**:所有API都要求用户登录,编辑和删除操作会检查创建者权限
|
||||||
|
4. **数据共享**:所有实验室成员可以查看所有内容,但只有创建者可以编辑
|
||||||
|
|
||||||
|
## 下一步改进建议
|
||||||
|
|
||||||
|
1. 添加富文本编辑器(用于笔记和会议记录)
|
||||||
|
2. 添加邮件通知功能(组会提醒、任务到期提醒)
|
||||||
|
3. 添加数据导出功能(导出项目报告、会议记录)
|
||||||
|
4. 添加搜索和筛选功能(按标签、日期等筛选)
|
||||||
|
5. 添加数据可视化(项目进度图表、任务统计)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>学术资料 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100 transition-all duration-300" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-20 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600 hidden sm:inline">Design AI & Ecology Lab</span></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button id="mobile-menu-btn" class="md:hidden p-2 text-gray-600">
|
||||||
|
<i data-lucide="menu"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center space-x-6 text-sm font-medium tracking-wide text-gray-800">
|
||||||
|
<a href="website.html#about" class="nav-link hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="nav-link hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="nav-link hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="nav-link hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="nav-link hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="nav-link hover:text-black">加入我们</a>
|
||||||
|
|
||||||
|
<form id="search-form" class="relative search-box">
|
||||||
|
<input type="text" id="search-input" placeholder="搜索..."
|
||||||
|
class="px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:border-black w-40">
|
||||||
|
<button type="submit" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-black">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="login.html" id="login-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">登录</a>
|
||||||
|
<a href="#" id="logout-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-gray-100 absolute w-full px-6 py-4 space-y-4 shadow-lg">
|
||||||
|
<a href="website.html#about" class="block text-gray-800 hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="block text-gray-800 hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="block text-gray-800 hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="block text-gray-800 hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="block text-gray-800 hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="block text-gray-800 hover:text-black">加入我们</a>
|
||||||
|
<a href="login.html" class="block text-gray-800 hover:text-black">登录</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page Header -->
|
||||||
|
<section class="pt-32 pb-16 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold mb-4">学术资料</h1>
|
||||||
|
<p class="text-gray-600">软件使用教程、研究方法和学术资源</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Academic Content -->
|
||||||
|
<section class="py-12 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
<!-- Software Tutorials -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">软件使用</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<a href="#" class="block border-l-2 border-black pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">EcoGen AI 使用指南</h3>
|
||||||
|
<p class="text-xs text-gray-500">快速入门教程</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="block border-l-2 border-gray-200 pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">BioSwarm 仿真教程</h3>
|
||||||
|
<p class="text-xs text-gray-500">高级功能说明</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="block border-l-2 border-gray-200 pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">API 文档</h3>
|
||||||
|
<p class="text-xs text-gray-500">开发者参考</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Research Methods -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">研究方法</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<a href="#" class="block border-l-2 border-black pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">生成式生态学方法</h3>
|
||||||
|
<p class="text-xs text-gray-500">理论基础与实践</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="block border-l-2 border-gray-200 pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">生物启发算法</h3>
|
||||||
|
<p class="text-xs text-gray-500">算法设计与优化</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="block border-l-2 border-gray-200 pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">数据收集与分析</h3>
|
||||||
|
<p class="text-xs text-gray-500">研究数据管理</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Academic Resources -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">学术资源</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<a href="#" class="block border-l-2 border-black pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">参考文献库</h3>
|
||||||
|
<p class="text-xs text-gray-500">相关研究文献</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="block border-l-2 border-gray-200 pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">数据集说明</h3>
|
||||||
|
<p class="text-xs text-gray-500">数据使用指南</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="block border-l-2 border-gray-200 pl-4 py-2 hover:bg-gray-50">
|
||||||
|
<h3 class="font-bold text-sm mb-1">LaTeX 模板</h3>
|
||||||
|
<p class="text-xs text-gray-500">论文写作模板</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-black text-white py-16 border-t border-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Contact</h5>
|
||||||
|
<p class="text-sm text-gray-400 leading-loose">
|
||||||
|
Design AI & Ecology Lab<br>
|
||||||
|
1234 Design Avenue, Innovation District<br>
|
||||||
|
Shanghai, China<br>
|
||||||
|
<a href="mailto:hello@dael.edu.cn" class="text-white hover:underline">hello@dael.edu.cn</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Social</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="#" class="hover:text-white transition-colors">WeChat / 微信公众号</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Instagram</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Twitter / X</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Resources</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="resources.html" class="hover:text-white transition-colors">资源下载</a>
|
||||||
|
<a href="academic.html" class="hover:text-white transition-colors">学术资料</a>
|
||||||
|
<a href="team.html" class="hover:text-white transition-colors">完整团队</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Join Us</h5>
|
||||||
|
<p class="text-sm text-gray-400 mb-4">
|
||||||
|
我们长期招收博士后、博士生及研究助理。欢迎具有跨学科背景的申请者。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-white text-sm border-b border-white hover:text-gray-300 hover:border-gray-300 pb-0.5">申请职位</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-800 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<p>© 2024 Design AI & Ecology Lab. All Rights Reserved.</p>
|
||||||
|
<p class="mt-2">页面访问数: <span id="visit-counter" class="font-bold">0</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-4 md:mt-0">
|
||||||
|
<a href="#" class="hover:text-gray-400">Privacy Policy</a>
|
||||||
|
<a href="#" class="hover:text-gray-400">Terms of Use</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>内容管理 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased bg-gray-50">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white border-b border-gray-200 shadow-sm" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-16 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600">Admin Panel</span></span>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<span id="admin-user" class="text-sm text-gray-600"></span>
|
||||||
|
<a href="website.html" class="text-sm text-gray-600 hover:text-black">返回网站</a>
|
||||||
|
<button id="logout-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Admin Panel -->
|
||||||
|
<div class="pt-20 pb-12">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<h1 class="text-3xl font-bold mb-8">内容管理系统</h1>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="flex space-x-4 border-b border-gray-200 mb-8">
|
||||||
|
<button class="admin-tab active pb-4 px-4 text-sm font-medium border-b-2 border-black" data-tab="articles">文章管理</button>
|
||||||
|
<button class="admin-tab pb-4 px-4 text-sm font-medium text-gray-500 hover:text-black" data-tab="resources">资源管理</button>
|
||||||
|
<button class="admin-tab pb-4 px-4 text-sm font-medium text-gray-500 hover:text-black" data-tab="team">团队管理</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Articles Tab -->
|
||||||
|
<div id="tab-articles" class="admin-tab-content">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-xl font-bold">文章列表</h2>
|
||||||
|
<button onclick="showArticleForm()" class="bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">+ 新建文章</button>
|
||||||
|
</div>
|
||||||
|
<div id="articles-list" class="space-y-4">
|
||||||
|
<!-- Articles will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Resources Tab -->
|
||||||
|
<div id="tab-resources" class="admin-tab-content hidden">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-xl font-bold">资源列表</h2>
|
||||||
|
<button onclick="showResourceForm()" class="bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">+ 上传资源</button>
|
||||||
|
</div>
|
||||||
|
<div id="resources-list" class="space-y-4">
|
||||||
|
<!-- Resources will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Team Tab -->
|
||||||
|
<div id="tab-team" class="admin-tab-content hidden">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-xl font-bold">团队成员</h2>
|
||||||
|
<button onclick="showTeamForm()" class="bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">+ 添加成员</button>
|
||||||
|
</div>
|
||||||
|
<div id="team-list" class="space-y-4">
|
||||||
|
<!-- Team members will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Article Form Modal -->
|
||||||
|
<div id="article-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white rounded-lg p-8 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||||
|
<h3 class="text-2xl font-bold mb-6" id="article-modal-title">新建文章</h3>
|
||||||
|
<form id="article-form" onsubmit="saveArticle(event)">
|
||||||
|
<input type="hidden" id="article-id">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">标题</label>
|
||||||
|
<input type="text" id="article-title" required class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">摘要</label>
|
||||||
|
<textarea id="article-summary" class="form-input w-full" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">内容</label>
|
||||||
|
<textarea id="article-content" required class="form-input w-full" rows="10"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">分类</label>
|
||||||
|
<select id="article-category" class="form-input w-full">
|
||||||
|
<option value="news">新闻</option>
|
||||||
|
<option value="publication">出版物</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">状态</label>
|
||||||
|
<select id="article-published" class="form-input w-full">
|
||||||
|
<option value="true">已发布</option>
|
||||||
|
<option value="false">草稿</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="checkbox" id="article-protected" class="mr-2">
|
||||||
|
<span class="text-sm">受保护内容(需要登录查看)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<button type="submit" class="bg-black text-white px-6 py-2 rounded hover:bg-gray-800">保存</button>
|
||||||
|
<button type="button" onclick="closeArticleForm()" class="bg-gray-200 text-gray-800 px-6 py-2 rounded hover:bg-gray-300">取消</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Resource Form Modal -->
|
||||||
|
<div id="resource-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white rounded-lg p-8 max-w-2xl w-full mx-4">
|
||||||
|
<h3 class="text-2xl font-bold mb-6">上传资源</h3>
|
||||||
|
<form id="resource-form" onsubmit="saveResource(event)">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">文件</label>
|
||||||
|
<input type="file" id="resource-file" required class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">名称</label>
|
||||||
|
<input type="text" id="resource-name" required class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">描述</label>
|
||||||
|
<textarea id="resource-description" class="form-input w-full" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">分类</label>
|
||||||
|
<select id="resource-category" class="form-input w-full">
|
||||||
|
<option value="software">软件</option>
|
||||||
|
<option value="dataset">数据集</option>
|
||||||
|
<option value="document">文档</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="checkbox" id="resource-protected" class="mr-2">
|
||||||
|
<span class="text-sm">受保护资源(需要登录下载)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<button type="submit" class="bg-black text-white px-6 py-2 rounded hover:bg-gray-800">上传</button>
|
||||||
|
<button type="button" onclick="closeResourceForm()" class="bg-gray-200 text-gray-800 px-6 py-2 rounded hover:bg-gray-300">取消</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Team Form Modal -->
|
||||||
|
<div id="team-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white rounded-lg p-8 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||||
|
<h3 class="text-2xl font-bold mb-6">添加团队成员</h3>
|
||||||
|
<form id="team-form" onsubmit="saveTeamMember(event)">
|
||||||
|
<input type="hidden" id="team-id">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">姓名</label>
|
||||||
|
<input type="text" id="team-name" required class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">职位(中文)</label>
|
||||||
|
<input type="text" id="team-title" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">职位(英文)</label>
|
||||||
|
<input type="text" id="team-title-en" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">简介(中文)</label>
|
||||||
|
<textarea id="team-bio" class="form-input w-full" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">简介(英文)</label>
|
||||||
|
<textarea id="team-bio-en" class="form-input w-full" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">角色</label>
|
||||||
|
<select id="team-role" class="form-input w-full">
|
||||||
|
<option value="professor">教授</option>
|
||||||
|
<option value="faculty">教师</option>
|
||||||
|
<option value="student">在读学生</option>
|
||||||
|
<option value="alumni">毕业学生</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">排序</label>
|
||||||
|
<input type="number" id="team-order" value="0" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">邮箱</label>
|
||||||
|
<input type="email" id="team-email" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">网站</label>
|
||||||
|
<input type="url" id="team-website" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<button type="submit" class="bg-black text-white px-6 py-2 rounded hover:bg-gray-800">保存</button>
|
||||||
|
<button type="button" onclick="closeTeamForm()" class="bg-gray-200 text-gray-800 px-6 py-2 rounded hover:bg-gray-300">取消</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
<script src="js/admin.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# DAEL Lab Backend API
|
||||||
|
|
||||||
|
Flask后端API服务器,提供用户认证、内容管理和数据API功能。
|
||||||
|
|
||||||
|
## 安装和设置
|
||||||
|
|
||||||
|
### 1. 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置环境变量
|
||||||
|
|
||||||
|
复制 `.env.example` 为 `.env` 并修改配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 初始化数据库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python init_data.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这将创建数据库表和默认账户:
|
||||||
|
- 管理员:`admin` / `admin123`
|
||||||
|
- 测试用户:`user` / `user123`
|
||||||
|
|
||||||
|
### 4. 启动服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
服务器将在 `http://localhost:5000` 启动。
|
||||||
|
|
||||||
|
## API端点
|
||||||
|
|
||||||
|
### 认证
|
||||||
|
- `POST /api/auth/register` - 用户注册
|
||||||
|
- `POST /api/auth/login` - 用户登录
|
||||||
|
- `POST /api/auth/logout` - 用户登出
|
||||||
|
- `GET /api/auth/check` - 检查认证状态
|
||||||
|
- `GET /api/auth/me` - 获取当前用户信息
|
||||||
|
|
||||||
|
### 内容管理
|
||||||
|
- `GET /api/cms/articles` - 获取文章列表
|
||||||
|
- `GET /api/cms/articles/<id>` - 获取单篇文章
|
||||||
|
- `POST /api/cms/articles` - 创建文章(管理员)
|
||||||
|
- `PUT /api/cms/articles/<id>` - 更新文章(管理员)
|
||||||
|
- `DELETE /api/cms/articles/<id>` - 删除文章(管理员)
|
||||||
|
|
||||||
|
- `GET /api/cms/resources` - 获取资源列表
|
||||||
|
- `POST /api/cms/resources` - 上传资源(管理员)
|
||||||
|
|
||||||
|
- `GET /api/cms/team` - 获取团队成员列表
|
||||||
|
- `POST /api/cms/team` - 创建团队成员(管理员)
|
||||||
|
|
||||||
|
### 其他API
|
||||||
|
- `GET /api/download/<resource_id>` - 下载资源文件
|
||||||
|
- `GET /api/stats` - 获取统计数据(需登录)
|
||||||
|
- `GET /api/protected-content` - 获取受保护内容(需登录)
|
||||||
|
|
||||||
|
## 数据库模型
|
||||||
|
|
||||||
|
- **User**: 用户模型
|
||||||
|
- **Article**: 文章/新闻模型
|
||||||
|
- **Resource**: 资源文件模型
|
||||||
|
- **TeamMember**: 团队成员模型
|
||||||
|
|
||||||
|
## 开发说明
|
||||||
|
|
||||||
|
- 数据库文件存储在 `../data/dael.db`
|
||||||
|
- 上传文件存储在 `../uploads/`
|
||||||
|
- 使用SQLite作为开发数据库,生产环境建议使用PostgreSQL
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
from flask import Flask, request, jsonify
|
||||||
|
from flask_cors import CORS
|
||||||
|
from flask_login import LoginManager
|
||||||
|
from models import db, User
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
|
||||||
|
|
||||||
|
# 获取项目根目录(backend的父目录)
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
data_dir = os.path.join(base_dir, 'data')
|
||||||
|
db_path = os.path.join(data_dir, 'dael.db')
|
||||||
|
|
||||||
|
# 确保data目录存在
|
||||||
|
os.makedirs(data_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 配置数据库URI(使用绝对路径)
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', f'sqlite:///{db_path}')
|
||||||
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
app.config['UPLOAD_FOLDER'] = os.path.join(base_dir, 'uploads')
|
||||||
|
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB max file size
|
||||||
|
|
||||||
|
# 确保上传目录存在
|
||||||
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
# 配置CORS以支持跨域请求和cookies
|
||||||
|
CORS(app,
|
||||||
|
origins=['http://localhost:6002', 'http://127.0.0.1:6002', 'http://8.152.100.83:6002'],
|
||||||
|
supports_credentials=True,
|
||||||
|
allow_headers=['Content-Type', 'Authorization'])
|
||||||
|
|
||||||
|
# Flask-Login配置
|
||||||
|
login_manager = LoginManager()
|
||||||
|
login_manager.init_app(app)
|
||||||
|
login_manager.login_view = 'login'
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return User.query.get(int(user_id))
|
||||||
|
|
||||||
|
# 导入路由
|
||||||
|
from routes import auth, cms, api, meetings, research
|
||||||
|
|
||||||
|
app.register_blueprint(auth.bp, url_prefix='/api/auth')
|
||||||
|
app.register_blueprint(cms.bp, url_prefix='/api/cms')
|
||||||
|
app.register_blueprint(api.bp, url_prefix='/api')
|
||||||
|
app.register_blueprint(meetings.bp, url_prefix='/api/meetings')
|
||||||
|
app.register_blueprint(research.bp, url_prefix='/api/research')
|
||||||
|
|
||||||
|
@app.route('/api/health', methods=['GET'])
|
||||||
|
def health_check():
|
||||||
|
"""健康检查"""
|
||||||
|
return jsonify({'status': 'ok', 'message': 'DAEL Lab API is running'})
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
"""初始化数据库"""
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# 创建默认管理员账户(如果不存在)
|
||||||
|
if not User.query.filter_by(username='admin').first():
|
||||||
|
admin = User(username='admin', email='admin@dael.edu.cn', is_admin=True)
|
||||||
|
admin.set_password('admin123') # 默认密码,生产环境应更改
|
||||||
|
db.session.add(admin)
|
||||||
|
db.session.commit()
|
||||||
|
print("Default admin user created: admin/admin123")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
init_db()
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||||
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""初始化数据库并添加示例数据"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 确保可以导入app
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from app import app, db
|
||||||
|
from models import User, Article, Resource, TeamMember
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def init_data():
|
||||||
|
# 确保data目录存在
|
||||||
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
data_dir = os.path.join(base_dir, 'data')
|
||||||
|
os.makedirs(data_dir, exist_ok=True)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
# 创建数据库表
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# 创建默认管理员
|
||||||
|
if not User.query.filter_by(username='admin').first():
|
||||||
|
admin = User(username='admin', email='admin@dael.edu.cn', is_admin=True)
|
||||||
|
admin.set_password('admin123')
|
||||||
|
db.session.add(admin)
|
||||||
|
print("Created admin user: admin/admin123")
|
||||||
|
|
||||||
|
# 创建示例用户
|
||||||
|
if not User.query.filter_by(username='user').first():
|
||||||
|
user = User(username='user', email='user@dael.edu.cn', is_admin=False)
|
||||||
|
user.set_password('user123')
|
||||||
|
db.session.add(user)
|
||||||
|
print("Created test user: user/user123")
|
||||||
|
|
||||||
|
# 创建示例文章(受保护)
|
||||||
|
if Article.query.count() == 0:
|
||||||
|
articles = [
|
||||||
|
Article(
|
||||||
|
title='实验室内部研究进展报告',
|
||||||
|
content='这是实验室的内部研究进展报告,包含详细的研究数据和成果分析...',
|
||||||
|
summary='2024年度实验室内部研究进展总结',
|
||||||
|
category='news',
|
||||||
|
is_protected=True,
|
||||||
|
author_id=1,
|
||||||
|
published=True
|
||||||
|
),
|
||||||
|
Article(
|
||||||
|
title='内部技术文档:生成式生态学算法详解',
|
||||||
|
content='本文档详细介绍了生成式生态学算法的实现原理和应用案例...',
|
||||||
|
summary='内部技术文档,仅供实验室成员查阅',
|
||||||
|
category='publication',
|
||||||
|
is_protected=True,
|
||||||
|
author_id=1,
|
||||||
|
published=True
|
||||||
|
)
|
||||||
|
]
|
||||||
|
for article in articles:
|
||||||
|
db.session.add(article)
|
||||||
|
print("Created sample protected articles")
|
||||||
|
|
||||||
|
# 创建示例资源(受保护)
|
||||||
|
if Resource.query.count() == 0:
|
||||||
|
resources = [
|
||||||
|
Resource(
|
||||||
|
name='内部数据集 v2.0',
|
||||||
|
description='实验室内部使用的完整数据集,包含所有研究数据',
|
||||||
|
file_path='/uploads/internal_dataset.zip',
|
||||||
|
file_size=1024 * 1024 * 500, # 500MB
|
||||||
|
file_type='zip',
|
||||||
|
category='dataset',
|
||||||
|
is_protected=True
|
||||||
|
),
|
||||||
|
Resource(
|
||||||
|
name='内部研究报告模板',
|
||||||
|
description='实验室内部研究报告的标准模板',
|
||||||
|
file_path='/uploads/report_template.docx',
|
||||||
|
file_size=1024 * 50, # 50KB
|
||||||
|
file_type='docx',
|
||||||
|
category='document',
|
||||||
|
is_protected=True
|
||||||
|
)
|
||||||
|
]
|
||||||
|
for resource in resources:
|
||||||
|
db.session.add(resource)
|
||||||
|
print("Created sample protected resources")
|
||||||
|
|
||||||
|
# 创建团队成员
|
||||||
|
if TeamMember.query.count() == 0:
|
||||||
|
members = [
|
||||||
|
TeamMember(
|
||||||
|
name='Dr. Lin Chen',
|
||||||
|
title='Principal Investigator / 实验室主任',
|
||||||
|
title_en='Principal Investigator',
|
||||||
|
bio='博士毕业于MIT Media Lab,专注于计算设计与人机交互领域的研究。',
|
||||||
|
bio_en='PhD from MIT Media Lab, focusing on computational design and HCI.',
|
||||||
|
role='professor',
|
||||||
|
email='lchen@dael.edu.cn',
|
||||||
|
order=1
|
||||||
|
),
|
||||||
|
TeamMember(
|
||||||
|
name='Sarah Wu',
|
||||||
|
title='Lead Ecologist / 生态学顾问',
|
||||||
|
title_en='Lead Ecologist',
|
||||||
|
bio='城市生态学专家,研究重点为城市微气候与生物多样性修复。',
|
||||||
|
bio_en='Urban ecology expert, focusing on microclimate and biodiversity restoration.',
|
||||||
|
role='faculty',
|
||||||
|
email='swu@dael.edu.cn',
|
||||||
|
order=2
|
||||||
|
)
|
||||||
|
]
|
||||||
|
for member in members:
|
||||||
|
db.session.add(member)
|
||||||
|
print("Created sample team members")
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
print("\nDatabase initialized successfully!")
|
||||||
|
print("\nDefault accounts:")
|
||||||
|
print(" Admin: admin / admin123")
|
||||||
|
print(" User: user / user123")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
init_data()
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 初始化数据库脚本
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# 检查虚拟环境
|
||||||
|
if [ -d ".venv" ]; then
|
||||||
|
source .venv/bin/activate
|
||||||
|
python init_data.py
|
||||||
|
else
|
||||||
|
echo "Virtual environment not found. Please run: python3 -m venv .venv"
|
||||||
|
echo "Then install dependencies: pip install -r requirements.txt"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_login import UserMixin
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
"""用户模型"""
|
||||||
|
__tablename__ = 'users'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||||
|
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
|
password_hash = db.Column(db.String(255), nullable=False)
|
||||||
|
is_admin = db.Column(db.Boolean, default=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
self.password_hash = generate_password_hash(password)
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
return check_password_hash(self.password_hash, password)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'username': self.username,
|
||||||
|
'email': self.email,
|
||||||
|
'is_admin': self.is_admin,
|
||||||
|
'created_at': self.created_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Article(db.Model):
|
||||||
|
"""文章/新闻模型"""
|
||||||
|
__tablename__ = 'articles'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
title = db.Column(db.String(200), nullable=False)
|
||||||
|
content = db.Column(db.Text, nullable=False)
|
||||||
|
summary = db.Column(db.Text)
|
||||||
|
category = db.Column(db.String(50), default='news') # news, publication, etc.
|
||||||
|
is_protected = db.Column(db.Boolean, default=False) # 是否需要登录查看
|
||||||
|
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
published = db.Column(db.Boolean, default=True)
|
||||||
|
|
||||||
|
author = db.relationship('User', backref='articles')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'title': self.title,
|
||||||
|
'content': self.content,
|
||||||
|
'summary': self.summary,
|
||||||
|
'category': self.category,
|
||||||
|
'is_protected': self.is_protected,
|
||||||
|
'author_id': self.author_id,
|
||||||
|
'created_at': self.created_at.isoformat(),
|
||||||
|
'updated_at': self.updated_at.isoformat(),
|
||||||
|
'published': self.published
|
||||||
|
}
|
||||||
|
|
||||||
|
class Resource(db.Model):
|
||||||
|
"""资源文件模型"""
|
||||||
|
__tablename__ = 'resources'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(200), nullable=False)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
file_path = db.Column(db.String(500), nullable=False)
|
||||||
|
file_size = db.Column(db.Integer) # 文件大小(字节)
|
||||||
|
file_type = db.Column(db.String(50)) # 文件类型
|
||||||
|
category = db.Column(db.String(50)) # software, dataset, document, etc.
|
||||||
|
is_protected = db.Column(db.Boolean, default=False) # 是否需要登录下载
|
||||||
|
download_count = db.Column(db.Integer, default=0)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'name': self.name,
|
||||||
|
'description': self.description,
|
||||||
|
'file_path': self.file_path,
|
||||||
|
'file_size': self.file_size,
|
||||||
|
'file_type': self.file_type,
|
||||||
|
'category': self.category,
|
||||||
|
'is_protected': self.is_protected,
|
||||||
|
'download_count': self.download_count,
|
||||||
|
'created_at': self.created_at.isoformat(),
|
||||||
|
'updated_at': self.updated_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
class TeamMember(db.Model):
|
||||||
|
"""团队成员模型"""
|
||||||
|
__tablename__ = 'team_members'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(100), nullable=False)
|
||||||
|
title = db.Column(db.String(200))
|
||||||
|
title_en = db.Column(db.String(200))
|
||||||
|
bio = db.Column(db.Text)
|
||||||
|
bio_en = db.Column(db.Text)
|
||||||
|
role = db.Column(db.String(50)) # professor, student, alumni
|
||||||
|
email = db.Column(db.String(120))
|
||||||
|
website = db.Column(db.String(500))
|
||||||
|
avatar_path = db.Column(db.String(500))
|
||||||
|
order = db.Column(db.Integer, default=0) # 排序
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'name': self.name,
|
||||||
|
'title': self.title,
|
||||||
|
'title_en': self.title_en,
|
||||||
|
'bio': self.bio,
|
||||||
|
'bio_en': self.bio_en,
|
||||||
|
'role': self.role,
|
||||||
|
'email': self.email,
|
||||||
|
'website': self.website,
|
||||||
|
'avatar_path': self.avatar_path,
|
||||||
|
'order': self.order,
|
||||||
|
'created_at': self.created_at.isoformat(),
|
||||||
|
'updated_at': self.updated_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Meeting(db.Model):
|
||||||
|
"""组会模型"""
|
||||||
|
__tablename__ = 'meetings'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
title = db.Column(db.String(200), nullable=False)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
date = db.Column(db.Date, nullable=False)
|
||||||
|
time = db.Column(db.Time, nullable=False)
|
||||||
|
location = db.Column(db.String(200))
|
||||||
|
organizer_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||||
|
status = db.Column(db.String(50), default='planned') # planned, ongoing, completed, cancelled
|
||||||
|
meeting_type = db.Column(db.String(50), default='weekly') # weekly, monthly, special
|
||||||
|
meeting_notes = db.Column(db.Text)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
organizer = db.relationship('User', backref='organized_meetings')
|
||||||
|
registrations = db.relationship('MeetingRegistration', backref='meeting', cascade='all, delete-orphan')
|
||||||
|
files = db.relationship('MeetingFile', backref='meeting', cascade='all, delete-orphan')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'title': self.title,
|
||||||
|
'description': self.description,
|
||||||
|
'date': self.date.isoformat() if self.date else None,
|
||||||
|
'time': self.time.strftime('%H:%M') if self.time else None,
|
||||||
|
'location': self.location,
|
||||||
|
'organizer_id': self.organizer_id,
|
||||||
|
'organizer_name': self.organizer.username if self.organizer else None,
|
||||||
|
'status': self.status,
|
||||||
|
'meeting_type': self.meeting_type,
|
||||||
|
'meeting_notes': self.meeting_notes,
|
||||||
|
'created_at': self.created_at.isoformat(),
|
||||||
|
'updated_at': self.updated_at.isoformat(),
|
||||||
|
'attendee_count': len(self.registrations) if self.registrations else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
class MeetingRegistration(db.Model):
|
||||||
|
"""组会报名模型"""
|
||||||
|
__tablename__ = 'meeting_registrations'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
meeting_id = db.Column(db.Integer, db.ForeignKey('meetings.id'), nullable=False)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||||
|
registered_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
status = db.Column(db.String(50), default='registered') # registered, checked_in, absent
|
||||||
|
|
||||||
|
user = db.relationship('User', backref='meeting_registrations')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'meeting_id': self.meeting_id,
|
||||||
|
'user_id': self.user_id,
|
||||||
|
'username': self.user.username if self.user else None,
|
||||||
|
'registered_at': self.registered_at.isoformat(),
|
||||||
|
'status': self.status
|
||||||
|
}
|
||||||
|
|
||||||
|
class MeetingFile(db.Model):
|
||||||
|
"""组会文件模型"""
|
||||||
|
__tablename__ = 'meeting_files'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
meeting_id = db.Column(db.Integer, db.ForeignKey('meetings.id'), nullable=False)
|
||||||
|
name = db.Column(db.String(200), nullable=False)
|
||||||
|
file_path = db.Column(db.String(500), nullable=False)
|
||||||
|
file_size = db.Column(db.Integer)
|
||||||
|
file_type = db.Column(db.String(50))
|
||||||
|
uploader_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||||
|
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
uploader = db.relationship('User', backref='uploaded_meeting_files')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'meeting_id': self.meeting_id,
|
||||||
|
'name': self.name,
|
||||||
|
'file_path': self.file_path,
|
||||||
|
'file_size': self.file_size,
|
||||||
|
'file_type': self.file_type,
|
||||||
|
'uploader_id': self.uploader_id,
|
||||||
|
'uploaded_at': self.uploaded_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResearchProject(db.Model):
|
||||||
|
"""研究项目模型"""
|
||||||
|
__tablename__ = 'research_projects'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
title = db.Column(db.String(200), nullable=False)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
start_date = db.Column(db.Date)
|
||||||
|
end_date = db.Column(db.Date)
|
||||||
|
owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||||
|
status = db.Column(db.String(50), default='active') # active, completed, paused, cancelled
|
||||||
|
progress = db.Column(db.Integer, default=0) # 0-100
|
||||||
|
tags = db.Column(db.String(500)) # 逗号分隔的标签
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
owner = db.relationship('User', backref='research_projects')
|
||||||
|
notes = db.relationship('ResearchNote', backref='project', cascade='all, delete-orphan')
|
||||||
|
tasks = db.relationship('ResearchTask', backref='project', cascade='all, delete-orphan')
|
||||||
|
files = db.relationship('ResearchFile', backref='project', cascade='all, delete-orphan')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'title': self.title,
|
||||||
|
'description': self.description,
|
||||||
|
'start_date': self.start_date.isoformat() if self.start_date else None,
|
||||||
|
'end_date': self.end_date.isoformat() if self.end_date else None,
|
||||||
|
'owner_id': self.owner_id,
|
||||||
|
'owner_name': self.owner.username if self.owner else None,
|
||||||
|
'status': self.status,
|
||||||
|
'progress': self.progress,
|
||||||
|
'tags': self.tags.split(',') if self.tags else [],
|
||||||
|
'created_at': self.created_at.isoformat(),
|
||||||
|
'updated_at': self.updated_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResearchNote(db.Model):
|
||||||
|
"""研究笔记模型"""
|
||||||
|
__tablename__ = 'research_notes'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
project_id = db.Column(db.Integer, db.ForeignKey('research_projects.id'), nullable=False)
|
||||||
|
title = db.Column(db.String(200), nullable=False)
|
||||||
|
content = db.Column(db.Text, nullable=False)
|
||||||
|
author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||||
|
note_type = db.Column(db.String(50), default='note') # note, experiment, literature, idea
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
author = db.relationship('User', backref='research_notes')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'project_id': self.project_id,
|
||||||
|
'title': self.title,
|
||||||
|
'content': self.content,
|
||||||
|
'author_id': self.author_id,
|
||||||
|
'author_name': self.author.username if self.author else None,
|
||||||
|
'note_type': self.note_type,
|
||||||
|
'created_at': self.created_at.isoformat(),
|
||||||
|
'updated_at': self.updated_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResearchTask(db.Model):
|
||||||
|
"""研究任务模型"""
|
||||||
|
__tablename__ = 'research_tasks'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
project_id = db.Column(db.Integer, db.ForeignKey('research_projects.id'), nullable=False)
|
||||||
|
title = db.Column(db.String(200), nullable=False)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
assignee_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||||
|
due_date = db.Column(db.Date)
|
||||||
|
status = db.Column(db.String(50), default='todo') # todo, in_progress, completed, cancelled
|
||||||
|
priority = db.Column(db.String(50), default='medium') # high, medium, low
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
assignee = db.relationship('User', backref='assigned_tasks')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'project_id': self.project_id,
|
||||||
|
'title': self.title,
|
||||||
|
'description': self.description,
|
||||||
|
'assignee_id': self.assignee_id,
|
||||||
|
'assignee_name': self.assignee.username if self.assignee else None,
|
||||||
|
'due_date': self.due_date.isoformat() if self.due_date else None,
|
||||||
|
'status': self.status,
|
||||||
|
'priority': self.priority,
|
||||||
|
'created_at': self.created_at.isoformat(),
|
||||||
|
'updated_at': self.updated_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResearchFile(db.Model):
|
||||||
|
"""研究文件模型"""
|
||||||
|
__tablename__ = 'research_files'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
project_id = db.Column(db.Integer, db.ForeignKey('research_projects.id'), nullable=False)
|
||||||
|
name = db.Column(db.String(200), nullable=False)
|
||||||
|
file_path = db.Column(db.String(500), nullable=False)
|
||||||
|
file_size = db.Column(db.Integer)
|
||||||
|
file_type = db.Column(db.String(50))
|
||||||
|
uploader_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||||
|
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
uploader = db.relationship('User', backref='uploaded_research_files')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'project_id': self.project_id,
|
||||||
|
'name': self.name,
|
||||||
|
'file_path': self.file_path,
|
||||||
|
'file_size': self.file_size,
|
||||||
|
'file_type': self.file_type,
|
||||||
|
'uploader_id': self.uploader_id,
|
||||||
|
'uploaded_at': self.uploaded_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
Flask==3.0.0
|
||||||
|
Flask-SQLAlchemy==3.1.1
|
||||||
|
Flask-Login==0.6.3
|
||||||
|
Flask-CORS==4.0.0
|
||||||
|
Werkzeug==3.0.1
|
||||||
|
bcrypt==4.1.2
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Routes package
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
from flask import Blueprint, request, jsonify, send_from_directory
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 添加父目录到路径
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
from models import db, Resource
|
||||||
|
|
||||||
|
bp = Blueprint('api', __name__)
|
||||||
|
|
||||||
|
@bp.route('/download/<int:resource_id>', methods=['GET'])
|
||||||
|
def download_resource(resource_id):
|
||||||
|
"""下载资源文件"""
|
||||||
|
resource = Resource.query.get_or_404(resource_id)
|
||||||
|
|
||||||
|
# 检查是否需要登录
|
||||||
|
if resource.is_protected and not current_user.is_authenticated:
|
||||||
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
|
# 增加下载计数
|
||||||
|
resource.download_count += 1
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# 返回文件
|
||||||
|
uploads_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'uploads')
|
||||||
|
filename = os.path.basename(resource.file_path)
|
||||||
|
|
||||||
|
return send_from_directory(uploads_dir, filename, as_attachment=True)
|
||||||
|
|
||||||
|
@bp.route('/stats', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_stats():
|
||||||
|
"""获取统计数据(需要登录)"""
|
||||||
|
from models import Article, Resource, TeamMember
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
'articles': Article.query.count(),
|
||||||
|
'resources': Resource.query.count(),
|
||||||
|
'team_members': TeamMember.query.count(),
|
||||||
|
'total_downloads': db.session.query(db.func.sum(Resource.download_count)).scalar() or 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify({'stats': stats}), 200
|
||||||
|
|
||||||
|
@bp.route('/protected-content', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_protected_content():
|
||||||
|
"""获取受保护内容(需要登录)"""
|
||||||
|
from models import Article, Resource
|
||||||
|
|
||||||
|
protected_articles = Article.query.filter_by(is_protected=True, published=True).all()
|
||||||
|
protected_resources = Resource.query.filter_by(is_protected=True).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'articles': [article.to_dict() for article in protected_articles],
|
||||||
|
'resources': [resource.to_dict() for resource in protected_resources]
|
||||||
|
}), 200
|
||||||
|
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 添加父目录到路径
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
from models import db, User
|
||||||
|
from werkzeug.security import check_password_hash
|
||||||
|
|
||||||
|
bp = Blueprint('auth', __name__)
|
||||||
|
|
||||||
|
@bp.route('/register', methods=['POST'])
|
||||||
|
def register():
|
||||||
|
"""用户注册"""
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if not data or not data.get('username') or not data.get('email') or not data.get('password'):
|
||||||
|
return jsonify({'error': 'Missing required fields'}), 400
|
||||||
|
|
||||||
|
# 检查用户名是否已存在
|
||||||
|
if User.query.filter_by(username=data['username']).first():
|
||||||
|
return jsonify({'error': 'Username already exists'}), 400
|
||||||
|
|
||||||
|
# 检查邮箱是否已存在
|
||||||
|
if User.query.filter_by(email=data['email']).first():
|
||||||
|
return jsonify({'error': 'Email already exists'}), 400
|
||||||
|
|
||||||
|
# 创建新用户
|
||||||
|
user = User(
|
||||||
|
username=data['username'],
|
||||||
|
email=data['email'],
|
||||||
|
is_admin=data.get('is_admin', False)
|
||||||
|
)
|
||||||
|
user.set_password(data['password'])
|
||||||
|
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'User registered successfully',
|
||||||
|
'user': user.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
@bp.route('/login', methods=['POST'])
|
||||||
|
def login():
|
||||||
|
"""用户登录"""
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if not data or not data.get('username') or not data.get('password'):
|
||||||
|
return jsonify({'error': 'Username and password required'}), 400
|
||||||
|
|
||||||
|
user = User.query.filter_by(username=data['username']).first()
|
||||||
|
|
||||||
|
if user and user.check_password(data['password']):
|
||||||
|
login_user(user, remember=data.get('remember', False))
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Login successful',
|
||||||
|
'user': user.to_dict()
|
||||||
|
}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({'error': 'Invalid username or password'}), 401
|
||||||
|
|
||||||
|
@bp.route('/logout', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
"""用户登出"""
|
||||||
|
logout_user()
|
||||||
|
return jsonify({'message': 'Logout successful'}), 200
|
||||||
|
|
||||||
|
@bp.route('/me', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_current_user():
|
||||||
|
"""获取当前登录用户信息"""
|
||||||
|
return jsonify({'user': current_user.to_dict()}), 200
|
||||||
|
|
||||||
|
@bp.route('/check', methods=['GET'])
|
||||||
|
def check_auth():
|
||||||
|
"""检查认证状态"""
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return jsonify({
|
||||||
|
'authenticated': True,
|
||||||
|
'user': current_user.to_dict()
|
||||||
|
}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({'authenticated': False}), 200
|
||||||
|
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
from flask import Blueprint, request, jsonify, send_file, current_app
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 添加父目录到路径
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
from models import db, Article, Resource, TeamMember
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
bp = Blueprint('cms', __name__)
|
||||||
|
|
||||||
|
def allowed_file(filename, allowed_extensions):
|
||||||
|
"""检查文件扩展名是否允许"""
|
||||||
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||||
|
|
||||||
|
# 文章管理
|
||||||
|
@bp.route('/articles', methods=['GET'])
|
||||||
|
def get_articles():
|
||||||
|
"""获取文章列表"""
|
||||||
|
category = request.args.get('category', 'news')
|
||||||
|
published_only = request.args.get('published_only', 'true').lower() == 'true'
|
||||||
|
|
||||||
|
query = Article.query.filter_by(category=category)
|
||||||
|
|
||||||
|
if published_only:
|
||||||
|
query = query.filter_by(published=True)
|
||||||
|
|
||||||
|
# 如果未登录,过滤受保护内容
|
||||||
|
if not current_user.is_authenticated:
|
||||||
|
query = query.filter_by(is_protected=False)
|
||||||
|
|
||||||
|
articles = query.order_by(Article.created_at.desc()).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'articles': [article.to_dict() for article in articles]
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/articles/<int:article_id>', methods=['GET'])
|
||||||
|
def get_article(article_id):
|
||||||
|
"""获取单篇文章"""
|
||||||
|
article = Article.query.get_or_404(article_id)
|
||||||
|
|
||||||
|
# 检查是否需要登录
|
||||||
|
if article.is_protected and not current_user.is_authenticated:
|
||||||
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
|
if not article.published:
|
||||||
|
if not current_user.is_authenticated or not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Article not found'}), 404
|
||||||
|
|
||||||
|
return jsonify({'article': article.to_dict()}), 200
|
||||||
|
|
||||||
|
@bp.route('/articles', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def create_article():
|
||||||
|
"""创建文章(需要管理员权限)"""
|
||||||
|
if not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Admin access required'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
article = Article(
|
||||||
|
title=data.get('title'),
|
||||||
|
content=data.get('content'),
|
||||||
|
summary=data.get('summary'),
|
||||||
|
category=data.get('category', 'news'),
|
||||||
|
is_protected=data.get('is_protected', False),
|
||||||
|
author_id=current_user.id,
|
||||||
|
published=data.get('published', True)
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(article)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Article created successfully',
|
||||||
|
'article': article.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
@bp.route('/articles/<int:article_id>', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_article(article_id):
|
||||||
|
"""更新文章(需要管理员权限)"""
|
||||||
|
if not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Admin access required'}), 403
|
||||||
|
|
||||||
|
article = Article.query.get_or_404(article_id)
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
article.title = data.get('title', article.title)
|
||||||
|
article.content = data.get('content', article.content)
|
||||||
|
article.summary = data.get('summary', article.summary)
|
||||||
|
article.category = data.get('category', article.category)
|
||||||
|
article.is_protected = data.get('is_protected', article.is_protected)
|
||||||
|
article.published = data.get('published', article.published)
|
||||||
|
article.updated_at = datetime.utcnow()
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Article updated successfully',
|
||||||
|
'article': article.to_dict()
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/articles/<int:article_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_article(article_id):
|
||||||
|
"""删除文章(需要管理员权限)"""
|
||||||
|
if not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Admin access required'}), 403
|
||||||
|
|
||||||
|
article = Article.query.get_or_404(article_id)
|
||||||
|
db.session.delete(article)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({'message': 'Article deleted successfully'}), 200
|
||||||
|
|
||||||
|
# 资源管理
|
||||||
|
@bp.route('/resources', methods=['GET'])
|
||||||
|
def get_resources():
|
||||||
|
"""获取资源列表"""
|
||||||
|
category = request.args.get('category')
|
||||||
|
|
||||||
|
query = Resource.query
|
||||||
|
|
||||||
|
if category:
|
||||||
|
query = query.filter_by(category=category)
|
||||||
|
|
||||||
|
# 如果未登录,过滤受保护资源
|
||||||
|
if not current_user.is_authenticated:
|
||||||
|
query = query.filter_by(is_protected=False)
|
||||||
|
|
||||||
|
resources = query.order_by(Resource.created_at.desc()).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'resources': [resource.to_dict() for resource in resources]
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/resources', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def upload_resource():
|
||||||
|
"""上传资源文件(需要管理员权限)"""
|
||||||
|
if not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Admin access required'}), 403
|
||||||
|
|
||||||
|
if 'file' not in request.files:
|
||||||
|
return jsonify({'error': 'No file provided'}), 400
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
if file.filename == '':
|
||||||
|
return jsonify({'error': 'No file selected'}), 400
|
||||||
|
|
||||||
|
if file and allowed_file(file.filename, {'pdf', 'zip', 'rar', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'}):
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
uploads_dir = current_app.config['UPLOAD_FOLDER']
|
||||||
|
os.makedirs(uploads_dir, exist_ok=True)
|
||||||
|
filepath = os.path.join(uploads_dir, filename)
|
||||||
|
file.save(filepath)
|
||||||
|
|
||||||
|
resource = Resource(
|
||||||
|
name=request.form.get('name', filename),
|
||||||
|
description=request.form.get('description', ''),
|
||||||
|
file_path=f'/uploads/{filename}',
|
||||||
|
file_size=os.path.getsize(filepath),
|
||||||
|
file_type=filename.rsplit('.', 1)[1].lower(),
|
||||||
|
category=request.form.get('category', 'document'),
|
||||||
|
is_protected=request.form.get('is_protected', 'false').lower() == 'true'
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(resource)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Resource uploaded successfully',
|
||||||
|
'resource': resource.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
return jsonify({'error': 'Invalid file type'}), 400
|
||||||
|
|
||||||
|
# 团队成员管理
|
||||||
|
@bp.route('/team', methods=['GET'])
|
||||||
|
def get_team():
|
||||||
|
"""获取团队成员列表"""
|
||||||
|
role = request.args.get('role')
|
||||||
|
|
||||||
|
query = TeamMember.query
|
||||||
|
|
||||||
|
if role:
|
||||||
|
query = query.filter_by(role=role)
|
||||||
|
|
||||||
|
members = query.order_by(TeamMember.order, TeamMember.created_at).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'members': [member.to_dict() for member in members]
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/team', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def create_team_member():
|
||||||
|
"""创建团队成员(需要管理员权限)"""
|
||||||
|
if not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Admin access required'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
member = TeamMember(
|
||||||
|
name=data.get('name'),
|
||||||
|
title=data.get('title'),
|
||||||
|
title_en=data.get('title_en'),
|
||||||
|
bio=data.get('bio'),
|
||||||
|
bio_en=data.get('bio_en'),
|
||||||
|
role=data.get('role', 'student'),
|
||||||
|
email=data.get('email'),
|
||||||
|
website=data.get('website'),
|
||||||
|
order=data.get('order', 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(member)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Team member created successfully',
|
||||||
|
'member': member.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
from flask import Blueprint, request, jsonify, send_from_directory, current_app
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from datetime import datetime, date, time
|
||||||
|
|
||||||
|
# 添加父目录到路径
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
from models import db, Meeting, MeetingRegistration, MeetingFile, User
|
||||||
|
|
||||||
|
bp = Blueprint('meetings', __name__)
|
||||||
|
|
||||||
|
def allowed_file(filename, allowed_extensions):
|
||||||
|
"""检查文件扩展名是否允许"""
|
||||||
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||||
|
|
||||||
|
@bp.route('/meetings', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_meetings():
|
||||||
|
"""获取组会列表(实验室成员可查看所有)"""
|
||||||
|
status = request.args.get('status')
|
||||||
|
meeting_type = request.args.get('type')
|
||||||
|
|
||||||
|
query = Meeting.query
|
||||||
|
|
||||||
|
if status:
|
||||||
|
query = query.filter_by(status=status)
|
||||||
|
if meeting_type:
|
||||||
|
query = query.filter_by(meeting_type=meeting_type)
|
||||||
|
|
||||||
|
meetings = query.order_by(Meeting.date.desc(), Meeting.time.desc()).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'meetings': [meeting.to_dict() for meeting in meetings]
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/meetings/<int:meeting_id>', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_meeting(meeting_id):
|
||||||
|
"""获取单个组会详情"""
|
||||||
|
meeting = Meeting.query.get_or_404(meeting_id)
|
||||||
|
|
||||||
|
# 获取参会人员列表
|
||||||
|
registrations = MeetingRegistration.query.filter_by(meeting_id=meeting_id).all()
|
||||||
|
attendees = [reg.to_dict() for reg in registrations]
|
||||||
|
|
||||||
|
# 获取文件列表
|
||||||
|
files = MeetingFile.query.filter_by(meeting_id=meeting_id).all()
|
||||||
|
file_list = [f.to_dict() for f in files]
|
||||||
|
|
||||||
|
result = meeting.to_dict()
|
||||||
|
result['attendees'] = attendees
|
||||||
|
result['files'] = file_list
|
||||||
|
|
||||||
|
return jsonify({'meeting': result}), 200
|
||||||
|
|
||||||
|
@bp.route('/meetings', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def create_meeting():
|
||||||
|
"""创建组会(需要登录)"""
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
# 解析日期和时间
|
||||||
|
meeting_date = datetime.strptime(data['date'], '%Y-%m-%d').date() if data.get('date') else None
|
||||||
|
meeting_time = datetime.strptime(data['time'], '%H:%M').time() if data.get('time') else None
|
||||||
|
|
||||||
|
meeting = Meeting(
|
||||||
|
title=data.get('title'),
|
||||||
|
description=data.get('description'),
|
||||||
|
date=meeting_date,
|
||||||
|
time=meeting_time,
|
||||||
|
location=data.get('location'),
|
||||||
|
organizer_id=current_user.id,
|
||||||
|
status=data.get('status', 'planned'),
|
||||||
|
meeting_type=data.get('meeting_type', 'weekly')
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(meeting)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Meeting created successfully',
|
||||||
|
'meeting': meeting.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
@bp.route('/meetings/<int:meeting_id>', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_meeting(meeting_id):
|
||||||
|
"""更新组会(只有创建者可以编辑)"""
|
||||||
|
meeting = Meeting.query.get_or_404(meeting_id)
|
||||||
|
|
||||||
|
# 检查权限:只有创建者可以编辑
|
||||||
|
if meeting.organizer_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the organizer can edit this meeting'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if 'title' in data:
|
||||||
|
meeting.title = data['title']
|
||||||
|
if 'description' in data:
|
||||||
|
meeting.description = data['description']
|
||||||
|
if 'date' in data:
|
||||||
|
meeting.date = datetime.strptime(data['date'], '%Y-%m-%d').date()
|
||||||
|
if 'time' in data:
|
||||||
|
meeting.time = datetime.strptime(data['time'], '%H:%M').time()
|
||||||
|
if 'location' in data:
|
||||||
|
meeting.location = data['location']
|
||||||
|
if 'status' in data:
|
||||||
|
meeting.status = data['status']
|
||||||
|
if 'meeting_type' in data:
|
||||||
|
meeting.meeting_type = data['meeting_type']
|
||||||
|
if 'meeting_notes' in data:
|
||||||
|
meeting.meeting_notes = data['meeting_notes']
|
||||||
|
|
||||||
|
meeting.updated_at = datetime.utcnow()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Meeting updated successfully',
|
||||||
|
'meeting': meeting.to_dict()
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/meetings/<int:meeting_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_meeting(meeting_id):
|
||||||
|
"""删除组会(只有创建者可以删除)"""
|
||||||
|
meeting = Meeting.query.get_or_404(meeting_id)
|
||||||
|
|
||||||
|
# 检查权限
|
||||||
|
if meeting.organizer_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the organizer can delete this meeting'}), 403
|
||||||
|
|
||||||
|
db.session.delete(meeting)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({'message': 'Meeting deleted successfully'}), 200
|
||||||
|
|
||||||
|
@bp.route('/meetings/<int:meeting_id>/register', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def register_meeting(meeting_id):
|
||||||
|
"""报名参加组会"""
|
||||||
|
meeting = Meeting.query.get_or_404(meeting_id)
|
||||||
|
|
||||||
|
# 检查是否已报名
|
||||||
|
existing = MeetingRegistration.query.filter_by(
|
||||||
|
meeting_id=meeting_id,
|
||||||
|
user_id=current_user.id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
return jsonify({'error': 'Already registered'}), 400
|
||||||
|
|
||||||
|
registration = MeetingRegistration(
|
||||||
|
meeting_id=meeting_id,
|
||||||
|
user_id=current_user.id,
|
||||||
|
status='registered'
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(registration)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Registered successfully',
|
||||||
|
'registration': registration.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
@bp.route('/meetings/<int:meeting_id>/checkin', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def checkin_meeting(meeting_id):
|
||||||
|
"""签到组会"""
|
||||||
|
registration = MeetingRegistration.query.filter_by(
|
||||||
|
meeting_id=meeting_id,
|
||||||
|
user_id=current_user.id
|
||||||
|
).first_or_404()
|
||||||
|
|
||||||
|
registration.status = 'checked_in'
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Checked in successfully',
|
||||||
|
'registration': registration.to_dict()
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/meetings/<int:meeting_id>/files', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def upload_meeting_file(meeting_id):
|
||||||
|
"""上传组会文件(只有创建者可以上传)"""
|
||||||
|
meeting = Meeting.query.get_or_404(meeting_id)
|
||||||
|
|
||||||
|
# 检查权限
|
||||||
|
if meeting.organizer_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the organizer can upload files'}), 403
|
||||||
|
|
||||||
|
if 'file' not in request.files:
|
||||||
|
return jsonify({'error': 'No file provided'}), 400
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
if file.filename == '':
|
||||||
|
return jsonify({'error': 'No file selected'}), 400
|
||||||
|
|
||||||
|
if file and allowed_file(file.filename, {'pdf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'zip', 'rar'}):
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
uploads_dir = current_app.config['UPLOAD_FOLDER']
|
||||||
|
meeting_dir = os.path.join(uploads_dir, 'meetings', str(meeting_id))
|
||||||
|
os.makedirs(meeting_dir, exist_ok=True)
|
||||||
|
|
||||||
|
filepath = os.path.join(meeting_dir, filename)
|
||||||
|
file.save(filepath)
|
||||||
|
|
||||||
|
meeting_file = MeetingFile(
|
||||||
|
meeting_id=meeting_id,
|
||||||
|
name=request.form.get('name', filename),
|
||||||
|
file_path=f'/uploads/meetings/{meeting_id}/{filename}',
|
||||||
|
file_size=os.path.getsize(filepath),
|
||||||
|
file_type=filename.rsplit('.', 1)[1].lower(),
|
||||||
|
uploader_id=current_user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(meeting_file)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'File uploaded successfully',
|
||||||
|
'file': meeting_file.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
return jsonify({'error': 'Invalid file type'}), 400
|
||||||
|
|
||||||
|
@bp.route('/meetings/<int:meeting_id>/files/<int:file_id>', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def download_meeting_file(meeting_id, file_id):
|
||||||
|
"""下载组会文件"""
|
||||||
|
meeting_file = MeetingFile.query.filter_by(
|
||||||
|
meeting_id=meeting_id,
|
||||||
|
id=file_id
|
||||||
|
).first_or_404()
|
||||||
|
|
||||||
|
uploads_dir = current_app.config['UPLOAD_FOLDER']
|
||||||
|
file_path = os.path.join(uploads_dir, 'meetings', str(meeting_id), os.path.basename(meeting_file.file_path))
|
||||||
|
|
||||||
|
return send_from_directory(
|
||||||
|
os.path.dirname(file_path),
|
||||||
|
os.path.basename(file_path),
|
||||||
|
as_attachment=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
from flask import Blueprint, request, jsonify, send_from_directory, current_app
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from datetime import datetime, date
|
||||||
|
|
||||||
|
# 添加父目录到路径
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
from models import db, ResearchProject, ResearchNote, ResearchTask, ResearchFile, User
|
||||||
|
|
||||||
|
bp = Blueprint('research', __name__)
|
||||||
|
|
||||||
|
def allowed_file(filename, allowed_extensions):
|
||||||
|
"""检查文件扩展名是否允许"""
|
||||||
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
|
||||||
|
|
||||||
|
# 研究项目管理
|
||||||
|
@bp.route('/projects', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_projects():
|
||||||
|
"""获取研究项目列表(实验室成员可查看所有)"""
|
||||||
|
status = request.args.get('status')
|
||||||
|
owner_id = request.args.get('owner_id') # 可选:筛选特定用户的项目
|
||||||
|
|
||||||
|
query = ResearchProject.query
|
||||||
|
|
||||||
|
if status:
|
||||||
|
query = query.filter_by(status=status)
|
||||||
|
if owner_id:
|
||||||
|
query = query.filter_by(owner_id=owner_id)
|
||||||
|
|
||||||
|
projects = query.order_by(ResearchProject.created_at.desc()).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'projects': [project.to_dict() for project in projects]
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/projects/<int:project_id>', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_project(project_id):
|
||||||
|
"""获取单个研究项目详情"""
|
||||||
|
project = ResearchProject.query.get_or_404(project_id)
|
||||||
|
|
||||||
|
# 获取笔记、任务、文件
|
||||||
|
notes = ResearchNote.query.filter_by(project_id=project_id).order_by(ResearchNote.created_at.desc()).all()
|
||||||
|
tasks = ResearchTask.query.filter_by(project_id=project_id).order_by(ResearchTask.created_at.desc()).all()
|
||||||
|
files = ResearchFile.query.filter_by(project_id=project_id).order_by(ResearchFile.uploaded_at.desc()).all()
|
||||||
|
|
||||||
|
result = project.to_dict()
|
||||||
|
result['notes'] = [note.to_dict() for note in notes]
|
||||||
|
result['tasks'] = [task.to_dict() for task in tasks]
|
||||||
|
result['files'] = [f.to_dict() for f in files]
|
||||||
|
|
||||||
|
return jsonify({'project': result}), 200
|
||||||
|
|
||||||
|
@bp.route('/projects', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def create_project():
|
||||||
|
"""创建研究项目(需要登录)"""
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
start_date = datetime.strptime(data['start_date'], '%Y-%m-%d').date() if data.get('start_date') else None
|
||||||
|
end_date = datetime.strptime(data['end_date'], '%Y-%m-%d').date() if data.get('end_date') else None
|
||||||
|
tags_str = ','.join(data.get('tags', [])) if isinstance(data.get('tags'), list) else data.get('tags', '')
|
||||||
|
|
||||||
|
project = ResearchProject(
|
||||||
|
title=data.get('title'),
|
||||||
|
description=data.get('description'),
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
|
owner_id=current_user.id,
|
||||||
|
status=data.get('status', 'active'),
|
||||||
|
progress=data.get('progress', 0),
|
||||||
|
tags=tags_str
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(project)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Project created successfully',
|
||||||
|
'project': project.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
@bp.route('/projects/<int:project_id>', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_project(project_id):
|
||||||
|
"""更新研究项目(只有创建者可以编辑)"""
|
||||||
|
project = ResearchProject.query.get_or_404(project_id)
|
||||||
|
|
||||||
|
# 检查权限
|
||||||
|
if project.owner_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the owner can edit this project'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if 'title' in data:
|
||||||
|
project.title = data['title']
|
||||||
|
if 'description' in data:
|
||||||
|
project.description = data['description']
|
||||||
|
if 'start_date' in data:
|
||||||
|
project.start_date = datetime.strptime(data['start_date'], '%Y-%m-%d').date() if data['start_date'] else None
|
||||||
|
if 'end_date' in data:
|
||||||
|
project.end_date = datetime.strptime(data['end_date'], '%Y-%m-%d').date() if data['end_date'] else None
|
||||||
|
if 'status' in data:
|
||||||
|
project.status = data['status']
|
||||||
|
if 'progress' in data:
|
||||||
|
project.progress = data['progress']
|
||||||
|
if 'tags' in data:
|
||||||
|
tags_str = ','.join(data['tags']) if isinstance(data['tags'], list) else data['tags']
|
||||||
|
project.tags = tags_str
|
||||||
|
|
||||||
|
project.updated_at = datetime.utcnow()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Project updated successfully',
|
||||||
|
'project': project.to_dict()
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/projects/<int:project_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_project(project_id):
|
||||||
|
"""删除研究项目(只有创建者可以删除)"""
|
||||||
|
project = ResearchProject.query.get_or_404(project_id)
|
||||||
|
|
||||||
|
# 检查权限
|
||||||
|
if project.owner_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the owner can delete this project'}), 403
|
||||||
|
|
||||||
|
db.session.delete(project)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({'message': 'Project deleted successfully'}), 200
|
||||||
|
|
||||||
|
# 研究笔记管理
|
||||||
|
@bp.route('/projects/<int:project_id>/notes', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_notes(project_id):
|
||||||
|
"""获取项目笔记列表"""
|
||||||
|
note_type = request.args.get('type')
|
||||||
|
|
||||||
|
query = ResearchNote.query.filter_by(project_id=project_id)
|
||||||
|
|
||||||
|
if note_type:
|
||||||
|
query = query.filter_by(note_type=note_type)
|
||||||
|
|
||||||
|
notes = query.order_by(ResearchNote.created_at.desc()).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'notes': [note.to_dict() for note in notes]
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/projects/<int:project_id>/notes', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def create_note(project_id):
|
||||||
|
"""创建研究笔记(需要登录)"""
|
||||||
|
project = ResearchProject.query.get_or_404(project_id)
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
note = ResearchNote(
|
||||||
|
project_id=project_id,
|
||||||
|
title=data.get('title'),
|
||||||
|
content=data.get('content'),
|
||||||
|
author_id=current_user.id,
|
||||||
|
note_type=data.get('note_type', 'note')
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(note)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Note created successfully',
|
||||||
|
'note': note.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
@bp.route('/notes/<int:note_id>', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_note(note_id):
|
||||||
|
"""更新研究笔记(只有创建者可以编辑)"""
|
||||||
|
note = ResearchNote.query.get_or_404(note_id)
|
||||||
|
|
||||||
|
# 检查权限
|
||||||
|
if note.author_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the author can edit this note'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if 'title' in data:
|
||||||
|
note.title = data['title']
|
||||||
|
if 'content' in data:
|
||||||
|
note.content = data['content']
|
||||||
|
if 'note_type' in data:
|
||||||
|
note.note_type = data['note_type']
|
||||||
|
|
||||||
|
note.updated_at = datetime.utcnow()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Note updated successfully',
|
||||||
|
'note': note.to_dict()
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/notes/<int:note_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_note(note_id):
|
||||||
|
"""删除研究笔记(只有创建者可以删除)"""
|
||||||
|
note = ResearchNote.query.get_or_404(note_id)
|
||||||
|
|
||||||
|
# 检查权限
|
||||||
|
if note.author_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the author can delete this note'}), 403
|
||||||
|
|
||||||
|
db.session.delete(note)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({'message': 'Note deleted successfully'}), 200
|
||||||
|
|
||||||
|
# 研究任务管理
|
||||||
|
@bp.route('/projects/<int:project_id>/tasks', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_tasks(project_id):
|
||||||
|
"""获取项目任务列表"""
|
||||||
|
status = request.args.get('status')
|
||||||
|
assignee_id = request.args.get('assignee_id')
|
||||||
|
|
||||||
|
query = ResearchTask.query.filter_by(project_id=project_id)
|
||||||
|
|
||||||
|
if status:
|
||||||
|
query = query.filter_by(status=status)
|
||||||
|
if assignee_id:
|
||||||
|
query = query.filter_by(assignee_id=assignee_id)
|
||||||
|
|
||||||
|
tasks = query.order_by(ResearchTask.due_date, ResearchTask.priority).all()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'tasks': [task.to_dict() for task in tasks]
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/projects/<int:project_id>/tasks', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def create_task(project_id):
|
||||||
|
"""创建研究任务(项目创建者可以创建)"""
|
||||||
|
project = ResearchProject.query.get_or_404(project_id)
|
||||||
|
|
||||||
|
# 只有项目创建者可以创建任务
|
||||||
|
if project.owner_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the project owner can create tasks'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
due_date = datetime.strptime(data['due_date'], '%Y-%m-%d').date() if data.get('due_date') else None
|
||||||
|
|
||||||
|
task = ResearchTask(
|
||||||
|
project_id=project_id,
|
||||||
|
title=data.get('title'),
|
||||||
|
description=data.get('description'),
|
||||||
|
assignee_id=data.get('assignee_id'),
|
||||||
|
due_date=due_date,
|
||||||
|
status=data.get('status', 'todo'),
|
||||||
|
priority=data.get('priority', 'medium')
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(task)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Task created successfully',
|
||||||
|
'task': task.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
@bp.route('/tasks/<int:task_id>', methods=['PUT'])
|
||||||
|
@login_required
|
||||||
|
def update_task(task_id):
|
||||||
|
"""更新研究任务(项目创建者或任务分配者可以编辑)"""
|
||||||
|
task = ResearchTask.query.get_or_404(task_id)
|
||||||
|
project = ResearchProject.query.get(task.project_id)
|
||||||
|
|
||||||
|
# 检查权限:项目创建者或任务分配者
|
||||||
|
if project.owner_id != current_user.id and task.assignee_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the project owner or assignee can edit this task'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if 'title' in data:
|
||||||
|
task.title = data['title']
|
||||||
|
if 'description' in data:
|
||||||
|
task.description = data['description']
|
||||||
|
if 'assignee_id' in data:
|
||||||
|
task.assignee_id = data['assignee_id']
|
||||||
|
if 'due_date' in data:
|
||||||
|
task.due_date = datetime.strptime(data['due_date'], '%Y-%m-%d').date() if data['due_date'] else None
|
||||||
|
if 'status' in data:
|
||||||
|
task.status = data['status']
|
||||||
|
if 'priority' in data:
|
||||||
|
task.priority = data['priority']
|
||||||
|
|
||||||
|
task.updated_at = datetime.utcnow()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Task updated successfully',
|
||||||
|
'task': task.to_dict()
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
@bp.route('/tasks/<int:task_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_task(task_id):
|
||||||
|
"""删除研究任务(只有项目创建者可以删除)"""
|
||||||
|
task = ResearchTask.query.get_or_404(task_id)
|
||||||
|
project = ResearchProject.query.get(task.project_id)
|
||||||
|
|
||||||
|
# 检查权限
|
||||||
|
if project.owner_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the project owner can delete this task'}), 403
|
||||||
|
|
||||||
|
db.session.delete(task)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({'message': 'Task deleted successfully'}), 200
|
||||||
|
|
||||||
|
# 研究文件管理
|
||||||
|
@bp.route('/projects/<int:project_id>/files', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def upload_research_file(project_id):
|
||||||
|
"""上传研究文件(需要登录)"""
|
||||||
|
project = ResearchProject.query.get_or_404(project_id)
|
||||||
|
|
||||||
|
if 'file' not in request.files:
|
||||||
|
return jsonify({'error': 'No file provided'}), 400
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
if file.filename == '':
|
||||||
|
return jsonify({'error': 'No file selected'}), 400
|
||||||
|
|
||||||
|
if file and allowed_file(file.filename, {'pdf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'zip', 'rar', 'txt', 'csv', 'json', 'py', 'ipynb'}):
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
uploads_dir = current_app.config['UPLOAD_FOLDER']
|
||||||
|
project_dir = os.path.join(uploads_dir, 'research', str(project_id))
|
||||||
|
os.makedirs(project_dir, exist_ok=True)
|
||||||
|
|
||||||
|
filepath = os.path.join(project_dir, filename)
|
||||||
|
file.save(filepath)
|
||||||
|
|
||||||
|
research_file = ResearchFile(
|
||||||
|
project_id=project_id,
|
||||||
|
name=request.form.get('name', filename),
|
||||||
|
file_path=f'/uploads/research/{project_id}/{filename}',
|
||||||
|
file_size=os.path.getsize(filepath),
|
||||||
|
file_type=filename.rsplit('.', 1)[1].lower(),
|
||||||
|
uploader_id=current_user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(research_file)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': 'File uploaded successfully',
|
||||||
|
'file': research_file.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
|
return jsonify({'error': 'Invalid file type'}), 400
|
||||||
|
|
||||||
|
@bp.route('/projects/<int:project_id>/files/<int:file_id>', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def download_research_file(project_id, file_id):
|
||||||
|
"""下载研究文件"""
|
||||||
|
research_file = ResearchFile.query.filter_by(
|
||||||
|
project_id=project_id,
|
||||||
|
id=file_id
|
||||||
|
).first_or_404()
|
||||||
|
|
||||||
|
uploads_dir = current_app.config['UPLOAD_FOLDER']
|
||||||
|
file_path = os.path.join(uploads_dir, 'research', str(project_id), os.path.basename(research_file.file_path))
|
||||||
|
|
||||||
|
return send_from_directory(
|
||||||
|
os.path.dirname(file_path),
|
||||||
|
os.path.basename(file_path),
|
||||||
|
as_attachment=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@bp.route('/projects/<int:project_id>/files/<int:file_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_research_file(project_id, file_id):
|
||||||
|
"""删除研究文件(只有上传者可以删除)"""
|
||||||
|
research_file = ResearchFile.query.filter_by(
|
||||||
|
project_id=project_id,
|
||||||
|
id=file_id
|
||||||
|
).first_or_404()
|
||||||
|
|
||||||
|
# 检查权限
|
||||||
|
if research_file.uploader_id != current_user.id:
|
||||||
|
return jsonify({'error': 'Only the uploader can delete this file'}), 403
|
||||||
|
|
||||||
|
db.session.delete(research_file)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({'message': 'File deleted successfully'}), 200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
/* Common Styles for DAEL Lab Website */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', 'Noto Sans SC', sans-serif;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1a1a1a;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation Link Animation */
|
||||||
|
.nav-link {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 1px;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
background-color: #000;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover::after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fade In Up Animation */
|
||||||
|
.fade-in-up {
|
||||||
|
animation: fadeInUp 0.8s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-100 { animation-delay: 0.1s; }
|
||||||
|
.delay-200 { animation-delay: 0.2s; }
|
||||||
|
.delay-300 { animation-delay: 0.3s; }
|
||||||
|
.delay-400 { animation-delay: 0.4s; }
|
||||||
|
|
||||||
|
/* Canvas container */
|
||||||
|
#canvas-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* News Card Styles */
|
||||||
|
.news-card {
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Box */
|
||||||
|
.search-box {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 100;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item:hover {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Login Form */
|
||||||
|
.login-form {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Protected Content */
|
||||||
|
.protected-content {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protected-content::after {
|
||||||
|
content: '登录后查看';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Transition */
|
||||||
|
.page-transition {
|
||||||
|
animation: fadeIn 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Counter */
|
||||||
|
.stats-counter {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
// API Client for DAEL Lab Backend
|
||||||
|
|
||||||
|
const API_BASE_URL = 'http://localhost:5000/api';
|
||||||
|
|
||||||
|
// 辅助函数:发送请求
|
||||||
|
async function apiRequest(endpoint, options = {}) {
|
||||||
|
const url = `${API_BASE_URL}${endpoint}`;
|
||||||
|
const config = {
|
||||||
|
credentials: 'include', // 包含cookies
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.body && typeof config.body === 'object' && !(config.body instanceof FormData)) {
|
||||||
|
config.body = JSON.stringify(config.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, config);
|
||||||
|
|
||||||
|
// 检查响应内容类型
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
let data;
|
||||||
|
|
||||||
|
if (contentType && contentType.includes('application/json')) {
|
||||||
|
data = await response.json();
|
||||||
|
} else {
|
||||||
|
const text = await response.text();
|
||||||
|
try {
|
||||||
|
data = JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
throw new Error(text || 'Invalid response');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || `Request failed with status ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API request failed:', error);
|
||||||
|
console.error('URL:', url);
|
||||||
|
console.error('Config:', config);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 认证API
|
||||||
|
const authAPI = {
|
||||||
|
async login(username, password, remember = false) {
|
||||||
|
return apiRequest('/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { username, password, remember }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
return apiRequest('/auth/logout', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async register(username, email, password) {
|
||||||
|
return apiRequest('/auth/register', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { username, email, password }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkAuth() {
|
||||||
|
return apiRequest('/auth/check', {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getCurrentUser() {
|
||||||
|
return apiRequest('/auth/me', {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 内容API
|
||||||
|
const contentAPI = {
|
||||||
|
async getArticles(category = 'news', publishedOnly = true) {
|
||||||
|
return apiRequest(`/cms/articles?category=${category}&published_only=${publishedOnly}`, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getArticle(articleId) {
|
||||||
|
return apiRequest(`/cms/articles/${articleId}`, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async createArticle(articleData) {
|
||||||
|
return apiRequest('/cms/articles', {
|
||||||
|
method: 'POST',
|
||||||
|
body: articleData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateArticle(articleId, articleData) {
|
||||||
|
return apiRequest(`/cms/articles/${articleId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: articleData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteArticle(articleId) {
|
||||||
|
return apiRequest(`/cms/articles/${articleId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getResources(category = null) {
|
||||||
|
const url = category ? `/cms/resources?category=${category}` : '/cms/resources';
|
||||||
|
return apiRequest(url, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async uploadResource(file, name, description, category, isProtected) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('name', name);
|
||||||
|
formData.append('description', description || '');
|
||||||
|
formData.append('category', category || 'document');
|
||||||
|
formData.append('is_protected', isProtected ? 'true' : 'false');
|
||||||
|
|
||||||
|
return apiRequest('/cms/resources', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {}, // 让浏览器设置Content-Type
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getTeam(role = null) {
|
||||||
|
const url = role ? `/cms/team?role=${role}` : '/cms/team';
|
||||||
|
return apiRequest(url, {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async createTeamMember(memberData) {
|
||||||
|
return apiRequest('/cms/team', {
|
||||||
|
method: 'POST',
|
||||||
|
body: memberData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 其他API
|
||||||
|
const api = {
|
||||||
|
async downloadResource(resourceId) {
|
||||||
|
window.location.href = `${API_BASE_URL}/download/${resourceId}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getStats() {
|
||||||
|
return apiRequest('/stats', {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getProtectedContent() {
|
||||||
|
return apiRequest('/protected-content', {
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组会管理API
|
||||||
|
const meetingsAPI = {
|
||||||
|
async getMeetings(status = null, type = null) {
|
||||||
|
let url = '/meetings/meetings';
|
||||||
|
const params = [];
|
||||||
|
if (status) params.push(`status=${status}`);
|
||||||
|
if (type) params.push(`type=${type}`);
|
||||||
|
if (params.length > 0) url += '?' + params.join('&');
|
||||||
|
return apiRequest(url, { method: 'GET' });
|
||||||
|
},
|
||||||
|
|
||||||
|
async getMeeting(meetingId) {
|
||||||
|
return apiRequest(`/meetings/meetings/${meetingId}`, { method: 'GET' });
|
||||||
|
},
|
||||||
|
|
||||||
|
async createMeeting(meetingData) {
|
||||||
|
return apiRequest('/meetings/meetings', {
|
||||||
|
method: 'POST',
|
||||||
|
body: meetingData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateMeeting(meetingId, meetingData) {
|
||||||
|
return apiRequest(`/meetings/meetings/${meetingId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: meetingData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteMeeting(meetingId) {
|
||||||
|
return apiRequest(`/meetings/meetings/${meetingId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerMeeting(meetingId) {
|
||||||
|
return apiRequest(`/meetings/meetings/${meetingId}/register`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkinMeeting(meetingId) {
|
||||||
|
return apiRequest(`/meetings/meetings/${meetingId}/checkin`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async uploadMeetingFile(meetingId, file, name) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('name', name || file.name);
|
||||||
|
|
||||||
|
return apiRequest(`/meetings/meetings/${meetingId}/files`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async downloadMeetingFile(meetingId, fileId) {
|
||||||
|
window.location.href = `${API_BASE_URL}/meetings/meetings/${meetingId}/files/${fileId}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 研究管理API
|
||||||
|
const researchAPI = {
|
||||||
|
async getProjects(status = null, ownerId = null) {
|
||||||
|
let url = '/research/projects';
|
||||||
|
const params = [];
|
||||||
|
if (status) params.push(`status=${status}`);
|
||||||
|
if (ownerId) params.push(`owner_id=${ownerId}`);
|
||||||
|
if (params.length > 0) url += '?' + params.join('&');
|
||||||
|
return apiRequest(url, { method: 'GET' });
|
||||||
|
},
|
||||||
|
|
||||||
|
async getProject(projectId) {
|
||||||
|
return apiRequest(`/research/projects/${projectId}`, { method: 'GET' });
|
||||||
|
},
|
||||||
|
|
||||||
|
async createProject(projectData) {
|
||||||
|
return apiRequest('/research/projects', {
|
||||||
|
method: 'POST',
|
||||||
|
body: projectData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateProject(projectId, projectData) {
|
||||||
|
return apiRequest(`/research/projects/${projectId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: projectData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteProject(projectId) {
|
||||||
|
return apiRequest(`/research/projects/${projectId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getNotes(projectId, noteType = null) {
|
||||||
|
let url = `/research/projects/${projectId}/notes`;
|
||||||
|
if (noteType) url += `?type=${noteType}`;
|
||||||
|
return apiRequest(url, { method: 'GET' });
|
||||||
|
},
|
||||||
|
|
||||||
|
async createNote(projectId, noteData) {
|
||||||
|
return apiRequest(`/research/projects/${projectId}/notes`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: noteData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateNote(noteId, noteData) {
|
||||||
|
return apiRequest(`/research/notes/${noteId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: noteData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteNote(noteId) {
|
||||||
|
return apiRequest(`/research/notes/${noteId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getTasks(projectId, status = null, assigneeId = null) {
|
||||||
|
let url = `/research/projects/${projectId}/tasks`;
|
||||||
|
const params = [];
|
||||||
|
if (status) params.push(`status=${status}`);
|
||||||
|
if (assigneeId) params.push(`assignee_id=${assigneeId}`);
|
||||||
|
if (params.length > 0) url += '?' + params.join('&');
|
||||||
|
return apiRequest(url, { method: 'GET' });
|
||||||
|
},
|
||||||
|
|
||||||
|
async createTask(projectId, taskData) {
|
||||||
|
return apiRequest(`/research/projects/${projectId}/tasks`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: taskData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateTask(taskId, taskData) {
|
||||||
|
return apiRequest(`/research/tasks/${taskId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: taskData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteTask(taskId) {
|
||||||
|
return apiRequest(`/research/tasks/${taskId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async uploadResearchFile(projectId, file, name) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('name', name || file.name);
|
||||||
|
|
||||||
|
return apiRequest(`/research/projects/${projectId}/files`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {},
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async downloadResearchFile(projectId, fileId) {
|
||||||
|
window.location.href = `${API_BASE_URL}/research/projects/${projectId}/files/${fileId}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteResearchFile(projectId, fileId) {
|
||||||
|
return apiRequest(`/research/projects/${projectId}/files/${fileId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出API
|
||||||
|
window.DAELAPI = {
|
||||||
|
auth: authAPI,
|
||||||
|
content: contentAPI,
|
||||||
|
api: api,
|
||||||
|
meetings: meetingsAPI,
|
||||||
|
research: researchAPI
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
// User Authentication Functions
|
||||||
|
|
||||||
|
let currentUser = null;
|
||||||
|
|
||||||
|
// Check if user is logged in
|
||||||
|
async function isLoggedIn() {
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.auth.checkAuth();
|
||||||
|
if (response.authenticated) {
|
||||||
|
currentUser = response.user;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current user
|
||||||
|
function getCurrentUser() {
|
||||||
|
return currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login function
|
||||||
|
async function login(username, password, remember = false) {
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.auth.login(username, password, remember);
|
||||||
|
currentUser = response.user;
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login failed:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout function
|
||||||
|
async function logout() {
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.auth.logout();
|
||||||
|
currentUser = null;
|
||||||
|
window.location.href = 'website.html';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout failed:', error);
|
||||||
|
// 即使API失败也清除本地状态
|
||||||
|
currentUser = null;
|
||||||
|
window.location.href = 'website.html';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check protected content access
|
||||||
|
async function checkProtectedContent() {
|
||||||
|
const loggedIn = await isLoggedIn();
|
||||||
|
const protectedElements = document.querySelectorAll('.protected-content');
|
||||||
|
|
||||||
|
if (loggedIn) {
|
||||||
|
protectedElements.forEach(el => {
|
||||||
|
el.classList.remove('protected-content');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载受保护内容
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.api.getProtectedContent();
|
||||||
|
displayProtectedContent(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load protected content:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
protectedElements.forEach(el => {
|
||||||
|
el.classList.add('protected-content');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示受保护内容
|
||||||
|
async function displayProtectedContent(data) {
|
||||||
|
// 更新受保护的文章
|
||||||
|
const protectedArticlesContainer = document.getElementById('protected-articles');
|
||||||
|
if (protectedArticlesContainer && data.articles && data.articles.length > 0) {
|
||||||
|
protectedArticlesContainer.innerHTML = data.articles.map(article => `
|
||||||
|
<div class="border-b border-gray-200 pb-6 mb-6">
|
||||||
|
<h3 class="text-xl font-bold mb-2">${article.title}</h3>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">${article.summary || article.content.substring(0, 100) + '...'}</p>
|
||||||
|
<div class="text-xs text-gray-400">${new Date(article.created_at).toLocaleDateString()}</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
protectedArticlesContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新受保护的资源
|
||||||
|
const protectedResourcesContainer = document.getElementById('protected-resources');
|
||||||
|
if (protectedResourcesContainer && data.resources && data.resources.length > 0) {
|
||||||
|
protectedResourcesContainer.innerHTML = data.resources.map(resource => `
|
||||||
|
<div class="border border-gray-200 p-4 mb-4">
|
||||||
|
<h4 class="font-bold mb-2">${resource.name}</h4>
|
||||||
|
<p class="text-sm text-gray-500 mb-2">${resource.description || ''}</p>
|
||||||
|
<p class="text-xs text-gray-400 mb-2">大小: ${(resource.file_size / 1024 / 1024).toFixed(2)} MB</p>
|
||||||
|
<button onclick="window.DAELAPI.api.downloadResource(${resource.id})"
|
||||||
|
class="text-sm text-black border-b border-black hover:text-gray-600">
|
||||||
|
下载 ->
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
protectedResourcesContainer.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新统计信息
|
||||||
|
try {
|
||||||
|
const statsResponse = await window.DAELAPI.api.getStats();
|
||||||
|
const stats = statsResponse.stats;
|
||||||
|
const statsContainer = document.getElementById('protected-stats');
|
||||||
|
if (statsContainer) {
|
||||||
|
const statElements = statsContainer.querySelectorAll('.text-3xl');
|
||||||
|
if (statElements.length >= 4) {
|
||||||
|
statElements[0].textContent = stats.articles || '0';
|
||||||
|
statElements[1].textContent = stats.resources || '0';
|
||||||
|
statElements[2].textContent = stats.team_members || '0';
|
||||||
|
statElements[3].textContent = stats.total_downloads || '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load stats:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize auth on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
// 等待API加载
|
||||||
|
if (typeof window.DAELAPI === 'undefined') {
|
||||||
|
console.warn('API not loaded, using fallback authentication');
|
||||||
|
// 使用本地存储作为后备
|
||||||
|
const localAuth = localStorage.getItem('dael_user_logged_in') === 'true';
|
||||||
|
updateUI(localAuth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await checkProtectedContent();
|
||||||
|
await updateAuthUI();
|
||||||
|
|
||||||
|
// Logout button handler
|
||||||
|
const logoutBtn = document.getElementById('logout-btn');
|
||||||
|
if (logoutBtn) {
|
||||||
|
logoutBtn.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (confirm('确定要退出登录吗?')) {
|
||||||
|
await logout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新认证UI
|
||||||
|
async function updateAuthUI() {
|
||||||
|
const loggedIn = await isLoggedIn();
|
||||||
|
const loginBtn = document.getElementById('login-btn');
|
||||||
|
const logoutBtn = document.getElementById('logout-btn');
|
||||||
|
const adminBtn = document.getElementById('admin-btn');
|
||||||
|
const meetingsLink = document.getElementById('meetings-link');
|
||||||
|
const researchLink = document.getElementById('research-link');
|
||||||
|
const userInfo = document.getElementById('user-info');
|
||||||
|
|
||||||
|
if (loggedIn) {
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (loginBtn) loginBtn.classList.add('hidden');
|
||||||
|
if (logoutBtn) logoutBtn.classList.remove('hidden');
|
||||||
|
if (adminBtn && user && user.is_admin) {
|
||||||
|
adminBtn.classList.remove('hidden');
|
||||||
|
} else if (adminBtn) {
|
||||||
|
adminBtn.classList.add('hidden');
|
||||||
|
}
|
||||||
|
// 显示组会和研究管理链接(所有登录用户)
|
||||||
|
if (meetingsLink) meetingsLink.classList.remove('hidden');
|
||||||
|
if (researchLink) researchLink.classList.remove('hidden');
|
||||||
|
const mobileMeetingsLink = document.getElementById('mobile-meetings-link');
|
||||||
|
const mobileResearchLink = document.getElementById('mobile-research-link');
|
||||||
|
if (mobileMeetingsLink) mobileMeetingsLink.classList.remove('hidden');
|
||||||
|
if (mobileResearchLink) mobileResearchLink.classList.remove('hidden');
|
||||||
|
if (userInfo && user) {
|
||||||
|
userInfo.textContent = user.username;
|
||||||
|
userInfo.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (loginBtn) loginBtn.classList.remove('hidden');
|
||||||
|
if (logoutBtn) logoutBtn.classList.add('hidden');
|
||||||
|
if (adminBtn) adminBtn.classList.add('hidden');
|
||||||
|
if (meetingsLink) meetingsLink.classList.add('hidden');
|
||||||
|
if (researchLink) researchLink.classList.add('hidden');
|
||||||
|
const mobileMeetingsLink = document.getElementById('mobile-meetings-link');
|
||||||
|
const mobileResearchLink = document.getElementById('mobile-research-link');
|
||||||
|
if (mobileMeetingsLink) mobileMeetingsLink.classList.add('hidden');
|
||||||
|
if (mobileResearchLink) mobileResearchLink.classList.add('hidden');
|
||||||
|
if (userInfo) userInfo.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后备UI更新函数
|
||||||
|
function updateUI(loggedIn) {
|
||||||
|
const loginBtn = document.getElementById('login-btn');
|
||||||
|
const logoutBtn = document.getElementById('logout-btn');
|
||||||
|
|
||||||
|
if (loggedIn) {
|
||||||
|
if (loginBtn) loginBtn.classList.add('hidden');
|
||||||
|
if (logoutBtn) logoutBtn.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
if (loginBtn) loginBtn.classList.remove('hidden');
|
||||||
|
if (logoutBtn) logoutBtn.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
// Common JavaScript Functions for DAEL Lab Website
|
||||||
|
|
||||||
|
// Initialize Lucide Icons
|
||||||
|
function initIcons() {
|
||||||
|
if (window.lucide) {
|
||||||
|
lucide.createIcons();
|
||||||
|
} else {
|
||||||
|
console.warn("Lucide icons failed to load.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile Menu Toggle
|
||||||
|
function initMobileMenu() {
|
||||||
|
const menuBtn = document.getElementById('mobile-menu-btn');
|
||||||
|
const mobileMenu = document.getElementById('mobile-menu');
|
||||||
|
|
||||||
|
if (menuBtn && mobileMenu) {
|
||||||
|
menuBtn.addEventListener('click', () => {
|
||||||
|
mobileMenu.classList.toggle('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navbar scroll effect
|
||||||
|
function initNavbarScroll() {
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
const nav = document.getElementById('navbar');
|
||||||
|
if (nav) {
|
||||||
|
if (window.scrollY > 50) {
|
||||||
|
nav.classList.add('shadow-sm');
|
||||||
|
if (nav.classList.contains('h-20')) {
|
||||||
|
nav.classList.replace('h-20', 'h-16');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nav.classList.remove('shadow-sm');
|
||||||
|
if (nav.classList.contains('h-16')) {
|
||||||
|
nav.classList.replace('h-16', 'h-20');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canvas Animation: Organic Connection Nodes
|
||||||
|
function initCanvasAnimation() {
|
||||||
|
const canvas = document.getElementById('heroCanvas');
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
let width, height;
|
||||||
|
let particles = [];
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
width = window.innerWidth;
|
||||||
|
height = window.innerHeight;
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Particle {
|
||||||
|
constructor() {
|
||||||
|
this.x = Math.random() * width;
|
||||||
|
this.y = Math.random() * height;
|
||||||
|
this.vx = (Math.random() - 0.5) * 0.5;
|
||||||
|
this.vy = (Math.random() - 0.5) * 0.5;
|
||||||
|
this.size = Math.random() * 2 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.x += this.vx;
|
||||||
|
this.y += this.vy;
|
||||||
|
|
||||||
|
if (this.x < 0 || this.x > width) this.vx *= -1;
|
||||||
|
if (this.y < 0 || this.y > height) this.vy *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initParticles() {
|
||||||
|
particles = [];
|
||||||
|
const particleCount = Math.min(window.innerWidth / 10, 100);
|
||||||
|
for (let i = 0; i < particleCount; i++) {
|
||||||
|
particles.push(new Particle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
for (let i = 0; i < particles.length; i++) {
|
||||||
|
particles[i].update();
|
||||||
|
particles[i].draw();
|
||||||
|
|
||||||
|
for (let j = i + 1; j < particles.length; j++) {
|
||||||
|
const dx = particles[i].x - particles[j].x;
|
||||||
|
const dy = particles[i].y - particles[j].y;
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (dist < 150) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.strokeStyle = `rgba(0, 0, 0, ${0.05 - dist/3000})`;
|
||||||
|
ctx.lineWidth = 0.5;
|
||||||
|
ctx.moveTo(particles[i].x, particles[i].y);
|
||||||
|
ctx.lineTo(particles[j].x, particles[j].y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
resize();
|
||||||
|
initParticles();
|
||||||
|
});
|
||||||
|
|
||||||
|
resize();
|
||||||
|
initParticles();
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit Counter
|
||||||
|
function initVisitCounter() {
|
||||||
|
const counterElement = document.getElementById('visit-counter');
|
||||||
|
if (!counterElement) return;
|
||||||
|
|
||||||
|
let count = parseInt(localStorage.getItem('dael_visit_count') || '0');
|
||||||
|
count++;
|
||||||
|
localStorage.setItem('dael_visit_count', count.toString());
|
||||||
|
counterElement.textContent = count.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize all common functions
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initIcons();
|
||||||
|
initMobileMenu();
|
||||||
|
initNavbarScroll();
|
||||||
|
initCanvasAnimation();
|
||||||
|
initVisitCounter();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
// Meetings Management JavaScript
|
||||||
|
|
||||||
|
let currentMeetings = [];
|
||||||
|
let currentFilter = 'all';
|
||||||
|
|
||||||
|
// 检查登录状态
|
||||||
|
async function checkLogin() {
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.auth.checkAuth();
|
||||||
|
if (!response.authenticated) {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载组会列表
|
||||||
|
async function loadMeetings() {
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.meetings.getMeetings();
|
||||||
|
currentMeetings = response.meetings;
|
||||||
|
displayMeetings(currentMeetings);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load meetings:', error);
|
||||||
|
document.getElementById('meetings-list').innerHTML = '<p class="text-red-600">加载失败</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示组会列表
|
||||||
|
function displayMeetings(meetings) {
|
||||||
|
const container = document.getElementById('meetings-list');
|
||||||
|
|
||||||
|
// 根据筛选器过滤
|
||||||
|
let filteredMeetings = meetings;
|
||||||
|
if (currentFilter !== 'all') {
|
||||||
|
filteredMeetings = meetings.filter(m => m.status === currentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredMeetings.length === 0) {
|
||||||
|
container.innerHTML = '<p class="text-gray-500 text-center py-12">暂无组会</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = filteredMeetings.map(meeting => {
|
||||||
|
const date = new Date(meeting.date);
|
||||||
|
const isOwner = meeting.organizer_id === (window.currentUser?.id);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="bg-white border border-gray-200 p-6 rounded-lg hover:shadow-md transition-shadow">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-3 mb-2">
|
||||||
|
<h3 class="text-xl font-bold">${meeting.title}</h3>
|
||||||
|
<span class="text-xs px-2 py-1 rounded ${getStatusColor(meeting.status)}">${getStatusText(meeting.status)}</span>
|
||||||
|
<span class="text-xs px-2 py-1 rounded bg-gray-100">${getTypeText(meeting.meeting_type)}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 text-sm mb-3">${meeting.description || ''}</p>
|
||||||
|
<div class="flex gap-6 text-sm text-gray-500">
|
||||||
|
<span>📅 ${date.toLocaleDateString('zh-CN')}</span>
|
||||||
|
<span>🕐 ${meeting.time || ''}</span>
|
||||||
|
<span>📍 ${meeting.location || '未指定'}</span>
|
||||||
|
<span>👤 组织者: ${meeting.organizer_name || ''}</span>
|
||||||
|
<span>👥 报名: ${meeting.attendee_count || 0} 人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 ml-4">
|
||||||
|
<button onclick="viewMeeting(${meeting.id})" class="text-sm text-blue-600 hover:text-blue-800 px-3 py-1 border border-blue-600 rounded">查看</button>
|
||||||
|
${isOwner ? `
|
||||||
|
<button onclick="editMeeting(${meeting.id})" class="text-sm text-gray-600 hover:text-gray-800 px-3 py-1 border border-gray-600 rounded">编辑</button>
|
||||||
|
<button onclick="deleteMeeting(${meeting.id})" class="text-sm text-red-600 hover:text-red-800 px-3 py-1 border border-red-600 rounded">删除</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态颜色
|
||||||
|
function getStatusColor(status) {
|
||||||
|
const colors = {
|
||||||
|
'planned': 'bg-blue-100 text-blue-800',
|
||||||
|
'ongoing': 'bg-green-100 text-green-800',
|
||||||
|
'completed': 'bg-gray-100 text-gray-800',
|
||||||
|
'cancelled': 'bg-red-100 text-red-800'
|
||||||
|
};
|
||||||
|
return colors[status] || 'bg-gray-100 text-gray-800';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态文本
|
||||||
|
function getStatusText(status) {
|
||||||
|
const texts = {
|
||||||
|
'planned': '计划中',
|
||||||
|
'ongoing': '进行中',
|
||||||
|
'completed': '已结束',
|
||||||
|
'cancelled': '已取消'
|
||||||
|
};
|
||||||
|
return texts[status] || status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型文本
|
||||||
|
function getTypeText(type) {
|
||||||
|
const texts = {
|
||||||
|
'weekly': '周会',
|
||||||
|
'monthly': '月会',
|
||||||
|
'special': '专题会'
|
||||||
|
};
|
||||||
|
return texts[type] || type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示组会表单
|
||||||
|
function showMeetingForm(meetingId = null) {
|
||||||
|
const modal = document.getElementById('meeting-modal');
|
||||||
|
const form = document.getElementById('meeting-form');
|
||||||
|
const title = document.getElementById('meeting-modal-title');
|
||||||
|
|
||||||
|
if (meetingId) {
|
||||||
|
const meeting = currentMeetings.find(m => m.id === meetingId);
|
||||||
|
if (meeting) {
|
||||||
|
title.textContent = '编辑组会';
|
||||||
|
document.getElementById('meeting-id').value = meeting.id;
|
||||||
|
document.getElementById('meeting-title').value = meeting.title;
|
||||||
|
document.getElementById('meeting-description').value = meeting.description || '';
|
||||||
|
document.getElementById('meeting-date').value = meeting.date;
|
||||||
|
document.getElementById('meeting-time').value = meeting.time || '';
|
||||||
|
document.getElementById('meeting-location').value = meeting.location || '';
|
||||||
|
document.getElementById('meeting-type').value = meeting.meeting_type;
|
||||||
|
document.getElementById('meeting-status').value = meeting.status;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
title.textContent = '创建组会';
|
||||||
|
form.reset();
|
||||||
|
document.getElementById('meeting-id').value = '';
|
||||||
|
// 设置默认日期为今天
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
document.getElementById('meeting-date').value = today;
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭组会表单
|
||||||
|
function closeMeetingForm() {
|
||||||
|
document.getElementById('meeting-modal').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存组会
|
||||||
|
async function saveMeeting(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const meetingId = document.getElementById('meeting-id').value;
|
||||||
|
const meetingData = {
|
||||||
|
title: document.getElementById('meeting-title').value,
|
||||||
|
description: document.getElementById('meeting-description').value,
|
||||||
|
date: document.getElementById('meeting-date').value,
|
||||||
|
time: document.getElementById('meeting-time').value,
|
||||||
|
location: document.getElementById('meeting-location').value,
|
||||||
|
meeting_type: document.getElementById('meeting-type').value,
|
||||||
|
status: document.getElementById('meeting-status').value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (meetingId) {
|
||||||
|
await window.DAELAPI.meetings.updateMeeting(meetingId, meetingData);
|
||||||
|
} else {
|
||||||
|
await window.DAELAPI.meetings.createMeeting(meetingData);
|
||||||
|
}
|
||||||
|
closeMeetingForm();
|
||||||
|
loadMeetings();
|
||||||
|
} catch (error) {
|
||||||
|
alert('保存失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看组会详情
|
||||||
|
async function viewMeeting(meetingId) {
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.meetings.getMeeting(meetingId);
|
||||||
|
const meeting = response.meeting;
|
||||||
|
displayMeetingDetail(meeting);
|
||||||
|
} catch (error) {
|
||||||
|
alert('加载失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示组会详情
|
||||||
|
function displayMeetingDetail(meeting) {
|
||||||
|
const modal = document.getElementById('meeting-detail-modal');
|
||||||
|
const container = document.getElementById('meeting-detail-content');
|
||||||
|
const date = new Date(meeting.date);
|
||||||
|
const isOwner = meeting.organizer_id === (window.currentUser?.id);
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<h3 class="text-2xl font-bold">${meeting.title}</h3>
|
||||||
|
<button onclick="closeMeetingDetail()" class="text-gray-400 hover:text-gray-600">✕</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-gray-500">日期时间:</span>
|
||||||
|
<span class="text-sm">${date.toLocaleDateString('zh-CN')} ${meeting.time || ''}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-gray-500">地点:</span>
|
||||||
|
<span class="text-sm">${meeting.location || '未指定'}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-gray-500">组织者:</span>
|
||||||
|
<span class="text-sm">${meeting.organizer_name || ''}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-gray-500">类型:</span>
|
||||||
|
<span class="text-sm">${getTypeText(meeting.meeting_type)}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-gray-500">状态:</span>
|
||||||
|
<span class="text-sm ${getStatusColor(meeting.status)} px-2 py-1 rounded">${getStatusText(meeting.status)}</span>
|
||||||
|
</div>
|
||||||
|
${meeting.description ? `
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-gray-500">描述:</span>
|
||||||
|
<p class="text-sm mt-1">${meeting.description}</p>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-6">
|
||||||
|
<h4 class="font-bold mb-3">参会人员 (${meeting.attendees?.length || 0})</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
${meeting.attendees && meeting.attendees.length > 0 ?
|
||||||
|
meeting.attendees.map(attendee => `
|
||||||
|
<div class="flex items-center justify-between p-2 bg-gray-50 rounded">
|
||||||
|
<span class="text-sm">${attendee.username}</span>
|
||||||
|
<span class="text-xs text-gray-500">${getStatusText(attendee.status)}</span>
|
||||||
|
</div>
|
||||||
|
`).join('') :
|
||||||
|
'<p class="text-sm text-gray-500">暂无报名</p>'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
${!isOwner && meeting.status === 'planned' ? `
|
||||||
|
<button onclick="registerForMeeting(${meeting.id})" class="mt-3 bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
|
||||||
|
报名参会
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${meeting.meeting_notes ? `
|
||||||
|
<div class="mb-6">
|
||||||
|
<h4 class="font-bold mb-3">会议记录</h4>
|
||||||
|
<div class="p-4 bg-gray-50 rounded text-sm whitespace-pre-wrap">${meeting.meeting_notes}</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
${meeting.files && meeting.files.length > 0 ? `
|
||||||
|
<div class="mb-6">
|
||||||
|
<h4 class="font-bold mb-3">附件 (${meeting.files.length})</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
${meeting.files.map(file => `
|
||||||
|
<div class="flex items-center justify-between p-2 bg-gray-50 rounded">
|
||||||
|
<span class="text-sm">${file.name}</span>
|
||||||
|
<button onclick="window.DAELAPI.meetings.downloadMeetingFile(${meeting.id}, ${file.id})"
|
||||||
|
class="text-sm text-blue-600 hover:text-blue-800">
|
||||||
|
下载
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
${isOwner ? `
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<button onclick="editMeeting(${meeting.id})" class="bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
|
||||||
|
编辑
|
||||||
|
</button>
|
||||||
|
<button onclick="deleteMeeting(${meeting.id})" class="bg-red-600 text-white px-4 py-2 rounded text-sm hover:bg-red-700">
|
||||||
|
删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
`;
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭组会详情
|
||||||
|
function closeMeetingDetail() {
|
||||||
|
document.getElementById('meeting-detail-modal').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑组会
|
||||||
|
function editMeeting(meetingId) {
|
||||||
|
closeMeetingDetail();
|
||||||
|
showMeetingForm(meetingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除组会
|
||||||
|
async function deleteMeeting(meetingId) {
|
||||||
|
if (!confirm('确定要删除这个组会吗?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.meetings.deleteMeeting(meetingId);
|
||||||
|
closeMeetingDetail();
|
||||||
|
loadMeetings();
|
||||||
|
} catch (error) {
|
||||||
|
alert('删除失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报名参会
|
||||||
|
async function registerForMeeting(meetingId) {
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.meetings.registerMeeting(meetingId);
|
||||||
|
alert('报名成功!');
|
||||||
|
viewMeeting(meetingId); // 刷新详情
|
||||||
|
} catch (error) {
|
||||||
|
alert('报名失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选器切换
|
||||||
|
document.querySelectorAll('.filter-tab').forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
document.querySelectorAll('.filter-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');
|
||||||
|
|
||||||
|
currentFilter = tab.dataset.filter;
|
||||||
|
displayMeetings(currentMeetings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
if (await checkLogin()) {
|
||||||
|
// 获取当前用户信息
|
||||||
|
try {
|
||||||
|
const authResponse = await window.DAELAPI.auth.checkAuth();
|
||||||
|
window.currentUser = authResponse.user;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get user info:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMeetings();
|
||||||
|
|
||||||
|
// 退出按钮
|
||||||
|
document.getElementById('logout-btn').addEventListener('click', async () => {
|
||||||
|
await logout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,573 @@
|
|||||||
|
// Research Management JavaScript
|
||||||
|
|
||||||
|
let currentProjects = [];
|
||||||
|
let currentFilter = 'all';
|
||||||
|
let currentProjectDetail = null;
|
||||||
|
|
||||||
|
// 检查登录状态
|
||||||
|
async function checkLogin() {
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.auth.checkAuth();
|
||||||
|
if (!response.authenticated) {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载项目列表
|
||||||
|
async function loadProjects() {
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.research.getProjects();
|
||||||
|
currentProjects = response.projects;
|
||||||
|
displayProjects(currentProjects);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load projects:', error);
|
||||||
|
document.getElementById('projects-list').innerHTML = '<p class="text-red-600">加载失败</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示项目列表
|
||||||
|
function displayProjects(projects) {
|
||||||
|
const container = document.getElementById('projects-list');
|
||||||
|
|
||||||
|
// 根据筛选器过滤
|
||||||
|
let filteredProjects = projects;
|
||||||
|
if (currentFilter !== 'all') {
|
||||||
|
filteredProjects = projects.filter(p => p.status === currentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredProjects.length === 0) {
|
||||||
|
container.innerHTML = '<p class="text-gray-500 text-center py-12">暂无项目</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = filteredProjects.map(project => {
|
||||||
|
const startDate = project.start_date ? new Date(project.start_date).toLocaleDateString('zh-CN') : '未设置';
|
||||||
|
const endDate = project.end_date ? new Date(project.end_date).toLocaleDateString('zh-CN') : '未设置';
|
||||||
|
const isOwner = project.owner_id === (window.currentUser?.id);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="bg-white border border-gray-200 p-6 rounded-lg hover:shadow-md transition-shadow">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-3 mb-2">
|
||||||
|
<h3 class="text-xl font-bold">${project.title}</h3>
|
||||||
|
<span class="text-xs px-2 py-1 rounded ${getStatusColor(project.status)}">${getStatusText(project.status)}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 text-sm mb-3">${project.description || ''}</p>
|
||||||
|
<div class="flex gap-6 text-sm text-gray-500 mb-3">
|
||||||
|
<span>📅 ${startDate} - ${endDate}</span>
|
||||||
|
<span>👤 ${project.owner_name || ''}</span>
|
||||||
|
<span>📊 进度: ${project.progress}%</span>
|
||||||
|
</div>
|
||||||
|
${project.tags && project.tags.length > 0 ? `
|
||||||
|
<div class="flex gap-2 flex-wrap">
|
||||||
|
${project.tags.map(tag => `<span class="text-xs px-2 py-1 bg-gray-100 rounded">${tag}</span>`).join('')}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 ml-4">
|
||||||
|
<button onclick="viewProject(${project.id})" class="text-sm text-blue-600 hover:text-blue-800 px-3 py-1 border border-blue-600 rounded">查看</button>
|
||||||
|
${isOwner ? `
|
||||||
|
<button onclick="editProject(${project.id})" class="text-sm text-gray-600 hover:text-gray-800 px-3 py-1 border border-gray-600 rounded">编辑</button>
|
||||||
|
<button onclick="deleteProject(${project.id})" class="text-sm text-red-600 hover:text-red-800 px-3 py-1 border border-red-600 rounded">删除</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 pt-4 border-t border-gray-200">
|
||||||
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||||
|
<div class="bg-black h-2 rounded-full" style="width: ${project.progress}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态颜色
|
||||||
|
function getStatusColor(status) {
|
||||||
|
const colors = {
|
||||||
|
'active': 'bg-green-100 text-green-800',
|
||||||
|
'completed': 'bg-gray-100 text-gray-800',
|
||||||
|
'paused': 'bg-yellow-100 text-yellow-800',
|
||||||
|
'cancelled': 'bg-red-100 text-red-800'
|
||||||
|
};
|
||||||
|
return colors[status] || 'bg-gray-100 text-gray-800';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态文本
|
||||||
|
function getStatusText(status) {
|
||||||
|
const texts = {
|
||||||
|
'active': '进行中',
|
||||||
|
'completed': '已完成',
|
||||||
|
'paused': '已暂停',
|
||||||
|
'cancelled': '已取消'
|
||||||
|
};
|
||||||
|
return texts[status] || status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示项目表单
|
||||||
|
function showProjectForm(projectId = null) {
|
||||||
|
const modal = document.getElementById('project-modal');
|
||||||
|
const form = document.getElementById('project-form');
|
||||||
|
const title = document.getElementById('project-modal-title');
|
||||||
|
|
||||||
|
if (projectId) {
|
||||||
|
const project = currentProjects.find(p => p.id === projectId);
|
||||||
|
if (project) {
|
||||||
|
title.textContent = '编辑项目';
|
||||||
|
document.getElementById('project-id').value = project.id;
|
||||||
|
document.getElementById('project-title').value = project.title;
|
||||||
|
document.getElementById('project-description').value = project.description || '';
|
||||||
|
document.getElementById('project-start-date').value = project.start_date || '';
|
||||||
|
document.getElementById('project-end-date').value = project.end_date || '';
|
||||||
|
document.getElementById('project-status').value = project.status;
|
||||||
|
document.getElementById('project-progress').value = project.progress;
|
||||||
|
document.getElementById('project-tags').value = project.tags ? project.tags.join(', ') : '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
title.textContent = '新建项目';
|
||||||
|
form.reset();
|
||||||
|
document.getElementById('project-id').value = '';
|
||||||
|
document.getElementById('project-progress').value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭项目表单
|
||||||
|
function closeProjectForm() {
|
||||||
|
document.getElementById('project-modal').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存项目
|
||||||
|
async function saveProject(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const projectId = document.getElementById('project-id').value;
|
||||||
|
const tagsInput = document.getElementById('project-tags').value;
|
||||||
|
const tags = tagsInput ? tagsInput.split(',').map(t => t.trim()).filter(t => t) : [];
|
||||||
|
|
||||||
|
const projectData = {
|
||||||
|
title: document.getElementById('project-title').value,
|
||||||
|
description: document.getElementById('project-description').value,
|
||||||
|
start_date: document.getElementById('project-start-date').value || null,
|
||||||
|
end_date: document.getElementById('project-end-date').value || null,
|
||||||
|
status: document.getElementById('project-status').value,
|
||||||
|
progress: parseInt(document.getElementById('project-progress').value) || 0,
|
||||||
|
tags: tags
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (projectId) {
|
||||||
|
await window.DAELAPI.research.updateProject(projectId, projectData);
|
||||||
|
} else {
|
||||||
|
await window.DAELAPI.research.createProject(projectData);
|
||||||
|
}
|
||||||
|
closeProjectForm();
|
||||||
|
loadProjects();
|
||||||
|
} catch (error) {
|
||||||
|
alert('保存失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看项目详情
|
||||||
|
async function viewProject(projectId) {
|
||||||
|
try {
|
||||||
|
const response = await window.DAELAPI.research.getProject(projectId);
|
||||||
|
currentProjectDetail = response.project;
|
||||||
|
displayProjectDetail(currentProjectDetail);
|
||||||
|
} catch (error) {
|
||||||
|
alert('加载失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示项目详情
|
||||||
|
function displayProjectDetail(project) {
|
||||||
|
const modal = document.getElementById('project-detail-modal');
|
||||||
|
const container = document.getElementById('project-detail-content');
|
||||||
|
const isOwner = project.owner_id === (window.currentUser?.id);
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-2xl font-bold mb-2">${project.title}</h3>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="text-xs px-2 py-1 rounded ${getStatusColor(project.status)}">${getStatusText(project.status)}</span>
|
||||||
|
<span class="text-sm text-gray-500">进度: ${project.progress}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onclick="closeProjectDetail()" class="text-gray-400 hover:text-gray-600">✕</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4 mb-6">
|
||||||
|
${project.description ? `
|
||||||
|
<div>
|
||||||
|
<h4 class="font-bold mb-2">项目描述</h4>
|
||||||
|
<p class="text-sm text-gray-600">${project.description}</p>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-500">开始日期:</span>
|
||||||
|
<span>${project.start_date ? new Date(project.start_date).toLocaleDateString('zh-CN') : '未设置'}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-500">结束日期:</span>
|
||||||
|
<span>${project.end_date ? new Date(project.end_date).toLocaleDateString('zh-CN') : '未设置'}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-500">负责人:</span>
|
||||||
|
<span>${project.owner_name || ''}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${project.tags && project.tags.length > 0 ? `
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-500 text-sm">标签:</span>
|
||||||
|
<div class="flex gap-2 flex-wrap mt-2">
|
||||||
|
${project.tags.map(tag => `<span class="text-xs px-2 py-1 bg-gray-100 rounded">${tag}</span>`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs for Notes, Tasks, Files -->
|
||||||
|
<div class="border-b border-gray-200 mb-4">
|
||||||
|
<button class="tab-btn active px-4 py-2 text-sm font-medium border-b-2 border-black" data-tab="notes">笔记 (${project.notes?.length || 0})</button>
|
||||||
|
<button class="tab-btn px-4 py-2 text-sm font-medium text-gray-500" data-tab="tasks">任务 (${project.tasks?.length || 0})</button>
|
||||||
|
<button class="tab-btn px-4 py-2 text-sm font-medium text-gray-500" data-tab="files">文件 (${project.files?.length || 0})</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-content-notes" class="tab-content">
|
||||||
|
${isOwner ? `
|
||||||
|
<button onclick="showNoteForm()" class="mb-4 bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
|
||||||
|
+ 新建笔记
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
<div class="space-y-3">
|
||||||
|
${project.notes && project.notes.length > 0 ?
|
||||||
|
project.notes.map(note => `
|
||||||
|
<div class="border border-gray-200 p-4 rounded">
|
||||||
|
<div class="flex justify-between items-start mb-2">
|
||||||
|
<h5 class="font-bold">${note.title}</h5>
|
||||||
|
${isOwner && note.author_id === window.currentUser?.id ? `
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button onclick="editNote(${note.id})" class="text-xs text-blue-600">编辑</button>
|
||||||
|
<button onclick="deleteNote(${note.id})" class="text-xs text-red-600">删除</button>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-600 mb-2">${note.content.substring(0, 200)}${note.content.length > 200 ? '...' : ''}</p>
|
||||||
|
<div class="text-xs text-gray-400">
|
||||||
|
${note.author_name} · ${new Date(note.created_at).toLocaleDateString('zh-CN')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('') :
|
||||||
|
'<p class="text-sm text-gray-500">暂无笔记</p>'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-content-tasks" class="tab-content hidden">
|
||||||
|
${isOwner ? `
|
||||||
|
<button onclick="showTaskForm()" class="mb-4 bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
|
||||||
|
+ 新建任务
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
<div class="space-y-3">
|
||||||
|
${project.tasks && project.tasks.length > 0 ?
|
||||||
|
project.tasks.map(task => `
|
||||||
|
<div class="border border-gray-200 p-4 rounded">
|
||||||
|
<div class="flex justify-between items-start mb-2">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<input type="checkbox" ${task.status === 'completed' ? 'checked' : ''}
|
||||||
|
onchange="toggleTask(${task.id}, this.checked)" class="mr-2">
|
||||||
|
<h5 class="font-bold">${task.title}</h5>
|
||||||
|
<span class="text-xs px-2 py-1 rounded ${getPriorityColor(task.priority)}">${getPriorityText(task.priority)}</span>
|
||||||
|
</div>
|
||||||
|
${isOwner ? `
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button onclick="editTask(${task.id})" class="text-xs text-blue-600">编辑</button>
|
||||||
|
<button onclick="deleteTask(${task.id})" class="text-xs text-red-600">删除</button>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
${task.description ? `<p class="text-sm text-gray-600 mb-2">${task.description}</p>` : ''}
|
||||||
|
<div class="text-xs text-gray-400">
|
||||||
|
${task.assignee_name || '未分配'} ·
|
||||||
|
${task.due_date ? new Date(task.due_date).toLocaleDateString('zh-CN') : '无截止日期'} ·
|
||||||
|
${getStatusText(task.status)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('') :
|
||||||
|
'<p class="text-sm text-gray-500">暂无任务</p>'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-content-files" class="tab-content hidden">
|
||||||
|
${isOwner ? `
|
||||||
|
<button onclick="showFileUpload()" class="mb-4 bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
|
||||||
|
+ 上传文件
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
<div class="space-y-3">
|
||||||
|
${project.files && project.files.length > 0 ?
|
||||||
|
project.files.map(file => `
|
||||||
|
<div class="border border-gray-200 p-4 rounded flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm font-medium">${file.name}</span>
|
||||||
|
<span class="text-xs text-gray-400 ml-2">${(file.file_size / 1024 / 1024).toFixed(2)} MB</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button onclick="window.DAELAPI.research.downloadResearchFile(${project.id}, ${file.id})"
|
||||||
|
class="text-xs text-blue-600">下载</button>
|
||||||
|
${isOwner && file.uploader_id === window.currentUser?.id ? `
|
||||||
|
<button onclick="deleteFile(${project.id}, ${file.id})" class="text-xs text-red-600">删除</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('') :
|
||||||
|
'<p class="text-sm text-gray-500">暂无文件</p>'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${isOwner ? `
|
||||||
|
<div class="mt-6 flex gap-4">
|
||||||
|
<button onclick="editProject(${project.id})" class="bg-black text-white px-4 py-2 rounded text-sm hover:bg-gray-800">
|
||||||
|
编辑项目
|
||||||
|
</button>
|
||||||
|
<button onclick="deleteProject(${project.id})" class="bg-red-600 text-white px-4 py-2 rounded text-sm hover:bg-red-700">
|
||||||
|
删除项目
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 初始化标签页切换
|
||||||
|
initDetailTabs();
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化详情页标签
|
||||||
|
function initDetailTabs() {
|
||||||
|
document.querySelectorAll('#project-detail-modal .tab-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const tabName = btn.dataset.tab;
|
||||||
|
|
||||||
|
// 更新按钮样式
|
||||||
|
document.querySelectorAll('#project-detail-modal .tab-btn').forEach(b => {
|
||||||
|
b.classList.remove('active', 'border-black', 'text-black');
|
||||||
|
b.classList.add('text-gray-500');
|
||||||
|
});
|
||||||
|
btn.classList.add('active', 'border-black', 'text-black');
|
||||||
|
btn.classList.remove('text-gray-500');
|
||||||
|
|
||||||
|
// 更新内容
|
||||||
|
document.querySelectorAll('#project-detail-modal .tab-content').forEach(content => {
|
||||||
|
content.classList.add('hidden');
|
||||||
|
});
|
||||||
|
document.getElementById(`tab-content-${tabName}`).classList.remove('hidden');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭项目详情
|
||||||
|
function closeProjectDetail() {
|
||||||
|
document.getElementById('project-detail-modal').classList.add('hidden');
|
||||||
|
currentProjectDetail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑项目
|
||||||
|
function editProject(projectId) {
|
||||||
|
closeProjectDetail();
|
||||||
|
showProjectForm(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除项目
|
||||||
|
async function deleteProject(projectId) {
|
||||||
|
if (!confirm('确定要删除这个项目吗?所有相关的笔记、任务和文件也将被删除。')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.research.deleteProject(projectId);
|
||||||
|
closeProjectDetail();
|
||||||
|
loadProjects();
|
||||||
|
} catch (error) {
|
||||||
|
alert('删除失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 笔记管理
|
||||||
|
function showNoteForm(noteId = null) {
|
||||||
|
// 简化版:使用prompt创建笔记
|
||||||
|
const title = prompt('笔记标题:');
|
||||||
|
if (!title) return;
|
||||||
|
|
||||||
|
const content = prompt('笔记内容:');
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
createNote(title, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createNote(title, content) {
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.research.createNote(currentProjectDetail.id, {
|
||||||
|
title: title,
|
||||||
|
content: content,
|
||||||
|
note_type: 'note'
|
||||||
|
});
|
||||||
|
viewProject(currentProjectDetail.id); // 刷新详情
|
||||||
|
} catch (error) {
|
||||||
|
alert('创建失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteNote(noteId) {
|
||||||
|
if (!confirm('确定要删除这条笔记吗?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.research.deleteNote(noteId);
|
||||||
|
viewProject(currentProjectDetail.id); // 刷新详情
|
||||||
|
} catch (error) {
|
||||||
|
alert('删除失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务管理
|
||||||
|
function showTaskForm() {
|
||||||
|
const title = prompt('任务标题:');
|
||||||
|
if (!title) return;
|
||||||
|
|
||||||
|
const description = prompt('任务描述:');
|
||||||
|
const dueDate = prompt('截止日期 (YYYY-MM-DD):');
|
||||||
|
|
||||||
|
createTask(title, description, dueDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTask(title, description, dueDate) {
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.research.createTask(currentProjectDetail.id, {
|
||||||
|
title: title,
|
||||||
|
description: description || '',
|
||||||
|
due_date: dueDate || null,
|
||||||
|
status: 'todo',
|
||||||
|
priority: 'medium'
|
||||||
|
});
|
||||||
|
viewProject(currentProjectDetail.id); // 刷新详情
|
||||||
|
} catch (error) {
|
||||||
|
alert('创建失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleTask(taskId, completed) {
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.research.updateTask(taskId, {
|
||||||
|
status: completed ? 'completed' : 'todo'
|
||||||
|
});
|
||||||
|
viewProject(currentProjectDetail.id); // 刷新详情
|
||||||
|
} catch (error) {
|
||||||
|
alert('更新失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteTask(taskId) {
|
||||||
|
if (!confirm('确定要删除这个任务吗?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.research.deleteTask(taskId);
|
||||||
|
viewProject(currentProjectDetail.id); // 刷新详情
|
||||||
|
} catch (error) {
|
||||||
|
alert('删除失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件管理
|
||||||
|
function showFileUpload() {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.multiple = false;
|
||||||
|
input.onchange = async (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.research.uploadResearchFile(currentProjectDetail.id, file, file.name);
|
||||||
|
viewProject(currentProjectDetail.id); // 刷新详情
|
||||||
|
} catch (error) {
|
||||||
|
alert('上传失败: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteFile(projectId, fileId) {
|
||||||
|
if (!confirm('确定要删除这个文件吗?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await window.DAELAPI.research.deleteResearchFile(projectId, fileId);
|
||||||
|
viewProject(projectId); // 刷新详情
|
||||||
|
} catch (error) {
|
||||||
|
alert('删除失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先级颜色
|
||||||
|
function getPriorityColor(priority) {
|
||||||
|
const colors = {
|
||||||
|
'high': 'bg-red-100 text-red-800',
|
||||||
|
'medium': 'bg-yellow-100 text-yellow-800',
|
||||||
|
'low': 'bg-blue-100 text-blue-800'
|
||||||
|
};
|
||||||
|
return colors[priority] || 'bg-gray-100 text-gray-800';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先级文本
|
||||||
|
function getPriorityText(priority) {
|
||||||
|
const texts = {
|
||||||
|
'high': '高',
|
||||||
|
'medium': '中',
|
||||||
|
'low': '低'
|
||||||
|
};
|
||||||
|
return texts[priority] || priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选器切换
|
||||||
|
document.querySelectorAll('.filter-tab').forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
document.querySelectorAll('.filter-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');
|
||||||
|
|
||||||
|
currentFilter = tab.dataset.filter;
|
||||||
|
displayProjects(currentProjects);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
if (await checkLogin()) {
|
||||||
|
// 获取当前用户信息
|
||||||
|
try {
|
||||||
|
const authResponse = await window.DAELAPI.auth.checkAuth();
|
||||||
|
window.currentUser = authResponse.user;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get user info:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProjects();
|
||||||
|
|
||||||
|
// 退出按钮
|
||||||
|
document.getElementById('logout-btn').addEventListener('click', async () => {
|
||||||
|
await logout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
// Search Functionality
|
||||||
|
|
||||||
|
// Search data structure (in production, this would come from a server)
|
||||||
|
const searchData = {
|
||||||
|
pages: [
|
||||||
|
{ title: '关于实验室', url: 'website.html#about', content: 'DAEL Design AI Ecology Lab 跨学科研究平台' },
|
||||||
|
{ title: '研究方向', url: 'website.html#research', content: '生成式生态学 生物启发式智能 数字可持续性' },
|
||||||
|
{ title: '研究团队', url: 'website.html#team', content: 'Dr. Lin Chen Sarah Wu James Zhao Yu Zhang' },
|
||||||
|
{ title: '新闻资讯', url: 'news.html', content: '最新资讯 最新文章 热点文章' },
|
||||||
|
{ title: '成果展示', url: 'publications.html', content: '专著教材 文章目录 软件开发 学位论文' },
|
||||||
|
{ title: '资源下载', url: 'resources.html', content: '软件下载 资料下载 开源代码' },
|
||||||
|
{ title: '学术资料', url: 'academic.html', content: '软件使用 教程 文档' }
|
||||||
|
],
|
||||||
|
news: [],
|
||||||
|
publications: [],
|
||||||
|
resources: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple search function
|
||||||
|
function performSearch(query) {
|
||||||
|
if (!query || query.trim().length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerQuery = query.toLowerCase().trim();
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
// Search in pages
|
||||||
|
searchData.pages.forEach(page => {
|
||||||
|
const titleMatch = page.title.toLowerCase().includes(lowerQuery);
|
||||||
|
const contentMatch = page.content.toLowerCase().includes(lowerQuery);
|
||||||
|
|
||||||
|
if (titleMatch || contentMatch) {
|
||||||
|
results.push({
|
||||||
|
type: 'page',
|
||||||
|
title: page.title,
|
||||||
|
url: page.url,
|
||||||
|
snippet: page.content.substring(0, 100) + '...'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize search
|
||||||
|
function initSearch() {
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
const searchResults = document.getElementById('search-results');
|
||||||
|
const searchForm = document.getElementById('search-form');
|
||||||
|
|
||||||
|
if (!searchInput || !searchResults) return;
|
||||||
|
|
||||||
|
// Real-time search
|
||||||
|
let searchTimeout;
|
||||||
|
searchInput.addEventListener('input', (e) => {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
const query = e.target.value.trim();
|
||||||
|
|
||||||
|
if (query.length === 0) {
|
||||||
|
searchResults.classList.remove('active');
|
||||||
|
searchResults.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchTimeout = setTimeout(() => {
|
||||||
|
const results = performSearch(query);
|
||||||
|
displaySearchResults(results, searchResults);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
if (searchForm) {
|
||||||
|
searchForm.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const query = searchInput.value.trim();
|
||||||
|
if (query) {
|
||||||
|
window.location.href = `search.html?q=${encodeURIComponent(query)}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close search results when clicking outside
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) {
|
||||||
|
searchResults.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display search results
|
||||||
|
function displaySearchResults(results, container) {
|
||||||
|
if (results.length === 0) {
|
||||||
|
container.innerHTML = '<div class="search-result-item text-gray-500 text-center py-4">未找到相关结果</div>';
|
||||||
|
container.classList.add('active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = results.map(result => `
|
||||||
|
<a href="${result.url}" class="search-result-item block">
|
||||||
|
<div class="font-semibold text-sm mb-1">${result.title}</div>
|
||||||
|
<div class="text-xs text-gray-500">${result.snippet}</div>
|
||||||
|
</a>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
container.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get search query from URL
|
||||||
|
function getSearchQuery() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
return params.get('q') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initSearch();
|
||||||
|
|
||||||
|
// If on search page, perform search
|
||||||
|
if (window.location.pathname.includes('search.html')) {
|
||||||
|
const query = getSearchQuery();
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
const resultsContainer = document.getElementById('search-results-container');
|
||||||
|
|
||||||
|
if (query && searchInput) {
|
||||||
|
searchInput.value = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query && resultsContainer) {
|
||||||
|
const results = performSearch(query);
|
||||||
|
displaySearchResults(results, resultsContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>用户登录 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased bg-gray-50">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100 transition-all duration-300" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-20 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600 hidden sm:inline">Design AI & Ecology Lab</span></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button id="mobile-menu-btn" class="md:hidden p-2 text-gray-600">
|
||||||
|
<i data-lucide="menu"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center space-x-6 text-sm font-medium tracking-wide text-gray-800">
|
||||||
|
<a href="website.html#about" class="nav-link hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="nav-link hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="nav-link hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="nav-link hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="nav-link hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="nav-link hover:text-black">加入我们</a>
|
||||||
|
|
||||||
|
<form id="search-form" class="relative search-box">
|
||||||
|
<input type="text" id="search-input" placeholder="搜索..."
|
||||||
|
class="px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:border-black w-40">
|
||||||
|
<button type="submit" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-black">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="login.html" id="login-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">登录</a>
|
||||||
|
<a href="#" id="logout-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-gray-100 absolute w-full px-6 py-4 space-y-4 shadow-lg">
|
||||||
|
<a href="website.html#about" class="block text-gray-800 hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="block text-gray-800 hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="block text-gray-800 hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="block text-gray-800 hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="block text-gray-800 hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="block text-gray-800 hover:text-black">加入我们</a>
|
||||||
|
<a href="login.html" class="block text-gray-800 hover:text-black">登录</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Login Form -->
|
||||||
|
<section class="pt-32 pb-24 min-h-screen flex items-center">
|
||||||
|
<div class="max-w-md mx-auto px-6 w-full">
|
||||||
|
<div class="bg-white p-8 border border-gray-200">
|
||||||
|
<h1 class="text-3xl font-bold mb-2">登录</h1>
|
||||||
|
<p class="text-gray-600 text-sm mb-8">登录以访问更多内容</p>
|
||||||
|
|
||||||
|
<form id="login-form" class="login-form">
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="username" class="block text-sm font-medium text-gray-700 mb-2">用户名</label>
|
||||||
|
<input type="text" id="username" name="username" required
|
||||||
|
class="form-input" placeholder="请输入用户名">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">密码</label>
|
||||||
|
<input type="password" id="password" name="password" required
|
||||||
|
class="form-input" placeholder="请输入密码">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-6 flex items-center justify-between">
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="checkbox" id="remember" class="mr-2">
|
||||||
|
<span class="text-sm text-gray-600">记住我</span>
|
||||||
|
</label>
|
||||||
|
<a href="#" class="text-sm text-gray-600 hover:text-black">忘记密码?</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="login-error" class="mb-4 text-sm text-red-600 hidden"></div>
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
class="w-full bg-black text-white py-3 px-4 rounded hover:bg-gray-800 transition-colors font-medium">
|
||||||
|
登录
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="mt-6 text-center text-sm text-gray-600">
|
||||||
|
<p>演示模式:任意用户名和密码即可登录</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-black text-white py-16 border-t border-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Contact</h5>
|
||||||
|
<p class="text-sm text-gray-400 leading-loose">
|
||||||
|
Design AI & Ecology Lab<br>
|
||||||
|
1234 Design Avenue, Innovation District<br>
|
||||||
|
Shanghai, China<br>
|
||||||
|
<a href="mailto:hello@dael.edu.cn" class="text-white hover:underline">hello@dael.edu.cn</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Social</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="#" class="hover:text-white transition-colors">WeChat / 微信公众号</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Instagram</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Twitter / X</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Resources</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="resources.html" class="hover:text-white transition-colors">资源下载</a>
|
||||||
|
<a href="academic.html" class="hover:text-white transition-colors">学术资料</a>
|
||||||
|
<a href="team.html" class="hover:text-white transition-colors">完整团队</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Join Us</h5>
|
||||||
|
<p class="text-sm text-gray-400 mb-4">
|
||||||
|
我们长期招收博士后、博士生及研究助理。欢迎具有跨学科背景的申请者。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-white text-sm border-b border-white hover:text-gray-300 hover:border-gray-300 pb-0.5">申请职位</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-800 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<p>© 2024 Design AI & Ecology Lab. All Rights Reserved.</p>
|
||||||
|
<p class="mt-2">页面访问数: <span id="visit-counter" class="font-bold">0</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-4 md:mt-0">
|
||||||
|
<a href="#" class="hover:text-gray-400">Privacy Policy</a>
|
||||||
|
<a href="#" class="hover:text-gray-400">Terms of Use</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
<script>
|
||||||
|
// Login form handler
|
||||||
|
document.getElementById('login-form').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const username = document.getElementById('username').value;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
const remember = document.getElementById('remember').checked;
|
||||||
|
const errorDiv = document.getElementById('login-error');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await login(username, password, remember);
|
||||||
|
if (success) {
|
||||||
|
// Redirect to home page
|
||||||
|
window.location.href = 'website.html';
|
||||||
|
} else {
|
||||||
|
errorDiv.textContent = '登录失败,请检查用户名和密码';
|
||||||
|
errorDiv.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorDiv.textContent = '登录失败:' + (error.message || '网络错误');
|
||||||
|
errorDiv.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>组会管理 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased bg-gray-50">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white border-b border-gray-200 shadow-sm" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-16 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600">组会管理</span></span>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a href="website.html" class="text-sm text-gray-600 hover:text-black">返回首页</a>
|
||||||
|
<a href="research.html" class="text-sm text-gray-600 hover:text-black">研究管理</a>
|
||||||
|
<button id="logout-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="pt-24 pb-12">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="flex justify-between items-center mb-8">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold mb-2">组会管理</h1>
|
||||||
|
<p class="text-gray-600">创建和管理实验室组会,报名参会,记录会议内容</p>
|
||||||
|
</div>
|
||||||
|
<button onclick="showMeetingForm()" class="bg-black text-white px-6 py-3 rounded hover:bg-gray-800">
|
||||||
|
+ 创建组会
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Tabs -->
|
||||||
|
<div class="flex space-x-4 border-b border-gray-200 mb-6">
|
||||||
|
<button class="filter-tab active pb-4 px-4 text-sm font-medium border-b-2 border-black" data-filter="all">全部</button>
|
||||||
|
<button class="filter-tab pb-4 px-4 text-sm font-medium text-gray-500 hover:text-black" data-filter="planned">计划中</button>
|
||||||
|
<button class="filter-tab pb-4 px-4 text-sm font-medium text-gray-500 hover:text-black" data-filter="ongoing">进行中</button>
|
||||||
|
<button class="filter-tab pb-4 px-4 text-sm font-medium text-gray-500 hover:text-black" data-filter="completed">已结束</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Meetings List -->
|
||||||
|
<div id="meetings-list" class="space-y-4">
|
||||||
|
<!-- Meetings will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Meeting Form Modal -->
|
||||||
|
<div id="meeting-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white rounded-lg p-8 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||||
|
<h3 class="text-2xl font-bold mb-6" id="meeting-modal-title">创建组会</h3>
|
||||||
|
<form id="meeting-form" onsubmit="saveMeeting(event)">
|
||||||
|
<input type="hidden" id="meeting-id">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">标题 *</label>
|
||||||
|
<input type="text" id="meeting-title" required class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">描述</label>
|
||||||
|
<textarea id="meeting-description" class="form-input w-full" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">日期 *</label>
|
||||||
|
<input type="date" id="meeting-date" required class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">时间 *</label>
|
||||||
|
<input type="time" id="meeting-time" required class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">地点</label>
|
||||||
|
<input type="text" id="meeting-location" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">类型</label>
|
||||||
|
<select id="meeting-type" class="form-input w-full">
|
||||||
|
<option value="weekly">周会</option>
|
||||||
|
<option value="monthly">月会</option>
|
||||||
|
<option value="special">专题会</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">状态</label>
|
||||||
|
<select id="meeting-status" class="form-input w-full">
|
||||||
|
<option value="planned">计划中</option>
|
||||||
|
<option value="ongoing">进行中</option>
|
||||||
|
<option value="completed">已结束</option>
|
||||||
|
<option value="cancelled">已取消</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<button type="submit" class="bg-black text-white px-6 py-2 rounded hover:bg-gray-800">保存</button>
|
||||||
|
<button type="button" onclick="closeMeetingForm()" class="bg-gray-200 text-gray-800 px-6 py-2 rounded hover:bg-gray-300">取消</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Meeting Detail Modal -->
|
||||||
|
<div id="meeting-detail-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white rounded-lg p-8 max-w-3xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||||
|
<div id="meeting-detail-content">
|
||||||
|
<!-- Meeting details will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
<script src="js/meetings.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>新闻资讯 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100 transition-all duration-300" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-20 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600 hidden sm:inline">Design AI & Ecology Lab</span></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button id="mobile-menu-btn" class="md:hidden p-2 text-gray-600">
|
||||||
|
<i data-lucide="menu"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center space-x-6 text-sm font-medium tracking-wide text-gray-800">
|
||||||
|
<a href="website.html#about" class="nav-link hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="nav-link hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="nav-link hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="nav-link hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="nav-link hover:text-black text-black border-b border-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="nav-link hover:text-black">加入我们</a>
|
||||||
|
|
||||||
|
<form id="search-form" class="relative search-box">
|
||||||
|
<input type="text" id="search-input" placeholder="搜索..."
|
||||||
|
class="px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:border-black w-40">
|
||||||
|
<button type="submit" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-black">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="login.html" id="login-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">登录</a>
|
||||||
|
<a href="#" id="logout-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-gray-100 absolute w-full px-6 py-4 space-y-4 shadow-lg">
|
||||||
|
<a href="website.html#about" class="block text-gray-800 hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="block text-gray-800 hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="block text-gray-800 hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="block text-gray-800 hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="block text-gray-800 hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="block text-gray-800 hover:text-black">加入我们</a>
|
||||||
|
<a href="login.html" class="block text-gray-800 hover:text-black">登录</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page Header -->
|
||||||
|
<section class="pt-32 pb-16 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold mb-4">新闻资讯</h1>
|
||||||
|
<p class="text-gray-600">了解实验室的最新动态、研究成果和活动信息</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- News Content -->
|
||||||
|
<section class="py-12 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-16">
|
||||||
|
<!-- Latest News -->
|
||||||
|
<div class="md:col-span-2">
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">最新资讯</h2>
|
||||||
|
<div class="space-y-8">
|
||||||
|
<article class="border-b border-gray-200 pb-8">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div class="text-xs text-gray-500 whitespace-nowrap">2024.12.15</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-xl font-bold mb-2 hover:text-gray-600 cursor-pointer">实验室新项目启动:AI驱动的城市生态规划</h3>
|
||||||
|
<p class="text-gray-600 text-sm leading-relaxed mb-4">
|
||||||
|
我们很高兴地宣布启动新的研究项目,该项目将利用生成式AI技术优化城市生态系统的规划与设计。项目将结合机器学习算法和生态学原理,为城市可持续发展提供创新解决方案。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600 hover:border-gray-600">阅读更多 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="border-b border-gray-200 pb-8">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div class="text-xs text-gray-500 whitespace-nowrap">2024.11.20</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-xl font-bold mb-2 hover:text-gray-600 cursor-pointer">研究成果在顶级期刊发表</h3>
|
||||||
|
<p class="text-gray-600 text-sm leading-relaxed mb-4">
|
||||||
|
实验室关于生物启发式智能算法的研究成果在Nature子刊发表。该研究提出了一种新的群体智能算法,可用于优化分布式系统的控制策略。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600 hover:border-gray-600">阅读更多 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="border-b border-gray-200 pb-8">
|
||||||
|
<div class="flex items-start gap-4">
|
||||||
|
<div class="text-xs text-gray-500 whitespace-nowrap">2024.10.10</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-xl font-bold mb-2 hover:text-gray-600 cursor-pointer">欢迎新成员加入实验室</h3>
|
||||||
|
<p class="text-gray-600 text-sm leading-relaxed mb-4">
|
||||||
|
我们很高兴地宣布,三位新的博士生和两位研究助理加入了我们的团队。新成员将在生成式生态学和数字可持续性等领域开展研究。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600 hover:border-gray-600">阅读更多 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">热点文章</h2>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="border-l-2 border-black pl-4">
|
||||||
|
<h4 class="font-bold text-sm mb-1 hover:text-gray-600 cursor-pointer">设计智能与生态系统的未来</h4>
|
||||||
|
<p class="text-xs text-gray-500">2024.09.15</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-l-2 border-gray-200 pl-4">
|
||||||
|
<h4 class="font-bold text-sm mb-1 hover:text-gray-600 cursor-pointer">AI在可持续建筑中的应用</h4>
|
||||||
|
<p class="text-xs text-gray-500">2024.08.20</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-l-2 border-gray-200 pl-4">
|
||||||
|
<h4 class="font-bold text-sm mb-1 hover:text-gray-600 cursor-pointer">生物启发算法的创新突破</h4>
|
||||||
|
<p class="text-xs text-gray-500">2024.07.10</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-black text-white py-16 border-t border-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Contact</h5>
|
||||||
|
<p class="text-sm text-gray-400 leading-loose">
|
||||||
|
Design AI & Ecology Lab<br>
|
||||||
|
1234 Design Avenue, Innovation District<br>
|
||||||
|
Shanghai, China<br>
|
||||||
|
<a href="mailto:hello@dael.edu.cn" class="text-white hover:underline">hello@dael.edu.cn</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Social</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="#" class="hover:text-white transition-colors">WeChat / 微信公众号</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Instagram</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Twitter / X</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Resources</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="resources.html" class="hover:text-white transition-colors">资源下载</a>
|
||||||
|
<a href="academic.html" class="hover:text-white transition-colors">学术资料</a>
|
||||||
|
<a href="team.html" class="hover:text-white transition-colors">完整团队</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Join Us</h5>
|
||||||
|
<p class="text-sm text-gray-400 mb-4">
|
||||||
|
我们长期招收博士后、博士生及研究助理。欢迎具有跨学科背景的申请者。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-white text-sm border-b border-white hover:text-gray-300 hover:border-gray-300 pb-0.5">申请职位</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-800 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<p>© 2024 Design AI & Ecology Lab. All Rights Reserved.</p>
|
||||||
|
<p class="mt-2">页面访问数: <span id="visit-counter" class="font-bold">0</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-4 md:mt-0">
|
||||||
|
<a href="#" class="hover:text-gray-400">Privacy Policy</a>
|
||||||
|
<a href="#" class="hover:text-gray-400">Terms of Use</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>成果展示 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100 transition-all duration-300" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-20 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600 hidden sm:inline">Design AI & Ecology Lab</span></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button id="mobile-menu-btn" class="md:hidden p-2 text-gray-600">
|
||||||
|
<i data-lucide="menu"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center space-x-6 text-sm font-medium tracking-wide text-gray-800">
|
||||||
|
<a href="website.html#about" class="nav-link hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="nav-link hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="nav-link hover:text-black text-black border-b border-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="nav-link hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="nav-link hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="nav-link hover:text-black">加入我们</a>
|
||||||
|
|
||||||
|
<form id="search-form" class="relative search-box">
|
||||||
|
<input type="text" id="search-input" placeholder="搜索..."
|
||||||
|
class="px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:border-black w-40">
|
||||||
|
<button type="submit" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-black">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="login.html" id="login-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">登录</a>
|
||||||
|
<a href="#" id="logout-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-gray-100 absolute w-full px-6 py-4 space-y-4 shadow-lg">
|
||||||
|
<a href="website.html#about" class="block text-gray-800 hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="block text-gray-800 hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="block text-gray-800 hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="block text-gray-800 hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="block text-gray-800 hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="block text-gray-800 hover:text-black">加入我们</a>
|
||||||
|
<a href="login.html" class="block text-gray-800 hover:text-black">登录</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page Header -->
|
||||||
|
<section class="pt-32 pb-16 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold mb-4">成果展示</h1>
|
||||||
|
<p class="text-gray-600">实验室的研究成果、出版物和软件工具</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Publications Content -->
|
||||||
|
<section class="py-12 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="flex space-x-8 border-b border-gray-200 mb-12">
|
||||||
|
<button class="tab-btn active pb-4 text-sm font-medium border-b-2 border-black" data-tab="books">专著教材</button>
|
||||||
|
<button class="tab-btn pb-4 text-sm font-medium text-gray-500 hover:text-black" data-tab="papers">文章目录</button>
|
||||||
|
<button class="tab-btn pb-4 text-sm font-medium text-gray-500 hover:text-black" data-tab="software">软件开发</button>
|
||||||
|
<button class="tab-btn pb-4 text-sm font-medium text-gray-500 hover:text-black" data-tab="theses">学位论文</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Books Tab -->
|
||||||
|
<div id="tab-books" class="tab-content">
|
||||||
|
<div class="space-y-8">
|
||||||
|
<div class="border-b border-gray-200 pb-8">
|
||||||
|
<h3 class="text-xl font-bold mb-2">《设计智能与生态计算》</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-4">2024年出版 | 科学出版社</p>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed">
|
||||||
|
本书系统介绍了设计智能与生态计算的基本理论和方法,涵盖了生成式生态学、生物启发式智能和数字可持续性等核心内容。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-b border-gray-200 pb-8">
|
||||||
|
<h3 class="text-xl font-bold mb-2">《AI驱动的可持续设计》</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-4">2023年出版 | 清华大学出版社</p>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed">
|
||||||
|
探讨人工智能技术在可持续设计中的应用,包括能源优化、材料选择和生命周期评估等方面。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Papers Tab -->
|
||||||
|
<div id="tab-papers" class="tab-content hidden">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="border-l-2 border-black pl-4">
|
||||||
|
<h3 class="text-lg font-bold mb-2">Generative Ecology: A Machine Learning Approach to Ecosystem Design</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-2">Nature Computational Science, 2024</p>
|
||||||
|
<p class="text-gray-500 text-sm">Chen, L., Wu, S., & Zhao, J.</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-l-2 border-gray-200 pl-4">
|
||||||
|
<h3 class="text-lg font-bold mb-2">Bio-inspired Swarm Intelligence for Distributed Architecture</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-2">IEEE Transactions on AI, 2024</p>
|
||||||
|
<p class="text-gray-500 text-sm">Zhang, Y., Chen, L., & Wu, S.</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-l-2 border-gray-200 pl-4">
|
||||||
|
<h3 class="text-lg font-bold mb-2">Digital Sustainability Metrics for Smart Cities</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-2">Sustainable Computing, 2023</p>
|
||||||
|
<p class="text-gray-500 text-sm">Zhao, J., Chen, L., & Zhang, Y.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Software Tab -->
|
||||||
|
<div id="tab-software" class="tab-content hidden">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div class="border border-gray-200 p-6">
|
||||||
|
<h3 class="text-lg font-bold mb-2">EcoGen AI</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-4">开源 | GitHub</p>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed mb-4">
|
||||||
|
基于生成对抗网络的生态系统设计工具,用于城市绿地和生物多样性规划。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600">查看代码 -></a>
|
||||||
|
</div>
|
||||||
|
<div class="border border-gray-200 p-6">
|
||||||
|
<h3 class="text-lg font-bold mb-2">BioSwarm Simulator</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-4">开源 | GitHub</p>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed mb-4">
|
||||||
|
生物启发式群体智能仿真平台,用于分布式系统优化和控制算法开发。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600">查看代码 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Theses Tab -->
|
||||||
|
<div id="tab-theses" class="tab-content hidden">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="border-l-2 border-black pl-4">
|
||||||
|
<h3 class="text-lg font-bold mb-2">基于生成式AI的城市生态系统优化设计研究</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-2">博士学位论文, 2024</p>
|
||||||
|
<p class="text-gray-500 text-sm">作者:XXX | 导师:Dr. Lin Chen</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-l-2 border-gray-200 pl-4">
|
||||||
|
<h3 class="text-lg font-bold mb-2">生物启发式算法在建筑机器人中的应用</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-2">硕士学位论文, 2023</p>
|
||||||
|
<p class="text-gray-500 text-sm">作者:XXX | 导师:Dr. Lin Chen</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-black text-white py-16 border-t border-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Contact</h5>
|
||||||
|
<p class="text-sm text-gray-400 leading-loose">
|
||||||
|
Design AI & Ecology Lab<br>
|
||||||
|
1234 Design Avenue, Innovation District<br>
|
||||||
|
Shanghai, China<br>
|
||||||
|
<a href="mailto:hello@dael.edu.cn" class="text-white hover:underline">hello@dael.edu.cn</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Social</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="#" class="hover:text-white transition-colors">WeChat / 微信公众号</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Instagram</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Twitter / X</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Resources</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="resources.html" class="hover:text-white transition-colors">资源下载</a>
|
||||||
|
<a href="academic.html" class="hover:text-white transition-colors">学术资料</a>
|
||||||
|
<a href="team.html" class="hover:text-white transition-colors">完整团队</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Join Us</h5>
|
||||||
|
<p class="text-sm text-gray-400 mb-4">
|
||||||
|
我们长期招收博士后、博士生及研究助理。欢迎具有跨学科背景的申请者。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-white text-sm border-b border-white hover:text-gray-300 hover:border-gray-300 pb-0.5">申请职位</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-800 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<p>© 2024 Design AI & Ecology Lab. All Rights Reserved.</p>
|
||||||
|
<p class="mt-2">页面访问数: <span id="visit-counter" class="font-bold">0</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-4 md:mt-0">
|
||||||
|
<a href="#" class="hover:text-gray-400">Privacy Policy</a>
|
||||||
|
<a href="#" class="hover:text-gray-400">Terms of Use</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
<script>
|
||||||
|
// Tab switching
|
||||||
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const tabName = btn.dataset.tab;
|
||||||
|
|
||||||
|
// Update buttons
|
||||||
|
document.querySelectorAll('.tab-btn').forEach(b => {
|
||||||
|
b.classList.remove('active', 'border-black', 'text-black');
|
||||||
|
b.classList.add('text-gray-500');
|
||||||
|
});
|
||||||
|
btn.classList.add('active', 'border-black', 'text-black');
|
||||||
|
btn.classList.remove('text-gray-500');
|
||||||
|
|
||||||
|
// Update content
|
||||||
|
document.querySelectorAll('.tab-content').forEach(content => {
|
||||||
|
content.classList.add('hidden');
|
||||||
|
});
|
||||||
|
document.getElementById(`tab-${tabName}`).classList.remove('hidden');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>研究管理 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased bg-gray-50">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white border-b border-gray-200 shadow-sm" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-16 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600">研究管理</span></span>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a href="website.html" class="text-sm text-gray-600 hover:text-black">返回首页</a>
|
||||||
|
<a href="meetings.html" class="text-sm text-gray-600 hover:text-black">组会管理</a>
|
||||||
|
<button id="logout-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="pt-24 pb-12">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="flex justify-between items-center mb-8">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold mb-2">研究管理</h1>
|
||||||
|
<p class="text-gray-600">管理您的研究项目、笔记、任务和文件</p>
|
||||||
|
</div>
|
||||||
|
<button onclick="showProjectForm()" class="bg-black text-white px-6 py-3 rounded hover:bg-gray-800">
|
||||||
|
+ 新建项目
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Tabs -->
|
||||||
|
<div class="flex space-x-4 border-b border-gray-200 mb-6">
|
||||||
|
<button class="filter-tab active pb-4 px-4 text-sm font-medium border-b-2 border-black" data-filter="all">全部</button>
|
||||||
|
<button class="filter-tab pb-4 px-4 text-sm font-medium text-gray-500 hover:text-black" data-filter="active">进行中</button>
|
||||||
|
<button class="filter-tab pb-4 px-4 text-sm font-medium text-gray-500 hover:text-black" data-filter="completed">已完成</button>
|
||||||
|
<button class="filter-tab pb-4 px-4 text-sm font-medium text-gray-500 hover:text-black" data-filter="paused">已暂停</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Projects List -->
|
||||||
|
<div id="projects-list" class="space-y-4">
|
||||||
|
<!-- Projects will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Form Modal -->
|
||||||
|
<div id="project-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white rounded-lg p-8 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||||
|
<h3 class="text-2xl font-bold mb-6" id="project-modal-title">新建项目</h3>
|
||||||
|
<form id="project-form" onsubmit="saveProject(event)">
|
||||||
|
<input type="hidden" id="project-id">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">项目名称 *</label>
|
||||||
|
<input type="text" id="project-title" required class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">项目描述</label>
|
||||||
|
<textarea id="project-description" class="form-input w-full" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">开始日期</label>
|
||||||
|
<input type="date" id="project-start-date" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">结束日期</label>
|
||||||
|
<input type="date" id="project-end-date" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">状态</label>
|
||||||
|
<select id="project-status" class="form-input w-full">
|
||||||
|
<option value="active">进行中</option>
|
||||||
|
<option value="completed">已完成</option>
|
||||||
|
<option value="paused">已暂停</option>
|
||||||
|
<option value="cancelled">已取消</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-2">进度 (%)</label>
|
||||||
|
<input type="number" id="project-progress" min="0" max="100" value="0" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium mb-2">标签(逗号分隔)</label>
|
||||||
|
<input type="text" id="project-tags" placeholder="AI, 生态, 设计" class="form-input w-full">
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<button type="submit" class="bg-black text-white px-6 py-2 rounded hover:bg-gray-800">保存</button>
|
||||||
|
<button type="button" onclick="closeProjectForm()" class="bg-gray-200 text-gray-800 px-6 py-2 rounded hover:bg-gray-300">取消</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Detail Modal -->
|
||||||
|
<div id="project-detail-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white rounded-lg p-8 max-w-4xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||||
|
<div id="project-detail-content">
|
||||||
|
<!-- Project details will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
<script src="js/research.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>资源下载 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100 transition-all duration-300" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-20 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600 hidden sm:inline">Design AI & Ecology Lab</span></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button id="mobile-menu-btn" class="md:hidden p-2 text-gray-600">
|
||||||
|
<i data-lucide="menu"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center space-x-6 text-sm font-medium tracking-wide text-gray-800">
|
||||||
|
<a href="website.html#about" class="nav-link hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="nav-link hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="nav-link hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="nav-link hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="nav-link hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="nav-link hover:text-black">加入我们</a>
|
||||||
|
|
||||||
|
<form id="search-form" class="relative search-box">
|
||||||
|
<input type="text" id="search-input" placeholder="搜索..."
|
||||||
|
class="px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:border-black w-40">
|
||||||
|
<button type="submit" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-black">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="login.html" id="login-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">登录</a>
|
||||||
|
<a href="#" id="logout-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-gray-100 absolute w-full px-6 py-4 space-y-4 shadow-lg">
|
||||||
|
<a href="website.html#about" class="block text-gray-800 hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="block text-gray-800 hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="block text-gray-800 hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="block text-gray-800 hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="block text-gray-800 hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="block text-gray-800 hover:text-black">加入我们</a>
|
||||||
|
<a href="login.html" class="block text-gray-800 hover:text-black">登录</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page Header -->
|
||||||
|
<section class="pt-32 pb-16 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold mb-4">资源下载</h1>
|
||||||
|
<p class="text-gray-600">下载实验室开发的软件、数据集和研究资料</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Resources Content -->
|
||||||
|
<section class="py-12 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<!-- Software Downloads -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">软件下载</h2>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="border border-gray-200 p-6">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold mb-2">EcoGen AI v2.0</h3>
|
||||||
|
<p class="text-sm text-gray-500 mb-2">生成式生态系统设计工具</p>
|
||||||
|
<p class="text-xs text-gray-400">更新日期: 2024.12.01 | 大小: 125 MB</p>
|
||||||
|
</div>
|
||||||
|
<i data-lucide="download" class="w-6 h-6 text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600">下载 -></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border border-gray-200 p-6">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold mb-2">BioSwarm Simulator v1.5</h3>
|
||||||
|
<p class="text-sm text-gray-500 mb-2">群体智能仿真平台</p>
|
||||||
|
<p class="text-xs text-gray-400">更新日期: 2024.11.15 | 大小: 89 MB</p>
|
||||||
|
</div>
|
||||||
|
<i data-lucide="download" class="w-6 h-6 text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600">下载 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Datasets & Materials -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">数据集与资料</h2>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="border border-gray-200 p-6">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold mb-2">城市生态系统数据集</h3>
|
||||||
|
<p class="text-sm text-gray-500 mb-2">包含50个城市的生态数据</p>
|
||||||
|
<p class="text-xs text-gray-400">更新日期: 2024.10.20 | 大小: 2.3 GB</p>
|
||||||
|
</div>
|
||||||
|
<i data-lucide="database" class="w-6 h-6 text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600">下载 -></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border border-gray-200 p-6">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold mb-2">研究资料包</h3>
|
||||||
|
<p class="text-sm text-gray-500 mb-2">实验室研究方法和工具文档</p>
|
||||||
|
<p class="text-xs text-gray-400">更新日期: 2024.09.10 | 大小: 45 MB</p>
|
||||||
|
</div>
|
||||||
|
<i data-lucide="file-text" class="w-6 h-6 text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="text-sm text-black border-b border-black hover:text-gray-600">下载 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Open Source Code -->
|
||||||
|
<div class="mt-16">
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">开源代码</h2>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<a href="#" class="border border-gray-200 p-6 hover:border-black transition-colors group">
|
||||||
|
<i data-lucide="github" class="w-8 h-8 mb-4 text-gray-400 group-hover:text-black transition-colors"></i>
|
||||||
|
<h3 class="font-bold mb-2">EcoGen AI</h3>
|
||||||
|
<p class="text-sm text-gray-500">GitHub Repository</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="border border-gray-200 p-6 hover:border-black transition-colors group">
|
||||||
|
<i data-lucide="github" class="w-8 h-8 mb-4 text-gray-400 group-hover:text-black transition-colors"></i>
|
||||||
|
<h3 class="font-bold mb-2">BioSwarm</h3>
|
||||||
|
<p class="text-sm text-gray-500">GitHub Repository</p>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="border border-gray-200 p-6 hover:border-black transition-colors group">
|
||||||
|
<i data-lucide="github" class="w-8 h-8 mb-4 text-gray-400 group-hover:text-black transition-colors"></i>
|
||||||
|
<h3 class="font-bold mb-2">更多项目</h3>
|
||||||
|
<p class="text-sm text-gray-500">查看所有开源项目</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-black text-white py-16 border-t border-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Contact</h5>
|
||||||
|
<p class="text-sm text-gray-400 leading-loose">
|
||||||
|
Design AI & Ecology Lab<br>
|
||||||
|
1234 Design Avenue, Innovation District<br>
|
||||||
|
Shanghai, China<br>
|
||||||
|
<a href="mailto:hello@dael.edu.cn" class="text-white hover:underline">hello@dael.edu.cn</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Social</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="#" class="hover:text-white transition-colors">WeChat / 微信公众号</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Instagram</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Twitter / X</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Resources</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="resources.html" class="hover:text-white transition-colors">资源下载</a>
|
||||||
|
<a href="academic.html" class="hover:text-white transition-colors">学术资料</a>
|
||||||
|
<a href="team.html" class="hover:text-white transition-colors">完整团队</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Join Us</h5>
|
||||||
|
<p class="text-sm text-gray-400 mb-4">
|
||||||
|
我们长期招收博士后、博士生及研究助理。欢迎具有跨学科背景的申请者。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-white text-sm border-b border-white hover:text-gray-300 hover:border-gray-300 pb-0.5">申请职位</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-800 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<p>© 2024 Design AI & Ecology Lab. All Rights Reserved.</p>
|
||||||
|
<p class="mt-2">页面访问数: <span id="visit-counter" class="font-bold">0</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-4 md:mt-0">
|
||||||
|
<a href="#" class="hover:text-gray-400">Privacy Policy</a>
|
||||||
|
<a href="#" class="hover:text-gray-400">Terms of Use</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>搜索结果 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100 transition-all duration-300" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-20 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600 hidden sm:inline">Design AI & Ecology Lab</span></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button id="mobile-menu-btn" class="md:hidden p-2 text-gray-600">
|
||||||
|
<i data-lucide="menu"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center space-x-6 text-sm font-medium tracking-wide text-gray-800">
|
||||||
|
<a href="website.html#about" class="nav-link hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="nav-link hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="nav-link hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="nav-link hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="nav-link hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="nav-link hover:text-black">加入我们</a>
|
||||||
|
|
||||||
|
<form id="search-form" class="relative search-box">
|
||||||
|
<input type="text" id="search-input" placeholder="搜索..."
|
||||||
|
class="px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:border-black w-40">
|
||||||
|
<button type="submit" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-black">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="login.html" id="login-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">登录</a>
|
||||||
|
<a href="#" id="logout-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-gray-100 absolute w-full px-6 py-4 space-y-4 shadow-lg">
|
||||||
|
<a href="website.html#about" class="block text-gray-800 hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="block text-gray-800 hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="block text-gray-800 hover:text-black">成果展示</a>
|
||||||
|
<a href="website.html#team" class="block text-gray-800 hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="block text-gray-800 hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="block text-gray-800 hover:text-black">加入我们</a>
|
||||||
|
<a href="login.html" class="block text-gray-800 hover:text-black">登录</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Search Results -->
|
||||||
|
<section class="pt-32 pb-24 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-3xl font-bold mb-4">搜索结果</h1>
|
||||||
|
<p class="text-gray-600" id="search-query-display">搜索关键词:</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="search-results-container" class="space-y-6">
|
||||||
|
<!-- Results will be populated by JavaScript -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="no-results" class="hidden text-center py-12">
|
||||||
|
<i data-lucide="search-x" class="w-16 h-16 mx-auto text-gray-300 mb-4"></i>
|
||||||
|
<p class="text-gray-500">未找到相关结果</p>
|
||||||
|
<p class="text-sm text-gray-400 mt-2">请尝试使用其他关键词搜索</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-black text-white py-16 border-t border-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Contact</h5>
|
||||||
|
<p class="text-sm text-gray-400 leading-loose">
|
||||||
|
Design AI & Ecology Lab<br>
|
||||||
|
1234 Design Avenue, Innovation District<br>
|
||||||
|
Shanghai, China<br>
|
||||||
|
<a href="mailto:hello@dael.edu.cn" class="text-white hover:underline">hello@dael.edu.cn</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Social</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="#" class="hover:text-white transition-colors">WeChat / 微信公众号</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Instagram</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Twitter / X</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Resources</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="resources.html" class="hover:text-white transition-colors">资源下载</a>
|
||||||
|
<a href="academic.html" class="hover:text-white transition-colors">学术资料</a>
|
||||||
|
<a href="team.html" class="hover:text-white transition-colors">完整团队</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Join Us</h5>
|
||||||
|
<p class="text-sm text-gray-400 mb-4">
|
||||||
|
我们长期招收博士后、博士生及研究助理。欢迎具有跨学科背景的申请者。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-white text-sm border-b border-white hover:text-gray-300 hover:border-gray-300 pb-0.5">申请职位</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-800 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<p>© 2024 Design AI & Ecology Lab. All Rights Reserved.</p>
|
||||||
|
<p class="mt-2">页面访问数: <span id="visit-counter" class="font-bold">0</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-4 md:mt-0">
|
||||||
|
<a href="#" class="hover:text-gray-400">Privacy Policy</a>
|
||||||
|
<a href="#" class="hover:text-gray-400">Terms of Use</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
<script>
|
||||||
|
// Display search results on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const query = getSearchQuery();
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
const queryDisplay = document.getElementById('search-query-display');
|
||||||
|
const resultsContainer = document.getElementById('search-results-container');
|
||||||
|
const noResults = document.getElementById('no-results');
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
if (searchInput) searchInput.value = query;
|
||||||
|
if (queryDisplay) queryDisplay.textContent = `搜索关键词:${query}`;
|
||||||
|
|
||||||
|
const results = performSearch(query);
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
resultsContainer.innerHTML = '';
|
||||||
|
noResults.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
noResults.classList.add('hidden');
|
||||||
|
resultsContainer.innerHTML = results.map(result => `
|
||||||
|
<div class="border-b border-gray-200 pb-6">
|
||||||
|
<a href="${result.url}" class="block">
|
||||||
|
<h3 class="text-xl font-bold mb-2 hover:text-gray-600">${result.title}</h3>
|
||||||
|
<p class="text-gray-500 text-sm">${result.snippet}</p>
|
||||||
|
<p class="text-xs text-gray-400 mt-2">${result.url}</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queryDisplay.textContent = '请输入搜索关键词';
|
||||||
|
resultsContainer.innerHTML = '';
|
||||||
|
noResults.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 启动后端服务器
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/backend"
|
||||||
|
|
||||||
|
# 检查虚拟环境
|
||||||
|
if [ ! -d "venv" ]; then
|
||||||
|
echo "Creating virtual environment..."
|
||||||
|
python3 -m venv venv
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
echo "Installing dependencies..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 初始化数据库
|
||||||
|
echo "Initializing database..."
|
||||||
|
python init_data.py
|
||||||
|
|
||||||
|
# 启动服务器
|
||||||
|
echo "Starting Flask server..."
|
||||||
|
python app.py
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>研究团队 | Design AI & Ecology Lab</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100 transition-all duration-300" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-20 flex items-center justify-between">
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600 hidden sm:inline">Design AI & Ecology Lab</span></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button id="mobile-menu-btn" class="md:hidden p-2 text-gray-600">
|
||||||
|
<i data-lucide="menu"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center space-x-6 text-sm font-medium tracking-wide text-gray-800">
|
||||||
|
<a href="website.html#about" class="nav-link hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="nav-link hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="nav-link hover:text-black">成果展示</a>
|
||||||
|
<a href="team.html" class="nav-link hover:text-black text-black border-b border-black">研究团队</a>
|
||||||
|
<a href="news.html" class="nav-link hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="nav-link hover:text-black">加入我们</a>
|
||||||
|
|
||||||
|
<form id="search-form" class="relative search-box">
|
||||||
|
<input type="text" id="search-input" placeholder="搜索..."
|
||||||
|
class="px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:border-black w-40">
|
||||||
|
<button type="submit" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-black">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="login.html" id="login-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">登录</a>
|
||||||
|
<a href="#" id="logout-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-gray-100 absolute w-full px-6 py-4 space-y-4 shadow-lg">
|
||||||
|
<a href="website.html#about" class="block text-gray-800 hover:text-black">关于实验室</a>
|
||||||
|
<a href="website.html#research" class="block text-gray-800 hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="block text-gray-800 hover:text-black">成果展示</a>
|
||||||
|
<a href="team.html" class="block text-gray-800 hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="block text-gray-800 hover:text-black">新闻资讯</a>
|
||||||
|
<a href="website.html#contact" class="block text-gray-800 hover:text-black">加入我们</a>
|
||||||
|
<a href="login.html" class="block text-gray-800 hover:text-black">登录</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page Header -->
|
||||||
|
<section class="pt-32 pb-16 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold mb-4">研究团队</h1>
|
||||||
|
<p class="text-gray-600">了解实验室的成员和研究背景</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Team Content -->
|
||||||
|
<section class="py-12 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<!-- Principal Investigators -->
|
||||||
|
<div class="mb-16">
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-8">Principal Investigators / 实验室主任</h2>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div class="border-b border-gray-200 pb-8">
|
||||||
|
<h3 class="text-xl font-bold mb-2">Dr. Lin Chen</h3>
|
||||||
|
<p class="text-gray-500 text-sm mb-4">Principal Investigator / 实验室主任</p>
|
||||||
|
<p class="text-gray-600 text-sm leading-relaxed mb-4">
|
||||||
|
博士毕业于MIT Media Lab,专注于计算设计与人机交互领域的研究。在生成式设计和AI辅助设计方面有丰富的研究经验。
|
||||||
|
</p>
|
||||||
|
<div class="flex space-x-4 text-sm text-gray-500">
|
||||||
|
<a href="#" class="hover:text-black">Email</a>
|
||||||
|
<a href="#" class="hover:text-black">Google Scholar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Faculty -->
|
||||||
|
<div class="mb-16">
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-8">Faculty / 教师</h2>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div class="border-b border-gray-200 pb-8">
|
||||||
|
<h3 class="text-lg font-bold mb-2">Sarah Wu</h3>
|
||||||
|
<p class="text-gray-500 text-sm mb-4">Lead Ecologist / 生态学顾问</p>
|
||||||
|
<p class="text-gray-600 text-sm leading-relaxed">
|
||||||
|
城市生态学专家,研究重点为城市微气候与生物多样性修复。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="border-b border-gray-200 pb-8">
|
||||||
|
<h3 class="text-lg font-bold mb-2">James Zhao</h3>
|
||||||
|
<p class="text-gray-500 text-sm mb-4">AI Researcher / 算法工程师</p>
|
||||||
|
<p class="text-gray-600 text-sm leading-relaxed">
|
||||||
|
专攻深度强化学习在复杂系统模拟中的应用。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Current Students -->
|
||||||
|
<div class="mb-16">
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-8">Current Students / 在读学生</h2>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">Wang Li</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">PhD Student</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
研究方向:生成式生态学
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">Liu Ming</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">PhD Student</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
研究方向:生物启发式智能
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">Zhang Wei</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">Master Student</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
研究方向:数字可持续性
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alumni -->
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-8">Alumni / 毕业学生</h2>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">Chen Xia</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">PhD, 2023</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
现就职于:XX科技公司
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">Zhou Fang</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">Master, 2023</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
现就职于:XX设计院
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-black text-white py-16 border-t border-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Contact</h5>
|
||||||
|
<p class="text-sm text-gray-400 leading-loose">
|
||||||
|
Design AI & Ecology Lab<br>
|
||||||
|
1234 Design Avenue, Innovation District<br>
|
||||||
|
Shanghai, China<br>
|
||||||
|
<a href="mailto:hello@dael.edu.cn" class="text-white hover:underline">hello@dael.edu.cn</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Social</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="#" class="hover:text-white transition-colors">WeChat / 微信公众号</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Instagram</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Twitter / X</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Resources</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="resources.html" class="hover:text-white transition-colors">资源下载</a>
|
||||||
|
<a href="academic.html" class="hover:text-white transition-colors">学术资料</a>
|
||||||
|
<a href="team.html" class="hover:text-white transition-colors">完整团队</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Join Us</h5>
|
||||||
|
<p class="text-sm text-gray-400 mb-4">
|
||||||
|
我们长期招收博士后、博士生及研究助理。欢迎具有跨学科背景的申请者。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-white text-sm border-b border-white hover:text-gray-300 hover:border-gray-300 pb-0.5">申请职位</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-800 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<p>© 2024 Design AI & Ecology Lab. All Rights Reserved.</p>
|
||||||
|
<p class="mt-2">页面访问数: <span id="visit-counter" class="font-bold">0</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-4 md:mt-0">
|
||||||
|
<a href="#" class="hover:text-gray-400">Privacy Policy</a>
|
||||||
|
<a href="#" class="hover:text-gray-400">Terms of Use</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test Login</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Login Test</h1>
|
||||||
|
<button onclick="testLogin()">Test Login</button>
|
||||||
|
<div id="result"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function testLogin() {
|
||||||
|
const resultDiv = document.getElementById('result');
|
||||||
|
resultDiv.innerHTML = 'Testing...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:5000/api/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin123'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
resultDiv.innerHTML = `
|
||||||
|
<h3>Status: ${response.status}</h3>
|
||||||
|
<pre>${JSON.stringify(data, null, 2)}</pre>
|
||||||
|
`;
|
||||||
|
} catch (error) {
|
||||||
|
resultDiv.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,540 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" class="scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Design AI & Ecology Lab | 设计智能与生态设计实验室</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="css/common.css">
|
||||||
|
<!-- 将 Lucide 脚本移至底部以确保加载顺序,或使用更稳定的 CDN -->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', 'Noto Sans SC', sans-serif;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #1a1a1a;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.nav-link::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 1px;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
background-color: #000;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
.nav-link:hover::after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in-up {
|
||||||
|
animation: fadeInUp 0.8s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-100 { animation-delay: 0.1s; }
|
||||||
|
.delay-200 { animation-delay: 0.2s; }
|
||||||
|
.delay-300 { animation-delay: 0.3s; }
|
||||||
|
|
||||||
|
/* Canvas container */
|
||||||
|
#canvas-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="antialiased">
|
||||||
|
|
||||||
|
<!-- Canvas Background for Hero -->
|
||||||
|
<div id="canvas-container">
|
||||||
|
<canvas id="heroCanvas"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="fixed w-full z-50 bg-white/90 backdrop-blur-sm border-b border-gray-100 transition-all duration-300" id="navbar">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 h-20 flex items-center justify-between">
|
||||||
|
<!-- Logo -->
|
||||||
|
<a href="website.html" class="text-xl tracking-tight font-bold flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 bg-black text-white flex items-center justify-center font-serif italic">D</div>
|
||||||
|
<span>DAEL<span class="text-gray-400 font-light mx-2">/</span><span class="text-sm font-normal text-gray-600 hidden sm:inline">Design AI & Ecology Lab</span></span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Button -->
|
||||||
|
<button id="mobile-menu-btn" class="md:hidden p-2 text-gray-600">
|
||||||
|
<i data-lucide="menu"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Desktop Links -->
|
||||||
|
<div class="hidden md:flex items-center space-x-6 text-sm font-medium tracking-wide text-gray-800">
|
||||||
|
<a href="#about" class="nav-link hover:text-black">关于实验室</a>
|
||||||
|
<a href="#research" class="nav-link hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="nav-link hover:text-black">成果展示</a>
|
||||||
|
<a href="#team" class="nav-link hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="nav-link hover:text-black">新闻资讯</a>
|
||||||
|
<a href="meetings.html" id="meetings-link" class="hidden nav-link hover:text-black">组会管理</a>
|
||||||
|
<a href="research.html" id="research-link" class="hidden nav-link hover:text-black">研究管理</a>
|
||||||
|
<a href="#contact" class="nav-link hover:text-black">加入我们</a>
|
||||||
|
|
||||||
|
<!-- Search Box -->
|
||||||
|
<form id="search-form" class="relative search-box">
|
||||||
|
<input type="text" id="search-input" placeholder="搜索..."
|
||||||
|
class="px-4 py-2 text-sm border border-gray-300 rounded-full focus:outline-none focus:border-black w-40">
|
||||||
|
<button type="submit" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-black">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Login Button -->
|
||||||
|
<a href="login.html" id="login-btn" class="text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">登录</a>
|
||||||
|
<a href="admin.html" id="admin-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">管理</a>
|
||||||
|
<a href="#" id="logout-btn" class="hidden text-xs border border-gray-300 px-4 py-2 rounded hover:bg-black hover:text-white transition-colors">退出</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu (Hidden by default) -->
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden bg-white border-b border-gray-100 absolute w-full px-6 py-4 space-y-4 shadow-lg">
|
||||||
|
<a href="#about" class="block text-gray-800 hover:text-black">关于实验室</a>
|
||||||
|
<a href="#research" class="block text-gray-800 hover:text-black">研究方向</a>
|
||||||
|
<a href="publications.html" class="block text-gray-800 hover:text-black">成果展示</a>
|
||||||
|
<a href="#team" class="block text-gray-800 hover:text-black">研究团队</a>
|
||||||
|
<a href="news.html" class="block text-gray-800 hover:text-black">新闻资讯</a>
|
||||||
|
<a href="meetings.html" id="mobile-meetings-link" class="hidden block text-gray-800 hover:text-black">组会管理</a>
|
||||||
|
<a href="research.html" id="mobile-research-link" class="hidden block text-gray-800 hover:text-black">研究管理</a>
|
||||||
|
<a href="#contact" class="block text-gray-800 hover:text-black">加入我们</a>
|
||||||
|
<a href="login.html" class="block text-gray-800 hover:text-black">登录</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<section class="relative min-h-screen flex items-center pt-20">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 w-full">
|
||||||
|
<div class="max-w-4xl">
|
||||||
|
<h1 class="text-5xl md:text-7xl lg:text-8xl font-bold tracking-tighter leading-[1.1] mb-8 fade-in-up text-black">
|
||||||
|
Design Intelligence <br>
|
||||||
|
<span class="text-gray-400 italic font-serif font-light">&</span> Ecology.
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl md:text-2xl text-gray-600 font-light leading-relaxed max-w-2xl fade-in-up delay-100">
|
||||||
|
设计智能与生态设计实验室致力于探索人工智能技术与生态系统的共生关系。我们通过数据驱动的设计方法,重塑人造环境与自然环境的边界。
|
||||||
|
</p>
|
||||||
|
<div class="mt-12 fade-in-up delay-200">
|
||||||
|
<a href="#about" class="inline-flex items-center gap-2 border-b border-black pb-1 text-sm font-bold uppercase tracking-widest hover:text-gray-600 hover:border-gray-600 transition-colors">
|
||||||
|
了解更多
|
||||||
|
<i data-lucide="arrow-down" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Manifesto / About Section -->
|
||||||
|
<section id="about" class="py-24 bg-black text-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-12 gap-12">
|
||||||
|
<div class="md:col-span-4">
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-4">Mission / 使命</h2>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-8">
|
||||||
|
<p class="text-3xl md:text-4xl font-light leading-tight mb-8">
|
||||||
|
我们相信,未来的设计不应仅服务于人类的需求,更应回应地球生态的呼唤。
|
||||||
|
</p>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 text-gray-400 font-light">
|
||||||
|
<p>
|
||||||
|
DAEL (Design AI & Ecology Lab) 是一个跨学科的研究平台。我们融合计算机科学、生态学、建筑学与交互设计,利用机器学习算法模拟自然形态的演变,寻求可持续发展的最优解。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
实验室致力于培养新一代具备计算思维与生态伦理的设计师,通过与产业界、学术界的紧密合作,推动从“以人为本”向“生态为本”的设计范式转移。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Research Domains -->
|
||||||
|
<section id="research" class="py-24 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-end mb-16">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-2">Research Areas</h2>
|
||||||
|
<h3 class="text-3xl font-bold">研究方向</h3>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 md:mt-0">
|
||||||
|
<a href="publications.html" class="text-sm underline underline-offset-4 hover:text-gray-600">查看所有出版物 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<!-- Card 1 -->
|
||||||
|
<div class="group cursor-pointer">
|
||||||
|
<div class="aspect-[4/3] bg-gray-100 mb-6 overflow-hidden relative">
|
||||||
|
<!-- Placeholder for visual -->
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-tr from-gray-200 to-gray-50 group-hover:scale-105 transition-transform duration-700"></div>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center opacity-20">
|
||||||
|
<i data-lucide="cpu" class="w-24 h-24"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4 class="text-xl font-bold mb-2 group-hover:text-gray-600 transition-colors">Generative Ecology<br>生成式生态学</h4>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed">
|
||||||
|
利用生成对抗网络(GANs)与进化算法,模拟复杂生态系统的演替过程,辅助城市绿地与生物多样性规划。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card 2 -->
|
||||||
|
<div class="group cursor-pointer">
|
||||||
|
<div class="aspect-[4/3] bg-gray-100 mb-6 overflow-hidden relative">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-tr from-gray-200 to-gray-50 group-hover:scale-105 transition-transform duration-700"></div>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center opacity-20">
|
||||||
|
<i data-lucide="sprout" class="w-24 h-24"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4 class="text-xl font-bold mb-2 group-hover:text-gray-600 transition-colors">Bio-inspired AI<br>生物启发式智能</h4>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed">
|
||||||
|
从自然界的群体智能(如蚁群、蜂群)中汲取灵感,开发适用于分布式建筑机器人与自适应材料的控制算法。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card 3 -->
|
||||||
|
<div class="group cursor-pointer">
|
||||||
|
<div class="aspect-[4/3] bg-gray-100 mb-6 overflow-hidden relative">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-tr from-gray-200 to-gray-50 group-hover:scale-105 transition-transform duration-700"></div>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center opacity-20">
|
||||||
|
<i data-lucide="globe-2" class="w-24 h-24"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4 class="text-xl font-bold mb-2 group-hover:text-gray-600 transition-colors">Digital Sustainability<br>数字可持续性</h4>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed">
|
||||||
|
量化数字基础设施的碳足迹,并开发基于AI的能源优化模型,用于智慧城市与低碳建筑的运营管理。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- News Preview Section -->
|
||||||
|
<section id="news-preview" class="py-24 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-end mb-16">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-2">Latest News</h2>
|
||||||
|
<h3 class="text-3xl font-bold">最新资讯</h3>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 md:mt-0">
|
||||||
|
<a href="news.html" class="text-sm underline underline-offset-4 hover:text-gray-600">查看全部 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<!-- News Card 1 -->
|
||||||
|
<a href="news.html" class="news-card group">
|
||||||
|
<div class="aspect-[4/3] bg-gray-100 mb-4 overflow-hidden relative">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-tr from-gray-200 to-gray-50"></div>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center opacity-20">
|
||||||
|
<i data-lucide="newspaper" class="w-16 h-16"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mb-2">2024.12.15</div>
|
||||||
|
<h4 class="text-lg font-bold mb-2 group-hover:text-gray-600 transition-colors">实验室新项目启动:AI驱动的城市生态规划</h4>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed">我们启动了新的研究项目,利用生成式AI技术优化城市生态系统的规划与设计...</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- News Card 2 -->
|
||||||
|
<a href="news.html" class="news-card group">
|
||||||
|
<div class="aspect-[4/3] bg-gray-100 mb-4 overflow-hidden relative">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-tr from-gray-200 to-gray-50"></div>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center opacity-20">
|
||||||
|
<i data-lucide="award" class="w-16 h-16"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mb-2">2024.11.20</div>
|
||||||
|
<h4 class="text-lg font-bold mb-2 group-hover:text-gray-600 transition-colors">研究成果在顶级期刊发表</h4>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed">实验室关于生物启发式智能算法的研究成果在Nature子刊发表...</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- News Card 3 -->
|
||||||
|
<a href="news.html" class="news-card group">
|
||||||
|
<div class="aspect-[4/3] bg-gray-100 mb-4 overflow-hidden relative">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-tr from-gray-200 to-gray-50"></div>
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center opacity-20">
|
||||||
|
<i data-lucide="users" class="w-16 h-16"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500 mb-2">2024.10.10</div>
|
||||||
|
<h4 class="text-lg font-bold mb-2 group-hover:text-gray-600 transition-colors">欢迎新成员加入实验室</h4>
|
||||||
|
<p class="text-gray-500 text-sm leading-relaxed">我们很高兴地宣布,三位新的博士生和两位研究助理加入了我们的团队...</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Publications Preview Section -->
|
||||||
|
<section id="publications-preview" class="py-24 bg-gray-50">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-end mb-16">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-2">Publications</h2>
|
||||||
|
<h3 class="text-3xl font-bold">成果展示</h3>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 md:mt-0">
|
||||||
|
<a href="publications.html" class="text-sm underline underline-offset-4 hover:text-gray-600">查看全部 -></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
<a href="publications.html" class="group">
|
||||||
|
<div class="border border-gray-200 p-6 hover:border-black transition-colors">
|
||||||
|
<i data-lucide="book" class="w-8 h-8 mb-4 text-gray-400 group-hover:text-black transition-colors"></i>
|
||||||
|
<h4 class="font-bold mb-2">专著教材</h4>
|
||||||
|
<p class="text-sm text-gray-500">查看实验室出版的专著和教材</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="publications.html" class="group">
|
||||||
|
<div class="border border-gray-200 p-6 hover:border-black transition-colors">
|
||||||
|
<i data-lucide="file-text" class="w-8 h-8 mb-4 text-gray-400 group-hover:text-black transition-colors"></i>
|
||||||
|
<h4 class="font-bold mb-2">文章目录</h4>
|
||||||
|
<p class="text-sm text-gray-500">学术论文和研究文章</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="publications.html" class="group">
|
||||||
|
<div class="border border-gray-200 p-6 hover:border-black transition-colors">
|
||||||
|
<i data-lucide="code" class="w-8 h-8 mb-4 text-gray-400 group-hover:text-black transition-colors"></i>
|
||||||
|
<h4 class="font-bold mb-2">软件开发</h4>
|
||||||
|
<p class="text-sm text-gray-500">开源软件和工具</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="publications.html" class="group">
|
||||||
|
<div class="border border-gray-200 p-6 hover:border-black transition-colors">
|
||||||
|
<i data-lucide="graduation-cap" class="w-8 h-8 mb-4 text-gray-400 group-hover:text-black transition-colors"></i>
|
||||||
|
<h4 class="font-bold mb-2">学位论文</h4>
|
||||||
|
<p class="text-sm text-gray-500">博士和硕士学位论文</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Data/Visual Break -->
|
||||||
|
<section class="py-24 bg-black text-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12 text-center">
|
||||||
|
<p class="font-serif italic text-2xl md:text-3xl text-gray-400 mb-8">
|
||||||
|
"Data is the soil, Algorithms are the seeds, Design is the fruit."
|
||||||
|
</p>
|
||||||
|
<div class="flex justify-center gap-12 text-center">
|
||||||
|
<div>
|
||||||
|
<span class="block text-4xl md:text-5xl font-bold text-white mb-2 stats-counter">20+</span>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-gray-500">Projects</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="block text-4xl md:text-5xl font-bold text-white mb-2 stats-counter">15</span>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-gray-500">Partners</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="block text-4xl md:text-5xl font-bold text-white mb-2 stats-counter">50+</span>
|
||||||
|
<span class="text-xs uppercase tracking-widest text-gray-500">Publications</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Protected Content Section (Login Required) -->
|
||||||
|
<section id="protected" class="py-24 bg-gray-50">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="mb-12">
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-2">Internal Resources</h2>
|
||||||
|
<h3 class="text-3xl font-bold">内部资源</h3>
|
||||||
|
<p class="text-gray-600 mt-4">登录后查看实验室内部资料和详细数据</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Protected Articles -->
|
||||||
|
<div id="protected-articles" class="hidden mb-12">
|
||||||
|
<h4 class="text-lg font-bold mb-6">内部文章</h4>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<!-- Content will be loaded dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Protected Resources -->
|
||||||
|
<div id="protected-resources" class="hidden mb-12">
|
||||||
|
<h4 class="text-lg font-bold mb-6">内部资源</h4>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<!-- Content will be loaded dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Protected Stats -->
|
||||||
|
<div id="protected-stats" class="protected-content">
|
||||||
|
<h4 class="text-lg font-bold mb-6">详细统计</h4>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-3xl font-bold mb-2">--</div>
|
||||||
|
<div class="text-sm text-gray-500">总文章数</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-3xl font-bold mb-2">--</div>
|
||||||
|
<div class="text-sm text-gray-500">总资源数</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-3xl font-bold mb-2">--</div>
|
||||||
|
<div class="text-sm text-gray-500">团队成员</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-3xl font-bold mb-2">--</div>
|
||||||
|
<div class="text-sm text-gray-500">总下载量</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Team Section -->
|
||||||
|
<section id="team" class="py-24 bg-white">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-12 gap-12">
|
||||||
|
<div class="md:col-span-4">
|
||||||
|
<h2 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-4">Team / 团队</h2>
|
||||||
|
<p class="text-gray-600 mb-8 text-sm">
|
||||||
|
我们是一个多元化的团队,成员背景涵盖计算机科学、建筑学、生物学及社会学。
|
||||||
|
</p>
|
||||||
|
<a href="team.html" class="inline-block border border-gray-300 px-6 py-3 text-sm hover:bg-black hover:text-white transition-colors">
|
||||||
|
查看全体成员名单
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-8">
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-12">
|
||||||
|
<!-- PI -->
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">Dr. Lin Chen</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">Principal Investigator / 实验室主任</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
博士毕业于MIT Media Lab,专注于计算设计与人机交互领域的研究。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Member -->
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">Sarah Wu</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">Lead Ecologist / 生态学顾问</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
城市生态学专家,研究重点为城市微气候与生物多样性修复。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Member -->
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">James Zhao</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">AI Researcher / 算法工程师</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
专攻深度强化学习在复杂系统模拟中的应用。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Member -->
|
||||||
|
<div>
|
||||||
|
<div class="w-full h-px bg-gray-200 mb-4"></div>
|
||||||
|
<h4 class="text-lg font-bold">Yu Zhang</h4>
|
||||||
|
<p class="text-gray-500 text-sm mb-2">Design Lead / 设计主创</p>
|
||||||
|
<p class="text-gray-400 text-xs leading-relaxed">
|
||||||
|
探索参数化设计与数字建造的结合。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer id="contact" class="bg-black text-white py-16 border-t border-gray-800">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 lg:px-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Contact</h5>
|
||||||
|
<p class="text-sm text-gray-400 leading-loose">
|
||||||
|
Design AI & Ecology Lab<br>
|
||||||
|
1234 Design Avenue, Innovation District<br>
|
||||||
|
Shanghai, China<br>
|
||||||
|
<a href="mailto:hello@dael.edu.cn" class="text-white hover:underline">hello@dael.edu.cn</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Social</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="#" class="hover:text-white transition-colors">WeChat / 微信公众号</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Instagram</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">Twitter / X</a>
|
||||||
|
<a href="#" class="hover:text-white transition-colors">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Resources</h5>
|
||||||
|
<div class="flex flex-col space-y-2 text-sm text-gray-400">
|
||||||
|
<a href="resources.html" class="hover:text-white transition-colors">资源下载</a>
|
||||||
|
<a href="academic.html" class="hover:text-white transition-colors">学术资料</a>
|
||||||
|
<a href="team.html" class="hover:text-white transition-colors">完整团队</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="text-sm font-bold uppercase tracking-widest text-gray-500 mb-6">Join Us</h5>
|
||||||
|
<p class="text-sm text-gray-400 mb-4">
|
||||||
|
我们长期招收博士后、博士生及研究助理。欢迎具有跨学科背景的申请者。
|
||||||
|
</p>
|
||||||
|
<a href="#" class="text-white text-sm border-b border-white hover:text-gray-300 hover:border-gray-300 pb-0.5">申请职位</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center pt-8 border-t border-gray-800 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<p>© 2024 Design AI & Ecology Lab. All Rights Reserved.</p>
|
||||||
|
<p class="mt-2">页面访问数: <span id="visit-counter" class="font-bold">0</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 mt-4 md:mt-0">
|
||||||
|
<a href="#" class="hover:text-gray-400">Privacy Policy</a>
|
||||||
|
<a href="#" class="hover:text-gray-400">Terms of Use</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script src="js/api.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/search.js"></script>
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# 后台运行前后端命令
|
||||||
|
## 启动后端服务器
|
||||||
|
cd /home/admin305/Projects/2025_DAEL/dofile/backend
|
||||||
|
source .venv/bin/activate
|
||||||
|
nohup python app.py > app.log 2>&1 &
|
||||||
|
|
||||||
|
## 启动前端服务器
|
||||||
|
cd /home/admin305/Projects/2025_DAEL/dofile
|
||||||
|
nohup python3 -m http.server 6002 --bind 0.0.0.0 > http.log 2>&1 &
|
||||||
|
|
||||||
|
- 端口号
|
||||||
|
[1] 1998431
|
||||||
|
[2] 1998432
|
||||||
Reference in New Issue
Block a user