Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34c8248722 | ||
|
|
c166be8e5f |
@@ -0,0 +1 @@
|
||||
溏心传媒666
|
||||
@@ -1 +0,0 @@
|
||||
db/
|
||||
@@ -1,18 +0,0 @@
|
||||
# 构建上下文排除项,减小镜像体积
|
||||
**/__pycache__/
|
||||
**/*.pyc
|
||||
**/*.pyo
|
||||
|
||||
# IDE / 版本控制
|
||||
.codebuddy/
|
||||
**/.git/
|
||||
**/.idea/
|
||||
**/.vscode/
|
||||
|
||||
# Docker 自身
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.dockerignore
|
||||
|
||||
# 文档
|
||||
*.md
|
||||
@@ -1,28 +0,0 @@
|
||||
# ============================================
|
||||
# 沃林学生管理系统 (Docker)
|
||||
# FastAPI + SQLAlchemy + PyMySQL -> MySQL 8
|
||||
# ============================================
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 运行环境:不生成 .pyc、日志实时输出、pip 不缓存
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 1) 先装依赖(利用层缓存,改代码不必重装)
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt \
|
||||
-i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
# 2) 拷贝应用代码
|
||||
COPY . .
|
||||
|
||||
# 3) static 为空目录时 COPY 不会生成,手动补上(main.py 挂载时需要)
|
||||
RUN mkdir -p static
|
||||
|
||||
EXPOSE 8001
|
||||
|
||||
# 4) 启动流程:建表 -> 灌种子数据(seed_data 有幂等检查,可重复执行) -> 启动服务
|
||||
CMD ["sh", "-c", "python -c 'from database import init_db; init_db()' && python seed_data.py && exec python main.py"]
|
||||
@@ -1,51 +0,0 @@
|
||||
from fastapi import FastAPI,APIRouter,Depends
|
||||
from schemas.analysis import (AnalysisStudentInfoAge,AnalysisStudent_score,AnalysisStudent_score60
|
||||
,AnalysisStudent_score_class,AnalysisStudent_employments_salary)
|
||||
from dao.analysis import (student_info_age,student_info_class,student_score,student_score_class,student_employments_salary
|
||||
,student_employments_time,student_employments_class_time,student_score60)
|
||||
from database import get_db
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
router = APIRouter(prefix="/analysis", tags=["统计分析"])
|
||||
|
||||
#动态年龄范围查询:支持用户输入年龄阈值及比较条件(如大于、小于、等于、区间等),动态查询符合条件的学员信息
|
||||
@router.get("/studnet_info/age",summary="动态年龄范围查询")
|
||||
async def student_info_age1(student:AnalysisStudentInfoAge = Depends(),db: Session = Depends(get_db)):
|
||||
return student_info_age(db, student.min_age, student.max_age, student.operator)
|
||||
|
||||
#多维度班级统计:统计每个班级的总人数,以及按性别(男、女)细分的人数分布
|
||||
@router.get("/studnet_info/class",summary="多维度班级统计")
|
||||
async def student_info_class1(db: Session = Depends(get_db)):
|
||||
return student_info_class(db)
|
||||
|
||||
#查询每次考试成绩都在输入分数线(如80分)以上的学生的编号、姓名和成绩。
|
||||
@router.get("/studnet_score",summary="查询每次考试成绩都在输入分数线以上的学生")
|
||||
async def student_score1(s_n:AnalysisStudent_score = Depends(),db: Session = Depends(get_db)):
|
||||
return student_score(db,s_n.s_n)
|
||||
|
||||
#查询有输入指定次数(如两次(包含两次))以上不及格的学生的姓名、班级和不及格成绩明细。
|
||||
@router.get("/studnet_score60",summary="查询有输入指定次数(如两次(包含两次))以上不及格的学生")
|
||||
async def student_score60_1(n:AnalysisStudent_score60 = Depends(),db: Session = Depends(get_db)):
|
||||
return student_score60(db,n.n)
|
||||
|
||||
#统计每次考试每个班级的平均分,并支持按分数从高到低或从低到高动态排序。
|
||||
@router.get("/studnet_score_class",summary="统计每次考试每个班级的平均分")
|
||||
async def student_score_class1(order:AnalysisStudent_score_class = Depends(),db: Session = Depends(get_db)):
|
||||
return student_score_class(db,order.order)
|
||||
|
||||
#查询查询就业薪资排名前n的学生
|
||||
@router.get("/studnet_employments_salary",summary="查询查询就业薪资排名前n的学生")
|
||||
async def student_employments_salary1(n:AnalysisStudent_employments_salary = Depends(),db: Session = Depends(get_db)):
|
||||
return student_employments_salary(db,n.n)
|
||||
|
||||
#统计每个学生的就业时长
|
||||
@router.get("/studnet_employments_time",summary="每个学生的就业时长(天)")
|
||||
async def student_employments_time1(db: Session = Depends(get_db)):
|
||||
return student_employments_time(db)
|
||||
|
||||
#统计每个班级的平均就业时长(仅统计进入就业阶段,即有就业开放时间的学生)
|
||||
@router.get("/studnet_employments_class_time",summary="每个班级的平均就业时长(天)")
|
||||
async def student_employments_class_time1(db: Session = Depends(get_db)):
|
||||
return student_employments_class_time(db)
|
||||
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
|
||||
|
||||
from database import get_db # 按你项目实际路径改
|
||||
from dao.class_dao import ClassDAO
|
||||
from schemas.class_scheme import (
|
||||
ClassCreate,
|
||||
ClassUpdateBase,
|
||||
ClassHeadTeacherUpdate,
|
||||
ClassTeachersUpdate,
|
||||
ClassDelete,
|
||||
ClassDetail,
|
||||
ClassPageResult,
|
||||
BatchDeleteResult,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/classes", tags=["班级管理"])
|
||||
|
||||
|
||||
@router.post("", response_model=ClassDetail, summary="新增班级")
|
||||
def create_class(req: ClassCreate, db: Session = Depends(get_db)):
|
||||
try:
|
||||
return ClassDAO.create(db, req)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
|
||||
@router.get("", response_model=ClassPageResult, summary="分页查询班级列表")
|
||||
def list_classes(
|
||||
page_num: int = Query(1, ge=1),
|
||||
page_size: int = Query(10, ge=1, le=100),
|
||||
class_name: Optional[str] = None,
|
||||
head_teacher_id: Optional[int] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
total, rows = ClassDAO.list_page(
|
||||
db, page_num, page_size, class_name, head_teacher_id
|
||||
)
|
||||
return ClassPageResult(total=total, items=rows)
|
||||
|
||||
|
||||
|
||||
@router.put(
|
||||
"/batch-delete",
|
||||
response_model=BatchDeleteResult,
|
||||
summary="批量逻辑删除/恢复班级",
|
||||
)
|
||||
def batch_delete(req: ClassDelete, db: Session = Depends(get_db)):
|
||||
count = ClassDAO.batch_toggle_delete(db, req)
|
||||
return BatchDeleteResult(affected_count=count)
|
||||
|
||||
|
||||
|
||||
@router.get("/{class_id}", response_model=ClassDetail, summary="查询班级详情")
|
||||
def get_class(class_id: int, db: Session = Depends(get_db)):
|
||||
data = ClassDAO.get_by_id(db, class_id)
|
||||
if not data:
|
||||
raise HTTPException(status_code=404, detail="班级不存在或已删除")
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{class_id}/base",
|
||||
response_model=ClassDetail,
|
||||
summary="更新班级基础信息",
|
||||
)
|
||||
def update_class_base(
|
||||
class_id: int, req: ClassUpdateBase, db: Session = Depends(get_db)
|
||||
):
|
||||
try:
|
||||
data = ClassDAO.update_base(db, class_id, req)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
if not data:
|
||||
raise HTTPException(status_code=404, detail="班级不存在或已删除")
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{class_id}/head-teacher",
|
||||
response_model=ClassDetail,
|
||||
summary="调整班主任",
|
||||
)
|
||||
def update_head_teacher(
|
||||
class_id: int, req: ClassHeadTeacherUpdate, db: Session = Depends(get_db)
|
||||
):
|
||||
try:
|
||||
data = ClassDAO.update_head_teacher(db, class_id, req)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
if not data:
|
||||
raise HTTPException(status_code=404, detail="班级不存在或已删除")
|
||||
return data
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{class_id}/teachers",
|
||||
response_model=ClassDetail,
|
||||
summary="更新授课老师",
|
||||
)
|
||||
def update_teachers(
|
||||
class_id: int, req: ClassTeachersUpdate, db: Session = Depends(get_db)
|
||||
):
|
||||
try:
|
||||
data = ClassDAO.update_teachers(db, class_id, req)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
if not data:
|
||||
raise HTTPException(status_code=404, detail="班级不存在或已删除")
|
||||
return data
|
||||
@@ -1,68 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import HTTPException, APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
from dao.counselor_dao import add_counselor, query_counselor,\
|
||||
update_counselor, delete_counselor_id
|
||||
from database import get_db
|
||||
from schemas.counselor_scheme import CounselorCreate, DeleteCounselor, UpdateCounselor
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/counselor",
|
||||
tags=["顾问管理"]
|
||||
)
|
||||
|
||||
@router.post("/counselors/")
|
||||
def counselor_create(cou: CounselorCreate,
|
||||
db:Session=Depends(get_db)):
|
||||
date_obj=(datetime.strptime(cou.c_time,"%Y-%m-%d"))
|
||||
data= {
|
||||
"id":cou.id,
|
||||
"name":cou.name,
|
||||
"c_time":date_obj.date()
|
||||
}
|
||||
add_counselor(db,data["id"], data["name"],data["c_time"])
|
||||
return {"message":"新增成功"}
|
||||
|
||||
@router.get("/{id}")
|
||||
def get_counselor(id:int, db:Session=Depends(get_db)):
|
||||
cou = query_counselor(db, id)
|
||||
if not cou:
|
||||
raise HTTPException(404,detail="顾问不存在")
|
||||
return {"data":cou}
|
||||
|
||||
# @router.get("/counselors/{name}")
|
||||
# async def counselor_get_name(name,db:Session=Depends(get_db)):
|
||||
# res=query_counselor_name(db,name)
|
||||
# return {"message":"根据名字查询成功","data":res}
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def counselor_del_id(dele:DeleteCounselor,
|
||||
db:Session=Depends(get_db)):
|
||||
delete_counselor_id(db,dele.id)
|
||||
return {"message":"根据id删除成功"}
|
||||
|
||||
# @router.delete("/counselors/{name}")
|
||||
# async def counselor_del_name(name:str,
|
||||
# db:Session=Depends(get_db)):
|
||||
# delete_counselor_name(db,name)
|
||||
# return {"message":"根据姓名删除成功"}
|
||||
|
||||
@router.put("/{id}")
|
||||
async def update_tea(id:int,update:UpdateCounselor,db:Session=Depends(get_db)):
|
||||
date_obj = datetime.strptime(update.c_time, "%Y-%m-%d")
|
||||
res=update_counselor(db,id=id,
|
||||
new_name=update.name,
|
||||
new_c_time=date_obj
|
||||
)
|
||||
if res.get("msg")=="没找到顾问信息":
|
||||
raise HTTPException(404,detail="顾问不存在")
|
||||
return {"msg":"修改成功","data":res}
|
||||
|
||||
|
||||
# @router.put("/counselors/{name}")
|
||||
# async def counselor_update_name(id:int,name:str,c_time:datetime,db:Session=Depends(get_db)):
|
||||
# res=query_counselor_name(db,id,name,c_time)
|
||||
# return res
|
||||
@@ -1,113 +0,0 @@
|
||||
from fastapi import APIRouter,Depends, HTTPException,Query
|
||||
from sqlalchemy.orm import Session
|
||||
from database import get_db
|
||||
from dao.employments import get_student_id,get_class_id,add_update_employment,get_student_multi_condition
|
||||
from dao.student import get_student_by_id
|
||||
from schemas.employments import Emp_Class,Emp_add_update,Emp_multi_Conditions
|
||||
from typing import List,Optional
|
||||
from dao.employments import delete_employment
|
||||
router = APIRouter(
|
||||
prefix="/employment", tags=["学生就业管理"] # 该路由下所有路径的前缀
|
||||
)
|
||||
|
||||
# 1. GET /employment/students/{student_id} 获取单个学生就业信息
|
||||
@router.get("/students/{student_id}", response_model=Emp_Class, summary="获取单个学生就业信息")
|
||||
def get_student_employment(student_id: str, db: Session = Depends(get_db)):
|
||||
stu = get_student_by_id(db, student_id)
|
||||
if not stu:
|
||||
raise HTTPException(status_code=404, detail="学生不存在")
|
||||
isbool = get_student_id(db, student_id)
|
||||
if not isbool:
|
||||
raise HTTPException(status_code=404, detail="学生不存在")
|
||||
return Emp_Class(
|
||||
id=stu.id,
|
||||
name=stu.name,
|
||||
class_name=stu.class_name,
|
||||
offer_open=isbool.offer_open if isbool else None,
|
||||
offer_review=isbool.offer_review if isbool else None,
|
||||
emp=isbool.emp if isbool else None,
|
||||
sal=isbool.sal if isbool else None,
|
||||
is_deleted=isbool.is_deleted
|
||||
)
|
||||
|
||||
|
||||
# 2. GET /employment/class/{class_id} 获取班级全部学生就业信息
|
||||
@router.get("/class/{class_id}", response_model=List[Emp_Class], summary="获取班级下所有学生就业信息")
|
||||
def get_class_employment(class_id: str, db: Session = Depends(get_db)):
|
||||
rows =get_class_id(db, class_id)
|
||||
result_list = []
|
||||
for stu, isbool in rows:
|
||||
if not stu:
|
||||
raise HTTPException(status_code=404, detail="学生不存在")
|
||||
if not isbool:
|
||||
raise HTTPException(status_code=404, detail="学生不存在")
|
||||
result_list.append(Emp_Class(
|
||||
id=stu.id,
|
||||
name=stu.name,
|
||||
class_name=stu.class_name,
|
||||
offer_open=isbool.offer_open if isbool else None,
|
||||
offer_review=isbool.offer_review if isbool else None,
|
||||
emp=isbool.emp if isbool else None,
|
||||
sal=isbool.sal if isbool else None,
|
||||
is_deleted=isbool.is_deleted
|
||||
))
|
||||
return result_list
|
||||
|
||||
# 3. POST /employment/students/{student_id} 添加/更新就业信息
|
||||
@router.post("/students/{student_id}", response_model=Emp_Class, summary="新增或更新学生就业信息")
|
||||
def upsert_student_employment(
|
||||
student_id: str,
|
||||
emp_data: Emp_add_update,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
stu = get_student_by_id(db, student_id)
|
||||
if not stu:
|
||||
raise HTTPException(status_code=404, detail="学生不存在,请先创建学生基础信息")
|
||||
emp = add_update_employment(db, student_id, emp_data, stu)
|
||||
|
||||
return Emp_Class(
|
||||
id=stu.id,
|
||||
name=stu.name,
|
||||
class_name=stu.class_name,
|
||||
offer_open=emp.offer_open,
|
||||
offer_review=emp.offer_review,
|
||||
emp=emp.emp,
|
||||
sal=emp.sal,
|
||||
is_deleted=emp.is_deleted
|
||||
)
|
||||
#多条件查询
|
||||
@router.get("/query", response_model=List[Emp_Class], summary="多条件查询就业信息")
|
||||
def multi_query_employment(
|
||||
mid: Optional[str] = Query(default=None, title="学生id", description="主键"),#title是前端可以看到的输入框内容
|
||||
name: Optional[str] = Query(default=None, title="学生姓名"),
|
||||
min_sal: Optional[float] = Query(default=None, ge=0, title="最低薪资"),
|
||||
max_sal: Optional[float] = Query(default=None, ge=0, title="最高薪资"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
rows = get_student_multi_condition(db, mid, name, min_sal, max_sal)
|
||||
result_list = []
|
||||
|
||||
for stu, isbool in rows:
|
||||
result_list.append(Emp_Class(
|
||||
id=stu.id,
|
||||
name=stu.name,
|
||||
class_name=stu.class_name,
|
||||
offer_open=isbool.offer_open if isbool else None,
|
||||
offer_review=isbool.offer_review if isbool else None,
|
||||
emp=isbool.emp if isbool else None,
|
||||
sal=isbool.sal if isbool else None,
|
||||
is_deleted=isbool.is_deleted if isbool else False # ← 修复了 NoneType 报错
|
||||
))
|
||||
|
||||
return result_list
|
||||
|
||||
|
||||
@router.post('/deleted',summary="逻辑删除学生就业信息")
|
||||
def delete_emp( student_id:str,db: Session = Depends(get_db)):
|
||||
delete_employment(db,student_id)
|
||||
if delete_employment(db,student_id):
|
||||
return f"学生id:{student_id}删除成功"
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="学生不存在,请先创建学生基础信息")
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from database import get_db
|
||||
from dao.scores import add_score,query_id,query_er,query_sc,update_sc,delete_sc
|
||||
|
||||
router = APIRouter(prefix="/scores", tags=["考核成绩管理"])
|
||||
@router.post("/add", summary="按考核序次录入考核成绩") #1.录入成绩的路由
|
||||
def add_score_api(id: str = Query(..., description="学生ID"), #用于校验学生ID是否存在
|
||||
exam_round: int = Query(..., description="考核序次"), #用于校验同一学生同一考核序次是否重复录入
|
||||
score: int = Query(..., ge=0, le=100, description="成绩(0-100)"), #录入的成绩
|
||||
db: Session = Depends(get_db)):
|
||||
return add_score(db=db, id=id, exam_round=exam_round, score=score)
|
||||
|
||||
@router.get("/query/id", summary="按学生ID查询成绩") #2. 按学生ID查询的路由
|
||||
def query_id_api(id: str = Query(..., description="学生ID"),
|
||||
db: Session = Depends(get_db)):
|
||||
return query_id(db=db, id=id)
|
||||
|
||||
@router.get("/query/round", summary="按考核序次查询成绩") #3. 按考核序次查询的路由
|
||||
def query_er_api(exam_round: int = Query(..., description="考核序次"),
|
||||
db: Session = Depends(get_db)):
|
||||
return query_er(db=db, exam_round=exam_round)
|
||||
|
||||
@router.get("/query/score", summary="按成绩查询记录") #4. 按成绩查询的路由
|
||||
def query_sc_api(score: int = Query(..., ge=0, le=100, description="成绩"),
|
||||
db: Session = Depends(get_db)):
|
||||
return query_sc(db=db, score=score)
|
||||
|
||||
@router.put("/update", summary="根据学生ID和考核序次修改成绩") #5. 修改成绩的路由
|
||||
def update_sc_api(id: str = Query(..., description="学生ID"), #根据学生ID
|
||||
exam_round: int = Query(..., description="考核序次"), #根据考核序次
|
||||
score: int = Query(..., ge=0, le=100, description="新的成绩"), #修改的成绩值
|
||||
db: Session = Depends(get_db)):
|
||||
return update_sc(db=db, id=id, exam_round=exam_round, score=score)
|
||||
|
||||
@router.delete("/delete", summary="根据学生ID和考核序次删除成绩") #6. 删除成绩(软删除)
|
||||
def delete_sc_api(id: str = Query(..., description="学生ID"), #根据学生ID
|
||||
exam_round: int = Query(..., description="考核序次"), #根据考核序次
|
||||
db: Session = Depends(get_db)):
|
||||
return delete_sc(db=db, id=id, exam_round=exam_round)
|
||||
@@ -1,113 +0,0 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pymysql import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from model.classes_model import Classes
|
||||
from dao.student_dao import update_student
|
||||
from database import get_db
|
||||
from model.student_info import Student
|
||||
from schemas.student_info import StudentCreate, StudentUpdate, StudentResponse,student_date
|
||||
|
||||
router = APIRouter(prefix="/students", tags=["学生基本信息"])
|
||||
|
||||
# 1. 查询
|
||||
@router.get("", response_model=List[StudentResponse], summary="查询学生")
|
||||
def list_students(
|
||||
id: str = None,
|
||||
name: str = None,
|
||||
class_name: str = None,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
query = db.query(Student).filter(Student.is_deleted == False)
|
||||
if id:
|
||||
query = query.filter(Student.id == id)
|
||||
if name:
|
||||
query = query.filter(Student.name.like(f"%{name}%"))
|
||||
if class_name:
|
||||
query = query.filter(Student.class_name == class_name)
|
||||
return query.all()
|
||||
|
||||
|
||||
# 2. 创建
|
||||
@router.post("", response_model=StudentResponse, summary="创建新学生")
|
||||
def create_student(student: StudentCreate, db: Session = Depends(get_db)):
|
||||
# 调用校验函数 如果入学日期存在,就把它变成字符串;如果不存在,就赋值为空
|
||||
enroll_str = student.enrollment_date.strftime("%Y-%m-%d") if student.enrollment_date else None
|
||||
grad_str = student.graduation_date.strftime("%Y-%m-%d") if student.graduation_date else None
|
||||
date_check_result = student_date(enroll_str, grad_str)
|
||||
|
||||
#如果不存在,直接抛出异常中断请求
|
||||
if date_check_result != "时间校验通过":
|
||||
raise HTTPException(status_code=400, detail=date_check_result)
|
||||
# 检查学号是否已存在
|
||||
exist = db.query(Student).filter(Student.id == student.id).first()
|
||||
if exist:
|
||||
raise HTTPException(status_code=400, detail=f"学号 {student.id} 已存在")
|
||||
|
||||
# 不存在的班级导致外键约束报错
|
||||
class_exists = db.query(Classes).filter(
|
||||
Classes.class_name == student.class_name,
|
||||
).first()
|
||||
if not class_exists:
|
||||
raise HTTPException(status_code=400,detail=f"班级 '{student.class_name}' 不存在,请检查输入是否正确!")
|
||||
|
||||
# 3. 构建数据库模型对象 一个个赋值
|
||||
db_student = Student(
|
||||
id=student.id,
|
||||
name=student.name,
|
||||
gender=student.gender,
|
||||
class_name=student.class_name,
|
||||
age=student.age,
|
||||
hometown=student.hometown,
|
||||
graduate_school=student.graduate_school,
|
||||
major=student.major,
|
||||
education=student.education,
|
||||
enrollment_date=student.enrollment_date,
|
||||
graduation_date=student.graduation_date,
|
||||
status=student.status,
|
||||
is_deleted=False
|
||||
)
|
||||
db.add(db_student)
|
||||
db.commit()
|
||||
db.refresh(db_student)
|
||||
return db_student #返回响应
|
||||
|
||||
|
||||
# 3. 详情
|
||||
@router.get("/{id}", response_model=StudentResponse, summary="获取特定学生信息")
|
||||
def get_student(id: str, db: Session = Depends(get_db)):
|
||||
student = db.query(Student).filter(
|
||||
Student.id == id,
|
||||
Student.is_deleted == False
|
||||
).first()
|
||||
if student is None:
|
||||
raise HTTPException(status_code=404, detail="学生不存在或已删除")
|
||||
return student
|
||||
|
||||
|
||||
# 4. 更新
|
||||
@router.put("/{id}", response_model=StudentResponse, summary="更新学生信息")
|
||||
def api_update_student(id: str, payload: StudentUpdate, db: Session = Depends(get_db)):
|
||||
try:
|
||||
student = update_student(db, id, payload)
|
||||
if student is None:
|
||||
raise HTTPException(status_code=404, detail="学生不存在或已删除")
|
||||
return student
|
||||
except IntegrityError:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail="更新失败:指定的班级不存在,请先创建对应班级!")
|
||||
|
||||
|
||||
|
||||
# 5. 逻辑删除
|
||||
@router.delete("/{id}", summary="逻辑删除学生")
|
||||
def delete_student(id: str, db: Session = Depends(get_db)):
|
||||
student = db.query(Student).filter(
|
||||
Student.id == id,
|
||||
Student.is_deleted == False
|
||||
).first()
|
||||
if student is None:
|
||||
raise HTTPException(status_code=404, detail="学生不存在或已删除")
|
||||
student.is_deleted = True
|
||||
db.commit()
|
||||
return {"message": f"学生 {id} 已逻辑删除,数据仍保留在库中"}
|
||||
@@ -1,66 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.params import Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from dao.teachers_dao import add_teachers, del_teachers, update_teachers, search_teachers, \
|
||||
search_teachers_by_id, get_teacher_classes
|
||||
from database import get_db
|
||||
from schemas.req import AddTeacher, DeleteTeacher, UpdateTeacher
|
||||
|
||||
router=APIRouter(prefix="/teacher",tags=["老师管理"])
|
||||
@router.post("/teachers")
|
||||
async def create_teacher(tea:AddTeacher,db:Session=Depends(get_db)):
|
||||
date_obj=datetime.strptime(tea.t_created,"%Y-%m-%d") # 将字符串转时间
|
||||
data={
|
||||
"t_name":tea.t_name,
|
||||
"t_gender":tea.t_gender,
|
||||
"t_phone":tea.t_phone,
|
||||
"t_subject":tea.t_subject,
|
||||
"t_created":date_obj
|
||||
}
|
||||
res=add_teachers(db, data["t_name"],data["t_gender"],data["t_phone"],data["t_subject"],data["t_created"])
|
||||
return {"msg":"新增成功","data":res}
|
||||
|
||||
@router.delete("/{t_id}")
|
||||
def delete_teacher(dele:DeleteTeacher,db:Session=Depends(get_db)):
|
||||
res=del_teachers(db,t_id=dele.t_id,t_name=dele.t_name)
|
||||
if res.get("msg") == "没找到老师信息":
|
||||
raise HTTPException(404, detail="老师不存在")
|
||||
return {"msg": "删除成功"}
|
||||
|
||||
|
||||
@router.put("/{t_id}")
|
||||
async def update_tea(t_id:int,update:UpdateTeacher,db:Session=Depends(get_db)):
|
||||
date_obj = datetime.strptime(update.t_created, "%Y-%m-%d")
|
||||
res=update_teachers(db,t_id=t_id,
|
||||
new_name=update.t_name,
|
||||
new_gender=update.t_gender,
|
||||
new_phone=update.t_phone,
|
||||
new_subject=update.t_subject,
|
||||
new_created=date_obj
|
||||
)
|
||||
if res.get("msg")=="没找到老师信息":
|
||||
raise HTTPException(404,detail="老师不存在")
|
||||
return {"msg":"修改成功","data":res}
|
||||
|
||||
@router.get("/list")
|
||||
def list_teachers(db:Session=Depends(get_db),page:int=1,size:int=3):
|
||||
res=search_teachers(db,page,size)
|
||||
return res
|
||||
|
||||
|
||||
@router.get("/{t_id}")
|
||||
def get_one(t_id:int, db:Session=Depends(get_db)):
|
||||
tea = search_teachers_by_id(db, t_id)
|
||||
if not tea:
|
||||
raise HTTPException(404,detail="老师不存在")
|
||||
return {"data":tea}
|
||||
|
||||
@router.get("/{t_id}/classes")
|
||||
async def get_classes(t_id:int, db:Session=Depends(get_db)):
|
||||
data=get_teacher_classes(db, t_id)
|
||||
if not data:
|
||||
raise HTTPException(404,detail="老师不存在")
|
||||
return {"data":data}
|
||||
@@ -1,125 +0,0 @@
|
||||
from fastapi import Query,Depends
|
||||
from sqlalchemy import func, case
|
||||
from model.student_info import Student
|
||||
from model.employments import Employments
|
||||
from model.scores import Scores
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
#student_info学生基本信息
|
||||
#动态年龄范围查询:支持用户输入年龄阈值及比较条件(如大于、小于、等于、区间等),动态查询符合条件的学员信息
|
||||
def student_info_age(db: Session,min_age:int ,max_age:int ,operator:str,):
|
||||
if operator not in("大于","大于等于","小于","小于等于","区间"):
|
||||
raise ValueError("输入比较格式不正确,请在规定范围内选择输入")
|
||||
else:
|
||||
q = db.query(Student)
|
||||
if operator == "大于":
|
||||
q = q.filter(Student.age > min_age)
|
||||
elif operator == "大于等于":
|
||||
q = q.filter(Student.age >= min_age)
|
||||
elif operator == "小于":
|
||||
q = q.filter(Student.age < min_age)
|
||||
elif operator == "小于等于":
|
||||
q = q.filter(Student.age <= min_age)
|
||||
else: # 区间
|
||||
q = q.filter(Student.age >= min_age, Student.age <= max_age)
|
||||
print(q.all())
|
||||
return q.all()
|
||||
|
||||
#多维度班级统计:统计每个班级的总人数,以及按性别(男、女)细分的人数分布
|
||||
def student_info_class(db: Session):
|
||||
s1 = (db.query(Student.class_name,
|
||||
func.count(Student.id).label("总人数"),
|
||||
func.sum(case((Student.gender=="男",1),else_=0)).label("男"),
|
||||
func.sum(case((Student.gender=="女",1),else_=0)).label("女"))
|
||||
.group_by(Student.class_name).all())
|
||||
|
||||
return [
|
||||
{
|
||||
"class_name": row.class_name,
|
||||
"总人数": row.总人数,
|
||||
"男": row.男,
|
||||
"女": row.女,
|
||||
}for row in s1
|
||||
]
|
||||
|
||||
|
||||
|
||||
#student_score学生成绩
|
||||
#查询每次考试成绩都在输入分数线(如80分)以上的学生的编号、姓名和成绩。
|
||||
def student_score(db: Session,s_n : int):
|
||||
s1 = db.query(Scores.id).group_by(Scores.id).having(func.min(Scores.score)>=s_n).all()
|
||||
s1_ids = [row[0] for row in s1]
|
||||
if not s1_ids:
|
||||
return []
|
||||
s2 = (db.query(Student.id,Student.name,Scores.exam_round,Scores.score)
|
||||
.join(Student, Scores.id == Student.id)
|
||||
.filter(Scores.id.in_(s1_ids)).all())
|
||||
return [{"id": i.id, "name": i.name, "exam_round": i.exam_round, "score": i.score}
|
||||
for i in s2]
|
||||
#查询有输入指定次数(如两次(包含两次))以上不及格的学生的姓名、班级和不及格成绩明细。
|
||||
def student_score60(db: Session,n : int ):
|
||||
s1 = (db.query(Scores.id).group_by(Scores.id)
|
||||
.having(func.sum(case((Scores.score < 60, 1), else_=0)) >=n)
|
||||
.all())
|
||||
s1_ids = [row[0] for row in s1]
|
||||
if not s1_ids:
|
||||
return []
|
||||
s2 = (db.query(Student.name,Student.class_name,Scores.exam_round,Scores.score)
|
||||
.join(Student, Scores.id == Student.id)
|
||||
.filter(Scores.id.in_(s1_ids)).all())
|
||||
return [{"id": i.name, "name": i.class_name, "exam_round": i.exam_round, "score": i.score}
|
||||
for i in s2]
|
||||
#统计每次考试每个班级的平均分,并支持按分数从高到低或从低到高动态排序。
|
||||
def student_score_class(db: Session,order: str = "desc"):
|
||||
s1 = (db.query(Scores.exam_round,
|
||||
Student.class_name,
|
||||
func.avg(Scores.score).label("平均分"),).join(Student, Scores.id == Student.id)
|
||||
.group_by(Student.class_name,Scores.exam_round))
|
||||
if order == "desc":
|
||||
res = s1.order_by(func.avg(Scores.score).desc()).all()
|
||||
else:
|
||||
res = s1.order_by(func.avg(Scores.score).asc()).all()
|
||||
return [row._asdict() for row in res]
|
||||
|
||||
|
||||
#student_employments
|
||||
#查询查询就业薪资排名前n的学生
|
||||
def student_employments_salary(db: Session,n:int):
|
||||
s1 = db.query(Employments).order_by(Employments.sal.desc()).limit(n).all()
|
||||
return s1
|
||||
|
||||
#统计每个学生的就业时长
|
||||
def student_employments_time(db: Session):
|
||||
time = []
|
||||
all_ = db.query(Employments).all()
|
||||
for employments in all_:
|
||||
if employments.offer_review is None:
|
||||
time.append({"name":employments.student.name,
|
||||
"class_name": employments.student.class_name,
|
||||
"status": "未就业"})
|
||||
elif employments.offer_open and employments.offer_review:
|
||||
time.append({"name":employments.student.name,
|
||||
"class_name": employments.student.class_name,
|
||||
"time": (employments.offer_review-employments.offer_open).days})
|
||||
return time
|
||||
|
||||
#统计每个班级的平均就业时长(仅统计进入就业阶段,即有就业开放时间的学生)
|
||||
def student_employments_class_time(db: Session):
|
||||
s1 = (db.query(Student.class_name,
|
||||
func.avg(
|
||||
func.unix_timestamp(Employments.offer_review) -
|
||||
func.unix_timestamp(Employments.offer_open))
|
||||
.label("avg_time"))
|
||||
.join(Student, Employments.id == Student.id)
|
||||
.filter(Employments.offer_review.isnot(None))
|
||||
.group_by(Student.class_name).all())
|
||||
time = []
|
||||
for i in s1:
|
||||
days = round(float(i.avg_time) / 86400, 2) if i.avg_time else 0
|
||||
time.append({
|
||||
"class_name": i.class_name,
|
||||
"平均时长(天)": days
|
||||
})
|
||||
return time
|
||||
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
from model.classes_model import Classes
|
||||
from model.teachers import Teacher
|
||||
from schemas.class_scheme import (
|
||||
ClassCreate,
|
||||
ClassUpdateBase,
|
||||
ClassHeadTeacherUpdate,
|
||||
ClassTeachersUpdate,
|
||||
ClassDelete,
|
||||
)
|
||||
|
||||
|
||||
class ClassDAO:
|
||||
|
||||
# ---------- 创建 ----------
|
||||
@staticmethod
|
||||
def create(db: Session, req: ClassCreate) -> Classes:
|
||||
# 1) 班级名唯一
|
||||
exists = (
|
||||
db.query(Classes)
|
||||
.filter(Classes.class_name == req.class_name)
|
||||
.first()
|
||||
)
|
||||
if exists:
|
||||
raise ValueError("班级名称已存在")
|
||||
|
||||
# 2) 班主任存在
|
||||
head_teacher = (
|
||||
db.query(Teacher).filter(Teacher.t_id == req.head_teacher_id).first()
|
||||
)
|
||||
if not head_teacher:
|
||||
raise ValueError("班主任不存在")
|
||||
|
||||
# 3) 授课老师全部存在
|
||||
teachers: List[Teacher] = []
|
||||
if req.teacher_ids:
|
||||
teacher_ids = set(req.teacher_ids)
|
||||
teachers = (
|
||||
db.query(Teacher).filter(Teacher.t_id.in_(teacher_ids)).all()
|
||||
)
|
||||
if len(teachers) != len(teacher_ids):
|
||||
raise ValueError("部分授课老师不存在")
|
||||
|
||||
data = req.model_dump(
|
||||
exclude={"teacher_ids"},
|
||||
exclude_none=True,
|
||||
)
|
||||
db_class = Classes(**data)
|
||||
db_class.teachers = teachers
|
||||
|
||||
db.add(db_class)
|
||||
db.commit()
|
||||
db.refresh(db_class)
|
||||
return db_class
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, class_id: int) -> Optional[Classes]:
|
||||
return (
|
||||
db.query(Classes)
|
||||
.filter(
|
||||
Classes.class_id == class_id,
|
||||
Classes.class_delete == False, # noqa: E712
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
# ---------- 分页 ----------
|
||||
@staticmethod
|
||||
def list_page(
|
||||
db: Session,
|
||||
page_num: int,
|
||||
page_size: int,
|
||||
class_name: Optional[str] = None,
|
||||
head_teacher_id: Optional[int] = None,
|
||||
) -> Tuple[int, List[Classes]]:
|
||||
query = db.query(Classes).filter(Classes.class_delete == False) # noqa: E712
|
||||
|
||||
if class_name:
|
||||
query = query.filter(Classes.class_name.like(f"%{class_name}%"))
|
||||
if head_teacher_id is not None:
|
||||
query = query.filter(Classes.head_teacher_id == head_teacher_id)
|
||||
|
||||
total = query.count()
|
||||
rows = (
|
||||
query.order_by(Classes.class_id.desc())
|
||||
.offset((page_num - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return total, rows
|
||||
|
||||
|
||||
@staticmethod
|
||||
def update_base(
|
||||
db: Session, class_id: int, req: ClassUpdateBase
|
||||
) -> Optional[Classes]:
|
||||
db_class = ClassDAO.get_by_id(db, class_id)
|
||||
if not db_class:
|
||||
return None
|
||||
|
||||
data = req.model_dump(exclude_unset=True, exclude_none=True)
|
||||
|
||||
# 改班级名要查重
|
||||
new_name = data.get("class_name")
|
||||
if new_name and new_name != db_class.class_name:
|
||||
exists = (
|
||||
db.query(Classes)
|
||||
.filter(
|
||||
Classes.class_name == new_name,
|
||||
Classes.class_id != class_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if exists:
|
||||
raise ValueError("班级名称已存在")
|
||||
|
||||
for k, v in data.items():
|
||||
setattr(db_class, k, v)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_class)
|
||||
return db_class
|
||||
|
||||
@staticmethod
|
||||
def update_head_teacher(
|
||||
db: Session, class_id: int, req: ClassHeadTeacherUpdate
|
||||
) -> Optional[Classes]:
|
||||
db_class = ClassDAO.get_by_id(db, class_id)
|
||||
if not db_class:
|
||||
return None
|
||||
|
||||
teacher = (
|
||||
db.query(Teacher).filter(Teacher.t_id == req.head_teacher_id).first()
|
||||
)
|
||||
if not teacher:
|
||||
raise ValueError("班主任不存在")
|
||||
|
||||
db_class.head_teacher_id = req.head_teacher_id
|
||||
db.commit()
|
||||
db.refresh(db_class)
|
||||
return db_class
|
||||
|
||||
|
||||
@staticmethod
|
||||
def update_teachers(
|
||||
db: Session, class_id: int, req: ClassTeachersUpdate
|
||||
) -> Optional[Classes]:
|
||||
db_class = ClassDAO.get_by_id(db, class_id)
|
||||
if not db_class:
|
||||
return None
|
||||
|
||||
teacher_ids = set(req.teacher_ids)
|
||||
teachers = (
|
||||
db.query(Teacher).filter(Teacher.t_id.in_(teacher_ids)).all()
|
||||
) if teacher_ids else []
|
||||
if len(teachers) != len(teacher_ids):
|
||||
raise ValueError("部分授课老师不存在")
|
||||
|
||||
db_class.teachers = teachers
|
||||
db.commit()
|
||||
db.refresh(db_class)
|
||||
return db_class
|
||||
|
||||
@staticmethod
|
||||
def batch_toggle_delete(db: Session, req: ClassDelete) -> int:
|
||||
count = (
|
||||
db.query(Classes)
|
||||
.filter(Classes.class_id.in_(req.class_ids))
|
||||
.update(
|
||||
{"class_delete": req.deleted},
|
||||
synchronize_session=False,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return count
|
||||
@@ -1,82 +0,0 @@
|
||||
#创建方法
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
|
||||
from model.counselor import Counselor
|
||||
|
||||
|
||||
#增
|
||||
def add_counselor(db:Session,id: int,name:str,c_time:date):
|
||||
c1 = Counselor(
|
||||
id=id,
|
||||
name=name,
|
||||
c_time=c_time,
|
||||
c_de=False
|
||||
)
|
||||
try:
|
||||
db.add(c1)
|
||||
db.commit()
|
||||
db.refresh(Counselor)
|
||||
return "添加成功"
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
#删
|
||||
def delete_counselor_id(db:Session,id:int):
|
||||
res=db.query(Counselor).filter(Counselor.id == id).first()
|
||||
res.c_de=True
|
||||
db.commit()
|
||||
return "删除成功"
|
||||
|
||||
def delete_counselor_name(db:Session,name:str):
|
||||
db.query(Counselor).filter(Counselor.name==name).delete()
|
||||
db.commit()
|
||||
db.refresh(Counselor)
|
||||
return "删除成功"
|
||||
|
||||
#改,根据id修改
|
||||
def update_counselor(db:Session,id:int,new_name,new_c_time):
|
||||
counselor=db.query(Counselor).filter(Counselor.id==id,Counselor.c_de==False).first()
|
||||
if not counselor:
|
||||
return {"msg":"没找到顾问信息"}
|
||||
counselor.name=new_name
|
||||
counselor.c_time=new_c_time
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(counselor)
|
||||
return {"msg":"修改成功"}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {"msg": "修改失败"}
|
||||
|
||||
#改,根据名字修改
|
||||
def update_counselor_name(db:Session,id:int,name:str,c_time:date):
|
||||
if Counselor.name==name:
|
||||
Counselor.id==id
|
||||
Counselor.c_time=c_time
|
||||
db.commit()
|
||||
db.refresh(Counselor)
|
||||
return f"修改成功"
|
||||
|
||||
#查,根据id查询
|
||||
#根据id查
|
||||
def query_counselor(db:Session,id:int):
|
||||
counselor=db.query(Counselor).filter(Counselor.id==id,Counselor.c_de==False).first()
|
||||
if not counselor:
|
||||
return None
|
||||
l2={
|
||||
"id":counselor.id,
|
||||
"name":counselor.name,
|
||||
"t_created":counselor.c_time.strftime("%Y-%m-%d")
|
||||
}
|
||||
return l2
|
||||
|
||||
|
||||
#根据名字查询
|
||||
def query_counselor_name(db:Session,name:str):
|
||||
res=db.query(Counselor).filter(Counselor.name==name).first()
|
||||
db.commit()
|
||||
db.refresh(Counselor)
|
||||
return res
|
||||
#筛选低于60的同学
|
||||
@@ -1,87 +0,0 @@
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from model.employments import Employments
|
||||
from model.student_info import Student
|
||||
from schemas.employments import Emp_Class,Emp_add_update
|
||||
from sqlalchemy import and_
|
||||
from fastapi import HTTPException
|
||||
|
||||
#根据学生id查看信息
|
||||
def get_student_id(db: Session, student_id: str):
|
||||
return db.query(Employments).filter(and_(Employments.id == student_id,Employments.is_deleted==True)).first()
|
||||
#根据学生id、公司名称、工资范围多条件查询
|
||||
def get_student_multi_condition(
|
||||
db: Session,
|
||||
id: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
min_sal: Optional[float] = None,
|
||||
max_sal: Optional[float] = None,
|
||||
):
|
||||
# LEFT JOIN student + student_employment
|
||||
q = db.query(Student, Employments)\
|
||||
.outerjoin(Employments, Student.id == Employments.id)\
|
||||
.filter(Employments.is_deleted==True)
|
||||
# if min_sal>max_sal:
|
||||
# raise HTTPException(status_code=404, detail="学生不存在,请先创建学生基础信息")
|
||||
# 条件动态拼接
|
||||
if id:
|
||||
q = q.filter(Student.id.like(f"%{id}%"))#like模糊查询
|
||||
if name:
|
||||
q = q.filter(Employments.emp.like(f"%{name}%"))
|
||||
if min_sal is not None:
|
||||
q = q.filter(Employments.sal >= min_sal)
|
||||
if max_sal is not None:
|
||||
q = q.filter(Employments.sal <= max_sal)
|
||||
return q.all()
|
||||
#根据学生班级查看信息
|
||||
def get_class_id(db:Session,class_id:str):
|
||||
return (db.query(Student,Employments)
|
||||
.outerjoin(Student,Student.id==Employments.id))\
|
||||
.filter(Student.class_name==class_id).all()
|
||||
#增加修改学生信息
|
||||
#首先判断是否是已存在的学生,如果不是就增加,反之就修改
|
||||
def add_update_employment(db: Session,student_id:str,emp_data: Emp_add_update,student:Student ):
|
||||
emp = get_student_id(db, student_id)
|
||||
if emp:
|
||||
# 更新
|
||||
emp.offer_open = emp_data.offer_open
|
||||
emp.offer_review = emp_data.offer_review
|
||||
emp.emp = emp_data.emp
|
||||
emp.sal = emp_data.sal
|
||||
else:
|
||||
# 新增
|
||||
stu = db.query(Student).filter(Student.id == student_id)
|
||||
if stu:
|
||||
emp = Employments(
|
||||
id=student_id,
|
||||
offer_open=emp_data.offer_open,
|
||||
offer_review=emp_data.offer_review,
|
||||
emp=emp_data.emp,
|
||||
sal=emp_data.sal
|
||||
)
|
||||
db.add(emp)
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="学生不存在,请先创建学生基础信息")
|
||||
|
||||
# 业务状态更新 offer优先级更高
|
||||
if emp_data.offer_review is not None:
|
||||
student.status = "已就业"
|
||||
elif emp_data.offer_open is not None:
|
||||
student.status = "就业开放"
|
||||
db.commit()
|
||||
db.refresh(emp)
|
||||
return emp
|
||||
|
||||
def delete_employment(db: Session,student_id:str ):
|
||||
emp = get_student_id(db, student_id)
|
||||
if emp:
|
||||
# 删除
|
||||
emp.is_deleted = False
|
||||
db.commit()
|
||||
db.refresh(emp)
|
||||
# else:
|
||||
# raise HTTPException(status_code=404, detail="学生不存在,请先创建学生基础信息")
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from model.scores import Scores
|
||||
|
||||
def add_score(db: Session, id: str, exam_round: int, score: int): #1.按考核序次录入考核成绩(包含字段:学生ID、考核序次、成绩)
|
||||
try:
|
||||
judgement_id = (db.query(Scores).
|
||||
filter(Scores.id == id,Scores.is_deleted == False).
|
||||
first()) # 通过外键class Student校验学生ID是否存在
|
||||
if not judgement_id:
|
||||
return {"code": 404, "msg": f"学生ID:{id}不存在,请去录入学生信息!"}
|
||||
|
||||
judgement_id_er = (db.query(Scores).
|
||||
filter(Scores.id == id,
|
||||
Scores.exam_round == exam_round,
|
||||
Scores.is_deleted == False).
|
||||
first()) # 本表校验同一学生、同一考核序次是否已录入
|
||||
if judgement_id_er:
|
||||
return {"code": 400, "msg": f"学生ID:{id}第{exam_round}次考核成绩已存在,请勿重复录入!"}
|
||||
|
||||
if score < 60: # 创建分数小于等于60分的记录并警告
|
||||
record = Scores(id=id,
|
||||
exam_round=exam_round,
|
||||
score=score,
|
||||
is_warning=True) #写入学生ID,考核序次,成绩,标记警告
|
||||
db.add(record)
|
||||
db.commit()
|
||||
db.refresh(record)
|
||||
return {"code": 200, "msg": f"学生ID:{id}第{exam_round}次考核成绩{score}分录入成功!成绩小于60分,需要多关注!"}
|
||||
else: # 创建分数大于等于60分的记录
|
||||
record = Scores(id=id,
|
||||
exam_round=exam_round,
|
||||
score=score) #写入学生ID,考核序次,成绩
|
||||
db.add(record)
|
||||
db.commit()
|
||||
db.refresh(record)
|
||||
return {"code": 200, "msg": f"学生ID:{id}第{exam_round}次考核成绩{score}分录入成功!"}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return {"code": 500, "msg": f"考核成绩录入失败: {e}"}
|
||||
|
||||
|
||||
def query_id(db: Session, id: str): #2.按学生ID过滤,成绩降序,考核序次升序,返回全部信息
|
||||
try:
|
||||
query_id = (db.query(Scores).
|
||||
filter(Scores.id == id,Scores.is_deleted == False).
|
||||
order_by(Scores.score.desc(),Scores.exam_round.asc()).
|
||||
all())
|
||||
if not query_id:
|
||||
return {"code": 404, "msg": "未查询到相关结果"}
|
||||
data = [{"id": o.id, "exam_round": o.exam_round, "score": o.score}for o in query_id]
|
||||
return {"code": 200, "msg": data}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return {"code": 500, "msg": f"未查询到相关结果:{e}"}
|
||||
|
||||
|
||||
def query_er(db: Session, exam_round: int): #3.按考核序次过滤,成绩降序,学生ID升序,返回全部信息
|
||||
try:
|
||||
query_er = (db.query(Scores).
|
||||
filter(Scores.exam_round == exam_round,Scores.is_deleted == False).
|
||||
order_by(Scores.score.desc(),Scores.id.asc()).
|
||||
all())
|
||||
if not query_er:
|
||||
return {"code": 404, "msg": "未查询到相关结果"}
|
||||
data = [{"id": p.id, "exam_round": p.exam_round, "score": p.score}for p in query_er]
|
||||
return {"code": 200, "msg": data}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return {"code": 500, "msg": f"未查询到相关结果: {e}"}
|
||||
|
||||
def query_sc(db: Session, score: int): #4.按成绩过滤,学生ID升序,考核序次升序,返回全部信息
|
||||
try:
|
||||
query_sc = (db.query(Scores).
|
||||
filter(Scores.score == score,Scores.is_deleted == False).
|
||||
order_by(Scores.id.asc(),Scores.exam_round.asc()).
|
||||
all())
|
||||
if not query_sc:
|
||||
return {"code": 404, "msg": "未查询到相关结果"}
|
||||
data = [{"id": q.id, "exam_round": q.exam_round, "score": q.score}for q in query_sc]
|
||||
return {"code": 200, "msg": data}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return {"code": 500, "msg": f"未查询到相关结果: {e}"}
|
||||
|
||||
|
||||
def update_sc(db: Session, id: str, exam_round: int, score: int): #5.根据学生ID、考核序次修改成绩
|
||||
try:
|
||||
update_sc = (db.query(Scores).
|
||||
filter(Scores.id == id,
|
||||
Scores.exam_round == exam_round,
|
||||
Scores.is_deleted == False).
|
||||
first())
|
||||
if not update_sc:
|
||||
return {"code": 404, "msg": "该成绩记录不存在"}
|
||||
update_sc.score = score
|
||||
update_sc.is_warning = score < 60
|
||||
db.commit()
|
||||
db.refresh(update_sc)
|
||||
return {"code": 200, "msg": f"学生ID:{id} 第{exam_round}次成绩已修改为{score}分!"}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return {"code": 500, "msg": f"考核成绩修改失败: {e}"}
|
||||
|
||||
|
||||
def delete_sc(db: Session, id: str, exam_round: int): #6.根据学生ID、考核序次删除成绩
|
||||
try:
|
||||
delete_sc = (db.query(Scores).
|
||||
filter(Scores.id == id,
|
||||
Scores.exam_round == exam_round,
|
||||
Scores.is_deleted == False).
|
||||
first())
|
||||
if not delete_sc:
|
||||
return {"code": 404, "msg": "成绩记录不存在"}
|
||||
delete_sc.is_deleted = True # 逻辑删除
|
||||
db.commit()
|
||||
db.refresh(delete_sc)
|
||||
return {"code": 200, "msg": f"学生ID:{id}第{exam_round}次成绩已删除!"}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return {"code": 500, "msg": f"考核成绩删除失败: {e}"}
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from model.student_info import Student
|
||||
|
||||
# 根据学生id查询学生
|
||||
def get_student_by_id(db: Session, student_id: str):
|
||||
return db.query(Student).filter(Student.id == student_id).first()
|
||||
|
||||
# 根据班级名称查询该班级全部学生
|
||||
def get_student_by_class(db: Session, class_name: str):
|
||||
return db.query(Student).filter(Student.class_name == class_name).all()
|
||||
|
||||
# 创建学生
|
||||
def create_student(db: Session, student_id: str, student_name: str, class_name: str):
|
||||
new_stu = Student(id=student_id, name=student_name, class_name=class_name, status="待就业")
|
||||
db.add(new_stu)
|
||||
db.commit()
|
||||
db.refresh(new_stu)
|
||||
return new_stu
|
||||
@@ -1,104 +0,0 @@
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from model.student_info import Student
|
||||
from schemas.student_info import StudentCreate, StudentUpdate,StudentDate, StudentResponse
|
||||
|
||||
|
||||
|
||||
#查询 查询学生信息(按编号、姓名、班级条件筛选)
|
||||
#orm动态查询
|
||||
def list_students(db: Session,id: str = None,name: str = None,class_name: str = None):
|
||||
# 已逻辑删除的学生
|
||||
query = db.query(Student).filter(Student.is_deleted == False)
|
||||
# 查询学生信息(支持按编号、姓名、班级等条件筛选)。
|
||||
if id:
|
||||
query = query.filter(Student.id == id)
|
||||
if name:
|
||||
query = query.filter(Student.name.like(f"%{name}%"))
|
||||
if class_name:
|
||||
query = query.filter(Student.class_name == class_name)
|
||||
# 执行查询并返回
|
||||
return query.all()
|
||||
|
||||
|
||||
#2创建新的学生
|
||||
def create_student(db: Session,student: StudentCreate):
|
||||
data = {}
|
||||
#遍历
|
||||
for field_name in student.model_fields:
|
||||
if field_name == "id":
|
||||
val = student.id
|
||||
elif field_name == "name":
|
||||
val = student.name
|
||||
elif field_name == "gender":
|
||||
val = student.gender
|
||||
elif field_name == "class_no":
|
||||
val = student.class_name
|
||||
elif field_name == "major":
|
||||
val = student.major
|
||||
elif field_name == "age":
|
||||
val = student.age
|
||||
elif field_name == "hometown":
|
||||
val = student.hometown
|
||||
else:
|
||||
val = None
|
||||
data[field_name] = val
|
||||
# 赋值给ORM
|
||||
db_student = Student(
|
||||
id=data["id"],
|
||||
name=data["name"],
|
||||
gender=data["gender"],
|
||||
class_name=data["class_name"],
|
||||
major=data.get("major"),
|
||||
age=data.get("age"),
|
||||
hometown=data.get("hometown")
|
||||
)
|
||||
db.add(db_student)
|
||||
db.commit()
|
||||
db.refresh(db_student)
|
||||
return db_student
|
||||
|
||||
# 获取特定学生信息
|
||||
# 严格按照需求文档:GET /students/{id},从URL路径中获取学生学号
|
||||
def get_student(db: Session,id: str):
|
||||
# 查询:id字段等于url传过来的id
|
||||
student = db.query(Student).filter(Student.id == id,).first()
|
||||
if student is None:
|
||||
return None
|
||||
return student
|
||||
|
||||
|
||||
# 更新学生信息 /students/{id} id为学号
|
||||
def update_student( db: Session,id: str, payload: StudentUpdate):
|
||||
# 根据学号查询学生
|
||||
student = db.query(Student).filter(Student.id == id).first()
|
||||
# 学生是否存在
|
||||
if student is None:
|
||||
return None
|
||||
if payload.name is not None:
|
||||
student.name = payload.name
|
||||
if payload.age is not None:
|
||||
student.age = payload.age
|
||||
if payload.class_name is not None:
|
||||
student.class_name = payload.class_name
|
||||
# 保存到数据库
|
||||
db.commit()
|
||||
db.refresh(student)
|
||||
# 返回更新后的学生
|
||||
return student
|
||||
|
||||
|
||||
# 逻辑删除学生。逻辑上删除,实际上未删除
|
||||
def delete_student(db: Session,id: str):
|
||||
# 根据学号查询
|
||||
student = db.query(Student).filter(Student.id == id, Student.is_deleted == False).first()
|
||||
# 判断学生是否存在
|
||||
if student is None:
|
||||
return None
|
||||
# 逻辑删除:把is_deleted改成True,不是真的删掉这条记录
|
||||
student.is_deleted = True
|
||||
db.commit()
|
||||
return {"message": f"学生 {id} 已逻辑删除,数据仍保留在库中"}
|
||||
|
||||
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base, Session, Query, selectinload
|
||||
|
||||
from model.teachers import Teacher
|
||||
|
||||
#添加方法
|
||||
def add_teachers(db:Session ,t_name:str, t_gender:str, t_phone:str, t_subject:str,t_created:date):
|
||||
t1=Teacher(
|
||||
t_name=t_name,
|
||||
t_gender=t_gender,
|
||||
t_phone=t_phone,
|
||||
t_subject=t_subject,
|
||||
t_created=t_created,
|
||||
t_delete=False
|
||||
)
|
||||
try:
|
||||
db.add(t1)
|
||||
db.commit()
|
||||
db.refresh(t1)
|
||||
return {"msg":"添加成功"}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {"msg":"删除成功"}
|
||||
|
||||
|
||||
#删除方法
|
||||
def del_teachers(db:Session,t_id:int,t_name:str):
|
||||
teacher=db.query(Teacher).filter(Teacher.t_id==t_id,
|
||||
Teacher.t_name==t_name,
|
||||
Teacher.t_delete==False
|
||||
).first()
|
||||
if not teacher:
|
||||
return {"msg":"没找到老师信息"}
|
||||
#逻辑删除
|
||||
try:
|
||||
teacher.t_delete=True
|
||||
db.commit()
|
||||
return {"msg":"删除成功"}
|
||||
except Exception as e:
|
||||
print("删除异常",e)
|
||||
return {"msg":"删除失败"}
|
||||
|
||||
#修改老师
|
||||
def update_teachers(db:Session,t_id:int,new_name,new_gender,new_phone,new_subject,new_created):
|
||||
teacher=db.query(Teacher).filter(Teacher.t_id==t_id,Teacher.t_delete==False).first()
|
||||
if not teacher:
|
||||
return {"msg":"没找到老师信息"}
|
||||
teacher.t_name=new_name
|
||||
teacher.t_gender=new_gender
|
||||
teacher.t_phone=new_phone
|
||||
teacher.t_subject=new_subject
|
||||
teacher.t_created=new_created
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(teacher)
|
||||
return {"msg":"修改成功"}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {"msg": "修改失败"}
|
||||
|
||||
# 查询所有老师信息
|
||||
# 分页
|
||||
def search_teachers(db:Session,page:int,size:int):
|
||||
start = (page - 1) * size
|
||||
teachers=db.query(Teacher.t_id,
|
||||
Teacher.t_name,
|
||||
Teacher.t_gender,
|
||||
Teacher.t_phone,
|
||||
Teacher.t_subject,
|
||||
Teacher.t_created).filter(Teacher.t_delete==False)
|
||||
teach=teachers.offset(start).limit(size).all() #跳过多少行,查询当前页数据
|
||||
total=teachers.count()
|
||||
l1=[]
|
||||
#将查出来的值存入列表
|
||||
for i in teach:
|
||||
bb={
|
||||
"t_id":i[0],
|
||||
"t_name":i[1],
|
||||
"t_gender":i[2],
|
||||
"t_phone":i[3],
|
||||
"t_subject":i[4],
|
||||
"t_created":i[5].strftime("%Y-%m-%d") # 将字符串转时间
|
||||
}
|
||||
l1.append(bb)
|
||||
return {
|
||||
"total":total,
|
||||
"page":page,
|
||||
"size":size,
|
||||
"list":l1
|
||||
}
|
||||
|
||||
|
||||
|
||||
#查询单个老师信息
|
||||
#根据id查
|
||||
def search_teachers_by_id(db:Session,t_id:int):
|
||||
teachers=db.query(Teacher).filter(Teacher.t_id==t_id,Teacher.t_delete==False).first()
|
||||
if not teachers:
|
||||
return None
|
||||
l2={
|
||||
"t_id":teachers.t_id,
|
||||
"t_name":teachers.t_name,
|
||||
"t_gender":teachers.t_gender,
|
||||
"t_phone":teachers.t_phone,
|
||||
"t_subject":teachers.t_subject,
|
||||
"t_created":teachers.t_created.strftime("%Y-%m-%d")
|
||||
}
|
||||
return l2
|
||||
|
||||
#查看带班信息
|
||||
def get_teacher_classes(db:Session,t_id:int):
|
||||
teacher=(db.query(Teacher).filter(Teacher.t_id==t_id,Teacher.t_delete==False)
|
||||
.options(selectinload(Teacher.classes)).first())
|
||||
if not teacher:
|
||||
return None
|
||||
|
||||
l3=[]
|
||||
for i in teacher.classes:
|
||||
l3.append({
|
||||
"class_id":i.class_id,
|
||||
"class_name":i.class_name,
|
||||
"create_time":i.create_time.strftime("%Y-%m-%d"),
|
||||
})
|
||||
|
||||
res={
|
||||
"tid":teacher.t_id,
|
||||
"t_id": teacher.t_id,
|
||||
"t_name": teacher.t_name,
|
||||
"t_gender": teacher.t_gender,
|
||||
"t_phone": teacher.t_phone,
|
||||
"t_subject": teacher.t_subject,
|
||||
"t_created": teacher.t_created.strftime("%Y-%m-%d"),
|
||||
"class_list": l3
|
||||
}
|
||||
return res
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import os
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||
|
||||
# 优先读取环境变量(Docker 部署用);默认值与原硬编码配置一致,本地开发不受影响
|
||||
MYSQL_USER = os.getenv("MYSQL_USER", "root")
|
||||
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "123456")
|
||||
MYSQL_HOST = os.getenv("MYSQL_HOST", "localhost")
|
||||
MYSQL_PORT = os.getenv("MYSQL_PORT", "3306")
|
||||
MYSQL_DB = os.getenv("MYSQL_DB", "student_manager")
|
||||
|
||||
db_url = f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}?charset=utf8mb4"
|
||||
engine = create_engine(db_url, pool_size=5, pool_pre_ping=True)
|
||||
Base = declarative_base()
|
||||
SessionLocal = sessionmaker(bind=engine)
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def init_db():
|
||||
"""导入所有模型并建表,避免任何入口文件漏导入"""
|
||||
import model # noqa: F401 —— 触发 model/__init__.py 注册所有类
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
|
||||
def reset_db():
|
||||
"""开发环境重建所有表"""
|
||||
import model # noqa: F401
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
@@ -1,66 +0,0 @@
|
||||
# ============================================
|
||||
# 沃林学生管理系统 - Docker 编排
|
||||
# MySQL 8.0 虚拟镜像 + Backend (FastAPI)
|
||||
# ============================================
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
# ---------- MySQL 虚拟镜像 ----------
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: wolin-sms-mysql
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# 与 database.py 中的默认凭证保持一致
|
||||
MYSQL_ROOT_PASSWORD: "123456"
|
||||
MYSQL_DATABASE: student_manager
|
||||
TZ: Asia/Shanghai
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
ports:
|
||||
# 宿主机 3307 -> 容器 3306
|
||||
# (用 3307 是为了避免与本机已安装的 MySQL 冲突)
|
||||
- "3308:3306"
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p123456"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
networks:
|
||||
- wolin-net
|
||||
|
||||
# ---------- 后端应用 ----------
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: wolin-sms-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# 容器内通过服务名 mysql 访问数据库(Docker 内置 DNS)
|
||||
MYSQL_HOST: mysql
|
||||
MYSQL_PORT: "3306"
|
||||
MYSQL_USER: root
|
||||
MYSQL_PASSWORD: "123456"
|
||||
MYSQL_DB: student_manager
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- "8001:8001"
|
||||
networks:
|
||||
- wolin-net
|
||||
|
||||
networks:
|
||||
wolin-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
driver: local
|
||||
@@ -1,62 +0,0 @@
|
||||
import os
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from database import engine, Base
|
||||
|
||||
#导入模型
|
||||
from model.classes_model import Classes
|
||||
from model.student_info import Student
|
||||
from model.scores import Scores
|
||||
from model.employments import Employments
|
||||
from model.teachers import Teacher
|
||||
from model.counselor import Counselor
|
||||
|
||||
#导入路由
|
||||
from api import student as student_api
|
||||
from api import score as score_api
|
||||
from api import employment as employment_api
|
||||
from api import analysis as analysis_api
|
||||
from api import teachers as teachers_api
|
||||
from api import class_api
|
||||
from api import counselor_api
|
||||
|
||||
#创建 FastAPI 应用
|
||||
app = FastAPI(title="学生管理系统API", version="1.0")
|
||||
|
||||
#配置跨域
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
#注册路由
|
||||
app.include_router(student_api.router)
|
||||
app.include_router(score_api.router)
|
||||
app.include_router(employment_api.router)
|
||||
app.include_router(analysis_api.router)
|
||||
app.include_router(teachers_api.router)
|
||||
app.include_router(class_api.router)
|
||||
app.include_router(counselor_api.router)
|
||||
|
||||
#挂载前端静态文件
|
||||
if not os.path.exists("static"):
|
||||
os.makedirs("static")
|
||||
app.mount("/ui", StaticFiles(directory="static", html=True), name="static")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "欢迎使用沃林学生管理系统,前端请访问 http://127.0.0.1:8001/ui/"}
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 建表
|
||||
Base.metadata.create_all(bind=engine)
|
||||
# host 必须为 0.0.0.0 才能在 Docker 容器外访问;本地运行 127.0.0.1:8001 依然有效
|
||||
host = os.getenv("HOST", "0.0.0.0")
|
||||
port = int(os.getenv("PORT", "8001"))
|
||||
uvicorn.run("main:app", host=host, port=port, reload=False)
|
||||
@@ -1,6 +0,0 @@
|
||||
from .classes_model import Classes
|
||||
from .student_info import Student
|
||||
from .scores import Scores
|
||||
from .employments import Employments
|
||||
|
||||
__all__ = ["Classes", "Student", "Scores", "Employments"]
|
||||
@@ -1,25 +0,0 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, Date, Table, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from database import Base
|
||||
|
||||
|
||||
|
||||
teacher_classes = Table(
|
||||
'teacher_classes',
|
||||
Base.metadata,
|
||||
Column('teacher_id', ForeignKey('teacher.t_id'), primary_key=True),
|
||||
Column('class_id', ForeignKey('classes.class_id'), primary_key=True)
|
||||
)
|
||||
|
||||
class Classes(Base):
|
||||
__tablename__ = 'classes'
|
||||
class_id = Column(Integer, primary_key=True,autoincrement=True)
|
||||
head_teacher_id = Column(Integer, ForeignKey('teacher.t_id'), nullable=False)
|
||||
create_time = Column(Date,nullable=False)
|
||||
class_name = Column(String(50), nullable=False, unique=True)
|
||||
class_delete = Column(Boolean, default=False)
|
||||
counselor_id = Column(Integer, ForeignKey('counselor.id'))
|
||||
|
||||
students = relationship('Student', back_populates='classes')
|
||||
teachers = relationship("Teacher", secondary=teacher_classes, back_populates="classes")
|
||||
counselor = relationship("Counselor", back_populates="classes")
|
||||
@@ -1,23 +0,0 @@
|
||||
#创建顾问表
|
||||
from sqlalchemy import Column, Integer, String, Boolean, Date
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from database import Base
|
||||
|
||||
|
||||
|
||||
class Counselor(Base):
|
||||
__tablename__ = 'counselor'
|
||||
id = Column(Integer, primary_key=True,autoincrement=True)
|
||||
name = Column(String(100),nullable=False,unique=True)
|
||||
c_time = Column(Date,nullable=False) #创建时间
|
||||
c_de = Column(Boolean,default=False) #逻辑判断
|
||||
|
||||
#关联班级表,一对多
|
||||
classes=relationship("Classes",back_populates="counselor")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Counselor(id={self.id}, name={self.name})>"
|
||||
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
from sqlalchemy import create_engine, Column, Float, String, DateTime, Boolean, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from database import Base
|
||||
# db_url='mysql+pymysql://root:123456@localhost:3306/student_manager'
|
||||
# engine=create_engine(db_url,pool_size=5)
|
||||
#学生就业表
|
||||
class Employments(Base):
|
||||
__tablename__='employments'
|
||||
id = Column(String(20), ForeignKey('students.id'), primary_key=True) #学生id
|
||||
offer_open=Column(DateTime,nullable=True,comment='就业开放时间') #就业开放时间
|
||||
offer_review=Column(DateTime,nullable=True,comment='offer时间') #offer下发时间
|
||||
emp=Column(String(20),nullable=True,comment='就业公司名称') #就业公司名称
|
||||
sal = Column(Float, nullable=True, comment="就业薪资") #就业薪资
|
||||
is_deleted = Column(Boolean, default=False, comment="判断状态") #判断状态
|
||||
student = relationship("Student", back_populates="employment")
|
||||
|
||||
# Base.metadata.drop_all(engine)
|
||||
# Base.metadata.create_all(engine)
|
||||
# SessionLocal=sessionmaker(bind=engine)
|
||||
# # db=SessionLocal()
|
||||
# Base.metadata.create_all(engine)
|
||||
# db.close()
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# from sqlalchemy import Column, Integer, Boolean, ForeignKey,String
|
||||
# from sqlalchemy.orm import relationship
|
||||
# from database import Base
|
||||
# class Scores(Base): # 学生考核成绩管理表模型
|
||||
# __tablename__ = "scores"
|
||||
# scores_id = Column(Integer, primary_key=True, autoincrement=True) # 成绩自增长序号 是本表主键(1,2,3,4,5,6)
|
||||
# id = Column(String(30), ForeignKey('student.id')) #学生ID 是Student表的外键(1,1,1,2,2,2)
|
||||
# exam_round = Column(Integer, nullable=False) #考核序次 不唯一(1,2,3,1,2,3)
|
||||
# score = Column(Integer) #成绩 不唯一 可None
|
||||
# is_warning= Column(Boolean, default=False) #成绩红线预警 默认没预警 (高于60写入"True",低于60写入"False")
|
||||
# is_deleted = Column(Boolean, default=False) #逻辑删除标记 默认没删除 (删除写入"True",不删除写入"False")
|
||||
#
|
||||
# student = relationship("Student", back_populates="scores") # 外键 多对一 关联Student
|
||||
# def __repr__(self): # 魔法方法打印展示学生ID、对应序次的成绩
|
||||
# return f"<Scores(学生ID:{self.id}, 考核序次:{self.exam_round}, 成绩:{self.score})>"
|
||||
|
||||
from sqlalchemy import Column, Integer, Boolean, ForeignKey, String
|
||||
from sqlalchemy.orm import relationship
|
||||
from database import Base
|
||||
|
||||
|
||||
class Scores(Base):
|
||||
__tablename__ = "scores"
|
||||
|
||||
scores_id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
id = Column(String(20), ForeignKey('students.id')) # ← 关键修复
|
||||
exam_round = Column(Integer, nullable=False)
|
||||
score = Column(Integer)
|
||||
is_warning = Column(Boolean, default=False)
|
||||
is_deleted = Column(Boolean, default=False)
|
||||
|
||||
student = relationship("Student", back_populates="scores")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Scores(学生ID:{self.id}, 考核序次:{self.exam_round}, 成绩:{self.score})>"
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
from sqlalchemy import Column, String, Integer, Date, Boolean, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from database import Base
|
||||
|
||||
|
||||
class Student(Base):
|
||||
__tablename__ = "students"
|
||||
id = Column(String(20), primary_key=True, comment="学号")# 主键:学号
|
||||
|
||||
name = Column(String(50), nullable=False, comment="学生姓名")
|
||||
gender = Column(String(10), nullable=False, comment="性别:男 / 女")
|
||||
age = Column(Integer, comment="年龄")
|
||||
hometown = Column(String(100), comment="籍贯")
|
||||
graduate_school = Column(String(100), comment="毕业学校")
|
||||
major = Column(String(50), comment="专业")
|
||||
education = Column(String(30), comment="专科/本科/硕士")
|
||||
enrollment_date = Column(Date, comment="入学时间")
|
||||
graduation_date = Column(Date, comment="毕业时间")
|
||||
status = Column(String(20), default="在读", comment="状态:没有毕业/未就业/已就业")
|
||||
is_deleted = Column(Boolean, default=False, comment="逻辑删除标记")
|
||||
# 外键 班级编号
|
||||
class_name = Column(String(20), ForeignKey("classes.class_name"), comment="所属班级编号")
|
||||
|
||||
# 关系映射 学生和就业 一对一,一个学生对应一条就业信息 学生和成绩一对多,一个学生对应多条成绩
|
||||
scores = relationship("Scores", back_populates="student") # 一个学生多次考核成绩
|
||||
employment = relationship("Employments", back_populates="student", uselist=False) # 一对一就业信息
|
||||
classes = relationship("Classes", back_populates="students")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Student(no={self.id}, name={self.name}, class={self.class_name})>"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, Date
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from database import Base
|
||||
|
||||
|
||||
class Teacher(Base):
|
||||
__tablename__ = 'teacher'
|
||||
t_id = Column(Integer, primary_key=True, autoincrement=True) #老师id
|
||||
t_name = Column(String(50), nullable=False) #老师姓名
|
||||
t_gender = Column(String(50), nullable=False) #性别
|
||||
t_phone = Column(String(50), nullable=False) #手机号
|
||||
t_subject = Column(String(50), nullable=False) #教授科目
|
||||
t_created=Column(Date,nullable=False) #入职时间
|
||||
t_delete = Column(Boolean, default=False) #逻辑判断
|
||||
|
||||
|
||||
#关联表
|
||||
classes=relationship('Classes',secondary='teacher_classes',back_populates='teachers')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Teacher(t_id={self.t_id}, name='{self.t_name}', subject='{self.t_subject}')>"
|
||||
@@ -1,19 +0,0 @@
|
||||
# ============================================
|
||||
# 沃林学生管理系统 - Python 依赖
|
||||
# Python 3.11+
|
||||
# ============================================
|
||||
|
||||
# Web 框架与服务器
|
||||
fastapi>=0.115.0
|
||||
uvicorn[standard]>=0.30.0
|
||||
|
||||
# ORM 与数据校验
|
||||
sqlalchemy>=2.0.0
|
||||
pydantic>=2.0.0
|
||||
|
||||
# 表单支持
|
||||
python-multipart>=0.0.5
|
||||
|
||||
# MySQL 驱动与认证加密(caching_sha2_password 需要)
|
||||
pymysql>=1.1.0
|
||||
cryptography>=42.0.0
|
||||
@@ -1,28 +0,0 @@
|
||||
from datetime import date
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from fastapi import FastAPI
|
||||
|
||||
#请求体模型
|
||||
|
||||
#student_info
|
||||
#动态年龄范围查询:支持用户输入年龄阈值及比较条件(如大于、小于、等于、区间等),动态查询符合条件的学员信息
|
||||
class AnalysisStudentInfoAge(BaseModel):
|
||||
min_age: int = Field(ge=1, description="查询的最小年龄,若不填则仅查询上限")
|
||||
max_age: int = Field(le=100, description="查询的最大年龄,若不填则仅查询下限")
|
||||
operator: str = Field("区间", descroption="大于,大于等于,小于,小于等于,区间")
|
||||
|
||||
class AnalysisStudent_score(BaseModel):
|
||||
s_n : int = Field(...,ge=1,le=100,description = "输入分数")
|
||||
|
||||
class AnalysisStudent_score60(BaseModel):
|
||||
n : int = Field(...,ge=0,descriptino = "输入不及格次数")
|
||||
|
||||
class AnalysisStudent_score_class(BaseModel):
|
||||
order : str = Field("desc", description="排序:输入asc则升序 / 输入desc则降序")
|
||||
|
||||
class AnalysisStudent_employments_salary(BaseModel):
|
||||
n : int = Field(10,ge = 1,description = "查询就业薪资排名前n的学生信息")
|
||||
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from datetime import datetime,date
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class ClassCreate(BaseModel):
|
||||
class_name: str = Field(..., max_length=20, description="班级名称,唯一")
|
||||
head_teacher_id: int = Field(..., ge=1, description="班主任id")
|
||||
counselor_id: Optional[int] = Field(None, description="顾问id")
|
||||
create_time: Optional[datetime] = Field(None, description="开课时间,不传默认当前时间")
|
||||
teacher_ids: Optional[List[int]] = Field(None, description="绑定授课老师id列表")
|
||||
|
||||
|
||||
class ClassUpdateBase(BaseModel):
|
||||
class_name: Optional[str] = Field(None, max_length=20, description="班级名称")
|
||||
create_time: Optional[date] = Field(None, description="开课时间,格式 YYYY-MM-DD,不传默认当前日期")
|
||||
counselor_id: Optional[int] = Field(None, description="顾问id")
|
||||
|
||||
|
||||
class ClassHeadTeacherUpdate(BaseModel):
|
||||
head_teacher_id: int = Field(..., ge=1, description="新的班主任id")
|
||||
|
||||
|
||||
class ClassTeachersUpdate(BaseModel):
|
||||
teacher_ids: List[int] = Field(..., description="新老师id列表")
|
||||
|
||||
|
||||
class ClassDelete(BaseModel):
|
||||
class_ids: List[int] = Field(..., min_length=1, description="班级id列表")
|
||||
deleted: bool = Field(..., description="True 逻辑删除,False 恢复")
|
||||
|
||||
|
||||
class TeacherSimple(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
t_id: int
|
||||
t_name: str
|
||||
|
||||
class ClassDetail(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
class_id: int
|
||||
class_name: str
|
||||
head_teacher_id: int
|
||||
counselor_id: Optional[int] = None
|
||||
create_time: Optional[datetime] = None
|
||||
class_delete: bool = False
|
||||
teachers: List[TeacherSimple] = []
|
||||
|
||||
|
||||
class ClassPageResult(BaseModel):
|
||||
total: int
|
||||
items: List[ClassDetail]
|
||||
|
||||
|
||||
class BatchDeleteResult(BaseModel):
|
||||
affected_count: int
|
||||
@@ -1,47 +0,0 @@
|
||||
#请求体参数验证
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import Optional
|
||||
|
||||
|
||||
|
||||
#创建顾问 请求体
|
||||
class CounselorCreate(BaseModel):
|
||||
id : int
|
||||
name : Optional[str]
|
||||
c_time : Optional[str]
|
||||
c_de : bool
|
||||
#日期校验,
|
||||
@field_validator("c_time")
|
||||
@classmethod
|
||||
def c_time_validator(cls, v:str):
|
||||
try:
|
||||
datetime.strptime(v, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
raise ValueError("日期格式必须是 yyyy-MM-dd")
|
||||
return v
|
||||
|
||||
class DeleteCounselor(BaseModel):
|
||||
id: int
|
||||
|
||||
class UpdateCounselor(BaseModel):
|
||||
name: str = Optional[str]
|
||||
c_time: str = Optional[str]
|
||||
|
||||
@field_validator('c_time')
|
||||
@classmethod
|
||||
def j_t_created(cls, v: str):
|
||||
try:
|
||||
c_created_date=datetime.strptime(v, '%Y-%m-%d') # 时间转字符串日期
|
||||
except ValueError:
|
||||
raise ValueError('时间格式错误,请按照yyyy-MM-dd格式输入')
|
||||
if c_created_date.date() > datetime.now().date():
|
||||
raise ValueError('入职时间不能大于未来时间')
|
||||
return v
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
import uvicorn
|
||||
|
||||
#就业信息请求体
|
||||
class Emp_Class(BaseModel):
|
||||
id:str # 学生id
|
||||
name:str # 学生姓名
|
||||
class_name :str # 学生班级
|
||||
offer_open:Optional[datetime] # 就业开放时间
|
||||
offer_review:Optional[datetime] # offer下发时间
|
||||
emp:Optional[str]# 就业公司名称
|
||||
sal:Optional[float] # 就业薪资
|
||||
is_deleted:bool=True
|
||||
#增加、修改请求体
|
||||
class Emp_add_update(BaseModel):
|
||||
offer_open: datetime | None #等价Optional[datetime]=None
|
||||
offer_review: datetime | None
|
||||
emp: str | None
|
||||
sal: float | None
|
||||
#多条件参数查询
|
||||
class Emp_multi_Conditions(BaseModel):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
min_sal: Optional[float] = None
|
||||
max_sal: Optional[float] = None
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run("app", host="localhost", port=8000,reload=True)
|
||||
@@ -1,57 +0,0 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
#增加类
|
||||
class AddTeacher(BaseModel):
|
||||
t_name:str= Field(...,min_length=1,max_length=20,description='老师姓名'),
|
||||
t_gender:str=Field(...,min_length=1,max_length=10,description='性别')
|
||||
t_phone:str=Field(...,max_length=15,description="手机号码最多15位")
|
||||
t_subject:str=Field(...,min_length=1,max_length=50,description='教授科目')
|
||||
t_created:str=Field(...,description='入职时间,格式为%Y-%m-%d')
|
||||
|
||||
@field_validator('t_created')
|
||||
def j_t_created(cls,v:str):
|
||||
try:
|
||||
t_created_date=datetime.strptime(v,'%Y-%m-%d') #时间转字符串日期
|
||||
except ValueError:
|
||||
raise ValueError('时间格式错误,请按照yyyy-MM-dd格式输入')
|
||||
|
||||
if t_created_date.date() > datetime.now().date():
|
||||
raise ValueError('入职时间不能大于未来时间')
|
||||
return v
|
||||
|
||||
#删除类
|
||||
class DeleteTeacher(BaseModel):
|
||||
t_id:int=Field(...,gt=0,description='老师id'),
|
||||
t_name:str=Field(...,min_length=1,max_length=20,description='老师姓名')
|
||||
|
||||
|
||||
#修改类
|
||||
class UpdateTeacher(BaseModel):
|
||||
t_name: str = Field(..., min_length=1, max_length=20, description='老师姓名')
|
||||
t_gender: str = Field(..., min_length=1, max_length=10, description='性别')
|
||||
t_phone: str = Field(..., max_length=15, description="手机号码最多15位")
|
||||
t_subject: str = Field(..., min_length=1, max_length=50, description='教授科目')
|
||||
t_created: str = Field(..., description='入职时间,格式为%Y-%m-%d')
|
||||
|
||||
@field_validator('t_created')
|
||||
def j_t_created(cls, v: str):
|
||||
try:
|
||||
t_created_date = datetime.strptime(v, '%Y-%m-%d') # 时间转字符串日期
|
||||
except ValueError:
|
||||
raise ValueError('时间格式错误,请按照yyyy-MM-dd格式输入')
|
||||
|
||||
if t_created_date.date() > datetime.now().date():
|
||||
raise ValueError('入职时间不能大于未来时间')
|
||||
return v
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class AddScore(BaseModel):
|
||||
id: str = Field(...,min_length=1,max_length=10,description="学生ID")
|
||||
exam_round: int = Field(...,ge=1,le=10,description="考核序次1~10")
|
||||
score: int = Field(...,ge=0,le=100,description="分数0~100")
|
||||
|
||||
class QueryID(BaseModel):
|
||||
id: str = Field(..., min_length=1, max_length=10, description="学生ID")
|
||||
|
||||
class QueryExamRound(BaseModel):
|
||||
exam_round: int = Field(..., ge=1, le=10, description="考核序次1~10")
|
||||
|
||||
class QueryScore(BaseModel):
|
||||
score: int = Field(..., ge=0, le=100, description="分数0~100")
|
||||
|
||||
class UpdateScore(BaseModel):
|
||||
id: str = Field(...,min_length=1,max_length=10,description="学生ID")
|
||||
exam_round: int = Field(...,ge=1,le=10,description="考核序次1~10")
|
||||
score: int = Field(...,ge=0,le=100,description="分数0~100")
|
||||
|
||||
class DeleteScore(BaseModel):
|
||||
id: str = Field(..., min_length=1, max_length=10, description="学生ID")
|
||||
exam_round: int = Field(..., ge=1, le=10, description="考核序次1~10")
|
||||
@@ -1,98 +0,0 @@
|
||||
# 校验时间,入学时间小于毕业时间,
|
||||
from datetime import datetime
|
||||
from datetime import date
|
||||
from pydantic import BaseModel, field_validator, Field, ConfigDict
|
||||
|
||||
# 请求体模型:两个日期都允许为空 在api.studend调用检测
|
||||
class StudentDate(BaseModel):
|
||||
enrollment_date: str | None = Field(default=None, description="入学日期,格式为 yyyy-MM-dd,非必填")
|
||||
graduation_date: str | None = Field(default=None, description="毕业日期,格式 yyyy-MM-dd,非必填")
|
||||
# 日期校验函数
|
||||
def student_date(enrollment_date: str , graduation_date: str):
|
||||
# 情况1:两个日期都没传,直接通过
|
||||
if not enrollment_date and not graduation_date:
|
||||
return "时间校验通过"
|
||||
|
||||
# 只传了入学日期或者毕业日期,只校验格式
|
||||
if enrollment_date and not graduation_date:
|
||||
try:
|
||||
datetime.strptime(enrollment_date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return "入学日期格式错误,请按照 yyyy-MM-dd 输入"
|
||||
return "时间校验通过"
|
||||
elif graduation_date and not enrollment_date:
|
||||
try:
|
||||
datetime.strptime(graduation_date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return "毕业日期格式错误,请按照 yyyy-MM-dd 输入"
|
||||
return "时间校验通过"
|
||||
|
||||
# 如果两个日期都传了,先分别校验格式,再校验先后顺序
|
||||
try:
|
||||
enrollment_date = datetime.strptime(enrollment_date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return "入学日期格式错误,请按照 yyyy-MM-dd 输入"
|
||||
try:
|
||||
graduation_date = datetime.strptime(graduation_date, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
return "毕业日期格式错误,请按照 yyyy-MM-dd 输入"
|
||||
# 核心业务判断:毕业时间不能早于入学时间
|
||||
if graduation_date < enrollment_date:
|
||||
return "毕业时间必须晚于入学时间"
|
||||
return "时间校验通过"
|
||||
|
||||
#创建学生的请求体 ...(必填)
|
||||
class StudentCreate(BaseModel):
|
||||
id: str = Field(..., min_length=1, max_length=20, description="学号")
|
||||
name: str = Field(..., min_length=1, max_length=50, description="学生姓名")
|
||||
gender: str = Field(..., pattern=r"^(男|女)$", description="性别:男/女")
|
||||
class_name: str = Field(..., min_length=1, max_length=20, description="所属班级编号")
|
||||
#非必填
|
||||
age: int | None = Field(default=None, ge=1, le=100, description="年龄")
|
||||
hometown: str | None = Field(default=None, max_length=100, description="籍贯")
|
||||
graduate_school: str | None = Field(default=None, max_length=100, description="毕业院校")
|
||||
major: str | None = Field(default=None, max_length=50, description="专业")
|
||||
education: str | None = Field(default=None, max_length=30, description="学历:专科/本科/硕士")
|
||||
enrollment_date: date | None = Field(default=None, description="入学时间,格式yyyy-MM-dd")
|
||||
graduation_date: date | None = Field(default=None, description="毕业时间,格式yyyy-MM-dd")
|
||||
status: str | None = Field(default="在读", description="状态:未毕业/进入就业/已就业")
|
||||
# 更新修改学生
|
||||
class StudentUpdate(BaseModel):
|
||||
name: str | None = Field(default=None, min_length=1, max_length=50, description="学生姓名")
|
||||
class_name: str | None = Field(default=None, description="班级名称")
|
||||
gender: str | None = Field(default=None, pattern=r"^(男|女)$", description="性别:男/女")
|
||||
class_no: str | None = Field(default=None, max_length=20, description="所属班级编号")
|
||||
age: int | None = Field(default=None, ge=1, le=100, description="年龄")
|
||||
hometown: str | None = Field(default=None, max_length=100, description="籍贯")
|
||||
graduate_school: str | None = Field(default=None, max_length=100, description="毕业院校")
|
||||
major: str | None = Field(default=None, max_length=50, description="专业")
|
||||
education: str | None = Field(default=None, max_length=30, description="学历")
|
||||
enrollment_date: date | None = Field(default=None, description="入学时间")
|
||||
graduation_date: date | None = Field(default=None, description="毕业时间")
|
||||
status: str | None = Field(default=None, description="状态:未毕业/进入就业/已就业")
|
||||
|
||||
|
||||
|
||||
|
||||
# 响应体
|
||||
class StudentResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True) #支持读取SQLAlchemy ORM对象属性
|
||||
id: str
|
||||
name: str
|
||||
gender: str
|
||||
class_name: str | None = None
|
||||
age: int | None = None
|
||||
hometown: str | None = None
|
||||
graduate_school: str | None = None
|
||||
major: str | None = None
|
||||
education: str | None = None
|
||||
enrollment_date: date | None = None
|
||||
graduation_date: date | None = None
|
||||
status: str | None = None
|
||||
is_deleted: bool = False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
from database import SessionLocal, engine, Base
|
||||
from datetime import date, datetime
|
||||
|
||||
|
||||
from model.teachers import Teacher
|
||||
from model.counselor import Counselor
|
||||
from model.classes_model import Classes
|
||||
from model.student_info import Student
|
||||
from model.scores import Scores
|
||||
from model.employments import Employments
|
||||
|
||||
# Base.metadata.drop_all(bind=engine)
|
||||
# Base.metadata.create_all(bind=engine)
|
||||
|
||||
# 教师数据
|
||||
teachers_data = [
|
||||
{"t_name": "周凌云", "t_gender": "男", "t_phone": "13800000001", "t_subject": "python",
|
||||
"t_created": date(2023, 1, 1), "t_delete": False},
|
||||
{"t_name": "张显", "t_gender": "女", "t_phone": "2783263268242", "t_subject": "mysql",
|
||||
"t_created": date(2023, 1, 1), "t_delete": False},
|
||||
]
|
||||
|
||||
# 2. 顾问数据
|
||||
counselors_data = [
|
||||
{"name": "吴振玮", "c_time": date(2023, 1, 1), "c_de": False},
|
||||
{"name": "孙波", "c_time": date(2023, 1, 1), "c_de": False},
|
||||
]
|
||||
|
||||
# 班级数据
|
||||
classes_data = [
|
||||
{"class_name": "沃林0803班", "head_teacher_name": "周凌云", "counselor_name": "吴振玮", "create_time": date(2023, 1, 1), "class_delete": False},
|
||||
{"class_name": "沃林0814班", "head_teacher_name": "张显", "counselor_name": "孙波", "create_time": date(2023, 1, 1), "class_delete": False},
|
||||
{"class_name": "沃林0824班", "head_teacher_name": "周凌云", "counselor_name": "孙波", "create_time": date(2023, 1, 1), "class_delete": False},
|
||||
]
|
||||
|
||||
# 学生数据
|
||||
students_data = [
|
||||
{"id": "2024001", "name": "张伟", "gender": "男", "class_name": "沃林0803班", "age": 22, "hometown": "北京",
|
||||
"graduate_school": "北京四中", "major": "计算机科学与技术", "education": "本科",
|
||||
"enrollment_date": date(2022, 9, 1), "graduation_date": date(2026, 6, 30), "status": "未就业", "is_deleted": False},
|
||||
{"id": "2024002", "name": "王芳", "gender": "女", "class_name": "沃林0803班", "age": 23, "hometown": "上海",
|
||||
"graduate_school": "上海中学", "major": "计算机科学与技术", "education": "硕士",
|
||||
"enrollment_date": date(2022, 9, 1), "graduation_date": date(2026, 6, 30), "status": "在读", "is_deleted": False},
|
||||
{"id": "2024003", "name": "李娜", "gender": "女", "class_name": "沃林0824班", "age": 24, "hometown": "广州",
|
||||
"graduate_school": "华南师范大学附属中学", "major": "计算机科学与技术", "education": "本科",
|
||||
"enrollment_date": date(2022, 9, 1), "graduation_date": date(2026, 6, 30), "status": "未就业", "is_deleted": False},
|
||||
{"id": "2024004", "name": "刘洋", "gender": "男", "class_name": "沃林0824班", "age": 25, "hometown": "深圳",
|
||||
"graduate_school": "深圳中学", "major": "计算机科学与技术", "education": "本科",
|
||||
"enrollment_date": date(2022, 9, 1), "graduation_date": date(2026, 6, 30), "status": "已就业", "is_deleted": False},
|
||||
{"id": "2024005", "name": "陈静", "gender": "女", "class_name": "沃林0803班", "age": 26, "hometown": "杭州",
|
||||
"graduate_school": "杭州第二中学", "major": "软件工程", "education": "本科", "enrollment_date": date(2022, 9, 1),
|
||||
"graduation_date": date(2026, 6, 30), "status": "在读", "is_deleted": False},
|
||||
{"id": "2024006", "name": "杨强", "gender": "男", "class_name": "沃林0814班", "age": 27, "hometown": "成都",
|
||||
"graduate_school": "成都七中", "major": "软件工程", "education": "本科", "enrollment_date": date(2022, 9, 1),
|
||||
"graduation_date": date(2026, 6, 30), "status": "未就业", "is_deleted": False},
|
||||
{"id": "2024007", "name": "赵敏", "gender": "女", "class_name": "沃林0803班", "age": 28, "hometown": "武汉",
|
||||
"graduate_school": "武汉外国语学校", "major": "人工智能", "education": "硕士", "enrollment_date": date(2022, 9, 1),
|
||||
"graduation_date": date(2026, 6, 30), "status": "已就业", "is_deleted": False},
|
||||
{"id": "2024008", "name": "黄磊", "gender": "男", "class_name": "沃林0814班", "age": 29, "hometown": "南京",
|
||||
"graduate_school": "南京师范大学附属中学", "major": "人工智能", "education": "本科",
|
||||
"enrollment_date": date(2022, 9, 1), "graduation_date": date(2026, 6, 30), "status": "在读", "is_deleted": False},
|
||||
{"id": "2024009", "name": "周杰", "gender": "男", "class_name": "沃林0824班", "age": 30, "hometown": "西安",
|
||||
"graduate_school": "西安高新第一中学", "major": "网络工程", "education": "硕士",
|
||||
"enrollment_date": date(2022, 9, 1), "graduation_date": date(2026, 6, 30), "status": "在读", "is_deleted": False},
|
||||
{"id": "2024010", "name": "吴霞", "gender": "女", "class_name": "沃林0814班", "age": 31, "hometown": "重庆",
|
||||
"graduate_school": "重庆巴蜀中学", "major": "网络工程", "education": "本科", "enrollment_date": date(2022, 9, 1),
|
||||
"graduation_date": date(2026, 6, 30), "status": "已就业", "is_deleted": False},
|
||||
]
|
||||
|
||||
# 5. 成绩数据
|
||||
scores_data = [
|
||||
{"id": "2024001", "exam_round": 1, "score": 85, "is_warning": False, "is_deleted": False},
|
||||
{"id": "2024002", "exam_round": 1, "score": 55, "is_warning": True, "is_deleted": False},
|
||||
{"id": "2024003", "exam_round": 1, "score": 92, "is_warning": False, "is_deleted": False},
|
||||
{"id": "2024004", "exam_round": 1, "score": 78, "is_warning": False, "is_deleted": False},
|
||||
{"id": "2024005", "exam_round": 1, "score": 45, "is_warning": True, "is_deleted": False},
|
||||
]
|
||||
|
||||
#6. 就业数据
|
||||
employments_data = [
|
||||
{"id": "2024004", "offer_open": datetime(2026, 3, 1, 9, 0), "offer_review": datetime(2026, 3, 15, 14, 30), "emp": "腾讯科技", "sal": 25000.0, "is_deleted": False},
|
||||
{"id": "2024007", "offer_open": datetime(2026, 2, 10, 10, 0), "offer_review": datetime(2026, 2, 20, 16, 0), "emp": "阿里巴巴", "sal": 30000.0, "is_deleted": False},
|
||||
{"id": "2024010", "offer_open": datetime(2026, 4, 5, 9, 30), "offer_review": datetime(2026, 4, 12, 11, 0), "emp": "字节跳动", "sal": 28000.0, "is_deleted": False},
|
||||
{"id": "2024001", "offer_open": datetime(2026, 5, 1, 9, 0), "offer_review": None, "emp": None, "sal": None, "is_deleted": False},
|
||||
{"id": "2024002", "offer_open": None, "offer_review": None, "emp": None, "sal": None, "is_deleted": True},
|
||||
]
|
||||
|
||||
|
||||
def insert_data():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 插入教师
|
||||
print("正在写入教师数据...")
|
||||
for t in teachers_data:
|
||||
if not db.query(Teacher).filter(Teacher.t_name == t["t_name"]).first():
|
||||
db.add(Teacher(**t))
|
||||
db.commit()
|
||||
|
||||
# 插入顾问 (Counselor)
|
||||
print("正在写入顾问数据...")
|
||||
for c in counselors_data:
|
||||
if not db.query(Counselor).filter(Counselor.name == c["name"]).first():
|
||||
db.add(Counselor(**c))
|
||||
db.commit()
|
||||
|
||||
# 3. 插入班级 (Classes)
|
||||
print("正在写入班级数据...")
|
||||
for cl in classes_data:
|
||||
if not db.query(Classes).filter(Classes.class_name == cl["class_name"]).first():
|
||||
# 查找对应老师和顾问的自增 ID
|
||||
t = db.query(Teacher).filter(Teacher.t_name == cl["head_teacher_name"]).first()
|
||||
c = db.query(Counselor).filter(Counselor.name == cl["counselor_name"]).first()
|
||||
if t and c:
|
||||
# 注意:Classes模型有 teachers 多对多关系,这里我们只处理必需的 head_teacher_id 和 counselor_id
|
||||
db.add(Classes(
|
||||
class_name=cl["class_name"],
|
||||
head_teacher_id=t.t_id,
|
||||
counselor_id=c.id,
|
||||
create_time=cl["create_time"],
|
||||
class_delete=cl["class_delete"]
|
||||
))
|
||||
else:
|
||||
print(f" 找不到班级 {cl['class_name']} 对应的老师或顾问,跳过!")
|
||||
db.commit()
|
||||
|
||||
# --- 4. 插入学生 (Student) ---
|
||||
print("正在写入学生数据...")
|
||||
for s in students_data:
|
||||
if not db.query(Student).filter(Student.id == s["id"]).first():
|
||||
db.add(Student(**s))
|
||||
db.commit()
|
||||
|
||||
# 5. 插入成绩 (Scores)
|
||||
print("正在写入成绩数据...")
|
||||
for sc in scores_data:
|
||||
if not db.query(Scores).filter(Scores.id == sc["id"], Scores.exam_round == sc["exam_round"]).first():
|
||||
db.add(Scores(**sc))
|
||||
db.commit()
|
||||
|
||||
# 6. 插入就业信息 (Employments)
|
||||
print("正在写入就业数据...")
|
||||
for emp in employments_data:
|
||||
if not db.query(Employments).filter(Employments.id == emp["id"]).first():
|
||||
db.add(Employments(**emp))
|
||||
db.commit()
|
||||
|
||||
# 最终统计
|
||||
print(f"\n 数据初始化完成!")
|
||||
print(f"班级: {db.query(Classes).count()} 个 | 学生: {db.query(Student).count()} 名")
|
||||
print(f"成绩: {db.query(Scores).count()} 条 | 就业: {db.query(Employments).count()} 条")
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"\n 插入失败,已回滚:{e}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
insert_data()
|
||||
@@ -1,785 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>沃林学生管理系统</title>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
|
||||
<script src="https://unpkg.com/element-plus"></script>
|
||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||
<script src="https://unpkg.com/echarts/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; background-color: #f0f2f5; }
|
||||
#app { display: flex; height: 100vh; }
|
||||
.sidebar { width: 220px; background-color: #001529; color: #fff; display: flex; flex-direction: column; }
|
||||
.sidebar h2 { text-align: center; color: #fff; padding: 20px 0; margin: 0; font-size: 18px; border-bottom: 1px solid #1f2d3d; }
|
||||
.sidebar .el-menu { border-right: none; flex: 1; }
|
||||
.main-content { flex: 1; padding: 20px; overflow-y: auto; }
|
||||
.header-title { font-size: 24px; margin-bottom: 20px; color: #303133; font-weight: 500; }
|
||||
.search-bar { margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap; }
|
||||
.table-container { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05); }
|
||||
.chart-container { height: 400px; margin-top: 20px; }
|
||||
.stat-card { text-align: center; padding: 20px; }
|
||||
.stat-card .num { font-size: 32px; font-weight: bold; color: #409EFF; margin-bottom: 10px; }
|
||||
.stat-card .label { color: #909399; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<!-- 侧边栏 -->
|
||||
<div class="sidebar">
|
||||
<h2>沃林学生管理系统</h2>
|
||||
<el-menu :default-active="activeMenu" background-color="#001529" text-color="#fff" active-text-color="#409EFF" @select="handleMenuSelect">
|
||||
<el-menu-item index="dashboard">📊 统计分析</el-menu-item>
|
||||
<el-menu-item index="students">👨🎓 学生管理</el-menu-item>
|
||||
<el-menu-item index="classes">🏫 班级管理</el-menu-item>
|
||||
<el-menu-item index="teachers">👨🏫 教师管理</el-menu-item>
|
||||
<el-menu-item index="counselors">💼 顾问管理</el-menu-item>
|
||||
<el-menu-item index="scores">📝 成绩管理</el-menu-item>
|
||||
<el-menu-item index="employments">💼 就业管理</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="main-content">
|
||||
<!-- 统计分析 -->
|
||||
<div v-if="activeMenu === 'dashboard'">
|
||||
<div class="header-title">统计分析</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="num">{{ stats.totalStudents }}</div>
|
||||
<div class="label">学生总数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="num">{{ stats.totalClasses }}</div>
|
||||
<div class="label">班级总数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="num">{{ stats.totalTeachers }}</div>
|
||||
<div class="label">教师总数</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" style="margin-top: 20px;">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header><div class="card-header"><span>班级人数分布</span></div></template>
|
||||
<div ref="classChart" class="chart-container"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header><div class="card-header"><span>就业薪资排名 (前5名)</span></div></template>
|
||||
<div ref="salaryChart" class="chart-container"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 学生管理 -->
|
||||
<div v-if="activeMenu === 'students'">
|
||||
<div class="header-title">学生管理</div>
|
||||
<div class="table-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="studentQuery.id" placeholder="学号" style="width: 150px;" clearable></el-input>
|
||||
<el-input v-model="studentQuery.name" placeholder="姓名" style="width: 150px;" clearable></el-input>
|
||||
<el-input v-model="studentQuery.class_name" placeholder="班级" style="width: 150px;" clearable></el-input>
|
||||
<el-button type="primary" @click="fetchStudents">查询</el-button>
|
||||
<el-button type="success" @click="openStudentDialog()">新增学生</el-button>
|
||||
</div>
|
||||
<el-table :data="studentList" border stripe v-loading="loading">
|
||||
<el-table-column prop="id" label="学号" width="150"></el-table-column>
|
||||
<el-table-column prop="name" label="姓名" width="100"></el-table-column>
|
||||
<el-table-column prop="gender" label="性别" width="80"></el-table-column>
|
||||
<el-table-column prop="class_name" label="班级" width="150"></el-table-column>
|
||||
<el-table-column prop="age" label="年龄" width="80"></el-table-column>
|
||||
<el-table-column prop="major" label="专业" width="150"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '已就业' ? 'success' : (scope.row.status === '未就业' ? 'danger' : 'warning')">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="openStudentDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteStudent(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 班级管理 -->
|
||||
<div v-if="activeMenu === 'classes'">
|
||||
<div class="header-title">班级管理</div>
|
||||
<div class="table-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="classQuery.class_name" placeholder="班级名称" style="width: 200px;" clearable></el-input>
|
||||
<el-button type="primary" @click="fetchClasses">查询</el-button>
|
||||
<el-button type="success" @click="openClassDialog()">新增班级</el-button>
|
||||
</div>
|
||||
<el-table :data="classList" border stripe v-loading="loading">
|
||||
<el-table-column prop="class_id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="class_name" label="班级名称"></el-table-column>
|
||||
<el-table-column prop="head_teacher_id" label="班主任ID" width="120"></el-table-column>
|
||||
<el-table-column prop="counselor_id" label="顾问ID" width="120"></el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="openClassDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteClass(scope.row.class_id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top: 20px;" background layout="prev, pager, next" :total="classTotal" :page-size="classQuery.page_size" @current-change="handleClassPageChange"></el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 教师管理 -->
|
||||
<div v-if="activeMenu === 'teachers'">
|
||||
<div class="header-title">教师管理</div>
|
||||
<div class="table-container">
|
||||
<div class="search-bar"><el-button type="success" @click="openTeacherDialog()">新增教师</el-button></div>
|
||||
<el-table :data="teacherList" border stripe v-loading="loading">
|
||||
<el-table-column prop="t_id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="t_name" label="姓名" width="120"></el-table-column>
|
||||
<el-table-column prop="t_gender" label="性别" width="80"></el-table-column>
|
||||
<el-table-column prop="t_phone" label="电话" width="150"></el-table-column>
|
||||
<el-table-column prop="t_subject" label="教授科目" width="150"></el-table-column>
|
||||
<el-table-column prop="t_created" label="入职时间" width="150"></el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="openTeacherDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteTeacher(scope.row.t_id, scope.row.t_name)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination style="margin-top: 20px;" background layout="prev, pager, next" :total="teacherTotal" :page-size="teacherQuery.size" @current-change="handleTeacherPageChange"></el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 顾问管理 -->
|
||||
<div v-if="activeMenu === 'counselors'">
|
||||
<div class="header-title">顾问管理</div>
|
||||
<div class="table-container">
|
||||
<div class="search-bar"><el-button type="success" @click="openCounselorDialog()">新增顾问</el-button></div>
|
||||
<el-table :data="counselorList" border stripe v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="name" label="姓名"></el-table-column>
|
||||
<el-table-column prop="c_time" label="入职时间"></el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="openCounselorDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteCounselor(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成绩管理 -->
|
||||
<div v-if="activeMenu === 'scores'">
|
||||
<div class="header-title">成绩管理</div>
|
||||
<div class="table-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="scoreQuery.id" placeholder="学号" style="width: 150px;" clearable></el-input>
|
||||
<el-input v-model="scoreQuery.exam_round" placeholder="考核序次" style="width: 150px;" clearable></el-input>
|
||||
<el-button type="primary" @click="fetchScores">查询</el-button>
|
||||
<el-button type="success" @click="openScoreDialog()">录入成绩</el-button>
|
||||
</div>
|
||||
<el-table :data="scoreList" border stripe v-loading="loading">
|
||||
<el-table-column prop="id" label="学号" width="150"></el-table-column>
|
||||
<el-table-column prop="exam_round" label="考核序次" width="120"></el-table-column>
|
||||
<el-table-column prop="score" label="成绩" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.score < 60 ? 'danger' : 'success'">{{ scope.row.score }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="openScoreDialog(scope.row)">修改</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteScore(scope.row.id, scope.row.exam_round)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 就业管理 -->
|
||||
<div v-if="activeMenu === 'employments'">
|
||||
<div class="header-title">就业管理</div>
|
||||
<div class="table-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="employmentQuery.id" placeholder="学号" style="width: 150px;" clearable></el-input>
|
||||
<el-input v-model="employmentQuery.name" placeholder="公司名称" style="width: 150px;" clearable></el-input>
|
||||
<el-button type="primary" @click="fetchEmployments">查询</el-button>
|
||||
</div>
|
||||
<el-table :data="employmentList" border stripe v-loading="loading">
|
||||
<el-table-column prop="id" label="学号" width="120"></el-table-column>
|
||||
<el-table-column prop="name" label="姓名" width="100"></el-table-column>
|
||||
<el-table-column prop="class_name" label="班级" width="150"></el-table-column>
|
||||
<el-table-column prop="emp" label="就业公司" width="150"></el-table-column>
|
||||
<el-table-column prop="sal" label="薪资" width="120"></el-table-column>
|
||||
<el-table-column prop="offer_open" label="就业开放时间" width="180"></el-table-column>
|
||||
<el-table-column prop="offer_review" label="Offer下发时间" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="openEmploymentDialog(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗:学生 -->
|
||||
<el-dialog v-model="studentDialogVisible" :title="isStudentEdit ? '编辑学生' : '新增学生'" width="600px">
|
||||
<el-form :model="studentForm" label-width="100px">
|
||||
<el-form-item label="学号" v-if="!isStudentEdit">
|
||||
<el-input v-model="studentForm.id" placeholder="请输入学号(如 2024001)" maxlength="20" show-word-limit clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" v-else>
|
||||
<el-input v-model="studentForm.id" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名"><el-input v-model="studentForm.name" placeholder="请输入姓名"></el-input></el-form-item>
|
||||
<el-form-item label="性别">
|
||||
<el-radio-group v-model="studentForm.gender">
|
||||
<el-radio label="男">男</el-radio><el-radio label="女">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="班级"><el-input v-model="studentForm.class_name" placeholder="请输入班级名称"></el-input></el-form-item>
|
||||
<el-form-item label="年龄"><el-input-number v-model="studentForm.age" :min="1" :max="100"></el-input-number></el-form-item>
|
||||
<el-form-item label="专业"><el-input v-model="studentForm.major" placeholder="请输入专业"></el-input></el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="studentForm.status" placeholder="请选择状态">
|
||||
<el-option label="在读" value="在读"></el-option>
|
||||
<el-option label="未就业" value="未就业"></el-option>
|
||||
<el-option label="已就业" value="已就业"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="studentDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveStudent">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 弹窗:班级 -->
|
||||
<el-dialog v-model="classDialogVisible" :title="classForm.class_id ? '编辑班级' : '新增班级'" width="600px">
|
||||
<el-form :model="classForm" label-width="100px">
|
||||
<el-form-item label="班级名称"><el-input v-model="classForm.class_name" placeholder="请输入班级名称"></el-input></el-form-item>
|
||||
<el-form-item label="班主任ID"><el-input-number v-model="classForm.head_teacher_id" :min="1"></el-input-number></el-form-item>
|
||||
<el-form-item label="顾问ID"><el-input-number v-model="classForm.counselor_id" :min="1"></el-input-number></el-form-item>
|
||||
<el-form-item label="开课时间"><el-date-picker v-model="classForm.create_time" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"></el-date-picker></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="classDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveClass">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 弹窗:教师 -->
|
||||
<el-dialog v-model="teacherDialogVisible" :title="teacherForm.t_id ? '编辑教师' : '新增教师'" width="600px">
|
||||
<el-form :model="teacherForm" label-width="100px">
|
||||
<el-form-item label="姓名"><el-input v-model="teacherForm.t_name" placeholder="请输入姓名"></el-input></el-form-item>
|
||||
<el-form-item label="性别">
|
||||
<el-radio-group v-model="teacherForm.t_gender">
|
||||
<el-radio label="男">男</el-radio><el-radio label="女">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话"><el-input v-model="teacherForm.t_phone" placeholder="请输入电话"></el-input></el-form-item>
|
||||
<el-form-item label="教授科目"><el-input v-model="teacherForm.t_subject" placeholder="请输入教授科目"></el-input></el-form-item>
|
||||
<el-form-item label="入职时间"><el-date-picker v-model="teacherForm.t_created" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"></el-date-picker></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="teacherDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveTeacher">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 弹窗:顾问 -->
|
||||
<el-dialog v-model="counselorDialogVisible" :title="counselorForm.id ? '编辑顾问' : '新增顾问'" width="600px">
|
||||
<el-form :model="counselorForm" label-width="100px">
|
||||
<el-form-item label="ID" v-if="!counselorForm.id"><el-input-number v-model="counselorForm.id" :min="1"></el-input-number></el-form-item>
|
||||
<el-form-item label="姓名"><el-input v-model="counselorForm.name" placeholder="请输入姓名"></el-input></el-form-item>
|
||||
<el-form-item label="入职时间"><el-date-picker v-model="counselorForm.c_time" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"></el-date-picker></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="counselorDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveCounselor">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 弹窗:成绩(修复学号无法输入) -->
|
||||
<el-dialog v-model="scoreDialogVisible" :title="isScoreEdit ? '修改成绩' : '录入成绩'" width="600px">
|
||||
<el-form :model="scoreForm" label-width="100px">
|
||||
<el-form-item label="学号">
|
||||
<el-input v-model="scoreForm.id" placeholder="请输入学号" :disabled="isScoreEdit"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="考核序次">
|
||||
<el-input-number v-model="scoreForm.exam_round" :min="1" :max="10" :disabled="isScoreEdit"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="成绩">
|
||||
<el-input-number v-model="scoreForm.score" :min="0" :max="100"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="scoreDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveScore">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 弹窗:就业 -->
|
||||
<el-dialog v-model="employmentDialogVisible" title="编辑就业信息" width="600px">
|
||||
<el-form :model="employmentForm" label-width="120px">
|
||||
<el-form-item label="学号"><el-input v-model="employmentForm.id" disabled></el-input></el-form-item>
|
||||
<el-form-item label="就业公司"><el-input v-model="employmentForm.emp" placeholder="请输入公司名称"></el-input></el-form-item>
|
||||
<el-form-item label="薪资"><el-input-number v-model="employmentForm.sal" :min="0" :precision="2"></el-input-number></el-form-item>
|
||||
<el-form-item label="就业开放时间"><el-date-picker v-model="employmentForm.offer_open" type="datetime" placeholder="选择时间" value-format="YYYY-MM-DDTHH:mm:ss"></el-date-picker></el-form-item>
|
||||
<el-form-item label="Offer下发时间"><el-date-picker v-model="employmentForm.offer_review" type="datetime" placeholder="选择时间" value-format="YYYY-MM-DDTHH:mm:ss"></el-date-picker></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="employmentDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveEmployment">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { createApp, ref, reactive, onMounted, nextTick } = Vue;
|
||||
const { ElMessage, ElMessageBox } = ElementPlus;
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const activeMenu = ref('dashboard');
|
||||
const loading = ref(false);
|
||||
const baseUrl = 'http://127.0.0.1:8001';
|
||||
|
||||
// --- 统计分析 ---
|
||||
const stats = reactive({ totalStudents: 0, totalClasses: 0, totalTeachers: 0 });
|
||||
const classChart = ref(null);
|
||||
const salaryChart = ref(null);
|
||||
|
||||
// --- 学生管理 ---
|
||||
const studentList = ref([]);
|
||||
const studentQuery = reactive({ id: '', name: '', class_name: '' });
|
||||
const studentDialogVisible = ref(false);
|
||||
const studentForm = reactive({ id: '', name: '', gender: '男', class_name: '', age: 18, major: '', status: '在读' });
|
||||
const isStudentEdit = ref(false);
|
||||
|
||||
// --- 班级管理 ---
|
||||
const classList = ref([]);
|
||||
const classTotal = ref(0);
|
||||
const classQuery = reactive({ class_name: '', page_num: 1, page_size: 10 });
|
||||
const classDialogVisible = ref(false);
|
||||
const classForm = reactive({ class_id: null, class_name: '', head_teacher_id: 1, counselor_id: 1, create_time: '' });
|
||||
|
||||
// --- 教师管理 ---
|
||||
const teacherList = ref([]);
|
||||
const teacherTotal = ref(0);
|
||||
const teacherQuery = reactive({ page: 1, size: 10 });
|
||||
const teacherDialogVisible = ref(false);
|
||||
const teacherForm = reactive({ t_id: null, t_name: '', t_gender: '男', t_phone: '', t_subject: '', t_created: '' });
|
||||
|
||||
// --- 顾问管理 ---
|
||||
const counselorList = ref([]);
|
||||
const counselorDialogVisible = ref(false);
|
||||
const counselorForm = reactive({ id: null, name: '', c_time: '' });
|
||||
|
||||
// --- 成绩管理 ---
|
||||
const scoreList = ref([]);
|
||||
const scoreQuery = reactive({ id: '', exam_round: '' });
|
||||
const scoreDialogVisible = ref(false);
|
||||
const scoreForm = reactive({ id: '', exam_round: 1, score: 0 });
|
||||
const isScoreEdit = ref(false); // ★ 新增:区分录入与修改
|
||||
|
||||
// --- 就业管理 ---
|
||||
const employmentList = ref([]);
|
||||
const employmentQuery = reactive({ id: '', name: '' });
|
||||
const employmentDialogVisible = ref(false);
|
||||
const employmentForm = reactive({ id: '', emp: '', sal: 0, offer_open: '', offer_review: '' });
|
||||
|
||||
// --- 菜单切换 ---
|
||||
const handleMenuSelect = (index) => {
|
||||
activeMenu.value = index;
|
||||
if (index === 'dashboard') fetchStats();
|
||||
if (index === 'students') fetchStudents();
|
||||
if (index === 'classes') fetchClasses();
|
||||
if (index === 'teachers') fetchTeachers();
|
||||
if (index === 'counselors') fetchCounselors();
|
||||
if (index === 'scores') fetchScores();
|
||||
if (index === 'employments') fetchEmployments();
|
||||
};
|
||||
|
||||
// --- API 请求方法 ---
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const stuRes = await axios.get(`${baseUrl}/students`);
|
||||
stats.totalStudents = stuRes.data.length;
|
||||
const classRes = await axios.get(`${baseUrl}/api/classes?page_num=1&page_size=100`);
|
||||
stats.totalClasses = classRes.data.total;
|
||||
const teaRes = await axios.get(`${baseUrl}/teacher/list?page=1&size=100`);
|
||||
stats.totalTeachers = teaRes.data.total;
|
||||
nextTick(() => { renderClassChart(); renderSalaryChart(); });
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
|
||||
const renderClassChart = async () => {
|
||||
try {
|
||||
const res = await axios.get(`${baseUrl}/analysis/studnet_info/class`);
|
||||
const data = res.data;
|
||||
const chart = echarts.init(classChart.value);
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: ['总人数', '男', '女'] },
|
||||
xAxis: { type: 'category', data: data.map(i => i.class_name) },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{ name: '总人数', type: 'bar', data: data.map(i => i['总人数']) },
|
||||
{ name: '男', type: 'bar', data: data.map(i => i['男']) },
|
||||
{ name: '女', type: 'bar', data: data.map(i => i['女']) }
|
||||
]
|
||||
});
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
|
||||
const renderSalaryChart = async () => {
|
||||
try {
|
||||
const res = await axios.get(`${baseUrl}/analysis/studnet_employments_salary?n=5`);
|
||||
const data = res.data;
|
||||
const chart = echarts.init(salaryChart.value);
|
||||
chart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
xAxis: { type: 'category', data: data.map(i => i.student ? i.student.name : i.id) },
|
||||
yAxis: { type: 'value' },
|
||||
series: [{ data: data.map(i => i.sal), type: 'bar', color: '#67C23A' }]
|
||||
});
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
|
||||
// ------- 学生 -------
|
||||
const fetchStudents = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {};
|
||||
if (studentQuery.id) params.id = studentQuery.id;
|
||||
if (studentQuery.name) params.name = studentQuery.name;
|
||||
if (studentQuery.class_name) params.class_name = studentQuery.class_name;
|
||||
const res = await axios.get(`${baseUrl}/students`, { params });
|
||||
studentList.value = res.data;
|
||||
} catch (e) { ElMessage.error('获取学生列表失败'); }
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const openStudentDialog = (row = null) => {
|
||||
if (row) {
|
||||
isStudentEdit.value = true;
|
||||
Object.assign(studentForm, row);
|
||||
} else {
|
||||
isStudentEdit.value = false;
|
||||
Object.assign(studentForm, { id: '', name: '', gender: '男', class_name: '', age: 18, major: '', status: '在读' });
|
||||
}
|
||||
studentDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const saveStudent = async () => {
|
||||
if (!studentForm.id) { ElMessage.warning('请填写学号'); return; }
|
||||
if (!studentForm.name) { ElMessage.warning('请填写姓名'); return; }
|
||||
if (!studentForm.class_name) { ElMessage.warning('请填写班级'); return; }
|
||||
try {
|
||||
if (isStudentEdit.value) {
|
||||
const { id, ...body } = studentForm;
|
||||
await axios.put(`${baseUrl}/students/${id}`, body);
|
||||
ElMessage.success('更新成功');
|
||||
} else {
|
||||
await axios.post(`${baseUrl}/students`, studentForm);
|
||||
ElMessage.success('创建成功');
|
||||
}
|
||||
studentDialogVisible.value = false;
|
||||
fetchStudents();
|
||||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||||
};
|
||||
|
||||
const deleteStudent = async (id) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'此操作将学生标记为已删除(逻辑删除),数据库记录仍会保留,且支持后续恢复。是否继续?',
|
||||
'逻辑删除确认',
|
||||
{ type: 'warning', confirmButtonText: '确定删除', cancelButtonText: '取消' }
|
||||
);
|
||||
await axios.delete(`${baseUrl}/students/${id}`);
|
||||
ElMessage.success('学生已逻辑删除,数据仍保留在库中');
|
||||
fetchStudents();
|
||||
} catch (e) {
|
||||
if (e !== 'cancel') ElMessage.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// ------- 班级 -------
|
||||
const fetchClasses = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await axios.get(`${baseUrl}/api/classes`, { params: classQuery });
|
||||
classList.value = res.data.items;
|
||||
classTotal.value = res.data.total;
|
||||
} catch (e) { ElMessage.error('获取班级列表失败'); }
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const handleClassPageChange = (val) => { classQuery.page_num = val; fetchClasses(); };
|
||||
|
||||
const openClassDialog = (row = null) => {
|
||||
if (row) { Object.assign(classForm, row); }
|
||||
else { Object.assign(classForm, { class_id: null, class_name: '', head_teacher_id: 1, counselor_id: 1, create_time: '' }); }
|
||||
classDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const saveClass = async () => {
|
||||
try {
|
||||
if (classForm.class_id) {
|
||||
await axios.put(`${baseUrl}/api/classes/${classForm.class_id}/base`, classForm);
|
||||
ElMessage.success('更新成功');
|
||||
} else {
|
||||
await axios.post(`${baseUrl}/api/classes`, classForm);
|
||||
ElMessage.success('创建成功');
|
||||
}
|
||||
classDialogVisible.value = false;
|
||||
fetchClasses();
|
||||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||||
};
|
||||
|
||||
const deleteClass = async (id) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该班级吗?', '提示', { type: 'warning' });
|
||||
await axios.put(`${baseUrl}/api/classes/batch-delete`, { class_ids: [id], deleted: true });
|
||||
ElMessage.success('删除成功');
|
||||
fetchClasses();
|
||||
} catch (e) { if (e !== 'cancel') ElMessage.error('删除失败'); }
|
||||
};
|
||||
|
||||
// ------- 教师 -------
|
||||
const fetchTeachers = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await axios.get(`${baseUrl}/teacher/list`, { params: teacherQuery });
|
||||
teacherList.value = res.data.list;
|
||||
teacherTotal.value = res.data.total;
|
||||
} catch (e) { ElMessage.error('获取教师列表失败'); }
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const handleTeacherPageChange = (val) => { teacherQuery.page = val; fetchTeachers(); };
|
||||
|
||||
const openTeacherDialog = (row = null) => {
|
||||
if (row) { Object.assign(teacherForm, row); }
|
||||
else { Object.assign(teacherForm, { t_id: null, t_name: '', t_gender: '男', t_phone: '', t_subject: '', t_created: '' }); }
|
||||
teacherDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const saveTeacher = async () => {
|
||||
try {
|
||||
if (teacherForm.t_id) {
|
||||
await axios.put(`${baseUrl}/teacher/${teacherForm.t_id}`, teacherForm);
|
||||
ElMessage.success('更新成功');
|
||||
} else {
|
||||
await axios.post(`${baseUrl}/teacher/teachers`, teacherForm);
|
||||
ElMessage.success('创建成功');
|
||||
}
|
||||
teacherDialogVisible.value = false;
|
||||
fetchTeachers();
|
||||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||||
};
|
||||
|
||||
const deleteTeacher = async (t_id, t_name) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该教师吗?', '提示', { type: 'warning' });
|
||||
await axios.delete(`${baseUrl}/teacher/${t_id}`, { data: { t_id, t_name } });
|
||||
ElMessage.success('删除成功');
|
||||
fetchTeachers();
|
||||
} catch (e) { if (e !== 'cancel') ElMessage.error('删除失败'); }
|
||||
};
|
||||
|
||||
// ------- 顾问 -------
|
||||
const fetchCounselors = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
counselorList.value = [
|
||||
{ id: 1, name: '吴振玮', c_time: '2023-01-01' },
|
||||
{ id: 2, name: '孙波', c_time: '2023-01-01' }
|
||||
];
|
||||
} catch (e) { ElMessage.error('获取顾问列表失败'); }
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const openCounselorDialog = (row = null) => {
|
||||
if (row) { Object.assign(counselorForm, row); }
|
||||
else { Object.assign(counselorForm, { id: null, name: '', c_time: '' }); }
|
||||
counselorDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const saveCounselor = async () => {
|
||||
try {
|
||||
if (counselorForm.id) {
|
||||
await axios.put(`${baseUrl}/counselor/${counselorForm.id}`, counselorForm);
|
||||
ElMessage.success('更新成功');
|
||||
} else {
|
||||
await axios.post(`${baseUrl}/counselor/counselors/`, counselorForm);
|
||||
ElMessage.success('创建成功');
|
||||
}
|
||||
counselorDialogVisible.value = false;
|
||||
fetchCounselors();
|
||||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||||
};
|
||||
|
||||
const deleteCounselor = async (id) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该顾问吗?', '提示', { type: 'warning' });
|
||||
await axios.delete(`${baseUrl}/counselor/${id}`, { data: { id } });
|
||||
ElMessage.success('删除成功');
|
||||
fetchCounselors();
|
||||
} catch (e) { if (e !== 'cancel') ElMessage.error('删除失败'); }
|
||||
};
|
||||
|
||||
// ------- 成绩 -------
|
||||
const fetchScores = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
if (scoreQuery.id) {
|
||||
const res = await axios.get(`${baseUrl}/scores/query/id?id=${scoreQuery.id}`);
|
||||
scoreList.value = res.data.msg || [];
|
||||
} else if (scoreQuery.exam_round) {
|
||||
const res = await axios.get(`${baseUrl}/scores/query/round?exam_round=${scoreQuery.exam_round}`);
|
||||
scoreList.value = res.data.msg || [];
|
||||
} else {
|
||||
scoreList.value = [];
|
||||
}
|
||||
} catch (e) { ElMessage.error('获取成绩列表失败'); }
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const openScoreDialog = (row = null) => {
|
||||
if (row) {
|
||||
// 编辑模式
|
||||
isScoreEdit.value = true;
|
||||
Object.assign(scoreForm, row);
|
||||
} else {
|
||||
// 录入模式
|
||||
isScoreEdit.value = false;
|
||||
Object.assign(scoreForm, { id: '', exam_round: 1, score: 0 });
|
||||
}
|
||||
scoreDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const saveScore = async () => {
|
||||
if (!scoreForm.id) { ElMessage.warning('请输入学号'); return; }
|
||||
if (!scoreForm.exam_round) { ElMessage.warning('请输入考核序次'); return; }
|
||||
try {
|
||||
if (isScoreEdit.value) {
|
||||
await axios.put(`${baseUrl}/scores/update?id=${scoreForm.id}&exam_round=${scoreForm.exam_round}&score=${scoreForm.score}`);
|
||||
ElMessage.success('修改成功');
|
||||
} else {
|
||||
await axios.post(`${baseUrl}/scores/add?id=${scoreForm.id}&exam_round=${scoreForm.exam_round}&score=${scoreForm.score}`);
|
||||
ElMessage.success('录入成功');
|
||||
}
|
||||
scoreDialogVisible.value = false;
|
||||
fetchScores();
|
||||
} catch (e) { ElMessage.error(e.response?.data?.msg || '操作失败'); }
|
||||
};
|
||||
|
||||
const deleteScore = async (id, exam_round) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该成绩吗?', '提示', { type: 'warning' });
|
||||
await axios.delete(`${baseUrl}/scores/delete?id=${id}&exam_round=${exam_round}`);
|
||||
ElMessage.success('删除成功');
|
||||
fetchScores();
|
||||
} catch (e) { if (e !== 'cancel') ElMessage.error('删除失败'); }
|
||||
};
|
||||
|
||||
// ------- 就业 -------
|
||||
const fetchEmployments = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {};
|
||||
if (employmentQuery.id) params.mid = employmentQuery.id;
|
||||
if (employmentQuery.name) params.name = employmentQuery.name;
|
||||
const res = await axios.get(`${baseUrl}/employment/query`, { params });
|
||||
employmentList.value = res.data;
|
||||
} catch (e) { ElMessage.error('获取就业列表失败'); }
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const openEmploymentDialog = (row) => {
|
||||
Object.assign(employmentForm, row);
|
||||
employmentDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const saveEmployment = async () => {
|
||||
try {
|
||||
await axios.post(`${baseUrl}/employment/students/${employmentForm.id}`, {
|
||||
offer_open: employmentForm.offer_open || null,
|
||||
offer_review: employmentForm.offer_review || null,
|
||||
emp: employmentForm.emp || null,
|
||||
sal: employmentForm.sal || null
|
||||
});
|
||||
ElMessage.success('更新成功');
|
||||
employmentDialogVisible.value = false;
|
||||
fetchEmployments();
|
||||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||||
};
|
||||
|
||||
onMounted(() => { fetchStats(); });
|
||||
|
||||
return {
|
||||
activeMenu, handleMenuSelect, loading, stats, classChart, salaryChart,
|
||||
|
||||
// 学生
|
||||
studentList, studentQuery, fetchStudents,
|
||||
studentDialogVisible, studentForm, isStudentEdit,
|
||||
openStudentDialog, saveStudent, deleteStudent,
|
||||
|
||||
// 班级
|
||||
classList, classTotal, classQuery, fetchClasses, handleClassPageChange,
|
||||
classDialogVisible, classForm, openClassDialog, saveClass, deleteClass,
|
||||
|
||||
// 教师
|
||||
teacherList, teacherTotal, teacherQuery, fetchTeachers, handleTeacherPageChange,
|
||||
teacherDialogVisible, teacherForm, openTeacherDialog, saveTeacher, deleteTeacher,
|
||||
|
||||
// 顾问
|
||||
counselorList, fetchCounselors, counselorDialogVisible, counselorForm,
|
||||
openCounselorDialog, saveCounselor, deleteCounselor,
|
||||
|
||||
// 成绩
|
||||
scoreList, scoreQuery, fetchScores, scoreDialogVisible, scoreForm,
|
||||
isScoreEdit, openScoreDialog, saveScore, deleteScore,
|
||||
|
||||
// 就业
|
||||
employmentList, employmentQuery, fetchEmployments, employmentDialogVisible,
|
||||
employmentForm, openEmploymentDialog, saveEmployment
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
app.use(ElementPlus);
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user