课程管理

接下来开发一个课程管理功能,用于管理和显示学生课程的

数据库

先设计一下课程数据表,命名为 course,字段(课程名称、课程编号、课程描述、课时、任课老师)

课程页面

在前端的项目目录下的/manage下创建一个Course.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-input
style="width: 260px"
v-model="data.name"
placeholder="请输入要查询的课程名称"
:prefix-icon="Search"
/>
<el-button type="primary" style="margin: 0 0 0 10px" plain
>查询</el-button
>
<el-button type="info" plain>重置</el-button>
</div>

<div class="card" style="margin-bottom: 10px">
<div>
<el-button type="primary" style="margin-bottom: 5px" plain
>新增</el-button
>
</div>
<div>
<el-table :data="data.tableData" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="课程名称" width="180" />
<el-table-column prop="number" label="课程编号" width="180" />
<el-table-column prop="description" label="课程描述" width="240" />
<el-table-column prop="periods" label="课时" width="180" />
<el-table-column prop="teacher" label="任课教师" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small" plain>编辑</el-button>
<el-button type="danger" size="small" plain>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>

<div class="card">
<el-pagination background layout="prev, pager, next" :total="1000" />
</div>
</div>
</template>

<script setup>
import { reactive } from "vue";
import { Search } from "@element-plus/icons-vue";

const data = reactive({
name: "",
tableData: [
{
id: 1,
name: "大学英语",
number: "001",
description: "大学英语不想学-6学分",
periods: "36课时",
teacher: "张三",
},
{
id: 2,
name: "高等数学",
number: "002",
description: "高等数学好难学-4学分",
periods: "24课时",
teacher: "李四",
},
{
id: 3,
name: "必修物理",
number: "003",
description: "必修物理有难度-2学分",
periods: "24课时",
teacher: "王五",
},
{
id: 4,
name: "思想政治",
number: "004",
description: "思想政治必修课-4学分",
periods: "18课时",
teacher: "赵六",
},
{
id: 5,
name: "微机原理",
number: "005",
description: "微机原理很基础-3学分",
periods: "24课时",
teacher: "钱七",
},
{
id: 6,
name: "通信原理",
number: "006",
description: "通信原理很难懂-4学分",
periods: "24课时",
teacher: "孙八",
},
{
id: 7,
name: "离散数学",
number: "007",
description: "离散数学很离散-2学分",
periods: "18课时",
teacher: "周九",
},
{
id: 8,
name: "工程制图",
number: "008",
description: "工程制图好有趣-3学分",
periods: "24课时",
teacher: "吴十",
},
],
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { createRouter, createWebHistory } from "vue-router";

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "Manager",
component: () => import("@/views/Manager.vue"),
redirect: "/home",
children: [
{
path: "home",
name: "Home",
component: () => import("@/views/manager/Home.vue"),
meta: { requiresAuth: true },
},
{
path: "course",
name: "Course",
component: () => import("@/views/manager/Course.vue"),
meta: { requiresAuth: true },
},
],
},
{
path: "/login",
name: "Login",
component: () => import("@/views/Login.vue"),
},
],
});

router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some((record) => record.meta.requiresAuth);
const user = JSON.parse(localStorage.getItem("student-user"));
if (requiresAuth && !user) {
// 如果目标路由需要认证,并且用户未登录
next("/login"); // 跳转到登录页面
} else {
next(); // 如果目标路由不需要认证,或者用户已登录,则正常导航到目标路由
}
});

export default router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<template>
<div>
<div
style="height: 60px; background-color: #eae8e8; display: flex; align-items: center; border-bottom: 1px solid #c4c2c2"
>
<div style="flex: 1">
<div style="padding-left: 20px; display: flex; align-items: center">
<img src="@/assets/imgs/logo.png" alt="" style="width: 40px" />
<div style="font-weight: bold; font-size: 24px; margin-left: 5px">
学生信息管理系统
</div>
</div>
</div>
<div
style="width: fit-content; padding-right: 10px; display: flex; align-items: center;"
>
<img
src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
alt=""
style="width: 40px; height: 40px"
/>
<span style="margin-left: 5px">管理员</span>
</div>
</div>

<div style="display: flex">
<div
style="width: 200px; border-right: 1px solid #f3eeee; min-height: calc(100vh - 60px)"
>
<el-menu
router
style="border: none"
:default-active="$route.path"
:default-openeds="['/home', '2']"
>
<el-menu-item index="/home">
<el-icon>
<HomeFilled />
</el-icon>
<span>系统首页</span>
</el-menu-item>

<el-sub-menu index="2">
<template #title>
<el-icon>
<Memo />
</el-icon>
<span>课程管理</span>
</template>
<el-menu-item index="/course">
<el-icon>
<Document />
</el-icon>
<span>课程信息</span>
</el-menu-item>
</el-sub-menu>

<el-menu-item index="/person">
<el-icon>
<User />
</el-icon>
<span>个人资料</span>
</el-menu-item>
<el-menu-item index="/login" @click="logout">
<el-icon>
<SwitchButton />
</el-icon>
<span>退出系统</span>
</el-menu-item>
</el-menu>
</div>

<div style="flex: 1; width: 0; background-color: #eaeaee; padding: 10px">
<router-view />
</div>
</div>
</div>
</template>

<script setup>
import { useRoute } from "vue-router";

const $route = useRoute();
console.log($route.path);

const logout = () => {
localStorage.removeItem("student-user");
};
</script>

<style scoped>
.el-menu-item.is-active {
background-color: #c3d7d3 !important;
}

.el-menu-item:hover {
color: #0c98d5;
}

:deep(th) {
color: #333;
}
</style>

课程查询 api

Course 实体类定义

在项目目录/model下创建一个course.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding:utf-8 -*-
# Author: Zachary
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from model import Base


