【wiki知识库】04.SpringBoot后端实现电子书的增删改查以及前端界面的展示
CSDN 2024-08-22 12:33:01 阅读 80
📝个人主页:哈__
期待您的关注
目录
一、🔥今日内容
二、🌏前端页面的改造
2.1新增电子书管理页面
2.2新增路由规则
2.3修改the-header代码
三、🚗SpringBoot后端Ebook模块改造
3.1增加电子书增/改接口
3.1.1新增EbookSaveParam
3.1.2添加Controller代码
3.1.3在Ebook实体类上增加一个注解
3.2 增加电子书删除接口
四、🔨测试
4.1添加功能测试
4.2修改功能测试。
4.3删除功能测试
一、🔥今日内容
【wiki知识库】03.前后端的初步交互(展现所有的电子书)-CSDN博客
上一次带领大家把前端的首页部分实现了一下,成功的从数据库当中取出了我们的信息并且展示在前端页面,到了下图的部分。
今天主要是把这个网页的界面初步优化一下,修改一下导航栏以及增加电子书管理模块。包含电子书的查询功能、新增功能、编辑功能和删除功能(不包括文档管理)。
二、🌏前端页面的改造
2.1新增电子书管理页面
我在src下新建了admin文件夹,这个文件夹中的内容是给网站管理员看到的,所以放到了admin目录,名字为admin-ebook.vue。
admin-ebook.vue的具体内容如下。这个文件里我注释掉了一些信息,而且这个文件中的内容包含了页面需要的功能很多,有的一些并不是今天要讲解的内容,所以并没有使用到。今天主要就是想带着大家做出一个电子书管理的模块来。
<code><template>
<a-layout>
<a-layout-content
:style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"code>
>
<p>
<a-form layout="inline" :model="param">code>
<a-form-item>
<a-input v-model:value="param.name" placeholder="名称">code>
</a-input>
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleQuery({page: 1, size: pagination.pageSize})">code>
查询
</a-button>
</a-form-item>
<a-form-item>
<a-button type="primary" @click="add()">code>
新增
</a-button>
</a-form-item>
</a-form>
</p>
<a-table
:columns="columns"code>
:row-key="record => record.id"code>
:data-source="ebooks"code>
:pagination="pagination"code>
:loading="loading"code>
@change="handleTableChange"code>
>
<template #cover="{ text: cover }">code>
<img v-if="cover" :src="cover" alt="avatar" />code>
</template>
<template v-slot:category="{ text, record }">code>
<!-- <span>{ { getCategoryName(record.category1Id) }} / { { getCategoryName(record.category2Id) }}</span> -->
</template>
<template v-slot:action="{ text, record }">code>
<a-space size="small">code>
<!-- <router-link :to="'/admin/doc?ebookId=' + record.id"> -->code>
<a-button type="primary">code>
文档管理
</a-button>
<!-- </router-link> -->
<a-button type="primary" @click="edit(record)">code>
编辑
</a-button>
<a-popconfirm
title="删除后不可恢复,确认删除?"code>
ok-text="是"code>
cancel-text="否"code>
@confirm="handleDelete(record.id)"code>
>
<a-button type="danger">code>
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</a-layout-content>
</a-layout>
<a-modal
title="电子书表单"code>
v-model:visible="modalVisible"code>
:confirm-loading="modalLoading"code>
@ok="handleModalOk"code>
>
<a-form :model="ebook" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">code>
<a-form-item label="封面">code>
<a-input v-model:value="ebook.cover" />code>
</a-form-item>
<a-form-item label="名称">code>
<a-input v-model:value="ebook.name" />code>
</a-form-item>
<a-form-item label="分类">code>
<a-cascader
v-model:value="categoryIds"code>
:field-names="{ label: 'name', value: 'id', children: 'children' }"code>
:options="level1"code>
/>
</a-form-item>
<a-form-item label="描述">code>
<a-input v-model:value="ebook.description" type="textarea" />code>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts">code>
import { defineComponent, onMounted, ref } from 'vue';
import axios from 'axios';
import { message } from 'ant-design-vue';
import {Tool} from "@/util/tool";
export default defineComponent({
name: 'AdminEbook',
setup() {
const param = ref();
param.value = {};
const ebooks = ref();
const pagination = ref({
current: 1,
pageSize: 10,
total: 0
});
const loading = ref(false);
const columns = [
{
title: '封面',
dataIndex: 'cover',
slots: { customRender: 'cover' }
},
{
title: '名称',
dataIndex: 'name'
},
{
title: '分类',
slots: { customRender: 'category' }
},
{
title: '文档数',
dataIndex: 'docCount'
},
{
title: '阅读数',
dataIndex: 'viewCount'
},
{
title: '点赞数',
dataIndex: 'voteCount'
},
{
title: 'Action',
key: 'action',
slots: { customRender: 'action' }
}
];
/**
* 数据查询
**/
const handleQuery = (params: any) => {
loading.value = true;
// 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
ebooks.value = [];
axios.get("/ebook/list", {
params: {
page: params.page,
size: params.size,
name: param.value.name
}
}).then((response) => {
loading.value = false;
const data = response.data;
if (data.success) {
ebooks.value = data.content.list;
// 重置分页按钮
pagination.value.current = params.page;
pagination.value.total = data.content.total;
} else {
message.error(data.message);
}
});
};
/**
* 表格点击页码时触发
*/
const handleTableChange = (pagination: any) => {
console.log("看看自带的分页参数都有啥:" + pagination);
handleQuery({
page: pagination.current,
size: pagination.pageSize
});
};
// -------- 表单 ---------
/**
* 数组,[100, 101]对应:前端开发 / Vue
*/
const categoryIds = ref();
const ebook = ref();
const modalVisible = ref(false);
const modalLoading = ref(false);
const handleModalOk = () => {
modalLoading.value = true;
ebook.value.category1Id = categoryIds.value[0];
ebook.value.category2Id = categoryIds.value[1];
axios.post("/ebook/save", ebook.value).then((response) => {
modalLoading.value = false;
const data = response.data; // data = commonResp
if (data.success) {
modalVisible.value = false;
// 重新加载列表
handleQuery({
page: pagination.value.current,
size: pagination.value.pageSize,
});
} else {
message.error(data.message);
}
});
};
/**
* 编辑
*/
const edit = (record: any) => {
modalVisible.value = true;
ebook.value = Tool.copy(record);
categoryIds.value = [ebook.value.category1Id, ebook.value.category2Id]
};
/**
* 新增
*/
const add = () => {
modalVisible.value = true;
ebook.value = {};
};
const handleDelete = (id: number) => {
axios.delete("/ebook/delete/" + id).then((response) => {
const data = response.data; // data = commonResp
if (data.success) {
// 重新加载列表
handleQuery({
page: pagination.value.current,
size: pagination.value.pageSize,
});
} else {
message.error(data.message);
}
});
};
const level1 = ref();
let categorys: any;
/**
* 查询所有分类
**/
const handleQueryCategory = () => {
loading.value = true;
axios.get("/category/all").then((response) => {
loading.value = false;
const data = response.data;
if (data.success) {
categorys = data.content;
console.log("原始数组:", categorys);
level1.value = [];
level1.value = Tool.array2Tree(categorys, 0);
console.log("树形结构:", level1.value);
// 加载完分类后,再加载电子书,否则如果分类树加载很慢,则电子书渲染会报错
handleQuery({
page: 1,
size: pagination.value.pageSize,
});
} else {
message.error(data.message);
}
});
};
const getCategoryName = (cid: number) => {
// console.log(cid)
let result = "";
categorys.forEach((item: any) => {
if (item.id === cid) {
// return item.name; // 注意,这里直接return不起作用
result = item.name;
}
});
return result;
};
onMounted(() => {
handleQuery({
page: pagination.value.current,
size: pagination.value.pageSize,
});
});
return {
param,
ebooks,
pagination,
columns,
loading,
handleTableChange,
handleQuery,
getCategoryName,
edit,
add,
ebook,
modalVisible,
modalLoading,
handleModalOk,
categoryIds,
level1,
handleDelete
}
}
});
</script>
<style scoped>
img {
width: 50px;
height: 50px;
}
</style>
上边的内容很多,但我们今天核心的前端调用部分是下边的代码。
const handleQuery = (params: any) => {
loading.value = true;
// 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
ebooks.value = [];
axios.get("/ebook/list", {
params: {
page: params.page,
size: params.size,
name: param.value.name
}
}).then((response) => {
loading.value = false;
const data = response.data;
if (data.success) {
ebooks.value = data.content.list;
// 重置分页按钮
pagination.value.current = params.page;
pagination.value.total = data.content.total;
} else {
message.error(data.message);
}
});
};
当我们进去这个页面的时候,首先就会调用下方代码,请求路径也恰好是我们后端之前写过的list接口,用来分页查询电子书信息。
onMounted(() => {
handleQuery({
page: pagination.value.current,
size: pagination.value.pageSize,
});
});
2.2新增路由规则
既然都要新增一个电子书的管理页面了,那我们也要为这个页面分配一个能够匹配到的路由路径。
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AdminEbook from '@/views/admin/admin-ebook.vue'
import AboutView from '../views/AboutView.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component:AboutView
},
{
path: '/admin/ebook',
name: 'AdminEbook',
component: AdminEbook
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
2.3修改the-header代码
我们新增的组件是通过点击the-header组件中的按钮实现跳转的,这里要修改一些代码。我在这个页面添加了一些路由用于跳转我们的组件。
<template>
<a-layout-header class="header">code>
<div class="logo" ></div>code>
<a-menu
theme="dark"code>
mode="horizontal"code>
v-model:selectedKeys="sselectedKeys1"code>
:style="{ lineHeight: '64px' }"code>
>
<a-menu-item key="/"><router-link to="/">首页</router-link></a-menu-item>code>
<a-menu-item key="/admin/ebook"><router-link to="/admin/ebook">电子书管理</router-link></a-menu-item>code>
<a-menu-item key="/about"><router-link to="/about">关于我们</router-link></a-menu-item>code>
</a-menu>
</a-layout-header>
</template>
至此我们前端改造成功,接下来就是后端了。
三、🚗SpringBoot后端Ebook模块改造
3.1增加电子书增/改接口
在我们点击新增按钮或者编辑按钮的时候,会弹出一个窗口来添加或者修改电子书的信息,当我们点击确定之后会向后端发送请求。请求接口是/ebook/save,注意,这里的save指代两个功能,第一个是新增,第二个是修改。
3.1.1新增EbookSaveParam
这个实体类用于封装我们前端传过来的电子书的信息。
<code>@Data
public class EbookSaveParam {
private Long id;
@NotNull(message = "【名称】不能为空")
private String name;
private Long category1Id;
private Long category2Id;
private String description;
private String cover;
private Integer docCount;
private Integer viewCount;
private Integer voteCount;
}
3.1.2添加Controller代码
这里我直接使用的MybatisPlus封装好的函数
/**
* 保存/修改电子书
* @param ebookQueryParam
* @return
*/
@PostMapping("/save")
public CommonResp save(@Validated @RequestBody EbookSaveParam ebookQueryParam){
boolean res = ebookService.saveOrUpdate(CopyUtil.copy(ebookQueryParam,Ebook.class));
String message = Boolean.TRUE.equals(res) ? "操作成功":"操作失败";
return new CommonResp<>(true,message,null);
}
3.1.3在Ebook实体类上增加一个注解
我们要使用雪花算法生成的id存储在数据库当中。
/**
* id
*/
@TableId(type = IdType.ASSIGN_UUID)
private Long id;
当然除了雪花id还有其他的id可供选择。这里就不一一给大家说了。
3.2 增加电子书删除接口
删除功能的接口是下边图中所示。采用的是Restful风格的请求。
对应Controller代码。
<code> /**
* 删除电子书
* @param id 电子书id
* @return
*/
@DeleteMapping("/delete/{id}")
public CommonResp delete(@PathVariable("id") Long id){
boolean res = ebookService.removeById(id);
String message = Boolean.TRUE.equals(res) ? "删除成功":"删除失败";
return new CommonResp<>(true,message,null);
}
四、🔨测试
4.1添加功能测试
测试之前还要注释两行代码。因为我们的分类模块还没写,这里不能传值。
随便加一个电子书上去。
结果还是没问题的。
4.2修改功能测试。
不在截图展示了,点击编辑按钮之后哦修改数据我这里是正确的。
4.3删除功能测试
这时就有问题了,我删除怎么成功不了?那么你是否会分析原因呢?先看看前端的打印。
仔细看看我们传过去的id是什么,再看看你的数据库里是否有这个id? 显然是没有的。
这里就要说一下前后端传输数据的数据精度丢失问题了,因为我们传的数据是一个整形,而且数值很大,在传输的过程总是有精度问题得,想要解决就需要在后端加一个配置类。
<code>package com.my.hawiki.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
/**
* 统一注解,解决前后端交互Long类型精度丢失的问题
*/
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}
之后在运行代码试试。大功告成。
电子书管理页面的基本几个功能差不多就这么多了。
下一篇: 【Web世界探险家】3. CSS美学(二)文本样式
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。