commit f1773af0ced18623d989f99bb5e129db43b97cb8 Author: sonorry <951452955@qq.com> Date: Fri Oct 11 23:44:20 2024 +0800 ReadMe diff --git a/backend/pick_student/.idea/.gitignore b/backend/pick_student/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/backend/pick_student/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/backend/pick_student/.idea/encodings.xml b/backend/pick_student/.idea/encodings.xml new file mode 100644 index 0000000..0391594 --- /dev/null +++ b/backend/pick_student/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/pick_student/.idea/inspectionProfiles/Project_Default.xml b/backend/pick_student/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..337a9bd --- /dev/null +++ b/backend/pick_student/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/backend/pick_student/.idea/inspectionProfiles/profiles_settings.xml b/backend/pick_student/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/backend/pick_student/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/backend/pick_student/.idea/misc.xml b/backend/pick_student/.idea/misc.xml new file mode 100644 index 0000000..213c1b6 --- /dev/null +++ b/backend/pick_student/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/backend/pick_student/.idea/modules.xml b/backend/pick_student/.idea/modules.xml new file mode 100644 index 0000000..ba025ab --- /dev/null +++ b/backend/pick_student/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/pick_student/.idea/pick_student.iml b/backend/pick_student/.idea/pick_student.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/backend/pick_student/.idea/pick_student.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/pick_student/__pycache__/analysis.cpython-38-pytest-8.3.3.pyc b/backend/pick_student/__pycache__/analysis.cpython-38-pytest-8.3.3.pyc new file mode 100644 index 0000000..5edc656 Binary files /dev/null and b/backend/pick_student/__pycache__/analysis.cpython-38-pytest-8.3.3.pyc differ diff --git a/backend/pick_student/__pycache__/locustfile.cpython-38.pyc b/backend/pick_student/__pycache__/locustfile.cpython-38.pyc new file mode 100644 index 0000000..baec1b8 Binary files /dev/null and b/backend/pick_student/__pycache__/locustfile.cpython-38.pyc differ diff --git a/backend/pick_student/__pycache__/run.cpython-310.pyc b/backend/pick_student/__pycache__/run.cpython-310.pyc new file mode 100644 index 0000000..4428e2b Binary files /dev/null and b/backend/pick_student/__pycache__/run.cpython-310.pyc differ diff --git a/backend/pick_student/__pycache__/run.cpython-38.pyc b/backend/pick_student/__pycache__/run.cpython-38.pyc new file mode 100644 index 0000000..cae1a78 Binary files /dev/null and b/backend/pick_student/__pycache__/run.cpython-38.pyc differ diff --git a/backend/pick_student/__pycache__/test.cpython-38-pytest-8.3.3.pyc b/backend/pick_student/__pycache__/test.cpython-38-pytest-8.3.3.pyc new file mode 100644 index 0000000..04fcc6e Binary files /dev/null and b/backend/pick_student/__pycache__/test.cpython-38-pytest-8.3.3.pyc differ diff --git a/backend/pick_student/__pycache__/test_login.cpython-38-pytest-8.3.3.pyc b/backend/pick_student/__pycache__/test_login.cpython-38-pytest-8.3.3.pyc new file mode 100644 index 0000000..6c67ab4 Binary files /dev/null and b/backend/pick_student/__pycache__/test_login.cpython-38-pytest-8.3.3.pyc differ diff --git a/backend/pick_student/__pycache__/test_main.cpython-38-pytest-8.3.3.pyc b/backend/pick_student/__pycache__/test_main.cpython-38-pytest-8.3.3.pyc new file mode 100644 index 0000000..dbcf607 Binary files /dev/null and b/backend/pick_student/__pycache__/test_main.cpython-38-pytest-8.3.3.pyc differ diff --git a/backend/pick_student/__pycache__/test_main.cpython-38.pyc b/backend/pick_student/__pycache__/test_main.cpython-38.pyc new file mode 100644 index 0000000..1f54c12 Binary files /dev/null and b/backend/pick_student/__pycache__/test_main.cpython-38.pyc differ diff --git a/backend/pick_student/locustfile.py b/backend/pick_student/locustfile.py new file mode 100644 index 0000000..d61be5b --- /dev/null +++ b/backend/pick_student/locustfile.py @@ -0,0 +1,65 @@ +# locustfile.py +import random +import string + +from locust import HttpUser, TaskSet, task, between + +class UserBehavior(TaskSet): + + def on_start(self): + """在测试开始时执行,进行登录并存储 token 和 username""" + # 假设有一个登录端点,可以获取 token 和 username + response = self.client.post("http://127.0.0.1:8000/teacher/jwt/token", data={ + "username": "starhun", + "password": "123456" + }) + if response.status_code == 200: + self.token = response.json()["access_token"] + self.username = response.json().get("username") + else: + self.token = None + self.username = None + + @task(1) + def get_classes(self): + """测试获取班级列表""" + if self.token: + with self.client.get("http://127.0.0.1:8000/teacher/pick_student/getclasses", headers={"Authorization": f"Bearer {self.token}"}) as response: + if response.status_code != 200: + response.failure(f"Failed to get classes: {response.text}") + + @task(2) + def create_class(self): + characters = string.ascii_letters + string.digits + # 使用 random.choice() 从字符集合中随机选择字符 + random_string = ''.join(random.choice(characters) for i in range(10)) + """测试创建班级""" + if self.token: + with self.client.post("http://127.0.0.1:8000/teacher/pick_student/create_class", params={"class_name": random_string,"class_time":"周三 5-6节"}, headers={"Authorization": f"Bearer {self.token}"}, catch_response=True) as response: + if response.status_code != 200: + response.failure(f"Failed to create class: {response.text}") + + @task(3) + def random_pick(self): + """测试随机点名""" + if self.token: + params = {"class_name": "aa"} + with self.client.get("http://127.0.0.1:8000/teacher/pick_student/random_pick/", params=params, headers={"Authorization": f"Bearer {self.token}"}, catch_response=True) as response: + if response.status_code != 200: + response.failure(f"Failed to random pick: {response.text}") + + @task(4) + def upload_students(self): + """测试上传学生信息""" + if self.token: + files = { + "file": ("stduents.xlsx", open("D:\python\code\pick_student\stduents.xlsx", "rb"), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + } + data = {"class_name": "班级1"} + with self.client.post("http://127.0.0.1:8000/teacher/pick_student/upload_students/", files=files, params=data, headers={"Authorization": f"Bearer {self.token}"}, catch_response=True) as response: + if response.status_code != 200: + response.failure(f"Failed to upload students: {response.text}") + +class WebsiteUser(HttpUser): + tasks = [UserBehavior] + wait_time = between(1, 5) diff --git a/backend/pick_student/pick_student/__init__.py b/backend/pick_student/pick_student/__init__.py new file mode 100644 index 0000000..cee49d3 --- /dev/null +++ b/backend/pick_student/pick_student/__init__.py @@ -0,0 +1,2 @@ +from .main import app1 +from .login import app2 \ No newline at end of file diff --git a/backend/pick_student/pick_student/__pycache__/__init__.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..1068e95 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/__init__.cpython-310.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/__init__.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..49d22d1 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/__init__.cpython-38.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/crud.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/crud.cpython-310.pyc new file mode 100644 index 0000000..732bbfc Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/crud.cpython-310.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/crud.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/crud.cpython-38.pyc new file mode 100644 index 0000000..51defc5 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/crud.cpython-38.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/database.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/database.cpython-310.pyc new file mode 100644 index 0000000..31c22ca Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/database.cpython-310.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/database.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/database.cpython-38.pyc new file mode 100644 index 0000000..4bca33b Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/database.cpython-38.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/login.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/login.cpython-310.pyc new file mode 100644 index 0000000..efe9f84 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/login.cpython-310.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/login.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/login.cpython-38.pyc new file mode 100644 index 0000000..271bca0 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/login.cpython-38.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/main.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000..6279a92 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/main.cpython-310.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/main.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/main.cpython-38.pyc new file mode 100644 index 0000000..6d92dfd Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/main.cpython-38.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/models.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..0615bd8 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/models.cpython-310.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/models.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..db6f7b5 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/models.cpython-38.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/progress_function.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/progress_function.cpython-310.pyc new file mode 100644 index 0000000..7d43189 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/progress_function.cpython-310.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/progress_function.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/progress_function.cpython-38.pyc new file mode 100644 index 0000000..bbd0adc Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/progress_function.cpython-38.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/schemas.cpython-310.pyc b/backend/pick_student/pick_student/__pycache__/schemas.cpython-310.pyc new file mode 100644 index 0000000..50d0bce Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/schemas.cpython-310.pyc differ diff --git a/backend/pick_student/pick_student/__pycache__/schemas.cpython-38.pyc b/backend/pick_student/pick_student/__pycache__/schemas.cpython-38.pyc new file mode 100644 index 0000000..f8a6fc4 Binary files /dev/null and b/backend/pick_student/pick_student/__pycache__/schemas.cpython-38.pyc differ diff --git a/backend/pick_student/pick_student/crud.py b/backend/pick_student/pick_student/crud.py new file mode 100644 index 0000000..df4bcf5 --- /dev/null +++ b/backend/pick_student/pick_student/crud.py @@ -0,0 +1,68 @@ +from sqlalchemy.orm import Session +from pick_student import models,schemas +from pick_student.schemas import read_class + + +def get_student_by_id(db: Session, stduent_id: int,class_name: str,user_name: str): + return db.query(models.Student).filter(models.Student.student_id == stduent_id).filter(models.Student.class_name == class_name).filter(models.Student.user_name == user_name).first() + +def get_student_by_name(db: Session, name: str): + return db.query(models.Student).filter(models.Student.name == name).first() +def get_students(db: Session, class_name: str, user_name: str,skip: int = 0, limit: int = 100): + return db.query(models.Student).filter(models.Student.class_name == class_name).filter(models.Student.user_name == user_name).offset(skip).limit(limit).all() + +def create_student(db: Session, student: schemas.create_student): + db_student = models.Student(**student.dict()) + db.add(db_student) + db.commit() + db.refresh(db_student) + return db_student +def update_student(db: Session, student: schemas.update_student,class_name: str, user_name: str): + db_student = get_student_by_id(db, student.student_id, class_name,user_name) + db_student.scores = student.scores + db_student.consecutive_calls=student.consecutive_calls + db_student.is_master=student.is_master + db_student.updated_at=student.updated_at + db_student.master_uses=student.master_uses + db.commit() + db.refresh(db_student) + return db_student + + +def delete_all_students(db: Session,class_name: str,user_name: str): + db.query(models.Student).filter(models.Student.class_name == class_name).filter(models.Student.user_name == user_name).delete() + db.commit() + +def get_class_by_id(db: Session, class_id: int): + return db.query(models.Class).filter(models.Class.id == class_id).first() +def create_class(db: Session,cclass: schemas.create_class): + db_class = models.Class(**cclass.dict()) + db.add(db_class) + db.commit() + db.refresh(db_class) + return db_class + +def get_class_by_name(db: Session, class_name: str,user_name: str): + return db.query(models.Class).filter(models.Class.class_name == class_name).filter(models.Class.user_name == user_name).first() + +def get_user(db: Session, username:str): + user= db.query(models.Teacher).filter(models.Teacher.user_name == username).first() + return user + + +def create_teacher(db: Session, user_name: str, password_hash: str): + db_teacher = models.Teacher(user_name=user_name, password_hash=password_hash) + db.add(db_teacher) + db.commit() + db.refresh(db_teacher) + return db_teacher +def get_classes_by_teacher_username(db: Session, user_name: str): + db_classes = db.query(models.Class).filter(models.Class.user_name == user_name).all() + return db_classes +def delete_class(db: Session, class_name: str, user_name: str): + + db.query(models.Student).filter(models.Student.class_name == class_name).filter(models.Student.user_name == user_name).delete() + db.commit() + deleted_rows=db.query(models.Class).filter(models.Class.class_name == class_name).filter(models.Class.user_name == user_name).delete() + print(f"Deleted rows: {deleted_rows}") + db.commit() \ No newline at end of file diff --git a/backend/pick_student/pick_student/database.py b/backend/pick_student/pick_student/database.py new file mode 100644 index 0000000..93b47ee --- /dev/null +++ b/backend/pick_student/pick_student/database.py @@ -0,0 +1,18 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker,declarative_base +SQLALCHEMY_DATABASE_URL = 'sqlite:///./pick_student.sqlite3' +engine = create_engine( + SQLALCHEMY_DATABASE_URL, echo=True,connect_args={'check_same_thread': False} +) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine,expire_on_commit=True) +Base = declarative_base() +def get_db(): + db = SessionLocal() + try: + yield db + except Exception as e: + db.close() + raise e # 重新抛出异常 + finally: + db.close() diff --git a/backend/pick_student/pick_student/login.py b/backend/pick_student/pick_student/login.py new file mode 100644 index 0000000..0eb9177 --- /dev/null +++ b/backend/pick_student/pick_student/login.py @@ -0,0 +1,94 @@ +import secrets +from datetime import datetime, timedelta + +from fastapi import APIRouter, Depends, HTTPException +from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer +from jose import jwt, JWTError +from passlib.context import CryptContext +from pydantic import BaseModel +from sqlalchemy.orm import Session + +from pick_student.database import SessionLocal, get_db +from pick_student.crud import get_user, create_teacher + +app2=APIRouter() + +SECRET_KEY = secrets.token_urlsafe() +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 +class Token(BaseModel): + access_token: str + token_type: str +pwd_context = CryptContext(schemes=["argon2"], deprecated="auto") +def verify_password(plain_password: str, hashed_password:str): + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password: str): + return pwd_context.hash(password) +def jwt_authenticate_user(db: Session, username: str, password: str): + user = get_user(db, username) + if not user: + return False + if not verify_password(password, user.password_hash): + return False + return user +def create_access_token(data: dict, expires_delta=None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) +@app2.post("/jwt/token", response_model=Token) +def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + user = jwt_authenticate_user(db, form_data.username, form_data.password) + if not user: + raise HTTPException(status_code=401, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}) + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": user.user_name}, expires_delta=access_token_expires + ) + return {"access_token": access_token, "token_type": "bearer"} + + +@app2.post("/jwt/register", response_model=Token) +def register_user(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + # 1. 检查用户名是否已经存在 + existing_user = get_user(db, form_data.username) + if existing_user: + raise HTTPException(status_code=400, detail="Username already registered") + # 2. 对密码进行哈希处理 + hashed_password = get_password_hash(form_data.password) + + # 3. 创建新的用户并存储到数据库 + user = create_teacher(db=db, user_name=form_data.username, password_hash=hashed_password) + + # 4. 注册成功后生成 access_token + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": user.user_name}, expires_delta=access_token_expires + ) + + # 5. 返回 access_token 和 token_type + return {"access_token": access_token, "token_type": "bearer","user_name": user.user_name} + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/teacher/jwt/token") +def get_current_teacher(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)): + credentials_exception = HTTPException( + status_code=401, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + except JWTError: + raise credentials_exception + + user = get_user(db, username=username) + if user is None: + raise credentials_exception + return user \ No newline at end of file diff --git a/backend/pick_student/pick_student/main.py b/backend/pick_student/pick_student/main.py new file mode 100644 index 0000000..7f89498 --- /dev/null +++ b/backend/pick_student/pick_student/main.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +from typing import List, Optional + +from cachetools import TTLCache +from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Query, APIRouter +import pandas as pd +import io +import uvicorn +from sqlalchemy.orm import Session + +from pick_student import crud, models +from pick_student.database import SessionLocal, Base, engine, get_db +from pick_student import schemas +from pick_student.login import get_current_teacher +from pick_student.progress_function import call_student, weighted_random_selection + + +app1 = APIRouter() +flag_cache = TTLCache(maxsize=1, ttl=86400) +flag_cache["is_reverse_day"] = False +Base.metadata.create_all(bind=engine) + + + +@app1.get("/") +def read_root(): + return {"message": "Welcome to the Student Management System!"} + + +@app1.post("/create_class", description="创建一个新班级") +def create_classroom(class_time:str,class_name: str, db: Session = Depends(get_db), + current_teacher: schemas.create_teacher = Depends(get_current_teacher)): + # 检查班级是否已经存在 + existing_class = crud.get_class_by_name(db, class_name,current_teacher.user_name) + if existing_class: + raise HTTPException(status_code=400, detail="班级名称已存在") + + # 创建班级 + cclass = schemas.create_class(class_name=class_name, user_name=current_teacher.user_name, class_time=class_time) + crud.create_class(db, cclass) + return {"message": "班级创建成功"} +@app1.post("/upload_students/",description="上传学生信息,包括姓名和学号,文件格式为Excel。") +async def upload_students(class_name: str,file: UploadFile = File(...),db: Session=Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)): + if file.content_type != 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': + raise HTTPException(status_code=400, detail="Invalid file type. Please upload an Excel file.") + # stdudents=crud.get_students(db, class_name) + # if stdudents: + # raise HTTPException(status_code=400, detail="Student list already exists") + # 读取上传的 Excel 文件 + content = await file.read() + df_new = pd.read_excel(io.BytesIO(content)) + df_new['class_name']= class_name + df_new['user_name']=current_teacher.user_name + # 确保包含所需列 + if not {'name', 'student_id'}.issubset(df_new.columns): + raise HTTPException(status_code=400, detail="Excel file must contain 'name' and 'student_id' columns.") + for _, row in df_new.iterrows(): + student_data = schemas.create_student(**row.to_dict()) + crud.create_student(db,student_data) + # 更新全局 df + + return {"message": "Student list uploaded successfully."} +@app1.get("/students/{id}",response_model=schemas.read_student,description="根据id获取学生信息") +def get_student(id: int,class_name: str = Query(..., description="班级名称", example="1班"), db: Session=Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)): + db_student = crud.get_student_by_id(db, id, class_name,user_name=current_teacher.user_name) + if db_student is None: + raise HTTPException(status_code=404, detail="Student not found") + db_student.updated_at = db_student.updated_at.strftime('%Y-%m-%d %H:%M:%S') + return db_student +@app1.get("/get_students/",response_model=List[schemas.read_student],description="获取学生列表,默认返回前100名学生") +def get_students(class_name: str = Query(..., description="班级名称", example="1班"),skip: int = 0, limit: int = 100, db: Session=Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)): + students = crud.get_students(db, class_name,skip=skip, limit=limit,user_name=current_teacher.user_name) + if not students: + raise HTTPException(status_code=404, detail="No students found or class not exit") + for student in students: + student.updated_at = student.updated_at.strftime('%Y-%m-%d %H:%M:%S') + return students + +@app1.put("/is_reverse_day/",description="是否是反转日(低分学生被点到概率翻倍,回答问题得分1.5倍)") +def is_reverse_day(flag:Optional[bool]=False): + """判断今天是否是反转日""" + # 这里可以根据日期或其他条件决定反转日 + # 例如每周四为反转日,或者根据某个条件触发 + # return datetime.today().weekday() == 3 # 每周四是反转日 + flag_cache["is_reverse_day"] = flag + return {"message": f"Flag has been set {flag_cache['is_reverse_day']}"} + + +@app1.get("/random_pick/",description="从对应的班级随机选取一名学生,分数越高概率越低") +def random_pick(class_name: str = Query(..., description="班级名称", example="1班"),db: Session = Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)): + # 获取学生列表 + students = crud.get_students(db,class_name=class_name,user_name=current_teacher.user_name,skip=0,limit=1000) + if not students: + raise HTTPException(status_code=404, detail="No students found or class not exit") + + # 随机选择学生 + selected_student = weighted_random_selection(students) + + return {"name": selected_student.name, "student_id": selected_student.student_id,"scores": selected_student.scores} + + +@app1.put("/update_score",description="更新学生的积分信息,依据到达课堂、问题回答的正确性和是否重复回答进行调整。在学生的数据库表中添加一个字段 consecutive_calls 记录学生连续被点名的次数。当某个学生连续三次被点名并且都答对了(分数大于2),就会进入“知识大师”状态,降低未来三次被点名的概率,如果被点到增加1分的得分。如果今天是反转日,分数低的学生双倍概率被抽到,并得到1.5倍的回答分数") +def update_score(class_name: str = Query(..., description="班级名称", example="1班"), + student_id: int = Query(..., description="学生的学号", example=102201415), + arrival: bool = Query(..., description="学生是否到达课堂", example=True), + question_repeated: bool = Query(..., description="问题是否重复回答", example=False), + question_correct: float = Query(..., description="问题回答的正确性评分,介于0.5到3之间", ge=0.5, le=3, example=2.5), + db: Session = Depends(get_db), + current_teacher: schemas.create_teacher = Depends(get_current_teacher) + ): + db_student = crud.get_student_by_id(db, student_id, class_name,user_name=current_teacher.user_name) + if db_student is None: + raise HTTPException(status_code=404, detail="Student not found") + # 更新到达课堂积分 + if arrival: + db_student.scores += 1 + # 更新回答问题积分 + if question_repeated: + db_student.scores += 0.5 + else: + db_student.scores -= 1 + if question_correct>3.0 or question_correct<0.5: + raise HTTPException(status_code=412, detail="请输入0.5到3之间的数据") + else: + if db_student.is_master: + db_student.scores += 1 + if flag_cache["is_reverse_day"]: + question_correct*=1.5 + db_student.scores += question_correct + + db_student=call_student(db_student,question_correct>2) + # 根据回答正确性加分 + crud.update_student(db, db_student,class_name,current_teacher.user_name) + return {"message": "Score updated", "scores": db_student.scores,"consecutive_calls":db_student.consecutive_calls, "is_master": db_student.is_master,"master_uses":db_student.master_uses,"is_reverse_day":flag_cache["is_reverse_day"]} + +@app1.get("/getclasses",response_model=List[schemas.read_class],description="根据教师用户名获取全部班级") +def get_classes(db: Session = Depends(get_db),user: schemas.create_teacher= Depends(get_current_teacher)): + classes = crud.get_classes_by_teacher_username(db, user.user_name) + if not classes: + raise HTTPException(status_code=404, detail="No classes found,please create class") + for class_ in classes: + class_.updated_at = class_.updated_at.strftime('%Y-%m-%d %H:%M:%S') + return classes +@app1.delete("/delete_students/", description="删除所有学生的记录") +def delete_all_students(db: Session = Depends(get_db),class_name: str = Query(..., description="班级名称", example="1班"),current_teacher: schemas.create_teacher = Depends(get_current_teacher)): + # 调用 crud 删除所有学生函数 + crud.delete_all_students(db, class_name,current_teacher.user_name) + + return {"message": f"All students in {class_name} have been deleted."} +@app1.delete("/delete_class/", description="删除班级") +def delete_class(class_name: str = Query(..., description="班级名称", example="1班"),db: Session = Depends(get_db),current_teacher: schemas.create_teacher = Depends(get_current_teacher)): + # 调用 crud 删除所有学生函数 + crud.delete_class(db, class_name,current_teacher.user_name) + return {"message": f"Class {class_name} has been deleted."} +# if __name__ == "__main__": +# uvicorn.run(app, host="127.0.0.1", port=8000) diff --git a/backend/pick_student/pick_student/models.py b/backend/pick_student/pick_student/models.py new file mode 100644 index 0000000..ee57b34 --- /dev/null +++ b/backend/pick_student/pick_student/models.py @@ -0,0 +1,43 @@ +from sqlalchemy import func +from sqlalchemy.orm import relationship +from sqlalchemy.sql.schema import Column, ForeignKey +from sqlalchemy.sql.sqltypes import Integer, String, FLOAT, DateTime, Boolean +from pick_student.database import Base, engine + +class Student(Base): # 类名使用大写 + __tablename__ = "student" + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + student_id = Column(Integer, nullable=False, comment='学号') + name = Column(String, nullable=False, comment='姓名') + scores = Column(FLOAT, comment='得分', default=0) + consecutive_calls = Column(Integer, default=0) # 记录连续被点名的次数 + is_master = Column(Boolean, default=False) # 是否进入知识大师状态 + master_uses = Column(Integer, default=0) # 知识大师状态下的剩余次数 + class_name = Column(String, ForeignKey('class.class_name'), nullable=False) + user_name = Column(String, ForeignKey('teacher.user_name'), nullable=False)# 外键引用修正 + class_ = relationship("Class", back_populates="students") + created_at = Column(DateTime, server_default=func.now(), comment='创建时间') + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间') + __mapped_args__ = {"order_by": id} + +class Teacher(Base): + __tablename__ = "teacher" + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + user_name = Column(String, nullable=False, comment='教师用户名') + password_hash = Column(String, nullable=False, comment='密码') + created_at = Column(DateTime, server_default=func.now(), comment='创建时间') + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间') + classes = relationship("Class", back_populates="teacher") + + +# 班级表 +class Class(Base): + __tablename__ = "class" + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + class_name = Column(String, nullable=False, comment='班级名称') + class_time = Column(String, nullable=False, comment='开班时间') + user_name = Column(String, ForeignKey('teacher.user_name'), nullable=False) + teacher = relationship("Teacher", back_populates="classes") + students = relationship("Student", back_populates="class_") # 使用 class_ 避免与类名冲突 + created_at = Column(DateTime, server_default=func.now(), comment='创建时间') + updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间') diff --git a/backend/pick_student/pick_student/progress_function.py b/backend/pick_student/pick_student/progress_function.py new file mode 100644 index 0000000..a72faea --- /dev/null +++ b/backend/pick_student/pick_student/progress_function.py @@ -0,0 +1,40 @@ +import random + + +def call_student(student, correct: bool): + if student.is_master: + student.master_uses -= 1 + if student.master_uses <= 0: + student.is_master = False # 退出知识大师状态 + + if correct: + student.consecutive_calls += 1 + if student.consecutive_calls >= 3: + student.consecutive_calls = 0 + student.is_master = True + student.master_uses = 3 # 未来三次被点名概率降低 + else: + student.consecutive_calls = 0 + + return student +def weighted_random_selection(students,reverse=False): + total_score = sum([student.scores for student in students]) + # 计算每个学生的权重 + probabilities = [] + for student in students: + if student.is_master and student.master_uses > 0: + # 如果是知识大师,概率大幅降低 + weight = (total_score - student.scores + 1) / (total_score + 1) * 0.1 + elif reverse and student.scores <= 5: + # 反转机制:低分学生被点名概率增加 + weight = (total_score - student.scores + 1) / (total_score + 1) * 2 # 概率翻倍 + else: + # 正常情况下的加权选择,分数越高概率越低 + weight = (total_score - student.scores + 1) / (total_score + 1) + probabilities.append(weight) + # 使用随机加权选择 + selected = random.choices(students, weights=probabilities, k=1)[0] + return selected + + + diff --git a/backend/pick_student/pick_student/schemas.py b/backend/pick_student/pick_student/schemas.py new file mode 100644 index 0000000..a824a01 --- /dev/null +++ b/backend/pick_student/pick_student/schemas.py @@ -0,0 +1,45 @@ +from datetime import datetime + +from pydantic import BaseModel +class create_student(BaseModel): + student_id: int + name: str + class_name: str + user_name: str +class update_student(create_student): + student_id : int + name: str + class_name: str + scores: float + consecutive_calls: int + master_uses: int + is_master: bool +class read_student(create_student): + id: int + student_id: int + name: str + scores: float + updated_at: str + consecutive_calls: int + is_master:bool + master_uses: int + class Config: + from_attributes = True +class create_class(BaseModel): + user_name: str + class_name: str + class_time: str + class Config: + from_attributes = True # 使模型与 SQLAlchemy ORM 模型兼容 +class create_teacher(BaseModel): + user_name: str + password: str + + +class read_class(BaseModel): + id: int + class_name: str + updated_at: str + class_time: str + class Config: + from_attributes = True \ No newline at end of file diff --git a/backend/pick_student/run.py b/backend/pick_student/run.py new file mode 100644 index 0000000..d16b5db --- /dev/null +++ b/backend/pick_student/run.py @@ -0,0 +1,22 @@ +from fastapi import FastAPI +import uvicorn +from starlette.middleware.cors import CORSMiddleware + +from pick_student import app1,app2 + +app=FastAPI( + title="学生点名系统", + description="学生点名系统", + version="1.0.0", +) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], # 允许所有方法 + allow_headers=["*"], +) +app.include_router(app1, prefix="/teacher/pick_student",tags=["学生点名系统"]) +app.include_router(app2, prefix="/teacher",tags=["教师登录系统"]) +if __name__=='__main__': + uvicorn.run('run:app',host='127.0.0.1',port=8000,reload=True) diff --git a/backend/pick_student/stduents.xlsx b/backend/pick_student/stduents.xlsx new file mode 100644 index 0000000..9612b78 Binary files /dev/null and b/backend/pick_student/stduents.xlsx differ diff --git a/backend/pick_student/test_login.py b/backend/pick_student/test_login.py new file mode 100644 index 0000000..7a9f8c6 --- /dev/null +++ b/backend/pick_student/test_login.py @@ -0,0 +1,86 @@ +import unittest +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from pick_student.database import Base +from pick_student.login import get_password_hash +from pick_student.main import get_db +from run import app # 从 run.py 导入主 app +from pick_student.crud import create_teacher + +# 设置测试数据库 +SQLALCHEMY_DATABASE_URL = "sqlite:///./test_test.db" # 使用不同的数据库文件名 +engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) +TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# 创建所有表 +Base.metadata.create_all(bind=engine) + +# 重写 get_db 函数用于测试 +def override_get_db(): + db = TestingSessionLocal() + try: + yield db + except Exception as e: + db.close() + raise e # 重新抛出异常 + finally: + db.close() # 确保数据库连接被正确关闭 + +# 覆盖 app 中的依赖项 +app.dependency_overrides[get_db] = override_get_db + +# 创建测试客户端 +client = TestClient(app) + +class TestAuthRoutes(unittest.TestCase): + + @classmethod + def setUpClass(cls): + # 在测试开始时设置初始数据库状态 + db = TestingSessionLocal() + hashed_password = get_password_hash("password") # 假设已经生成一个哈希密码 + create_teacher(db, user_name="test_teacher", password_hash=hashed_password) + db.close() + + def test_login_success(self): + # 测试登录成功的情况 + response = client.post( + "/teacher/jwt/token", + data={"username": "test_teacher", "password": "password"}, + ) + print(response.json()) + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertIn("access_token", data) + self.assertIn("token_type", data) + + def test_login_failure(self): + # 测试登录失败的情况 + response = client.post( + "/teacher/jwt/token", + data={"username": "wrong_teacher", "password": "wrong_password"}, + ) + self.assertEqual(response.status_code, 401) + data = response.json() + self.assertEqual(data["detail"], "Incorrect username or password") + + def test_register_user(self): + # 测试注册新用户 + response = client.post( + "/teacher/jwt/register", + data={"username": "new_teacher", "password": "new_password"}, + ) + print(response.json()) + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertIn("access_token", data) + self.assertIn("token_type", data) + + @classmethod + def tearDownClass(cls): + # 清理测试数据库 + Base.metadata.drop_all(bind=engine) + +if __name__ == "__main__": + unittest.main() diff --git a/backend/pick_student/test_main.py b/backend/pick_student/test_main.py new file mode 100644 index 0000000..d93b342 --- /dev/null +++ b/backend/pick_student/test_main.py @@ -0,0 +1,153 @@ +import io +import unittest + +import pandas as pd +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from pick_student import crud, schemas +from pick_student.database import Base +from pick_student.login import get_password_hash +from pick_student.main import get_db +from run import app # 从 run.py 导入主 app +from pick_student.crud import create_teacher, create_class + +# 设置测试数据库 +SQLALCHEMY_DATABASE_URL = "sqlite:///./test_test.db" # 使用不同的数据库文件名 +engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) +TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# 创建所有表 +Base.metadata.create_all(bind=engine) + +# 重写 get_db 函数用于测试 +def override_get_db(): + db = TestingSessionLocal() + try: + yield db + except Exception as e: + db.close() + raise e # 重新抛出异常 + finally: + db.close() # 确保数据库连接被正确关闭 + +# 覆盖 app 中的依赖项 +app.dependency_overrides[get_db] = override_get_db + +# 创建测试客户端 +client = TestClient(app) +class TestMainRoutes(unittest.TestCase): + + @classmethod + def setUpClass(cls): + # 在测试开始时设置初始数据库状态 + db = TestingSessionLocal() + hashed_password = get_password_hash("password") # 假设已经生成一个哈希密码 + create_teacher(db, user_name="test_teacher", password_hash=hashed_password) + crud.create_class(db, cclass=schemas.create_class(class_name="2班", user_name="test_teacher",class_time="周三 5-6节")) + crud.create_class(db, cclass=schemas.create_class(class_name="3班", user_name="test_teacher",class_time="周三 5-6节")) + crud.create_student(db, student=schemas.create_student(class_name="2班", name="Alice", student_id=101, user_name="test_teacher")) + crud.create_student(db, student=schemas.create_student(class_name="2班", name="Bob", student_id=102,user_name="test_teacher")) + crud.create_student(db, student=schemas.create_student(class_name="2班", name="Charlie", student_id=103, user_name="test_teacher")) + crud.create_student(db, student=schemas.create_student(class_name="3班", name="David", student_id=104, user_name="test_teacher")) + db.close() + + def test_create_classroom(self): + # 测试创建班级的成功情况 + response = client.post( + "/teacher/pick_student/create_class/", + params={"class_name": "1班","class_time":"周三 5-6节"}, + headers={"Authorization": "Bearer " + self.get_access_token()} + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["message"], "班级创建成功") + + def test_upload_students(self): + # 测试上传学生信息的成功情况 + class_name = "1班" + data = { + 'name': ['Alice', 'Bob'], + 'student_id': [101, 102] + , 'user_name': ['test_teacher', 'test_teacher'] + } + df = pd.DataFrame(data) + excel_buffer = io.BytesIO() + df.to_excel(excel_buffer, index=False) + excel_buffer.seek(0) + + response = client.post( + "/teacher/pick_student/upload_students/", + files={"file": ("students.xlsx", excel_buffer, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}, + params={"class_name": class_name}, + headers={"Authorization": "Bearer " + self.get_access_token()} + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["message"], "Student list uploaded successfully.") + def test_get_students(self): + response = client.get( + "/teacher/pick_student/get_students/", + params={"class_name": "2班"}, + headers={"Authorization": "Bearer " + self.get_access_token()} + ) + self.assertEqual(response.status_code, 200) + def test_update_score(self): + response = client.put( + "/teacher/pick_student/update_score/", + params={"class_name": "2班", "student_id": 101, "arrival": True, "question_repeated": False, "question_correct": 2.5}, + headers={"Authorization": "Bearer " + self.get_access_token()} + ) + print(response.json()) + self.assertEqual(response.status_code, 200) + self.assertIn("scores", response.json()) + def test_is_reverse_day(self): + response = client.put( + "/teacher/pick_student/is_reverse_day/", + params={"flag": True}, + headers={"Authorization": "Bearer " + self.get_access_token()} + ) + print(response.json()) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"message": "Flag has been set True"}) + def test_random_pick(self): + # 测试随机点名的功能 + response = client.get( + "/teacher/pick_student/random_pick/", + params={"class_name": "2班"}, + headers={"Authorization": "Bearer " + self.get_access_token()} + ) + print(response.json()) + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertIn("name", data) + self.assertIn("student_id", data) + + def test_get_classes(self): + response = client.get( + "/teacher/pick_student/getclasses/", + headers={"Authorization": "Bearer " + self.get_access_token()} + ) + self.assertEqual(response.status_code, 200) + data = response.json() + def test_delete_class(self): + response = client.delete( + "/teacher/pick_student/delete_class/", + params={"class_name": "3班"}, + headers={"Authorization": "Bearer " + self.get_access_token()} + ) + self.assertEqual(response.status_code, 200) + def get_access_token(self): + # 获取访问令牌 + response = client.post( + "/teacher/jwt/token", + data={"username": "test_teacher", "password": "password"}, + ) + return response.json()["access_token"] + + @classmethod + def tearDownClass(cls): + # 清理测试数据库 + Base.metadata.drop_all(bind=engine) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/backend/pick_student/test_test.db b/backend/pick_student/test_test.db new file mode 100644 index 0000000..b2ad5db Binary files /dev/null and b/backend/pick_student/test_test.db differ diff --git a/frontend/roll-book/.gitignore b/frontend/roll-book/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/roll-book/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/roll-book/.vscode/extensions.json b/frontend/roll-book/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/frontend/roll-book/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/frontend/roll-book/README.md b/frontend/roll-book/README.md new file mode 100644 index 0000000..1511959 --- /dev/null +++ b/frontend/roll-book/README.md @@ -0,0 +1,5 @@ +# Vue 3 + Vite + +This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` + + \ No newline at end of file diff --git a/frontend/roll-book/jsconfig.json b/frontend/roll-book/jsconfig.json new file mode 100644 index 0000000..6f510ca --- /dev/null +++ b/frontend/roll-book/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/frontend/roll-book/package-lock.json b/frontend/roll-book/package-lock.json new file mode 100644 index 0000000..b3cc5f6 --- /dev/null +++ b/frontend/roll-book/package-lock.json @@ -0,0 +1,483 @@ +{ + "name": "roll-book", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "roll-book", + "version": "0.0.0", + "dependencies": { + "vue": "^3.5.10" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.4", + "vite": "^5.4.8" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", + "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", + "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", + "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.11.tgz", + "integrity": "sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.11", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.11.tgz", + "integrity": "sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.11", + "@vue/shared": "3.5.11" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.11.tgz", + "integrity": "sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.11", + "@vue/compiler-dom": "3.5.11", + "@vue/compiler-ssr": "3.5.11", + "@vue/shared": "3.5.11", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.47", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.11.tgz", + "integrity": "sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.11", + "@vue/shared": "3.5.11" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.11.tgz", + "integrity": "sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.11" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.11.tgz", + "integrity": "sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.11", + "@vue/shared": "3.5.11" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.11.tgz", + "integrity": "sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.11", + "@vue/runtime-core": "3.5.11", + "@vue/shared": "3.5.11", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.11.tgz", + "integrity": "sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.11", + "@vue/shared": "3.5.11" + }, + "peerDependencies": { + "vue": "3.5.11" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.11.tgz", + "integrity": "sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/vite": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.11", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.11.tgz", + "integrity": "sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.11", + "@vue/compiler-sfc": "3.5.11", + "@vue/runtime-dom": "3.5.11", + "@vue/server-renderer": "3.5.11", + "@vue/shared": "3.5.11" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + } + } +} diff --git a/frontend/roll-book/package.json b/frontend/roll-book/package.json new file mode 100644 index 0000000..ecaad5f --- /dev/null +++ b/frontend/roll-book/package.json @@ -0,0 +1,20 @@ +{ + "name": "roll-book", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.7.7", + "vue": "^3.5.10", + "vue-router": "^4.4.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.4", + "vite": "^5.4.8" + } +} diff --git a/frontend/roll-book/public/vite.svg b/frontend/roll-book/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/roll-book/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/roll-book/src/App.vue b/frontend/roll-book/src/App.vue new file mode 100644 index 0000000..ce48e85 --- /dev/null +++ b/frontend/roll-book/src/App.vue @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/frontend/roll-book/src/assets/arrow.png b/frontend/roll-book/src/assets/arrow.png new file mode 100644 index 0000000..f1d0cc4 Binary files /dev/null and b/frontend/roll-book/src/assets/arrow.png differ diff --git a/frontend/roll-book/src/assets/knowledgemaster.png b/frontend/roll-book/src/assets/knowledgemaster.png new file mode 100644 index 0000000..6e26120 Binary files /dev/null and b/frontend/roll-book/src/assets/knowledgemaster.png differ diff --git a/frontend/roll-book/src/assets/rabbit.png b/frontend/roll-book/src/assets/rabbit.png new file mode 100644 index 0000000..e5b49fe Binary files /dev/null and b/frontend/roll-book/src/assets/rabbit.png differ diff --git a/frontend/roll-book/src/assets/rabbit2.png b/frontend/roll-book/src/assets/rabbit2.png new file mode 100644 index 0000000..41d9ab4 Binary files /dev/null and b/frontend/roll-book/src/assets/rabbit2.png differ diff --git a/frontend/roll-book/src/assets/vue.svg b/frontend/roll-book/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/frontend/roll-book/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/roll-book/src/components/Register.vue b/frontend/roll-book/src/components/Register.vue new file mode 100644 index 0000000..bf27dd5 --- /dev/null +++ b/frontend/roll-book/src/components/Register.vue @@ -0,0 +1,96 @@ + + + + + \ No newline at end of file diff --git a/frontend/roll-book/src/main.js b/frontend/roll-book/src/main.js new file mode 100644 index 0000000..e3a485c --- /dev/null +++ b/frontend/roll-book/src/main.js @@ -0,0 +1,8 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' +import router from './router' + +const app = createApp(App) +app.use(router) +app.mount('#app') diff --git a/frontend/roll-book/src/router/index.js b/frontend/roll-book/src/router/index.js new file mode 100644 index 0000000..42f1483 --- /dev/null +++ b/frontend/roll-book/src/router/index.js @@ -0,0 +1,45 @@ +import { createRouter, createWebHistory } from 'vue-router' +import LoginPage from '../views/LoginPage.vue' +import RegisterPage from '../views/RegisterPage.vue' +import ClassManagement from '../views/ClassManagement.vue' +import CreateClass from '../views/CreateClass.vue' +import StudentManagement from '../views/StudentManagement.vue' +import RollBook from '../views/RollBook.vue' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { + path: '/', + name: 'ClassManagement', + component: ClassManagement + }, + { + path: '/login', + name: 'Login', + component: LoginPage + }, + { + path: '/register', + name: 'Register', + component: RegisterPage + }, + { + path: '/create_class', + name: 'CreateClass', + component: CreateClass + }, + { + path: '/student_management/:className', + name: 'StudentManagement', + component: StudentManagement + }, + { + path: '/random_pick/:className', + name: 'RollBook', + component: RollBook + }, + ] +}) + +export default router diff --git a/frontend/roll-book/src/style.css b/frontend/roll-book/src/style.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/roll-book/src/views/ClassManagement.vue b/frontend/roll-book/src/views/ClassManagement.vue new file mode 100644 index 0000000..5c87bff --- /dev/null +++ b/frontend/roll-book/src/views/ClassManagement.vue @@ -0,0 +1,309 @@ + + + + + \ No newline at end of file diff --git a/frontend/roll-book/src/views/CreateClass.vue b/frontend/roll-book/src/views/CreateClass.vue new file mode 100644 index 0000000..207f32c --- /dev/null +++ b/frontend/roll-book/src/views/CreateClass.vue @@ -0,0 +1,223 @@ + + + + + +> diff --git a/frontend/roll-book/src/views/LoginPage.vue b/frontend/roll-book/src/views/LoginPage.vue new file mode 100644 index 0000000..3231f1a --- /dev/null +++ b/frontend/roll-book/src/views/LoginPage.vue @@ -0,0 +1,190 @@ + + + + + + \ No newline at end of file diff --git a/frontend/roll-book/src/views/RegisterPage.vue b/frontend/roll-book/src/views/RegisterPage.vue new file mode 100644 index 0000000..4143c65 --- /dev/null +++ b/frontend/roll-book/src/views/RegisterPage.vue @@ -0,0 +1,189 @@ + + + + + + \ No newline at end of file diff --git a/frontend/roll-book/src/views/RollBook.vue b/frontend/roll-book/src/views/RollBook.vue new file mode 100644 index 0000000..75f6dbb --- /dev/null +++ b/frontend/roll-book/src/views/RollBook.vue @@ -0,0 +1,630 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/roll-book/src/views/StudentManagement.vue b/frontend/roll-book/src/views/StudentManagement.vue new file mode 100644 index 0000000..5540d4a --- /dev/null +++ b/frontend/roll-book/src/views/StudentManagement.vue @@ -0,0 +1,326 @@ + + + + + \ No newline at end of file diff --git a/frontend/roll-book/vite.config.js b/frontend/roll-book/vite.config.js new file mode 100644 index 0000000..05c1740 --- /dev/null +++ b/frontend/roll-book/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) diff --git a/frontend/roll-book/yarn.lock b/frontend/roll-book/yarn.lock new file mode 100644 index 0000000..29ac458 --- /dev/null +++ b/frontend/roll-book/yarn.lock @@ -0,0 +1,261 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/helper-string-parser@^7.25.7": + version "7.25.7" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz" + integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== + +"@babel/helper-validator-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== + +"@babel/parser@^7.25.3": + version "7.25.7" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz" + integrity sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw== + dependencies: + "@babel/types" "^7.25.7" + +"@babel/types@^7.25.7": + version "7.25.7" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz" + integrity sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ== + dependencies: + "@babel/helper-string-parser" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + to-fast-properties "^2.0.0" + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@rollup/rollup-win32-x64-msvc@4.24.0": + version "4.24.0" + resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz" + integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw== + +"@types/estree@1.0.6": + version "1.0.6" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@vitejs/plugin-vue@^5.1.4": + version "5.1.4" + resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz" + integrity sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A== + +"@vue/compiler-core@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.11.tgz" + integrity sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg== + dependencies: + "@babel/parser" "^7.25.3" + "@vue/shared" "3.5.11" + entities "^4.5.0" + estree-walker "^2.0.2" + source-map-js "^1.2.0" + +"@vue/compiler-dom@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.11.tgz" + integrity sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew== + dependencies: + "@vue/compiler-core" "3.5.11" + "@vue/shared" "3.5.11" + +"@vue/compiler-sfc@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.11.tgz" + integrity sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw== + dependencies: + "@babel/parser" "^7.25.3" + "@vue/compiler-core" "3.5.11" + "@vue/compiler-dom" "3.5.11" + "@vue/compiler-ssr" "3.5.11" + "@vue/shared" "3.5.11" + estree-walker "^2.0.2" + magic-string "^0.30.11" + postcss "^8.4.47" + source-map-js "^1.2.0" + +"@vue/compiler-ssr@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.11.tgz" + integrity sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA== + dependencies: + "@vue/compiler-dom" "3.5.11" + "@vue/shared" "3.5.11" + +"@vue/reactivity@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.11.tgz" + integrity sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w== + dependencies: + "@vue/shared" "3.5.11" + +"@vue/runtime-core@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.11.tgz" + integrity sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA== + dependencies: + "@vue/reactivity" "3.5.11" + "@vue/shared" "3.5.11" + +"@vue/runtime-dom@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.11.tgz" + integrity sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ== + dependencies: + "@vue/reactivity" "3.5.11" + "@vue/runtime-core" "3.5.11" + "@vue/shared" "3.5.11" + csstype "^3.1.3" + +"@vue/server-renderer@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.11.tgz" + integrity sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA== + dependencies: + "@vue/compiler-ssr" "3.5.11" + "@vue/shared" "3.5.11" + +"@vue/shared@3.5.11": + version "3.5.11" + resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.5.11.tgz" + integrity sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ== + +csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +entities@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +magic-string@^0.30.11: + version "0.30.11" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz" + integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +picocolors@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + +postcss@^8.4.43, postcss@^8.4.47: + version "8.4.47" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz" + integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.0" + source-map-js "^1.2.1" + +rollup@^4.20.0: + version "4.24.0" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz" + integrity sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.24.0" + "@rollup/rollup-android-arm64" "4.24.0" + "@rollup/rollup-darwin-arm64" "4.24.0" + "@rollup/rollup-darwin-x64" "4.24.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.24.0" + "@rollup/rollup-linux-arm-musleabihf" "4.24.0" + "@rollup/rollup-linux-arm64-gnu" "4.24.0" + "@rollup/rollup-linux-arm64-musl" "4.24.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.24.0" + "@rollup/rollup-linux-riscv64-gnu" "4.24.0" + "@rollup/rollup-linux-s390x-gnu" "4.24.0" + "@rollup/rollup-linux-x64-gnu" "4.24.0" + "@rollup/rollup-linux-x64-musl" "4.24.0" + "@rollup/rollup-win32-arm64-msvc" "4.24.0" + "@rollup/rollup-win32-ia32-msvc" "4.24.0" + "@rollup/rollup-win32-x64-msvc" "4.24.0" + fsevents "~2.3.2" + +source-map-js@^1.2.0, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +vite@^5.0.0, vite@^5.4.8: + version "5.4.8" + resolved "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz" + integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vue@^3.2.25, vue@^3.5.10, vue@3.5.11: + version "3.5.11" + resolved "https://registry.npmjs.org/vue/-/vue-3.5.11.tgz" + integrity sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg== + dependencies: + "@vue/compiler-dom" "3.5.11" + "@vue/compiler-sfc" "3.5.11" + "@vue/runtime-dom" "3.5.11" + "@vue/server-renderer" "3.5.11" + "@vue/shared" "3.5.11"