class Course(Base):
__tablename__ = "course"
id: Mapped[int] = mapped_column(Integer, primary_key=True, nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
number: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str] = mapped_column(String(255), nullable=False)
periods: Mapped[str] = mapped_column(String(255), nullable=False)
teacher: Mapped[str] = mapped_column(String(255), nullable=False)

课程管理 Api 接口

courseApi

在项目目录/api下创建一个courseApi.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding:utf-8 -*-
# Author: Zachary
from fastapi import APIRouter, Query, Depends

from api import app
from common.pageHelper import PageHelper
from common.result import ResultModel, Result
from model import Session, get_db_session
from service.courseService import CourseService

course_router = APIRouter(prefix="/course")


@course_router.get("/selectPage", response_model=ResultModel)
async def select_page(page: int = Query(1, ge=1, alias="pageNum", description="Page number"),
size: int = Query(5, gt=0, le=100, alias="pageSize", description="Page size"),
db_session: Session = Depends(get_db_session)):
pageInfo = PageHelper.startPage(page, size)
course_list = CourseService.select_page(db_session)
result = Result.success(pageInfo.of(course_list))
return result


app.include_router(course_router)

需要在/api/__init__.py下增加 courseApi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding:utf-8 -*-
# Author: Zachary

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

app = FastAPI()

# 跨域问题
origins = ["http://localhost:5173", "http://127.0.0.1:5173"] # 替换为你的前端应用的实际地址

app.add_middleware(
CORSMiddleware,
allow_origins=origins, # 允许跨域访问的来源域名列表
allow_credentials=True, # 是否允许携带cookie
allow_methods=["*"], # 允许的方法,默认包含常见的GET、POST等,"*"表示所有方法
allow_headers=["*"], # 允许的请求头,默认包含常见的Content-Type等,"*"表示所有请求头
)

from api import adminApi, exceptionHandler, courseApi

courseService

在项目目录/service下创建一个courseService.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding:utf-8 -*-
# Author: Zachary
from sqlalchemy import desc, select

from model import Session
from model.course import Course


class CourseService:

@staticmethod
def select_page(db_session: Session):
query = select(Course).order_by(desc(Course.id))
result = db_session.execute(query).scalars().all()
return result

分页插件

在项目路径的/common下创建一个pageHelper.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# -*- coding:utf-8 -*-
# Author: Zachary
from fastapi.encoders import jsonable_encoder


class Page:
list: list
total: int
pageNum: int
pageSize: int

def __init__(self, list: list, total: int, pageNum: int, pageSize: int):
self.list = list
self.total = total
self.pageNum = pageNum
self.pageSize = pageSize


class PageHelper:
page: int
size: int
limit: int
offset: int

def __init__(self, page: int, size: int, limit: int, offset: int):
self.page = page
self.size = size
self.limit = limit
self.offset = offset

@classmethod
def startPage(cls, page: int, size: int):
limit = size
offset = size * (page - 1)
return cls(page, size, limit, offset)

def of(self, data):
data_list = [jsonable_encoder(dataitem) for dataitem in data[self.offset:self.offset + self.limit]]
data_total = len(data)
page = Page(data_list, data_total, self.page, self.size)
return jsonable_encoder(page)

测试

可以发现,请求是成功的,但就是没数据,因为数据库里面是空的,给数据库中插入数据

再次使用 postman 测试一下

增删查改实现

数据分页

前端这块需要调用分页查询的请求接口,并将结果显示出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-input
style="width: 260px"
v-model="data.name"
placeholder="请输入要查询的课程名称"
:prefix-icon="Search"
/>
<el-button type="primary" style="margin: 0 0 0 10px" plain
>查询</el-button
>
<el-button type="info" plain>重置</el-button>
</div>

<div class="card" style="margin-bottom: 10px">
<div>
<el-button type="primary" style="margin-bottom: 5px" plain
>新增</el-button
>
</div>
<div>
<el-table :data="data.tableData" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="课程名称" width="180" />
<el-table-column prop="number" label="课程编号" width="180" />
<el-table-column prop="description" label="课程描述" width="240" />
<el-table-column prop="periods" label="课时" width="180" />
<el-table-column prop="teacher" label="任课教师" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small" plain>编辑</el-button>
<el-button type="danger" size="small" plain>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>

<div class="card">
<el-pagination
background
layout="prev, pager, next"
:total="data.total"
/>
</div>
</div>
</template>

<script setup>
import { reactive } from "vue";
import { Search } from "@element-plus/icons-vue";
import request from "@/utils/request";

const data = reactive({
name: "",
tableData: [],
total: 0,
});

const load = () => {
request
.get("/course/selectPage", {
params: {
pageNum: 1,
pageSize: 5,
},
})
.then((res) => {
data.tableData = res.data?.list || [];
data.total = res.data?.total || 0;
});
};

// 加载一次 获取课程数据
load();
</script>

虽然可以显示一部分数据,但是这个换页有问题,不显示后续跳转的页面按钮

需要绑定一下这两个值

同时还需要,在换了页面之后也重新刷新一下数据,触发 current-change

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-input
style="width: 260px"
v-model="data.name"
placeholder="请输入要查询的课程名称"
:prefix-icon="Search"
/>
<el-button type="primary" style="margin: 0 0 0 10px" plain
>查询</el-button
>
<el-button type="info" plain>重置</el-button>
</div>

<div class="card" style="margin-bottom: 10px">
<div>
<el-button type="primary" style="margin-bottom: 5px" plain
>新增</el-button
>
</div>
<div>
<el-table :data="data.tableData" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="课程名称" width="180" />
<el-table-column prop="number" label="课程编号" width="180" />
<el-table-column prop="description" label="课程描述" width="240" />
<el-table-column prop="periods" label="课时" width="180" />
<el-table-column prop="teacher" label="任课教师" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small" plain>编辑</el-button>
<el-button type="danger" size="small" plain>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>

