• 【wiki知识库】04.SpringBoot后端实现电子书的增删改查以及前端界面的展示


      📝个人主页:哈__

    期待您的关注 

    目录

    一、🔥今日内容

    二、🌏前端页面的改造

    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的具体内容如下。这个文件里我注释掉了一些信息,而且这个文件中的内容包含了页面需要的功能很多,有的一些并不是今天要讲解的内容,所以并没有使用到。今天主要就是想带着大家做出一个电子书管理的模块来。

    1. <template>
    2. <a-layout>
    3. <a-layout-content
    4. :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
    5. >
    6. <p>
    7. <a-form layout="inline" :model="param">
    8. <a-form-item>
    9. <a-input v-model:value="param.name" placeholder="名称">
    10. a-input>
    11. a-form-item>
    12. <a-form-item>
    13. <a-button type="primary" @click="handleQuery({page: 1, size: pagination.pageSize})">
    14. 查询
    15. a-button>
    16. a-form-item>
    17. <a-form-item>
    18. <a-button type="primary" @click="add()">
    19. 新增
    20. a-button>
    21. a-form-item>
    22. a-form>
    23. p>
    24. <a-table
    25. :columns="columns"
    26. :row-key="record => record.id"
    27. :data-source="ebooks"
    28. :pagination="pagination"
    29. :loading="loading"
    30. @change="handleTableChange"
    31. >
    32. <template #cover="{ text: cover }">
    33. <img v-if="cover" :src="cover" alt="avatar" />
    34. template>
    35. <template v-slot:category="{ text, record }">
    36. template>
    37. <template v-slot:action="{ text, record }">
    38. <a-space size="small">
    39. <a-button type="primary">
    40. 文档管理
    41. a-button>
    42. <a-button type="primary" @click="edit(record)">
    43. 编辑
    44. a-button>
    45. <a-popconfirm
    46. title="删除后不可恢复,确认删除?"
    47. ok-text="是"
    48. cancel-text="否"
    49. @confirm="handleDelete(record.id)"
    50. >
    51. <a-button type="danger">
    52. 删除
    53. a-button>
    54. a-popconfirm>
    55. a-space>
    56. template>
    57. a-table>
    58. a-layout-content>
    59. a-layout>
    60. <a-modal
    61. title="电子书表单"
    62. v-model:visible="modalVisible"
    63. :confirm-loading="modalLoading"
    64. @ok="handleModalOk"
    65. >
    66. <a-form :model="ebook" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
    67. <a-form-item label="封面">
    68. <a-input v-model:value="ebook.cover" />
    69. a-form-item>
    70. <a-form-item label="名称">
    71. <a-input v-model:value="ebook.name" />
    72. a-form-item>
    73. <a-form-item label="分类">
    74. <a-cascader
    75. v-model:value="categoryIds"
    76. :field-names="{ label: 'name', value: 'id', children: 'children' }"
    77. :options="level1"
    78. />
    79. a-form-item>
    80. <a-form-item label="描述">
    81. <a-input v-model:value="ebook.description" type="textarea" />
    82. a-form-item>
    83. a-form>
    84. a-modal>
    85. template>
    86. <script lang="ts">
    87. import { defineComponent, onMounted, ref } from 'vue';
    88. import axios from 'axios';
    89. import { message } from 'ant-design-vue';
    90. import {Tool} from "@/util/tool";
    91. export default defineComponent({
    92. name: 'AdminEbook',
    93. setup() {
    94. const param = ref();
    95. param.value = {};
    96. const ebooks = ref();
    97. const pagination = ref({
    98. current: 1,
    99. pageSize: 10,
    100. total: 0
    101. });
    102. const loading = ref(false);
    103. const columns = [
    104. {
    105. title: '封面',
    106. dataIndex: 'cover',
    107. slots: { customRender: 'cover' }
    108. },
    109. {
    110. title: '名称',
    111. dataIndex: 'name'
    112. },
    113. {
    114. title: '分类',
    115. slots: { customRender: 'category' }
    116. },
    117. {
    118. title: '文档数',
    119. dataIndex: 'docCount'
    120. },
    121. {
    122. title: '阅读数',
    123. dataIndex: 'viewCount'
    124. },
    125. {
    126. title: '点赞数',
    127. dataIndex: 'voteCount'
    128. },
    129. {
    130. title: 'Action',
    131. key: 'action',
    132. slots: { customRender: 'action' }
    133. }
    134. ];
    135. /**
    136. * 数据查询
    137. **/
    138. const handleQuery = (params: any) => {
    139. loading.value = true;
    140. // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
    141. ebooks.value = [];
    142. axios.get("/ebook/list", {
    143. params: {
    144. page: params.page,
    145. size: params.size,
    146. name: param.value.name
    147. }
    148. }).then((response) => {
    149. loading.value = false;
    150. const data = response.data;
    151. if (data.success) {
    152. ebooks.value = data.content.list;
    153. // 重置分页按钮
    154. pagination.value.current = params.page;
    155. pagination.value.total = data.content.total;
    156. } else {
    157. message.error(data.message);
    158. }
    159. });
    160. };
    161. /**
    162. * 表格点击页码时触发
    163. */
    164. const handleTableChange = (pagination: any) => {
    165. console.log("看看自带的分页参数都有啥:" + pagination);
    166. handleQuery({
    167. page: pagination.current,
    168. size: pagination.pageSize
    169. });
    170. };
    171. // -------- 表单 ---------
    172. /**
    173. * 数组,[100, 101]对应:前端开发 / Vue
    174. */
    175. const categoryIds = ref();
    176. const ebook = ref();
    177. const modalVisible = ref(false);
    178. const modalLoading = ref(false);
    179. const handleModalOk = () => {
    180. modalLoading.value = true;
    181. ebook.value.category1Id = categoryIds.value[0];
    182. ebook.value.category2Id = categoryIds.value[1];
    183. axios.post("/ebook/save", ebook.value).then((response) => {
    184. modalLoading.value = false;
    185. const data = response.data; // data = commonResp
    186. if (data.success) {
    187. modalVisible.value = false;
    188. // 重新加载列表
    189. handleQuery({
    190. page: pagination.value.current,
    191. size: pagination.value.pageSize,
    192. });
    193. } else {
    194. message.error(data.message);
    195. }
    196. });
    197. };
    198. /**
    199. * 编辑
    200. */
    201. const edit = (record: any) => {
    202. modalVisible.value = true;
    203. ebook.value = Tool.copy(record);
    204. categoryIds.value = [ebook.value.category1Id, ebook.value.category2Id]
    205. };
    206. /**
    207. * 新增
    208. */
    209. const add = () => {
    210. modalVisible.value = true;
    211. ebook.value = {};
    212. };
    213. const handleDelete = (id: number) => {
    214. axios.delete("/ebook/delete/" + id).then((response) => {
    215. const data = response.data; // data = commonResp
    216. if (data.success) {
    217. // 重新加载列表
    218. handleQuery({
    219. page: pagination.value.current,
    220. size: pagination.value.pageSize,
    221. });
    222. } else {
    223. message.error(data.message);
    224. }
    225. });
    226. };
    227. const level1 = ref();
    228. let categorys: any;
    229. /**
    230. * 查询所有分类
    231. **/
    232. const handleQueryCategory = () => {
    233. loading.value = true;
    234. axios.get("/category/all").then((response) => {
    235. loading.value = false;
    236. const data = response.data;
    237. if (data.success) {
    238. categorys = data.content;
    239. console.log("原始数组:", categorys);
    240. level1.value = [];
    241. level1.value = Tool.array2Tree(categorys, 0);
    242. console.log("树形结构:", level1.value);
    243. // 加载完分类后,再加载电子书,否则如果分类树加载很慢,则电子书渲染会报错
    244. handleQuery({
    245. page: 1,
    246. size: pagination.value.pageSize,
    247. });
    248. } else {
    249. message.error(data.message);
    250. }
    251. });
    252. };
    253. const getCategoryName = (cid: number) => {
    254. // console.log(cid)
    255. let result = "";
    256. categorys.forEach((item: any) => {
    257. if (item.id === cid) {
    258. // return item.name; // 注意,这里直接return不起作用
    259. result = item.name;
    260. }
    261. });
    262. return result;
    263. };
    264. onMounted(() => {
    265. handleQuery({
    266. page: pagination.value.current,
    267. size: pagination.value.pageSize,
    268. });
    269. });
    270. return {
    271. param,
    272. ebooks,
    273. pagination,
    274. columns,
    275. loading,
    276. handleTableChange,
    277. handleQuery,
    278. getCategoryName,
    279. edit,
    280. add,
    281. ebook,
    282. modalVisible,
    283. modalLoading,
    284. handleModalOk,
    285. categoryIds,
    286. level1,
    287. handleDelete
    288. }
    289. }
    290. });
    291. script>
    292. <style scoped>
    293. img {
    294. width: 50px;
    295. height: 50px;
    296. }
    297. style>

    上边的内容很多,但我们今天核心的前端调用部分是下边的代码。

    1. const handleQuery = (params: any) => {
    2. loading.value = true;
    3. // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
    4. ebooks.value = [];
    5. axios.get("/ebook/list", {
    6. params: {
    7. page: params.page,
    8. size: params.size,
    9. name: param.value.name
    10. }
    11. }).then((response) => {
    12. loading.value = false;
    13. const data = response.data;
    14. if (data.success) {
    15. ebooks.value = data.content.list;
    16. // 重置分页按钮
    17. pagination.value.current = params.page;
    18. pagination.value.total = data.content.total;
    19. } else {
    20. message.error(data.message);
    21. }
    22. });
    23. };

    当我们进去这个页面的时候,首先就会调用下方代码,请求路径也恰好是我们后端之前写过的list接口,用来分页查询电子书信息。

    1. onMounted(() => {
    2. handleQuery({
    3. page: pagination.value.current,
    4. size: pagination.value.pageSize,
    5. });
    6. });

    2.2新增路由规则

    既然都要新增一个电子书的管理页面了,那我们也要为这个页面分配一个能够匹配到的路由路径。

    1. import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
    2. import HomeView from '../views/HomeView.vue'
    3. import AdminEbook from '@/views/admin/admin-ebook.vue'
    4. import AboutView from '../views/AboutView.vue'
    5. const routes: Array<RouteRecordRaw> = [
    6. {
    7. path: '/',
    8. name: 'home',
    9. component: HomeView
    10. },
    11. {
    12. path: '/about',
    13. name: 'about',
    14. component:AboutView
    15. },
    16. {
    17. path: '/admin/ebook',
    18. name: 'AdminEbook',
    19. component: AdminEbook
    20. },
    21. ]
    22. const router = createRouter({
    23. history: createWebHistory(process.env.BASE_URL),
    24. routes
    25. })
    26. export default router

    2.3修改the-header代码

     我们新增的组件是通过点击the-header组件中的按钮实现跳转的,这里要修改一些代码。我在这个页面添加了一些路由用于跳转我们的组件。

    1. <template>
    2. <a-layout-header class="header">
    3. <div class="logo" >div>
    4. <a-menu
    5. theme="dark"
    6. mode="horizontal"
    7. v-model:selectedKeys="sselectedKeys1"
    8. :style="{ lineHeight: '64px' }"
    9. >
    10. <a-menu-item key="/"><router-link to="/">首页router-link>a-menu-item>
    11. <a-menu-item key="/admin/ebook"><router-link to="/admin/ebook">电子书管理router-link>a-menu-item>
    12. <a-menu-item key="/about"><router-link to="/about">关于我们router-link>a-menu-item>
    13. a-menu>
    14. a-layout-header>
    15. template>

    至此我们前端改造成功,接下来就是后端了。

    三、🚗SpringBoot后端Ebook模块改造

    3.1增加电子书增/改接口

    在我们点击新增按钮或者编辑按钮的时候,会弹出一个窗口来添加或者修改电子书的信息,当我们点击确定之后会向后端发送请求。请求接口是/ebook/save,注意,这里的save指代两个功能,第一个是新增,第二个是修改。

    3.1.1新增EbookSaveParam

    这个实体类用于封装我们前端传过来的电子书的信息。

    1. @Data
    2. public class EbookSaveParam {
    3. private Long id;
    4. @NotNull(message = "【名称】不能为空")
    5. private String name;
    6. private Long category1Id;
    7. private Long category2Id;
    8. private String description;
    9. private String cover;
    10. private Integer docCount;
    11. private Integer viewCount;
    12. private Integer voteCount;
    13. }

    3.1.2添加Controller代码

    这里我直接使用的MybatisPlus封装好的函数

    1. /**
    2. * 保存/修改电子书
    3. * @param ebookQueryParam
    4. * @return
    5. */
    6. @PostMapping("/save")
    7. public CommonResp save(@Validated @RequestBody EbookSaveParam ebookQueryParam){
    8. boolean res = ebookService.saveOrUpdate(CopyUtil.copy(ebookQueryParam,Ebook.class));
    9. String message = Boolean.TRUE.equals(res) ? "操作成功":"操作失败";
    10. return new CommonResp<>(true,message,null);
    11. }

    3.1.3在Ebook实体类上增加一个注解

    我们要使用雪花算法生成的id存储在数据库当中。

    1. /**
    2. * id
    3. */
    4. @TableId(type = IdType.ASSIGN_UUID)
    5. private Long id;

    当然除了雪花id还有其他的id可供选择。这里就不一一给大家说了。


    3.2 增加电子书删除接口

    删除功能的接口是下边图中所示。采用的是Restful风格的请求。

     对应Controller代码。

    1. /**
    2. * 删除电子书
    3. * @param id 电子书id
    4. * @return
    5. */
    6. @DeleteMapping("/delete/{id}")
    7. public CommonResp delete(@PathVariable("id") Long id){
    8. boolean res = ebookService.removeById(id);
    9. String message = Boolean.TRUE.equals(res) ? "删除成功":"删除失败";
    10. return new CommonResp<>(true,message,null);
    11. }

    四、🔨测试 

    4.1添加功能测试

    测试之前还要注释两行代码。因为我们的分类模块还没写,这里不能传值。

    随便加一个电子书上去。

    结果还是没问题的。

    4.2修改功能测试。

    不在截图展示了,点击编辑按钮之后哦修改数据我这里是正确的。

    4.3删除功能测试

    这时就有问题了,我删除怎么成功不了?那么你是否会分析原因呢?先看看前端的打印。

    仔细看看我们传过去的id是什么,再看看你的数据库里是否有这个id? 显然是没有的。

    这里就要说一下前后端传输数据的数据精度丢失问题了,因为我们传的数据是一个整形,而且数值很大,在传输的过程总是有精度问题得,想要解决就需要在后端加一个配置类。

    1. package com.my.hawiki.config;
    2. import com.fasterxml.jackson.databind.ObjectMapper;
    3. import com.fasterxml.jackson.databind.module.SimpleModule;
    4. import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
    5. import org.springframework.context.annotation.Bean;
    6. import org.springframework.context.annotation.Configuration;
    7. import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
    8. /**
    9. * 统一注解,解决前后端交互Long类型精度丢失的问题
    10. */
    11. @Configuration
    12. public class JacksonConfig {
    13. @Bean
    14. public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
    15. ObjectMapper objectMapper = builder.createXmlMapper(false).build();
    16. SimpleModule simpleModule = new SimpleModule();
    17. simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
    18. objectMapper.registerModule(simpleModule);
    19. return objectMapper;
    20. }
    21. }

    之后在运行代码试试。大功告成。

     电子书管理页面的基本几个功能差不多就这么多了。

  • 相关阅读:
    文件内容操作
    【劳动者捍卫自己的权利】
    聊聊 mysql 事务?(三:从redo log恢复数据)
    Open3D(C++)欧式聚类分割
    Java面向对象16:接口的定义与实现
    数据库安全:Hadoop 未授权访问-命令执行漏洞.
    使用NodeList
    【Arthas性能排查系列(一)】检查Java程序调用链路的耗时情况
    standard_init_linux.go:211: exec user process caused “exec format error“
    【微服务|SCG】Filters的33种用法
  • 原文地址:https://blog.csdn.net/qq_61024956/article/details/139395392