用户登录功能
This commit is contained in:
13
.env.example
Normal file
13
.env.example
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Flask配置
|
||||||
|
SECRET_KEY=your-secret-key-here-please-change-this
|
||||||
|
FLASK_ENV=production
|
||||||
|
|
||||||
|
# OpenAI API配置(可选,用于AI润色功能)
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
|
||||||
|
# 默认用户配置
|
||||||
|
# Docker首次启动时会自动创建此用户
|
||||||
|
DEFAULT_USERNAME=admin
|
||||||
|
DEFAULT_PASSWORD=admin123
|
||||||
|
|
||||||
|
# 注意: 生产环境请务必修改默认密码!
|
||||||
@@ -24,10 +24,14 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
|||||||
# 复制项目文件
|
# 复制项目文件
|
||||||
COPY backend/ /app/backend/
|
COPY backend/ /app/backend/
|
||||||
COPY frontend/ /app/frontend/
|
COPY frontend/ /app/frontend/
|
||||||
|
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
|
||||||
|
|
||||||
# 创建数据目录
|
# 创建数据目录
|
||||||
RUN mkdir -p /app/data
|
RUN mkdir -p /app/data
|
||||||
|
|
||||||
|
# 设置启动脚本权限
|
||||||
|
RUN chmod +x /app/docker-entrypoint.sh
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
@@ -35,4 +39,4 @@ EXPOSE 5000
|
|||||||
WORKDIR /app/backend
|
WORKDIR /app/backend
|
||||||
|
|
||||||
# 启动应用
|
# 启动应用
|
||||||
CMD ["python", "app.py"]
|
CMD ["/app/docker-entrypoint.sh"]
|
||||||
|
|||||||
82
README.md
82
README.md
@@ -4,12 +4,14 @@
|
|||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
|
- 🔐 **用户认证**: 密码登录保护,确保数据安全
|
||||||
- ✅ **任务管理**: 添加、编辑、删除工作任务
|
- ✅ **任务管理**: 添加、编辑、删除工作任务
|
||||||
- ⏱️ **时间追踪**: 点击开始/停止计时,自动记录任务时长
|
- ⏱️ **时间追踪**: 点击开始/停止计时,自动记录任务时长
|
||||||
- 📊 **时间统计**: 查看每日、每周的工作时间统计
|
- 📊 **时间统计**: 查看每日、每周的工作时间统计
|
||||||
- 🤖 **AI润色**: 使用AI优化任务描述,让工作记录更专业
|
- 🤖 **AI润色**: 使用AI优化任务描述,让工作记录更专业
|
||||||
- 📱 **响应式设计**: 支持桌面和移动设备
|
- 📱 **响应式设计**: 支持桌面和移动设备
|
||||||
- 💾 **本地存储**: 数据存储在本地SQLite数据库中
|
- 💾 **本地存储**: 数据存储在本地SQLite数据库中
|
||||||
|
- 🐳 **Docker支持**: 一键部署,开箱即用
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
@@ -20,12 +22,40 @@
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 1. 环境要求
|
### 方法一: 使用Docker (推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 克隆项目
|
||||||
|
git clone <repository-url>
|
||||||
|
cd worklist
|
||||||
|
|
||||||
|
# 2. (可选) 配置环境变量
|
||||||
|
cp .env.example .env
|
||||||
|
# 编辑 .env 文件,设置自定义用户名和密码
|
||||||
|
|
||||||
|
# 3. 启动Docker容器
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 4. 访问应用
|
||||||
|
# 打开浏览器访问 http://localhost:5000
|
||||||
|
# 默认用户名: admin
|
||||||
|
# 默认密码: admin123
|
||||||
|
```
|
||||||
|
|
||||||
|
**首次启动说明:**
|
||||||
|
- Docker会自动创建数据库和默认用户
|
||||||
|
- 默认用户名: `admin`, 默认密码: `admin123`
|
||||||
|
- 可通过环境变量 `DEFAULT_USERNAME` 和 `DEFAULT_PASSWORD` 自定义
|
||||||
|
- 登录后请立即修改密码
|
||||||
|
|
||||||
|
### 方法二: 本地运行
|
||||||
|
|
||||||
|
#### 1. 环境要求
|
||||||
|
|
||||||
- Python 3.7 或更高版本
|
- Python 3.7 或更高版本
|
||||||
- 现代浏览器 (Chrome, Firefox, Safari, Edge)
|
- 现代浏览器 (Chrome, Firefox, Safari, Edge)
|
||||||
|
|
||||||
### 2. 安装和运行
|
#### 2. 安装和运行
|
||||||
|
|
||||||
#### 方法一:使用启动脚本(推荐)
|
#### 方法一:使用启动脚本(推荐)
|
||||||
|
|
||||||
@@ -44,30 +74,64 @@ python start.py
|
|||||||
- 启动服务器
|
- 启动服务器
|
||||||
- 自动打开浏览器
|
- 自动打开浏览器
|
||||||
|
|
||||||
|
**首次使用需创建用户:**
|
||||||
|
```bash
|
||||||
|
# 运行用户创建脚本
|
||||||
|
python create_user.py
|
||||||
|
```
|
||||||
|
|
||||||
#### 方法二:手动启动
|
#### 方法二:手动启动
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 安装Python依赖
|
# 1. 安装Python依赖
|
||||||
pip install -r backend/requirements.txt
|
pip install -r backend/requirements.txt
|
||||||
|
|
||||||
# 2. 启动服务器
|
# 2. 创建初始用户
|
||||||
|
python create_user.py
|
||||||
|
|
||||||
|
# 3. 启动服务器
|
||||||
cd backend
|
cd backend
|
||||||
python app.py
|
python app.py
|
||||||
|
|
||||||
# 3. 在浏览器中访问
|
# 4. 在浏览器中访问
|
||||||
# http://localhost:5000
|
# http://localhost:5000
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 配置AI润色功能(可选)
|
### 3. 配置说明
|
||||||
|
|
||||||
如需使用AI润色功能,请:
|
#### 环境变量配置
|
||||||
|
|
||||||
1. 获取OpenAI API密钥
|
#### 环境变量配置
|
||||||
2. 编辑 `backend/.env` 文件
|
|
||||||
3. 设置 `OPENAI_API_KEY=your_api_key_here`
|
创建 `.env` 文件 (可复制 `.env.example`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Flask配置
|
||||||
|
SECRET_KEY=your-secret-key-here-please-change-this
|
||||||
|
|
||||||
|
# OpenAI API配置(可选,用于AI润色功能)
|
||||||
|
OPENAI_API_KEY=your_openai_api_key
|
||||||
|
|
||||||
|
# Docker首次启动时的默认用户(仅Docker部署时有效)
|
||||||
|
DEFAULT_USERNAME=admin
|
||||||
|
DEFAULT_PASSWORD=admin123
|
||||||
|
```
|
||||||
|
|
||||||
|
**安全建议:**
|
||||||
|
- 生产环境务必修改 `SECRET_KEY`
|
||||||
|
- 修改默认用户名和密码
|
||||||
|
- 登录后立即在系统中修改密码
|
||||||
|
|
||||||
|
#### AI润色功能(可选)
|
||||||
|
|
||||||
## 使用指南
|
## 使用指南
|
||||||
|
|
||||||
|
### 登录系统
|
||||||
|
|
||||||
|
1. 首次访问会显示登录页面
|
||||||
|
2. 输入用户名和密码
|
||||||
|
3. 登录成功后进入主界面
|
||||||
|
|
||||||
### 基本操作
|
### 基本操作
|
||||||
|
|
||||||
1. **添加任务**
|
1. **添加任务**
|
||||||
|
|||||||
@@ -6,16 +6,21 @@ import os
|
|||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# 配置
|
# 配置
|
||||||
app.config['SECRET_KEY'] = 'your-secret-key-here'
|
app.config['SECRET_KEY'] = 'your-secret-key-here-change-in-production'
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///worklist.db'
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///worklist.db'
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
|
||||||
|
# Session配置
|
||||||
|
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
||||||
|
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
||||||
|
app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24小时
|
||||||
|
|
||||||
# 初始化扩展
|
# 初始化扩展
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
CORS(app) # 允许跨域请求
|
CORS(app, supports_credentials=True) # 允许跨域请求并支持凭证
|
||||||
|
|
||||||
# 注册蓝图
|
# 注册蓝图
|
||||||
app.register_blueprint(api, url_prefix='/api')
|
app.register_blueprint(api, url_prefix='/api')
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,34 @@
|
|||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
"""用户模型"""
|
||||||
|
__tablename__ = 'users'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||||
|
password_hash = db.Column(db.String(200), nullable=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,
|
||||||
|
'created_at': self.created_at.isoformat() if self.created_at else None
|
||||||
|
}
|
||||||
|
|
||||||
class Task(db.Model):
|
class Task(db.Model):
|
||||||
"""任务模型"""
|
"""任务模型"""
|
||||||
__tablename__ = 'tasks'
|
__tablename__ = 'tasks'
|
||||||
|
|||||||
@@ -1,11 +1,81 @@
|
|||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify, session
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from models import db, Task, TimeRecord
|
from models import db, Task, TimeRecord, User
|
||||||
from ai_service import ai_service
|
from ai_service import ai_service
|
||||||
import json
|
import json
|
||||||
|
|
||||||
api = Blueprint('api', __name__)
|
api = Blueprint('api', __name__)
|
||||||
|
|
||||||
|
# 认证API
|
||||||
|
@api.route('/auth/login', methods=['POST'])
|
||||||
|
def login():
|
||||||
|
"""用户登录"""
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if not data or 'username' not in data or 'password' not in data:
|
||||||
|
return jsonify({'error': '用户名和密码不能为空'}), 400
|
||||||
|
|
||||||
|
username = data['username']
|
||||||
|
password = data['password']
|
||||||
|
|
||||||
|
user = User.query.filter_by(username=username).first()
|
||||||
|
|
||||||
|
if user and user.check_password(password):
|
||||||
|
# 登录成功,设置session
|
||||||
|
session['user_id'] = user.id
|
||||||
|
session['username'] = user.username
|
||||||
|
return jsonify({
|
||||||
|
'message': '登录成功',
|
||||||
|
'user': user.to_dict()
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({'error': '用户名或密码错误'}), 401
|
||||||
|
|
||||||
|
@api.route('/auth/logout', methods=['POST'])
|
||||||
|
def logout():
|
||||||
|
"""用户登出"""
|
||||||
|
session.clear()
|
||||||
|
return jsonify({'message': '登出成功'})
|
||||||
|
|
||||||
|
@api.route('/auth/check', methods=['GET'])
|
||||||
|
def check_auth():
|
||||||
|
"""检查登录状态"""
|
||||||
|
if 'user_id' in session:
|
||||||
|
user = User.query.get(session['user_id'])
|
||||||
|
if user:
|
||||||
|
return jsonify({
|
||||||
|
'authenticated': True,
|
||||||
|
'user': user.to_dict()
|
||||||
|
})
|
||||||
|
return jsonify({'authenticated': False}), 401
|
||||||
|
|
||||||
|
@api.route('/auth/register', methods=['POST'])
|
||||||
|
def register():
|
||||||
|
"""用户注册(可选,用于创建初始用户)"""
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if not data or 'username' not in data or 'password' not in data:
|
||||||
|
return jsonify({'error': '用户名和密码不能为空'}), 400
|
||||||
|
|
||||||
|
username = data['username']
|
||||||
|
password = data['password']
|
||||||
|
|
||||||
|
# 检查用户是否已存在
|
||||||
|
if User.query.filter_by(username=username).first():
|
||||||
|
return jsonify({'error': '用户名已存在'}), 400
|
||||||
|
|
||||||
|
# 创建新用户
|
||||||
|
user = User(username=username)
|
||||||
|
user.set_password(password)
|
||||||
|
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': '注册成功',
|
||||||
|
'user': user.to_dict()
|
||||||
|
}), 201
|
||||||
|
|
||||||
# 任务管理API
|
# 任务管理API
|
||||||
@api.route('/tasks', methods=['GET'])
|
@api.route('/tasks', methods=['GET'])
|
||||||
def get_tasks():
|
def get_tasks():
|
||||||
|
|||||||
38
create_user.py
Normal file
38
create_user.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
创建初始用户脚本
|
||||||
|
用于创建管理员账户
|
||||||
|
"""
|
||||||
|
|
||||||
|
from backend.app import create_app
|
||||||
|
from backend.models import db, User
|
||||||
|
|
||||||
|
def create_initial_user():
|
||||||
|
"""创建初始用户"""
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
# 检查是否已有用户
|
||||||
|
existing_user = User.query.first()
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
print(f"用户已存在: {existing_user.username}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建默认管理员用户
|
||||||
|
username = input("请输入用户名 (默认: admin): ").strip() or "admin"
|
||||||
|
password = input("请输入密码 (默认: admin123): ").strip() or "admin123"
|
||||||
|
|
||||||
|
user = User(username=username)
|
||||||
|
user.set_password(password)
|
||||||
|
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
print(f"用户创建成功!")
|
||||||
|
print(f"用户名: {username}")
|
||||||
|
print(f"请妥善保管密码!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
create_initial_user()
|
||||||
@@ -14,6 +14,9 @@ services:
|
|||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
- SECRET_KEY=${SECRET_KEY:-your-secret-key-here}
|
- SECRET_KEY=${SECRET_KEY:-your-secret-key-here}
|
||||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||||
|
# 默认用户配置
|
||||||
|
- DEFAULT_USERNAME=${DEFAULT_USERNAME:-admin}
|
||||||
|
- DEFAULT_PASSWORD=${DEFAULT_PASSWORD:-admin123}
|
||||||
volumes:
|
volumes:
|
||||||
# 持久化数据库
|
# 持久化数据库
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
|
|||||||
44
docker-entrypoint.sh
Normal file
44
docker-entrypoint.sh
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "正在启动工作任务管理系统..."
|
||||||
|
|
||||||
|
# 切换到backend目录
|
||||||
|
cd /app/backend
|
||||||
|
|
||||||
|
# 等待数据库初始化
|
||||||
|
echo "初始化数据库..."
|
||||||
|
python -c "
|
||||||
|
from app import create_app
|
||||||
|
from models import db, User
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
with app.app_context():
|
||||||
|
# 创建所有表
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# 检查是否已有用户
|
||||||
|
existing_user = User.query.first()
|
||||||
|
|
||||||
|
if not existing_user:
|
||||||
|
# 从环境变量获取默认用户信息
|
||||||
|
default_username = os.getenv('DEFAULT_USERNAME', 'admin')
|
||||||
|
default_password = os.getenv('DEFAULT_PASSWORD', 'admin123')
|
||||||
|
|
||||||
|
# 创建默认用户
|
||||||
|
user = User(username=default_username)
|
||||||
|
user.set_password(default_password)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
print(f'已创建默认用户: {default_username}')
|
||||||
|
print(f'默认密码: {default_password}')
|
||||||
|
print('请登录后立即修改密码!')
|
||||||
|
else:
|
||||||
|
print('用户已存在,跳过初始化')
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "启动Flask应用..."
|
||||||
|
# 启动应用
|
||||||
|
exec python app.py
|
||||||
@@ -812,3 +812,98 @@ body {
|
|||||||
from { transform: translateX(-100%); }
|
from { transform: translateX(-100%); }
|
||||||
to { transform: translateX(0); }
|
to { transform: translateX(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 登录页面样式 */
|
||||||
|
.login-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 40px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 450px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
animation: modalSlideIn 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header i {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-header h1 {
|
||||||
|
color: #2d3748;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form .form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #4a5568;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form label i {
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-block {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #fed7d7;
|
||||||
|
color: #c53030;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #fc8181;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户信息显示 */
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #4a5568;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #f7fafc;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info i {
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,11 +8,44 @@
|
|||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<!-- 登录页面 -->
|
||||||
|
<div id="loginPage" class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-header">
|
||||||
|
<i class="fas fa-tasks"></i>
|
||||||
|
<h1>工作任务管理系统</h1>
|
||||||
|
</div>
|
||||||
|
<form id="loginForm" class="login-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="loginUsername">
|
||||||
|
<i class="fas fa-user"></i> 用户名
|
||||||
|
</label>
|
||||||
|
<input type="text" id="loginUsername" name="username" required autofocus>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="loginPassword">
|
||||||
|
<i class="fas fa-lock"></i> 密码
|
||||||
|
</label>
|
||||||
|
<input type="password" id="loginPassword" name="password" required>
|
||||||
|
</div>
|
||||||
|
<div id="loginError" class="error-message" style="display: none;"></div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">
|
||||||
|
<i class="fas fa-sign-in-alt"></i> 登录
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主应用页面 -->
|
||||||
|
<div id="mainApp" class="container" style="display: none;">
|
||||||
<!-- 头部 -->
|
<!-- 头部 -->
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<h1><i class="fas fa-tasks"></i> 工作任务管理系统</h1>
|
<h1><i class="fas fa-tasks"></i> 工作任务管理系统</h1>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<span class="user-info">
|
||||||
|
<i class="fas fa-user-circle"></i>
|
||||||
|
<span id="currentUsername"></span>
|
||||||
|
</span>
|
||||||
<button id="addTaskBtn" class="btn btn-primary">
|
<button id="addTaskBtn" class="btn btn-primary">
|
||||||
<i class="fas fa-plus"></i> 添加任务
|
<i class="fas fa-plus"></i> 添加任务
|
||||||
</button>
|
</button>
|
||||||
@@ -22,6 +55,9 @@
|
|||||||
<button id="timeHistoryBtn" class="btn btn-secondary">
|
<button id="timeHistoryBtn" class="btn btn-secondary">
|
||||||
<i class="fas fa-history"></i> 时间历史
|
<i class="fas fa-history"></i> 时间历史
|
||||||
</button>
|
</button>
|
||||||
|
<button id="logoutBtn" class="btn btn-outline">
|
||||||
|
<i class="fas fa-sign-out-alt"></i> 退出
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class WorkListAPI {
|
|||||||
async request(endpoint, options = {}) {
|
async request(endpoint, options = {}) {
|
||||||
const url = `${this.baseURL}${endpoint}`;
|
const url = `${this.baseURL}${endpoint}`;
|
||||||
const config = {
|
const config = {
|
||||||
|
credentials: 'same-origin', // 包含cookies以支持session
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
...options.headers
|
...options.headers
|
||||||
@@ -17,7 +18,7 @@ class WorkListAPI {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, config);
|
const response = await fetch(url, config);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorData.error || `HTTP错误: ${response.status}`);
|
throw new Error(errorData.error || `HTTP错误: ${response.status}`);
|
||||||
@@ -30,6 +31,31 @@ class WorkListAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 认证API
|
||||||
|
async login(username, password) {
|
||||||
|
return this.request('/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ username, password })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
return this.request('/auth/logout', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkAuth() {
|
||||||
|
return this.request('/auth/check');
|
||||||
|
}
|
||||||
|
|
||||||
|
async register(username, password) {
|
||||||
|
return this.request('/auth/register', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ username, password })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 任务管理API
|
// 任务管理API
|
||||||
async getTasks() {
|
async getTasks() {
|
||||||
return this.request('/tasks');
|
return this.request('/tasks');
|
||||||
|
|||||||
@@ -5,18 +5,111 @@ class WorkListApp {
|
|||||||
this.currentEditingTask = null;
|
this.currentEditingTask = null;
|
||||||
this.currentFilter = 'all';
|
this.currentFilter = 'all';
|
||||||
this.excludedStatuses = new Set();
|
this.excludedStatuses = new Set();
|
||||||
|
this.currentUser = null;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化应用
|
// 初始化应用
|
||||||
init() {
|
async init() {
|
||||||
this.bindEvents();
|
// 先检查登录状态
|
||||||
this.loadTasks();
|
await this.checkAuthStatus();
|
||||||
this.setupDatePicker();
|
|
||||||
|
// 如果已登录,绑定事件并加载任务
|
||||||
|
if (this.currentUser) {
|
||||||
|
this.bindEvents();
|
||||||
|
this.loadTasks();
|
||||||
|
this.setupDatePicker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查认证状态
|
||||||
|
async checkAuthStatus() {
|
||||||
|
try {
|
||||||
|
const response = await api.checkAuth();
|
||||||
|
if (response.authenticated) {
|
||||||
|
this.currentUser = response.user;
|
||||||
|
this.showMainApp();
|
||||||
|
} else {
|
||||||
|
this.showLoginPage();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('未登录:', error);
|
||||||
|
this.showLoginPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示登录页面
|
||||||
|
showLoginPage() {
|
||||||
|
document.getElementById('loginPage').style.display = 'flex';
|
||||||
|
document.getElementById('mainApp').style.display = 'none';
|
||||||
|
|
||||||
|
// 绑定登录表单事件
|
||||||
|
const loginForm = document.getElementById('loginForm');
|
||||||
|
loginForm.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await this.handleLogin();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示主应用页面
|
||||||
|
showMainApp() {
|
||||||
|
document.getElementById('loginPage').style.display = 'none';
|
||||||
|
document.getElementById('mainApp').style.display = 'block';
|
||||||
|
|
||||||
|
// 显示用户名
|
||||||
|
if (this.currentUser) {
|
||||||
|
document.getElementById('currentUsername').textContent = this.currentUser.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理登录
|
||||||
|
async handleLogin() {
|
||||||
|
const username = document.getElementById('loginUsername').value;
|
||||||
|
const password = document.getElementById('loginPassword').value;
|
||||||
|
const errorDiv = document.getElementById('loginError');
|
||||||
|
|
||||||
|
try {
|
||||||
|
errorDiv.style.display = 'none';
|
||||||
|
const response = await api.login(username, password);
|
||||||
|
|
||||||
|
if (response.user) {
|
||||||
|
this.currentUser = response.user;
|
||||||
|
this.showMainApp();
|
||||||
|
|
||||||
|
// 绑定事件并加载任务
|
||||||
|
this.bindEvents();
|
||||||
|
this.loadTasks();
|
||||||
|
this.setupDatePicker();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorDiv.textContent = error.message || '登录失败,请检查用户名和密码';
|
||||||
|
errorDiv.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理登出
|
||||||
|
async handleLogout() {
|
||||||
|
try {
|
||||||
|
await api.logout();
|
||||||
|
this.currentUser = null;
|
||||||
|
this.tasks = [];
|
||||||
|
this.showLoginPage();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('登出失败:', error);
|
||||||
|
alert('登出失败: ' + error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绑定事件
|
// 绑定事件
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
|
// 登出按钮
|
||||||
|
const logoutBtn = document.getElementById('logoutBtn');
|
||||||
|
if (logoutBtn && !logoutBtn.hasAttribute('data-bound')) {
|
||||||
|
logoutBtn.addEventListener('click', () => {
|
||||||
|
this.handleLogout();
|
||||||
|
});
|
||||||
|
logoutBtn.setAttribute('data-bound', 'true');
|
||||||
|
}
|
||||||
// 添加任务按钮
|
// 添加任务按钮
|
||||||
document.getElementById('addTaskBtn').addEventListener('click', () => {
|
document.getElementById('addTaskBtn').addEventListener('click', () => {
|
||||||
this.showTaskModal();
|
this.showTaskModal();
|
||||||
|
|||||||
Reference in New Issue
Block a user