<div class="card">
<el-pagination
v-model:current-page="data.pageNum"
v-model:page-size="data.pageSize"
@current-change="handleCurrentChange"
background
layout="prev, pager, next"
:total="data.total"
/>
</div>
</div>
</template>

<script setup>
import { reactive } from "vue";
import { Search } from "@element-plus/icons-vue";
import request from "@/utils/request";

const data = reactive({
name: "",
tableData: [],
total: 0,
pageNum: 1,
pageSize: 5,
});

const load = () => {
request
.get("/course/selectPage", {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
},
})
.then((res) => {
data.tableData = res.data?.list || [];
data.total = res.data?.total || 0;
});
};

// 加载一次 获取课程数据
load();

const handleCurrentChange = () => {
// 当翻页的时候重新加载数据
load();
};
</script>

分页成功实现了

查询和重置

为了依据课程名称实现筛查的功能,需要传递一个name字段传给后端进行数据库层面的模糊查询

重置按钮要将查询的内容清空,同时显示回完整的数据

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-input
@input="load"
style="width: 260px"
v-model="data.name"
placeholder="请输入要查询的课程名称"
:prefix-icon="Search"
/>
<el-button type="info" plain style="margin: 0 0 0 10px" @click="reset"
>重置</el-button
>
</div>

<div class="card" style="margin-bottom: 10px">
<div>
<el-button type="primary" style="margin-bottom: 5px" plain
>新增</el-button
>
</div>
<div>
<el-table :data="data.tableData" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="课程名称" width="180" />
<el-table-column prop="number" label="课程编号" width="180" />
<el-table-column prop="description" label="课程描述" width="240" />
<el-table-column prop="periods" label="课时" width="180" />
<el-table-column prop="teacher" label="任课教师" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small" plain>编辑</el-button>
<el-button type="danger" size="small" plain>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>

<div class="card">
<el-pagination
v-model:current-page="data.pageNum"
v-model:page-size="data.pageSize"
@current-change="handleCurrentChange"
background
layout="prev, pager, next"
:total="data.total"
/>
</div>
</div>
</template>

<script setup>
import { reactive } from "vue";
import { Search } from "@element-plus/icons-vue";
import request from "@/utils/request";

const data = reactive({
name: "",
tableData: [],
total: 0,
pageNum: 1,
pageSize: 5,
});

const load = () => {
request
.get("/course/selectPage", {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
name: data.name,
},
})
.then((res) => {
data.tableData = res.data?.list || [];
data.total = res.data?.total || 0;
});
};

// 加载一次 获取课程数据
load();

const handleCurrentChange = () => {
// 当翻页的时候重新加载数据
load();
};

const reset = () => {
data.name = "";
load();
};
</script>

后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -*- coding:utf-8 -*-
# Author: Zachary
from typing import Optional

from fastapi import APIRouter, Query, Depends

from api import app
from common.pageHelper import PageHelper
from common.result import ResultModel, Result
from model import Session, get_db_session
from service.courseService import CourseService

course_router = APIRouter(prefix="/course")


@course_router.get("/selectPage", response_model=ResultModel)
async def select_page(page: int = Query(1, ge=1, alias="pageNum", description="Page number"),
size: int = Query(5, gt=0, le=100, alias="pageSize", description="Page size"),
name: Optional[str] = Query(None, description="Course name"),
db_session: Session = Depends(get_db_session)):
pageInfo = PageHelper.startPage(page, size)
course_list = CourseService.select_page(name, db_session)
result = Result.success(pageInfo.of(course_list))
return result


app.include_router(course_router)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding:utf-8 -*-
# Author: Zachary
from sqlalchemy import desc, select

from model import Session
from model.course import Course


class CourseService:

@staticmethod
def select_page(name, db_session: Session):
query = select(Course).order_by(desc(Course.id))
if name:
query = query.where(Course.name.like(f"%{name}%"))
result = db_session.execute(query).scalars().all()
return result

测试

查询扩展

当需要筛选的字段不只是课程名称时,需要怎么处理;比如加上课程编号任课教师进行多条件模糊查询

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-input
@input="load"
style="width: 260px"
v-model="data.name"
placeholder="请输入要查询的课程名称"
:prefix-icon="Search"
/>
<el-input
@input="load"
style="margin-left:10px; width: 260px"
v-model="data.number"
placeholder="请输入课程编号"
:prefix-icon="Search"
/>
<el-input
@input="load"
style="margin-left:10px; width: 260px"
v-model="data.teacher"
placeholder="请输入任课老师"
:prefix-icon="Search"
/>
<el-button type="info" plain style="margin: 0 0 0 10px" @click="reset"
>重置</el-button
>
</div>

<div class="card" style="margin-bottom: 10px">
<div>
<el-button type="primary" style="margin-bottom: 5px" plain
>新增</el-button
>
</div>
<div>
<el-table :data="data.tableData" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="课程名称" width="180" />
<el-table-column prop="number" label="课程编号" width="180" />
<el-table-column prop="description" label="课程描述" width="240" />
<el-table-column prop="periods" label="课时" width="180" />
<el-table-column prop="teacher" label="任课教师" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small" plain>编辑</el-button>
<el-button type="danger" size="small" plain>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>

<div class="card">
<el-pagination
v-model:current-page="data.pageNum"
v-model:page-size="data.pageSize"
@current-change="handleCurrentChange"
background
layout="prev, pager, next"
:total="data.total"
/>
</div>
</div>
</template>

<script setup>
import { reactive } from "vue";
import { Search } from "@element-plus/icons-vue";
import request from "@/utils/request";

const data = reactive({
name: "",
number: "",
teacher: "",
tableData: [],
total: 0,
pageNum: 1,
pageSize: 5,
});

