📝个人主页:哈__
期待您的关注
目录
[3.2 增加电子书删除接口](#3.2 增加电子书删除接口)
一、🔥今日内容
【wiki知识库】03.前后端的初步交互(展现所有的电子书)-CSDN博客
上一次带领大家把前端的首页部分实现了一下,成功的从数据库当中取出了我们的信息并且展示在前端页面,到了下图的部分。
今天主要是把这个网页的界面初步优化一下,修改一下导航栏以及增加电子书管理模块。包含电子书的查询功能、新增功能、编辑功能和删除功能(不包括文档管理)。
二、🌏前端页面的改造
2.1新增电子书管理页面
我在src下新建了admin文件夹,这个文件夹中的内容是给网站管理员看到的,所以放到了admin目录,名字为admin-ebook.vue。
admin-ebook.vue的具体内容如下。这个文件里我注释掉了一些信息,而且这个文件中的内容包含了页面需要的功能很多,有的一些并不是今天要讲解的内容,所以并没有使用到。今天主要就是想带着大家做出一个电子书管理的模块来。
html<template> <a-layout> <a-layout-content :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }" > <p> <a-form layout="inline" :model="param"> <a-form-item> <a-input v-model:value="param.name" placeholder="名称"> </a-input> </a-form-item> <a-form-item> <a-button type="primary" @click="handleQuery({page: 1, size: pagination.pageSize})"> 查询 </a-button> </a-form-item> <a-form-item> <a-button type="primary" @click="add()"> 新增 </a-button> </a-form-item> </a-form> </p> <a-table :columns="columns" :row-key="record => record.id" :data-source="ebooks" :pagination="pagination" :loading="loading" @change="handleTableChange" > <template #cover="{ text: cover }"> <img v-if="cover" :src="cover" alt="avatar" /> </template> <template v-slot:category="{ text, record }"> <!-- <span>{{ getCategoryName(record.category1Id) }} / {{ getCategoryName(record.category2Id) }}</span> --> </template> <template v-slot:action="{ text, record }"> <a-space size="small"> <!-- <router-link :to="'/admin/doc?ebookId=' + record.id"> --> <a-button type="primary"> 文档管理 </a-button> <!-- </router-link> --> <a-button type="primary" @click="edit(record)"> 编辑 </a-button> <a-popconfirm title="删除后不可恢复,确认删除?" ok-text="是" cancel-text="否" @confirm="handleDelete(record.id)" > <a-button type="danger"> 删除 </a-button> </a-popconfirm> </a-space> </template> </a-table> </a-layout-content> </a-layout> <a-modal title="电子书表单" v-model:visible="modalVisible" :confirm-loading="modalLoading" @ok="handleModalOk" > <a-form :model="ebook" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }"> <a-form-item label="封面"> <a-input v-model:value="ebook.cover" /> </a-form-item> <a-form-item label="名称"> <a-input v-model:value="ebook.name" /> </a-form-item> <a-form-item label="分类"> <a-cascader v-model:value="categoryIds" :field-names="{ label: 'name', value: 'id', children: 'children' }" :options="level1" /> </a-form-item> <a-form-item label="描述"> <a-input v-model:value="ebook.description" type="textarea" /> </a-form-item> </a-form> </a-modal> </template> <script lang="ts"> 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>
上边的内容很多,但我们今天核心的前端调用部分是下边的代码。
javascriptconst 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接口,用来分页查询电子书信息。
javascriptonMounted(() => { handleQuery({ page: pagination.value.current, size: pagination.value.pageSize, }); });
2.2新增路由规则
既然都要新增一个电子书的管理页面了,那我们也要为这个页面分配一个能够匹配到的路由路径。
javascriptimport { 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组件中的按钮实现跳转的,这里要修改一些代码。我在这个页面添加了一些路由用于跳转我们的组件。
html<template> <a-layout-header class="header"> <div class="logo" ></div> <a-menu theme="dark" mode="horizontal" v-model:selectedKeys="sselectedKeys1" :style="{ lineHeight: '64px' }" > <a-menu-item key="/"><router-link to="/">首页</router-link></a-menu-item> <a-menu-item key="/admin/ebook"><router-link to="/admin/ebook">电子书管理</router-link></a-menu-item> <a-menu-item key="/about"><router-link to="/about">关于我们</router-link></a-menu-item> </a-menu> </a-layout-header> </template>
至此我们前端改造成功,接下来就是后端了。
三、🚗SpringBoot后端Ebook模块改造
3.1增加电子书增/改接口
在我们点击新增按钮或者编辑按钮的时候,会弹出一个窗口来添加或者修改电子书的信息,当我们点击确定之后会向后端发送请求。请求接口是/ebook/save,注意,这里的save指代两个功能,第一个是新增,第二个是修改。
3.1.1新增EbookSaveParam
这个实体类用于封装我们前端传过来的电子书的信息。
java@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封装好的函数
java/** * 保存/修改电子书 * @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存储在数据库当中。
java/** * id */ @TableId(type = IdType.ASSIGN_UUID) private Long id;
当然除了雪花id还有其他的id可供选择。这里就不一一给大家说了。
3.2 增加电子书删除接口
删除功能的接口是下边图中所示。采用的是Restful风格的请求。
对应Controller代码。
java/** * 删除电子书 * @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? 显然是没有的。
这里就要说一下前后端传输数据的数据精度丢失问题了,因为我们传的数据是一个整形,而且数值很大,在传输的过程总是有精度问题得,想要解决就需要在后端加一个配置类。
javapackage 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; } }
之后在运行代码试试。大功告成。
电子书管理页面的基本几个功能差不多就这么多了。