commit
f1773af0ce
@ -0,0 +1,3 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/pick_student/pick_student.sqlite3" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,12 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredIdentifiers">
|
||||
<list>
|
||||
<option value="test.fre.*" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="rp" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
|
||||
</project>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/pick_student.iml" filepath="$PROJECT_DIR$/.idea/pick_student.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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)
|
@ -0,0 +1,2 @@
|
||||
from .main import app1
|
||||
from .login import app2
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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()
|
@ -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()
|
@ -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
|
@ -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='更新时间')
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
@ -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)
|
Binary file not shown.
@ -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()
|
@ -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()
|
Binary file not shown.
@ -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?
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
@ -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 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
|
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>萝卜册登录</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view path=""></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Global styles */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 742 B |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 749 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 496 B |
@ -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')
|
@ -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
|
@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<div class="class-management-page">
|
||||
|
||||
<div>
|
||||
<div class="bgdiagram_1"></div>
|
||||
<div class="bgdiagram_2"></div>
|
||||
<div class="bgdiagram_3"></div>
|
||||
<div class="bgdiagram_4"></div>
|
||||
<div class="bgdiagram_5"></div>
|
||||
<span class="NickName">{{nickname}}</span>
|
||||
<router-link to="/login">
|
||||
<span class="login_in" v-if="nickname ===''">登录</span>
|
||||
</router-link>
|
||||
<router-link to="/login">
|
||||
<span class="quitLogin">退出</span>
|
||||
</router-link>
|
||||
<span class="classCount">班级数 1</span>
|
||||
<router-link to="/create_class">
|
||||
<button class="createClass">+新建班级</button>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<table class="class_manage">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>班级名</th>
|
||||
<th>上课时间</th>
|
||||
<th>动作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="paginatedClasses.length">
|
||||
<tr v-for="cclass in paginatedClasses" :key="cclass.id">
|
||||
<td>{{ cclass.class_name }}</td>
|
||||
<td>{{ cclass.class_time }}</td>
|
||||
<td>
|
||||
<button class="button_style" @click="goToStudentManagement(cclass.class_name)">萝卜册</button>
|
||||
<button class="button_style" @click="manageClass(cclass.class_name)">数萝卜</button>
|
||||
<button class="button_style" @click="deleteClass(cclass.class_name)">吃掉</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
<button @click="prevPage" :disabled="currentPage === 1">上一页</button>
|
||||
<button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
|
||||
<span>{{ currentPage }} / {{ totalPages }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
export default{
|
||||
data() {
|
||||
return {
|
||||
nickname: '',
|
||||
className: '',
|
||||
classes: [],
|
||||
classTime: '',
|
||||
errorMessage: '',
|
||||
currentPage: 1, // 当前页
|
||||
pageSize: 5, // 每页显示的班级数量
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// className() {
|
||||
// return this.$route.params.className;
|
||||
// },
|
||||
totalPages() {
|
||||
return Math.ceil(this.classes.length / this.pageSize); // 计算总页数
|
||||
},
|
||||
paginatedClasses() {
|
||||
const start = (this.currentPage - 1) * this.pageSize; // 计算开始索引
|
||||
return this.classes.slice(start, start + this.pageSize); // 返回当前页的学生数据
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async fetchClasses() {
|
||||
try {
|
||||
const response = await axios.get('http://127.0.0.1:8000/teacher/pick_student/getclasses', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
}
|
||||
});
|
||||
this.classes = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取班级信息失败', error);
|
||||
}
|
||||
},
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.currentPage++;
|
||||
}
|
||||
},
|
||||
prevPage() {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--;
|
||||
}
|
||||
},
|
||||
|
||||
// 删除指定班级
|
||||
async deleteClass(className) {
|
||||
try {
|
||||
const response = await axios.delete(`http://127.0.0.1:8000/teacher/pick_student/delete_class/?class_name=${className}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('access_token')}`,
|
||||
},
|
||||
});
|
||||
alert(response.data.message); // 显示删除结果
|
||||
this.fetchClasses(); // 重新获取班级列表
|
||||
} catch (error) {
|
||||
console.error('删除班级失败', error);
|
||||
alert('删除班级失败,请稍后重试。');
|
||||
}
|
||||
},
|
||||
|
||||
manageClass(className) {
|
||||
// 使用路由导航
|
||||
this.$router.push({ name: 'RollBook', params: { className } });
|
||||
},
|
||||
|
||||
goToStudentManagement(className) {
|
||||
// 跳转到学生管理页面
|
||||
this.$router.push({ name: 'StudentManagement', params: { className } });
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchClasses(); // 页面加载时获取班级信息
|
||||
this.nickname = localStorage.getItem('nickname'); // 从本地存储中检索昵称
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.bgdiagram_1 {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: rgba(94,162,218,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
border-top-left-radius: 15px;
|
||||
border-top-right-radius: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.bgdiagram_2 {
|
||||
width: 197px;
|
||||
height: 197px;
|
||||
background: rgba(217,217,217,0.20000000298023224);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 77px;
|
||||
left: 57px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.bgdiagram_3 {
|
||||
width: 266px;
|
||||
height: 215px;
|
||||
background: rgba(217,217,217,0.20000000298023224);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 132px;
|
||||
left: 666px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.bgdiagram_4 {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
background: linear-gradient(rgba(217,217,217,0.20000000298023224), rgba(115,115,115,0.20000000298023224));
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 36px;
|
||||
left: 1045px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.bgdiagram_5 {
|
||||
width: 80%;
|
||||
height: 60px;
|
||||
background: rgba(217,217,217,0.4000000059604645);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 240px;
|
||||
left: 120px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.class_manage {
|
||||
width: 80%;
|
||||
height: 200px;
|
||||
background: rgba(255,255,255,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 324px;
|
||||
left: 120px;
|
||||
border: 1px solid rgba(0,0,0,0.10000000149011612);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.NickName {
|
||||
width: 198px;
|
||||
color: rgba(255,255,255,1);
|
||||
position: absolute;
|
||||
top: 130px;
|
||||
left: 120px;
|
||||
font-family: Inter;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: left;
|
||||
}
|
||||
.quitLogin {
|
||||
width: 60px;
|
||||
color: rgba(255,255,255,1);
|
||||
position: absolute;
|
||||
top: 180px;
|
||||
left: 220px;
|
||||
font-family: Inter;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: left;
|
||||
}
|
||||
.classCount {
|
||||
width: 104px;
|
||||
color: rgba(255,255,255,1);
|
||||
position: absolute;
|
||||
top: 259px;
|
||||
left: 150px;
|
||||
font-family: Inter;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.createClass {
|
||||
width: 125px;
|
||||
color: rgba(255,255,255,100);
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
border-width: 1px;
|
||||
position: absolute;
|
||||
top: 258px;
|
||||
left: 318px;
|
||||
font-family: Microsoft Himalaya;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button_style {
|
||||
width: 90px;
|
||||
height: 40px;
|
||||
background: rgba(24,144,255,1);
|
||||
opacity: 1;
|
||||
color: white;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login_in {
|
||||
width: 80px;
|
||||
color: rgba(255,255,255,1);
|
||||
position: absolute;
|
||||
top: 180px;
|
||||
left: 120px;
|
||||
font-family: Inter;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
top: 850px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
text-align: center; /* 居中对齐 */
|
||||
margin: 20px 0; /* 添加上下边距 */
|
||||
}
|
||||
|
||||
.pagination button {
|
||||
padding: 10px 20px; /* 添加内边距 */
|
||||
margin: 0 10px; /* 添加左右边距 */
|
||||
border: none;
|
||||
border-radius: 5px; /* 添加圆角 */
|
||||
background: rgba(24, 144, 255, 1);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination button:disabled {
|
||||
background: rgba(200, 200, 200, 0.5); /* 禁用状态颜色 */
|
||||
cursor: not-allowed; /* 光标样式 */
|
||||
}
|
||||
</style>
|
@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<div class="v32_21">
|
||||
<div class="v32_22"></div>
|
||||
<div class="v32_23"></div>
|
||||
<div class="v32_37"></div>
|
||||
<h2 class="v32_24">新建班级</h2>
|
||||
<h4 class="v32_30">班级名称</h4>
|
||||
<h4 class="v32_31">上课时间</h4>
|
||||
<div @submit.prevent="createClass">
|
||||
<input type="text" v-model="className" class="v32_25">
|
||||
<input type="text" v-model="classTime" class="v32_26">
|
||||
<button class="v32_28" @click="createClass">创建班级</button>
|
||||
</div>
|
||||
<p class="error-message" v-if="errorMessage">{{ errorMessage }}</p>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
export default{
|
||||
data() {
|
||||
return {
|
||||
className: '',
|
||||
classTime: '',
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createClass() {
|
||||
const username = localStorage.getItem('username');
|
||||
this.errorMessage = ''; // 清空错误信息
|
||||
try {
|
||||
await axios.post('http://127.0.0.1:8000/teacher/pick_student/create_class', null, {
|
||||
params: {
|
||||
user_name: username,
|
||||
class_name: this.className,
|
||||
class_time: this.classTime
|
||||
},
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
}
|
||||
});
|
||||
this.className = ''; // 清空输入框
|
||||
this.classTime = ''; // 清空输入框
|
||||
setTimeout(() => {
|
||||
window.location.href = '/'; // 假设班级管理页面为 class_management
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
// 处理错误信息
|
||||
if (error.response && error.response.data) {
|
||||
this.errorMessage = error.response.data.detail || '创建班级失败,请重试';
|
||||
} else {
|
||||
this.errorMessage = '网络错误,请重试';
|
||||
}
|
||||
console.error('创建班级失败', error);
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-size: 14px;
|
||||
}
|
||||
.v32_21 {
|
||||
width: 100%;
|
||||
height: 1024px;
|
||||
background: rgba(250,250,250,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.v32_22 {
|
||||
width: 80%;
|
||||
height: 48px;
|
||||
background: rgba(241,248,255,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 300px;
|
||||
left: 120px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.v32_23 {
|
||||
width: 80%;
|
||||
height: 500px;
|
||||
background: rgba(255,255,255,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 348px;
|
||||
left: 120px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.v32_28 {
|
||||
width: 100px;
|
||||
height: 48px;
|
||||
background: rgba(24,144,255,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 680px;
|
||||
left: 160px;
|
||||
border: 0px;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
font-family: Microsoft Himalaya;
|
||||
font-weight: Regular;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.v32_24 {
|
||||
width: 400px;
|
||||
color: rgba(0,0,0,1);
|
||||
position: absolute;
|
||||
top: 270px;
|
||||
left: 500px;
|
||||
font-family: Microsoft Himalaya;
|
||||
font-weight: Regular;
|
||||
font-size: 36px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.v32_25 {
|
||||
width: 400px;
|
||||
height: 60px;
|
||||
background: rgba(255,255,255,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 418px;
|
||||
left: 160px;
|
||||
border: 1px solid rgba(0,0,0,0.20000000298023224);
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.v32_26 {
|
||||
width: 700px;
|
||||
height: 60px;
|
||||
background: rgba(255,255,255,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 548px;
|
||||
left: 160px;
|
||||
border: 1px solid rgba(0,0,0,0.20000000298023224);
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.v32_30 {
|
||||
width: 200px;
|
||||
color: rgba(0,0,0,1);
|
||||
position: absolute;
|
||||
top: 360px;
|
||||
left: 160px;
|
||||
font-family: Microsoft Himalaya;
|
||||
font-weight: Regular;
|
||||
font-size: 20px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.v32_31 {
|
||||
width: 200px;
|
||||
color: rgba(0,0,0,1);
|
||||
position: absolute;
|
||||
top: 490px;
|
||||
left: 160px;
|
||||
font-family: Microsoft Himalaya;
|
||||
font-weight: Regular;
|
||||
font-size: 20px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.v32_34 {
|
||||
width: 100px;
|
||||
color: rgba(255,255,255,1);
|
||||
position: absolute;
|
||||
top: 686px;
|
||||
left: 160px;
|
||||
|
||||
font-family: Microsoft Himalaya;
|
||||
font-weight: Regular;
|
||||
font-size: 20px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.v32_37 {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: rgba(94,162,218,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
border-top-left-radius: 15px;
|
||||
border-top-right-radius: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 750px;
|
||||
left: 160px;
|
||||
}
|
||||
</style>>
|
@ -0,0 +1,326 @@
|
||||
<template>
|
||||
<div class="student-management-page">
|
||||
|
||||
<div>
|
||||
<div class="bgdiagram_1"></div>
|
||||
<div class="bgdiagram_5"></div>
|
||||
|
||||
<span class="NickName_style">{{nickname}}</span>
|
||||
<router-link to="/">
|
||||
<span class="return_style">返回</span>
|
||||
</router-link>
|
||||
<span class="show_name">班级名:{{className}}</span>
|
||||
<label class="upload_student" for="file-upload">上传学生</label>
|
||||
<input type="file" id="file-upload" @change="uploadFile" accept=".xlsx" />
|
||||
<!-- 删除所有学生按钮 -->
|
||||
<button class="drop_student" @click="deleteAllStudents">删除学生</button>
|
||||
</div>
|
||||
|
||||
<div v-if="uploadSuccessMessage" class="success-message">{{ uploadSuccessMessage }}</div>
|
||||
<div v-if="uploadErrorMessage" class="error-message">{{ uploadErrorMessage }}</div>
|
||||
|
||||
<table class="student_manage">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>学号</th>
|
||||
<th>姓名</th>
|
||||
<th>得分</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="paginatedStudents.length">
|
||||
<tr v-for="student in paginatedStudents" :key="student.id">
|
||||
<td>{{ student.student_id }}</td>
|
||||
<td>{{ student.name }}</td>
|
||||
<td>{{ student.scores }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
<button @click="prevPage" :disabled="currentPage === 1">上一页</button>
|
||||
<button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
|
||||
<span>{{ currentPage }} / {{ totalPages }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
export default{
|
||||
data() {
|
||||
return {
|
||||
students: [],
|
||||
message: '',
|
||||
uploadSuccessMessage: '', // 上传成功信息
|
||||
uploadErrorMessage: '', // 上传失败信息
|
||||
currentPage: 1, // 当前页
|
||||
pageSize: 20, // 每页显示的学生数量
|
||||
nickname: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchStudents(); // 页面加载时自动获取学生列表
|
||||
this.nickname = localStorage.getItem('nickname'); // 从 localStorage 中获取昵称
|
||||
},
|
||||
computed: {
|
||||
className() {
|
||||
return this.$route.params.className;
|
||||
},
|
||||
totalPages() {
|
||||
return Math.ceil(this.students.length / this.pageSize); // 计算总页数
|
||||
},
|
||||
paginatedStudents() {
|
||||
const start = (this.currentPage - 1) * this.pageSize; // 计算开始索引
|
||||
return this.students.slice(start, start + this.pageSize); // 返回当前页的学生数据
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async fetchStudents() {
|
||||
try {
|
||||
const response = await axios.get(`http://127.0.0.1:8000/teacher/pick_student/get_students/?class_name=${this.className}`,{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
}
|
||||
});
|
||||
this.students = response.data;
|
||||
} catch (error) {
|
||||
this.message = '获取学生列表失败';
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
async deleteAllStudents() {
|
||||
try {
|
||||
const response = await axios.delete(`http://127.0.0.1:8000/teacher/pick_student/delete_students/?class_name=${this.className}`,{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
}
|
||||
});
|
||||
this.message = response.data.message;
|
||||
this.students = []; // 清空学生列表
|
||||
} catch (error) {
|
||||
this.message = '删除所有学生失败';
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.currentPage++;
|
||||
}
|
||||
},
|
||||
prevPage() {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--;
|
||||
}
|
||||
},
|
||||
async uploadFile(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const response = await axios.post(`http://127.0.0.1:8000/teacher/pick_student/upload_students/?class_name=${this.className}`, formData, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
}
|
||||
});
|
||||
this.uploadSuccessMessage = response.data.message; // 显示成功提示
|
||||
this.uploadErrorMessage = ''; // 清空错误信息
|
||||
this.fetchStudents(); // 上传成功后重新获取学生列表
|
||||
|
||||
setTimeout(() => {
|
||||
this.uploadSuccessMessage = '';
|
||||
}, 3000);
|
||||
|
||||
event.target.value = ''; // 清空文件输入框的值
|
||||
} catch (error) {
|
||||
console.error('上传学生信息失败', error);
|
||||
this.uploadErrorMessage = error.response?.data?.detail || '上传失败,请重试。'; // 显示错误提示
|
||||
this.uploadSuccessMessage = ''; // 清空成功信息
|
||||
|
||||
setTimeout(() => {
|
||||
this.uploadErrorMessage = '';
|
||||
}, 3000);
|
||||
}
|
||||
} else {
|
||||
this.uploadErrorMessage = '请上传文件。';
|
||||
|
||||
setTimeout(() => {
|
||||
this.uploadErrorMessage = '';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.bgdiagram_1 {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: rgba(94,162,218,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
border-top-left-radius: 15px;
|
||||
border-top-right-radius: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.bgdiagram_5 {
|
||||
width: 80%;
|
||||
height: 60px;
|
||||
background: rgba(217,217,217,0.4000000059604645);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 240px;
|
||||
left: 120px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.student_manage {
|
||||
width: 80%;
|
||||
height: 200px;
|
||||
background: rgba(255,255,255,1);
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 324px;
|
||||
left: 120px;
|
||||
border: 1px solid rgba(0,0,0,0.10000000149011612);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.NickName_style {
|
||||
width: 198px;
|
||||
color: rgba(255,255,255,1);
|
||||
position: absolute;
|
||||
top: 130px;
|
||||
left: 120px;
|
||||
font-family: Inter;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.show_name {
|
||||
width: 160px;
|
||||
color: rgba(255,255,255,1);
|
||||
position: absolute;
|
||||
top: 259px;
|
||||
left: 150px;
|
||||
font-family: Inter;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.upload_student {
|
||||
width: 125px;
|
||||
color: rgba(255,255,255,100);
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
border-width: 1px;
|
||||
position: absolute;
|
||||
top: 258px;
|
||||
left: 400px;
|
||||
font-family: Microsoft Himalaya;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.drop_student {
|
||||
width: 125px;
|
||||
color: rgba(255,255,255,100);
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
border-width: 0px;
|
||||
position: absolute;
|
||||
top: 258px;
|
||||
left: 600px;
|
||||
font-family: Microsoft Himalaya;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button_style {
|
||||
width: 90px;
|
||||
height: 40px;
|
||||
background: rgba(24,144,255,1);
|
||||
opacity: 1;
|
||||
color: white;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.return_style {
|
||||
width: 80px;
|
||||
color: rgba(255,255,255,1);
|
||||
position: absolute;
|
||||
top: 180px;
|
||||
left: 120px;
|
||||
font-family: Inter;
|
||||
font-weight: Regular;
|
||||
font-size: 24px;
|
||||
opacity: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
top: 850px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
text-align: center; /* 居中对齐 */
|
||||
margin: 20px 0; /* 添加上下边距 */
|
||||
}
|
||||
|
||||
.pagination button {
|
||||
padding: 10px 20px; /* 添加内边距 */
|
||||
margin: 0 10px; /* 添加左右边距 */
|
||||
border: none;
|
||||
border-radius: 5px; /* 添加圆角 */
|
||||
background: rgba(24, 144, 255, 1);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination button:disabled {
|
||||
background: rgba(200, 200, 200, 0.5); /* 禁用状态颜色 */
|
||||
cursor: not-allowed; /* 光标样式 */
|
||||
}
|
||||
|
||||
#file-upload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
top: 260px;
|
||||
left: 800px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
top: 260px;
|
||||
left: 800px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
})
|
@ -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"
|
Loading…
Reference in new issue