const load = () => {
request
.get("/course/selectPage", {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
name: data.name,
number: data.number,
teacher: data.teacher,
},
})
.then((res) => {
data.tableData = res.data?.list || [];
data.total = res.data?.total || 0;
});
};

// 加载一次 获取课程数据
load();

const handleCurrentChange = () => {
// 当翻页的时候重新加载数据
load();
};

const reset = () => {
data.name = "";
data.number = "";
data.teacher = "";
load();
};
</script>

后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding:utf-8 -*-
# Author: Zachary
from typing import Optional

from fastapi import APIRouter, Query, Depends

from api import app
from common.pageHelper import PageHelper
from common.result import ResultModel, Result
from model import Session, get_db_session
from model.course import Course, CourseSearch
from service.courseService import CourseService

course_router = APIRouter(prefix="/course")


@course_router.get("/selectPage", response_model=ResultModel)
async def select_page(page: int = Query(1, ge=1, alias="pageNum", description="Page number"),
size: int = Query(5, gt=0, le=100, alias="pageSize", description="Page size"),
name: Optional[str] = Query(None, description="Course name"),
number: Optional[str] = Query(None, description="Course number"),
teacher: Optional[str] = Query(None, description="Course teacher"),
db_session: Session = Depends(get_db_session)):
pageInfo = PageHelper.startPage(page, size)
course_search = CourseSearch(name=name, number=number, teacher=teacher)
course_list = CourseService.select_page(course_search, db_session)
result = Result.success(pageInfo.of(course_list))
return result


app.include_router(course_router)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding:utf-8 -*-
# Author: Zachary
from sqlalchemy import desc, select

from model import Session
from model.course import Course, CourseSearch


class CourseService:

@staticmethod
def select_page(course_search: CourseSearch, db_session: Session):
query = select(Course).order_by(desc(Course.id))
if course_search.name:
query = query.where(Course.name.like(f"%{course_search.name}%"))
if course_search.number:
query = query.where(Course.number.like(f"%{course_search.number}%"))
if course_search.teacher:
query = query.where(Course.teacher.like(f"%{course_search.teacher}%"))
result = db_session.execute(query).scalars().all()
return result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
# Author: Zachary
from pydantic import BaseModel
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from model import Base


class Course(Base):
__tablename__ = "course"
id: Mapped[int] = mapped_column(Integer, primary_key=True, nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
number: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str] = mapped_column(String(255), nullable=False)
periods: Mapped[str] = mapped_column(String(255), nullable=False)
teacher: Mapped[str] = mapped_column(String(255), nullable=False)


class CourseSearch(BaseModel):
name: str | None
number: str | None
teacher: str | None

测试

新增

下面实现给课程表新增数据的功能,需要点击新增按钮后,出现弹窗

在官网找一个Dialog组件

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-input
@input="load"
style="width: 260px"
v-model="data.name"
placeholder="请输入要查询的课程名称"
:prefix-icon="Search"
/>
<el-input
@input="load"
style="margin-left:10px; width: 260px"
v-model="data.number"
placeholder="请输入课程编号"
:prefix-icon="Search"
/>
<el-input
@input="load"
style="margin-left:10px; width: 260px"
v-model="data.teacher"
placeholder="请输入任课老师"
:prefix-icon="Search"
/>
<el-button type="info" plain style="margin: 0 0 0 10px" @click="reset"
>重置</el-button
>
</div>

<div class="card" style="margin-bottom: 10px">
<div>
<el-button
type="primary"
style="margin-bottom: 5px"
plain
@click="handleAdd"
>新增</el-button
>
</div>
<div>
<el-table :data="data.tableData" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="课程名称" width="180" />
<el-table-column prop="number" label="课程编号" width="180" />
<el-table-column prop="description" label="课程描述" width="240" />
<el-table-column prop="periods" label="课时" width="180" />
<el-table-column prop="teacher" label="任课教师" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small" plain>编辑</el-button>
<el-button type="danger" size="small" plain>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>

<div class="card">
<el-pagination
v-model:current-page="data.pageNum"
v-model:page-size="data.pageSize"
@current-change="handleCurrentChange"
background
layout="prev, pager, next"
:total="data.total"
/>
</div>

<el-dialog width="35%" v-model="data.formVisible" title="课程信息">
<el-form
:model="data.form"
label-width="100px"
label-position="right"
style="padding-right: 45px"
>
<el-form-item label="课程名称">
<el-input v-model="data.form.name" autocomplete="off" />
</el-form-item>
<el-form-item label="课程编号">
<el-input v-model="data.form.number" autocomplete="off" />
</el-form-item>
<el-form-item label="课程描述">
<el-input v-model="data.form.description" autocomplete="off" />
</el-form-item>
<el-form-item label="课时">
<el-input v-model="data.form.periods" autocomplete="off" />
</el-form-item>
<el-form-item label="任课教师">
<el-input v-model="data.form.teacher" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false" plain>取消</el-button>
<el-button type="primary" @click="save" plain>保存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>

<script setup>
import { reactive } from "vue";
import { Search } from "@element-plus/icons-vue";
import request from "@/utils/request";
import { ElMessage } from "element-plus";

const data = reactive({
name: "",
number: "",
teacher: "",
tableData: [],
total: 0,
pageNum: 1,
pageSize: 5,
formVisible: false,
form: {},
});

const load = () => {
request
.get("/course/selectPage", {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
name: data.name,
number: data.number,
teacher: data.teacher,
},
})
.then((res) => {
data.tableData = res.data?.list || [];
data.total = res.data?.total || 0;
});
};

// 加载一次 获取课程数据
load();

const handleCurrentChange = () => {
// 当翻页的时候重新加载数据
load();
};

const reset = () => {
data.name = "";
data.number = "";
data.teacher = "";
load();
};

