03. 开发图书分类信息管理功能
SQL
CREATE TABLE `category` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (1, '文学');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (2, '科幻/奇幻');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (3, '历史');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (4, '科学技术');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (5, '经济管理');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (6, '哲学/心理学');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (7, '艺术设计');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (8, '生活/健康');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (9, '儿童读物');
INSERT INTO `book_system`.`category` (`id`, `name`) VALUES (10, '教育/考试');
后端接口
from typing import List, Optional
from fastapi import APIRouter
from pydantic import create_model
from tortoise.contrib.pydantic import pydantic_model_creator
from common.result import Result, PageInfo
from models import Category
router = APIRouter(prefix="/category")
# 创建 pydantic 只读模型
CategoryPydantic = pydantic_model_creator(Category)
# 自动生成所有字段为 Optional 的更新模型
CategoryCreatePydantic = create_model(
"CategoryPydantic",
**{
name: (Optional[field.annotation], None)
for name, field in CategoryPydantic.model_fields.items()
}
)
# 新增
@router.post("/add")
async def add(category_create_pydantic: CategoryCreatePydantic):
create_data = category_create_pydantic.model_dump(exclude_unset=True, exclude={'id'})
await Category.create(**create_data)
return Result.success()
# 修改
@router.put("/update")
async def update(category_create_pydantic: CategoryPydantic):
update_data = category_create_pydantic.model_dump(exclude_unset=True, exclude={"id"})
await Category.filter(id=category_create_pydantic.id).update(**update_data)
return Result.success()
# 删除
@router.delete("/delete/{category_id}")
async def delete(category_id: int):
await Category.filter(id=category_id).delete()
return Result.success()
# 批量删除
@router.delete("/deleteBatch")
async def delete_batch(ids: List[int]):
await Category.filter(id__in=ids).delete()
return Result.success()
# 单个查询
@router.get("/selectById/{category_id}")
async def select_one(category_id: int):
category = await Category.get_or_none(id=category_id)
return Result.success(category)
# 查询所有
@router.get("/selectAll")
async def select_all(name: str = ""):
category_list = await Category.filter(name__contains=name)
return Result.success(category_list)
# 分页查询
@router.get("/selectPage")
async def select_page(name: str = "", pageNum: int = 1, pageSize: int = 10):
# 同时获取分页数据和总数
query = Category.filter(name__contains=name)
# 获取分页数据
category_list = await query.order_by("-id").offset((pageNum - 1) * pageSize).limit(pageSize)
category_list = [
CategoryPydantic.model_validate(category).model_dump()
for category in category_list
]
# 计算总数
total = await query.count()
# 封装分页数据
pageinfo = PageInfo(total=total, list=category_list)
return Result.success(pageinfo)
前端页面
Category.vue
<template>
<div>
<div class="card" style="margin-bottom: 5px;">
<el-input v-model="data.name" style="width: 300px; margin-right: 10px" placeholder="请输入名称查询"></el-input>
<el-button type="primary" @click="load">查询</el-button>
<el-button type="info" style="margin: 0 10px" @click="reset">重置</el-button>
</div>
<div class="card" style="margin-bottom: 5px">
<div style="margin-bottom: 10px">
<el-button type="primary" @click="handleAdd">新增</el-button>
</div>
<el-table :data="data.tableData" stripe>
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="操作" align="center" width="160">
<template #default="scope">
<el-button type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="card">
<el-pagination @current-change="load" background layout="total, prev, pager, next" v-model:page-size="data.pageSize" v-model:current-page="data.pageNum" :total="data.total"/>
</div>
<el-dialog title="分类信息" width="40%" v-model="data.formVisible" :close-on-click-modal="false" destroy-on-close>
<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="100px" style="padding-right: 50px">
<el-form-item label="名称" prop="name">
<el-input v-model="data.form.name" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false">取 消</el-button>
<el-button type="primary" @click="save">保 存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import request from "@/utils/request";
import {reactive, ref} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
pageNum: 1,
pageSize: 10,
total: 0,
formVisible: false,
form: {},
tableData: [],
name: null,
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
}
})
// 分页查询
const load = () => {
request.get('/category/selectPage', {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
name: data.name
}
}).then(res => {
data.tableData = res.data?.list
data.total = res.data?.total
})
}
// 新增
const handleAdd = () => {
data.form = {}
data.formVisible = true
}
// 编辑
const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row))
data.formVisible = true
}
// 新增保存
const add = () => {
request.post('/category/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 编辑保存
const update = () => {
request.put('/category/update', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 弹窗保存
const save = () => {
formRef.value.validate(valid => {
if (valid) {
// data.form有id就是更新,没有就是新增
data.form.id ? update() : add()
}
})
}
// 删除
const handleDelete = (id) => {
ElMessageBox.confirm('删除后数据无法恢复,您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {
request.delete('/category/delete/' + id).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}
// 重置
const reset = () => {
data.name = null
load()
}
load()
</script>