const handleAdd = () => {
data.formVisible = true;
data.form = {};
};

const save = () => {
request
.post("/course/add", data.form)
.then((res) => {
if (res.code === "200") {
ElMessage.success("新增成功");
data.formVisible = false;
load();
} else {
ElMessage.error(res.msg);
}
})
.catch((err) => {
ElMessage.error(err.response?.data?.msg || err.message);
});
};
</script>

后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# -*- coding:utf-8 -*-
# Author: Zachary
from typing import Optional

from fastapi import APIRouter, Query, Depends

from api import app
from common.pageHelper import PageHelper
from common.result import ResultModel, Result
from model import Session, get_db_session
from model.course import Course, CourseSearch, CourseCreate
from service.courseService import CourseService

course_router = APIRouter(prefix="/course")


@course_router.get("/selectPage", response_model=ResultModel)
async def select_page(page: int = Query(1, ge=1, alias="pageNum", description="Page number"),
size: int = Query(5, gt=0, le=100, alias="pageSize", description="Page size"),
name: Optional[str] = Query(None, description="Course name"),
number: Optional[str] = Query(None, description="Course number"),
teacher: Optional[str] = Query(None, description="Course teacher"),
db_session: Session = Depends(get_db_session)):
pageInfo = PageHelper.startPage(page, size)
course_search = CourseSearch(name=name, number=number, teacher=teacher)
course_list = CourseService.select_page(course_search, db_session)
result = Result.success(pageInfo.of(course_list))
return result


@course_router.post("/add", response_model=ResultModel)
async def add(course: CourseCreate, db_session: Session = Depends(get_db_session)):
CourseService.add_course(course, db_session)
result = Result.success()
return result


app.include_router(course_router)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# -*- coding:utf-8 -*-
# Author: Zachary
from sqlalchemy import desc, select

from exception.customException import CourseExistException
from model import Session
from model.course import Course, CourseSearch, CourseCreate


class CourseService:

@staticmethod
def select_page(course_search: CourseSearch, db_session: Session):
query = select(Course).order_by(desc(Course.id))
if course_search.name:
query = query.where(Course.name.like(f"%{course_search.name}%"))
if course_search.number:
query = query.where(Course.number.like(f"%{course_search.number}%"))
if course_search.teacher:
query = query.where(Course.teacher.like(f"%{course_search.teacher}%"))
result = db_session.execute(query).scalars().all()
return result

@staticmethod
def add_course(course: CourseCreate, db_session: Session):
query = select(Course).where(Course.name == course.name)
exist_course: Course = db_session.execute(query).scalars().all()
if exist_course:
raise CourseExistException("课程名已存在")
course = Course(**course.dict())
db_session.add(course)
db_session.commit()
return course
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# -*- coding:utf-8 -*-
# Author: Zachary
from pydantic import BaseModel
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from model import Base


class Course(Base):
__tablename__ = "course"
id: Mapped[int] = mapped_column(Integer, primary_key=True, nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
number: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str] = mapped_column(String(255), nullable=False)
periods: Mapped[str] = mapped_column(String(255), nullable=False)
teacher: Mapped[str] = mapped_column(String(255), nullable=False)


class CourseSearch(BaseModel):
name: str | None
number: str | None
teacher: str | None


class CourseBase(BaseModel):
name: str
number: str
description: str
periods: str
teacher: str


class CourseCreate(CourseBase):
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding:utf-8 -*-
# Author: Zachary

class UserNotFoundException(Exception):
def __init__(self, message: str):
self.message = message


class PasswordNotMatchException(Exception):
def __init__(self, message: str):
self.message = message


class TokenException(Exception):
def __init__(self, message: str):
self.message = message


class CourseExistException(Exception):
def __init__(self, message: str):
self.message = message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# -*- coding:utf-8 -*-
# Author: Zachary
from fastapi.encoders import jsonable_encoder
from starlette.responses import JSONResponse

from api import app
from fastapi import Request

from common.result import Result
from exception.customException import UserNotFoundException, PasswordNotMatchException, TokenException, \
CourseExistException


@app.exception_handler(Exception)
async def exception_handler(request: Request, exc: Exception):
result = Result.error(code="500", msg=str(exc))
return JSONResponse(status_code=500, content=jsonable_encoder(result))


@app.exception_handler(UserNotFoundException)
async def user_not_fount_exception_handler(request: Request, exc: UserNotFoundException):
result = Result.error(code="404", msg=exc.message)
return JSONResponse(status_code=404, content=jsonable_encoder(result))


@app.exception_handler(PasswordNotMatchException)
async def password_not_fount_exception_handler(request: Request, exc: PasswordNotMatchException):
result = Result.error(code="401", msg=exc.message)
return JSONResponse(status_code=401, content=jsonable_encoder(result))


@app.exception_handler(TokenException)
async def token_exception_handler(request: Request, exc: TokenException):
result = Result.error(code="401", msg=exc.message)
return JSONResponse(status_code=401, content=jsonable_encoder(result))


@app.exception_handler(CourseExistException)
async def course_exist_exception_handler(request: Request, exc: CourseExistException):
result = Result.error(code="400", msg=exc.message)
return JSONResponse(status_code=400, content=jsonable_encoder(result))

测试

编辑

点击数据中的编辑按钮可以对已有的课程数据,进行修改

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-input
@input="load"
style="width: 260px"
v-model="data.name"
placeholder="请输入要查询的课程名称"
:prefix-icon="Search"
/>
<el-input
@input="load"
style="margin-left:10px; width: 260px"
v-model="data.number"
placeholder="请输入课程编号"
:prefix-icon="Search"
/>
<el-input
@input="load"
style="margin-left:10px; width: 260px"
v-model="data.teacher"
placeholder="请输入任课老师"
:prefix-icon="Search"
/>
<el-button type="info" plain style="margin: 0 0 0 10px" @click="reset"
>重置</el-button
>
</div>

<div class="card" style="margin-bottom: 10px">
<div>
<el-button
type="primary"
style="margin-bottom: 5px"
plain
@click="handleAdd"
>新增</el-button
>
</div>
<div>
<el-table :data="data.tableData" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="课程名称" width="180" />
<el-table-column prop="number" label="课程编号" width="180" />
<el-table-column prop="description" label="课程描述" width="240" />
<el-table-column prop="periods" label="课时" width="180" />
<el-table-column prop="teacher" label="任课教师" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
type="primary"
size="small"
plain
@click="handleEdit(scope.row)"
>编辑</el-button
>
<el-button type="danger" size="small" plain>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>

<div class="card">
<el-pagination
v-model:current-page="data.pageNum"
v-model:page-size="data.pageSize"
@current-change="handleCurrentChange"
background
layout="prev, pager, next"
:total="data.total"
/>
</div>

<el-dialog width="35%" v-model="data.formVisible" title="课程信息">
<el-form
:model="data.form"
label-width="100px"
label-position="right"
style="padding-right: 45px"
>
<el-form-item label="课程名称">
<el-input v-model="data.form.name" autocomplete="off" />
</el-form-item>
<el-form-item label="课程编号">
<el-input v-model="data.form.number" autocomplete="off" />
</el-form-item>
<el-form-item label="课程描述">
<el-input v-model="data.form.description" autocomplete="off" />
</el-form-item>
<el-form-item label="课时">
<el-input v-model="data.form.periods" autocomplete="off" />
</el-form-item>
<el-form-item label="任课教师">
<el-input v-model="data.form.teacher" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false" plain>取消</el-button>
<el-button type="primary" @click="save" plain>保存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>

<script setup>
import { reactive } from "vue";
import { Search } from "@element-plus/icons-vue";
import request from "@/utils/request";
import { ElMessage } from "element-plus";

const data = reactive({
name: "",
number: "",
teacher: "",
tableData: [],
total: 0,
pageNum: 1,
pageSize: 5,
formVisible: false,
form: {},
});

const load = () => {
request
.get("/course/selectPage", {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
name: data.name,
number: data.number,
teacher: data.teacher,
},
})
.then((res) => {
data.tableData = res.data?.list || [];
data.total = res.data?.total || 0;
});
};

// 加载一次 获取课程数据
load();

const handleCurrentChange = () => {
// 当翻页的时候重新加载数据
load();
};

const reset = () => {
data.name = "";
data.number = "";
data.teacher = "";
load();
};

const handleAdd = () => {
data.formVisible = true;
data.form = {};
};

const save = () => {
request
.request({
url: data.form.id ? "/course/update" : "/course/add",
method: data.form.id ? "put" : "post",
data: data.form,
})
.then((res) => {
if (res.code === "200") {
ElMessage.success("操作成功");
data.formVisible = false;
load();
} else {
ElMessage.error(res.msg);
}
});
};

const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row));
data.formVisible = true;
};
</script>

后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# -*- coding:utf-8 -*-
# Author: Zachary
from typing import Optional

from fastapi import APIRouter, Query, Depends

from api import app
from common.pageHelper import PageHelper
from common.result import ResultModel, Result
from model import Session, get_db_session
from model.course import Course, CourseSearch, CourseCreate, CourseUpdate
from service.courseService import CourseService

course_router = APIRouter(prefix="/course")


@course_router.get("/selectPage", response_model=ResultModel)
async def select_page(page: int = Query(1, ge=1, alias="pageNum", description="Page number"),
size: int = Query(5, gt=0, le=100, alias="pageSize", description="Page size"),
name: Optional[str] = Query(None, description="Course name"),
number: Optional[str] = Query(None, description="Course number"),
teacher: Optional[str] = Query(None, description="Course teacher"),
db_session: Session = Depends(get_db_session)):
pageInfo = PageHelper.startPage(page, size)
course_search = CourseSearch(name=name, number=number, teacher=teacher)
course_list = CourseService.select_page(course_search, db_session)
result = Result.success(pageInfo.of(course_list))
return result


@course_router.post("/add", response_model=ResultModel)
async def add(course: CourseCreate, db_session: Session = Depends(get_db_session)):
CourseService.add_course(course, db_session)
result = Result.success()
return result


@course_router.put("/update", response_model=ResultModel)
async def update(course: CourseUpdate, db_session: Session = Depends(get_db_session)):
CourseService.update_by_id(course, db_session)
result = Result.success()
return result


app.include_router(course_router)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# -*- coding:utf-8 -*-
# Author: Zachary
from fastapi.encoders import jsonable_encoder
from sqlalchemy import desc, select

from common.utils import set_attrs
from exception.customException import CourseExistException, CourseNotExistException
from model import Session
from model.course import Course, CourseSearch, CourseCreate, CourseUpdate


class CourseService:

@staticmethod
def select_page(course_search: CourseSearch, db_session: Session):
query = select(Course).order_by(desc(Course.id))
if course_search.name:
query = query.where(Course.name.like(f"%{course_search.name}%"))
if course_search.number:
query = query.where(Course.number.like(f"%{course_search.number}%"))
if course_search.teacher:
query = query.where(Course.teacher.like(f"%{course_search.teacher}%"))
result = db_session.execute(query).scalars().all()
return result

@staticmethod
def add_course(course: CourseCreate, db_session: Session):
query = select(Course).where(Course.name == course.name)
exist_course: Course = db_session.execute(query).scalars().all()
if exist_course:
raise CourseExistException("课程名已存在")
course = Course(**course.dict())
db_session.add(course)
db_session.commit()
return course

@staticmethod
def update_by_id(course: CourseUpdate, db_session: Session):
query = select(Course).where(Course.id == course.id)
exist_course: Course = db_session.execute(query).scalar()
if not exist_course:
raise CourseNotExistException("课程不存在")
set_attrs(exist_course, jsonable_encoder(course))
db_session.commit()
return exist_course
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# -*- coding:utf-8 -*-
# Author: Zachary
from pydantic import BaseModel
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from model import Base


class Course(Base):
__tablename__ = "course"
id: Mapped[int] = mapped_column(Integer, primary_key=True, nullable=False)
name: Mapped[str] = mapped_column(String(255), nullable=False)
number: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str] = mapped_column(String(255), nullable=False)
periods: Mapped[str] = mapped_column(String(255), nullable=False)
teacher: Mapped[str] = mapped_column(String(255), nullable=False)


class CourseSearch(BaseModel):
name: str | None
number: str | None
teacher: str | None


class CourseBase(BaseModel):
name: str
number: str
description: str
periods: str
teacher: str


class CourseCreate(CourseBase):
...


class CourseUpdate(CourseBase):
id: int

实现给对象的属性更新的一个方法,在项目目录/common下,新建一个utils.py

1
2
3
4
5
6
7
8
9
# -*- coding:utf-8 -*-
# Author: Zachary


# 用于更新对象属性
def set_attrs(obj, data: dict):
if data:
for key, value in data.items():
setattr(obj, key, value)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -*- coding:utf-8 -*-
# Author: Zachary

class UserNotFoundException(Exception):
def __init__(self, message: str):
self.message = message


class PasswordNotMatchException(Exception):
def __init__(self, message: str):
self.message = message


class TokenException(Exception):
def __init__(self, message: str):
self.message = message


class CourseExistException(Exception):
def __init__(self, message: str):
self.message = message


class CourseNotExistException(Exception):
def __init__(self, message: str):
self.message = message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# -*- coding:utf-8 -*-
# Author: Zachary
from fastapi.encoders import jsonable_encoder
from starlette.responses import JSONResponse

from api import app
from fastapi import Request

from common.result import Result
from exception.customException import UserNotFoundException, PasswordNotMatchException, TokenException, \
CourseExistException, CourseNotExistException


@app.exception_handler(Exception)
async def exception_handler(request: Request, exc: Exception):
result = Result.error(code="500", msg=str(exc))
return JSONResponse(status_code=500, content=jsonable_encoder(result))


@app.exception_handler(UserNotFoundException)
async def user_not_fount_exception_handler(request: Request, exc: UserNotFoundException):
result = Result.error(code="404", msg=exc.message)
return JSONResponse(status_code=404, content=jsonable_encoder(result))


@app.exception_handler(PasswordNotMatchException)
async def password_not_fount_exception_handler(request: Request, exc: PasswordNotMatchException):
result = Result.error(code="401", msg=exc.message)
return JSONResponse(status_code=401, content=jsonable_encoder(result))


@app.exception_handler(TokenException)
async def token_exception_handler(request: Request, exc: TokenException):
result = Result.error(code="401", msg=exc.message)
return JSONResponse(status_code=401, content=jsonable_encoder(result))


@app.exception_handler(CourseExistException)
async def course_exist_exception_handler(request: Request, exc: CourseExistException):
result = Result.error(code="400", msg=exc.message)
return JSONResponse(status_code=400, content=jsonable_encoder(result))


@app.exception_handler(CourseNotExistException)
async def course_not_exist_exception_handler(request: Request, exc: CourseNotExistException):
result = Result.error(code="404", msg=exc.message)
return JSONResponse(status_code=404, content=jsonable_encoder(result))

测试

删除

点击已有课程数据中的删除按钮,实现删除数据表中对应数据

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
<template>
<div>
<div class="card" style="margin-bottom: 10px">
<el-input
@input="load"
style="width: 260px"
v-model="data.name"
placeholder="请输入要查询的课程名称"
:prefix-icon="Search"
/>
<el-input
@input="load"
style="margin-left:10px; width: 260px"
v-model="data.number"
placeholder="请输入课程编号"
:prefix-icon="Search"
/>
<el-input
@input="load"
style="margin-left:10px; width: 260px"
v-model="data.teacher"
placeholder="请输入任课老师"
:prefix-icon="Search"
/>
<el-button type="info" plain style="margin: 0 0 0 10px" @click="reset"
>重置</el-button
>
</div>

<div class="card" style="margin-bottom: 10px">
<div>
<el-button
type="primary"
style="margin-bottom: 5px"
plain
@click="handleAdd"
>新增</el-button
>
</div>
<div>
<el-table :data="data.tableData" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="课程名称" width="180" />
<el-table-column prop="number" label="课程编号" width="180" />
<el-table-column prop="description" label="课程描述" width="240" />
<el-table-column prop="periods" label="课时" width="180" />
<el-table-column prop="teacher" label="任课教师" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
type="primary"
size="small"
plain
@click="handleEdit(scope.row)"
>编辑</el-button
>
<el-button
type="danger"
size="small"
plain
@click="handleDelete(scope.row.id)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</div>
</div>

<div class="card">
<el-pagination
v-model:current-page="data.pageNum"
v-model:page-size="data.pageSize"
@current-change="handleCurrentChange"
background
layout="prev, pager, next"
:total="data.total"
/>
</div>

<el-dialog width="35%" v-model="data.formVisible" title="课程信息">
<el-form
:model="data.form"
label-width="100px"
label-position="right"
style="padding-right: 45px"
>
<el-form-item label="课程名称">
<el-input v-model="data.form.name" autocomplete="off" />
</el-form-item>
<el-form-item label="课程编号">
<el-input v-model="data.form.number" autocomplete="off" />
</el-form-item>
<el-form-item label="课程描述">
<el-input v-model="data.form.description" autocomplete="off" />
</el-form-item>
<el-form-item label="课时">
<el-input v-model="data.form.periods" autocomplete="off" />
</el-form-item>
<el-form-item label="任课教师">
<el-input v-model="data.form.teacher" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false" plain>取消</el-button>
<el-button type="primary" @click="save" plain>保存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>

<script setup>
import { reactive } from "vue";
import { Search } from "@element-plus/icons-vue";
import request from "@/utils/request";
import { ElMessage, ElMessageBox } from "element-plus";

const data = reactive({
name: "",
number: "",
teacher: "",
tableData: [],
total: 0,
pageNum: 1,
pageSize: 5,
formVisible: false,
form: {},
});

const load = () => {
request
.get("/course/selectPage", {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
name: data.name,
number: data.number,
teacher: data.teacher,
},
})
.then((res) => {
data.tableData = res.data?.list || [];
data.total = res.data?.total || 0;
});
};

// 加载一次 获取课程数据
load();

const handleCurrentChange = () => {
// 当翻页的时候重新加载数据
load();
};

const reset = () => {
data.name = "";
data.number = "";
data.teacher = "";
load();
};

const handleAdd = () => {
data.formVisible = true;
data.form = {};
};

const save = () => {
request
.request({
url: data.form.id ? "/course/update" : "/course/add",
method: data.form.id ? "put" : "post",
data: data.form,
})
.then((res) => {
if (res.code === "200") {
ElMessage.success("操作成功");
data.formVisible = false;
load();
} else {
ElMessage.error(res.msg);
}
});
};

const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row));
data.formVisible = true;
};

const handleDelete = (id) => {
ElMessageBox.confirm("删除后内容将无法恢复,您确认删除嘛?", "删除确认", {
type: "warning",
})
.then((res) => {
request.delete("/course/delete/" + id).then((res) => {
if (res.code === "200") {
ElMessage.success("删除成功");
load();
} else {
ElMessage.error(res.msg);
}
});
})
.catch((err) => {
ElMessage.error(err.response?.data?.msg || err.message);
});
};
</script>

后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# -*- coding:utf-8 -*-
# Author: Zachary
from typing import Optional

from fastapi import APIRouter, Query, Depends

from api import app
from common.pageHelper import PageHelper
from common.result import ResultModel, Result
from model import Session, get_db_session
from model.course import Course, CourseSearch, CourseCreate, CourseUpdate
from service.courseService import CourseService

course_router = APIRouter(prefix="/course")


@course_router.get("/selectPage", response_model=ResultModel)
async def select_page(page: int = Query(1, ge=1, alias="pageNum", description="Page number"),
size: int = Query(5, gt=0, le=100, alias="pageSize", description="Page size"),
name: Optional[str] = Query(None, description="Course name"),
number: Optional[str] = Query(None, description="Course number"),
teacher: Optional[str] = Query(None, description="Course teacher"),
db_session: Session = Depends(get_db_session)):
pageInfo = PageHelper.startPage(page, size)
course_search = CourseSearch(name=name, number=number, teacher=teacher)
course_list = CourseService.select_page(course_search, db_session)
result = Result.success(pageInfo.of(course_list))
return result


@course_router.post("/add", response_model=ResultModel)
async def add(course: CourseCreate, db_session: Session = Depends(get_db_session)):
CourseService.add_course(course, db_session)
result = Result.success()
return result


@course_router.put("/update", response_model=ResultModel)
async def update(course: CourseUpdate, db_session: Session = Depends(get_db_session)):
CourseService.update_by_id(course, db_session)
result = Result.success()
return result


@course_router.delete("/delete/{id}", response_model=ResultModel)
async def delete(id: int, db_session: Session = Depends(get_db_session)):
CourseService.delete_by_id(id, db_session)
result = Result.success()
return result


app.include_router(course_router)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# -*- coding:utf-8 -*-
# Author: Zachary
from fastapi.encoders import jsonable_encoder
from sqlalchemy import desc, select

from common.utils import set_attrs
from exception.customException import CourseExistException, CourseNotExistException
from model import Session
from model.course import Course, CourseSearch, CourseCreate, CourseUpdate


class CourseService:

@staticmethod
def select_page(course_search: CourseSearch, db_session: Session):
query = select(Course).order_by(desc(Course.id))
if course_search.name:
query = query.where(Course.name.like(f"%{course_search.name}%"))
if course_search.number:
query = query.where(Course.number.like(f"%{course_search.number}%"))
if course_search.teacher:
query = query.where(Course.teacher.like(f"%{course_search.teacher}%"))
result = db_session.execute(query).scalars().all()
return result

@staticmethod
def add_course(course: CourseCreate, db_session: Session):
query = select(Course).where(Course.name == course.name)
exist_course: Course = db_session.execute(query).scalars().all()
if exist_course:
raise CourseExistException("课程名已存在")
course = Course(**course.dict())
db_session.add(course)
db_session.commit()
return course

@staticmethod
def update_by_id(course: CourseUpdate, db_session: Session):
exist_course: Course = check_course_exist(course.id, db_session)
set_attrs(exist_course, jsonable_encoder(course))
db_session.commit()
return exist_course

@staticmethod
def delete_by_id(id: int, db_session: Session):
exist_course: Course = check_course_exist(id, db_session)
db_session.delete(exist_course)
db_session.commit()
return exist_course


def check_course_exist(course_id: int, db_session: Session):
query = select(Course).where(Course.id == course_id)
exist_course: Course = db_session.execute(query).scalar()
if not exist_course:
raise CourseNotExistException("课程不存在")
return exist_course

测试

先创建一个课程

然后删除

删除成功


更新: 2024-05-03 22:02:17
原文: https://www.yuque.com/zacharyblock/iacda/lsqxkzaok37g7aaz