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

<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>

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

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">
      <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

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

@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代码。

 /**
     * 删除电子书
     * @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? 显然是没有的。

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

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;
    }
}

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/674221.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Cacti EZ中文版 12.2.27 ISO 下载安装

简介 修改了yum源为中国高校联合镜像源 github改为gitee。 系统增加中文语言包。 修改时区为东八区。 增加了常用的软件包。 PS&#xff1a;CactiEZ是一个自动化安装cacti和插件的ISO镜像&#xff0c;本教程的ISO是基于官方的IOS针对国内网络做了修改。 可按照目前最新的Ca…

[AI Google] 新的生成媒体模型和工具,专为创作者设计和构建

我们推出了 Veo&#xff0c;我们最强大的高清晰度视频生成模型&#xff0c;以及 Imagen 3&#xff0c;我们质量最高的文本生成图像模型。我们还分享了一些使用我们的 Music AI Sandbox 创作的新演示录音。 在过去的一年里&#xff0c;我们在提升生成媒体技术质量方面取得了令人…

智能售货机的小投入大回报创业机遇

智能售货机的小投入大回报创业机遇 在当今这个快速进化的数字时代&#xff0c;智能售货机作为零售领域的新秀&#xff0c;正以其独特的便捷性和创新性逐步重塑传统零售格局。24小时不间断服务与自动化管理的结合&#xff0c;大幅度削减人力成本&#xff0c;使得智能售货机成为…

生信算法7 - 核酸序列Fasta和蛋白PDB文件读写与检索

python 3.9实现以下算法。 1. 简单的写文件和读文件 # 写 file1 open(count.txt,w) file1.write(this is a test) file1.close()# 读 file2 open(my_file) print(file2.read())2. 将列表内容写入文本文件 # 生成100-500数字列表 data [i * 100 for i in range(1, 6)] pri…

AI大模型,正在排队寻求“卖身”!请保持冷静!

AI独角兽卖身大潮&#xff0c;再添一员 就在上周&#xff0c;备受瞩目的明星企业Stability AI&#xff0c;这家估值接近300亿的大模型领域的佼佼者&#xff0c;**突然传出资金链断裂的严峻消息&#xff0c;并正积极寻求合并的可能性。**与此同时&#xff0c;媒体也透露&#x…

RetroMAE-文本embedding算法

1)输入文本经掩码操作后由编码器&#xff08;Encoder&#xff09;映射为隐空间中的语义向量&#xff1b;而后解码器&#xff08;Decoder&#xff09;借助语义向量将另一段独立掩码的输入文本还原为原始的输入文本 2)编码器的掩码率为15%-30%&#xff1b;解码器的掩码率为50%-70…

HMM地图匹配算法库Barefoot环境搭建

1.引入gps路径匹配开源项目barefoot 克隆仓库 git clone https://github.com/bmwcarit/barefoot.git打开项目执行mvn命令将项目打包到maven仓库 mvn install -DskipTests在自己的maven项目中引入barefoot依赖 <dependency><groupId>com.bmw-carit</groupId&g…

k8s 1.28.x 配置nfs

1.安装nfs&#xff0c;在每个节点上安装 yum install -y nfs-utils 2.创建共享目录(主节点上操作) mkdir -p /opt/nfs/k8s 3.编写NFS的共享配置 /opt/nfs/k8s *(rw,no_root_squash) #*代表对所有IP都开放此目录&#xff0c;rw是读写 4.启动nfs systemctl enable nfs-ser…

Flutter基础 -- Dart 语言 -- 进阶使用

目录 1. 泛型 generics 1.1 泛型使用 1.2 泛型函数 1.3 构造函数泛型 1.4 泛型限制 2. 异步 async 2.1 异步回调 then 2.2 异步等待 await 2.3 异步返回值 3. 生成器 generate &#xff08;了解&#xff09; 3.1 同步生成器 sync* 使用 sync* 的场景 总结 3.2 异…

CRM系统主要是干什么?CRM系统主要功能和作用

什么是CRM 系统&#xff1f;CRM系统到底是干什么的&#xff1f;不同的企业人员该如何利用CRM去解决他们的问题等等&#xff0c;问题太多了&#xff0c;今天来为大家详细介绍。 干货满满&#xff0c;建议收藏&#xff01;&#xff01; 首先第一个问题&#xff0c;什么是CRM系统…

Tween.js在Three.js中的应用:为3D动画添加流畅过渡

前言 在Web开发领域&#xff0c;Three.js已经成为构建精彩3D内容的首选库之一。它让开发者能够轻松地在浏览器中创建和展示复杂的3D场景。然而&#xff0c;要让这些场景栩栩如生&#xff0c;平滑的动画效果是必不可少的。这就引入了Tween.js——一个轻量级但功能强大的JavaScr…

MyBatis核心对象

MyBatis核心类对象主要有俩个&#xff1a; 1&#xff1a;对相关配置文件信息进行封装的Configuration对象 2&#xff1a;用来执行数据库操作的Executor对象。 核心对象----存储类对象Configuration Configuration对象主要有三个作用&#xff1a; 1&#xff1a;封装MyBatis…

linux进程加载和启动过程分析

我们的源代码通过预处理,编译,汇编,链接后形成可执行文件,那么当我们在终端敲下指令$ ./a.out argv1 argv2 后,操作系统是怎么将我们的可执行文件加载并运行的呢? 首先知道,计算机的操作系统的启动程序是写死在硬件上的,每次计算机上电时,都将自动加载启动程序,之后…

R语言数据分析-针对芬兰污染指数的分析与考察

1. 研究背景及意义 近年来&#xff0c;随着我国科技和经济高速发展&#xff0c;人们生活质量也随之显著提高。但是&#xff0c; 环境污染问题也日趋严重&#xff0c;给人们的生活质量和社会生产的各个方面都造成了许多不 利的影响。空气污染作为环境污染主要方面&#xff0c;更…

重生之我要精通JAVA--第七周笔记

文章目录 IO流字符流字符流原理解析flush和close方法 文件拷贝代码文件加密解密修改文件中的数据 缓冲流字节缓冲流字符缓冲流例题 转换流序列化流序列化流/对象操作输出流 反序列化流序列化流/反序列化流的细节汇总打印流字节打印流字符打印流 解压缩流压缩流Commons-io常见方…

代码随想录--哈希表--两数之和

题目 给定一个整数数组 nums 和一个目标值 target&#xff0c;请你在该数组中找出和为目标值的那 两个 整数&#xff0c;并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素不能使用两遍。 示例: 给定 nums [2, 7, 11, 15], t…

【RuoYi】如何解决Postman无法访问RuoYi中的接口数据

一、前言 最近&#xff0c;写项目要求需要将数据返回&#xff0c;指定的接口&#xff0c;并且需要使用Postman来测试接口数据&#xff0c;看是否能够请求到数据。然后项目用的是RuoYi的框架&#xff0c;RuoYi使用了SpringSecurity来做的安全框架&#xff0c;所以在访问的时候&a…

【C语言】编译与链接:深入理解程序构建过程

&#x1f525;引言 本篇将深入理解程序构建过程&#xff0c;以便于我们在编写程序的过程同时&#xff0c;理解底层是如何从程序的创建到生成可执行程序的。 &#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专…

django使用fetch上传文件

在上一篇文章中&#xff0c;我包装了fetch方法&#xff0c;使其携带cookie。但是之前fetch传递的是json数据&#xff0c;现在有了一个上传文件的需求&#xff0c;因此需要进行修改&#xff1a; const sendRequest (url, method, data) > {const csrftoken Cookies.get(cs…

【Effective Python教程】(90个有效方法)笔记——第1章:培养pythonic思维——7:尽量用enumerate取代range

文章目录 第1章&#xff1a;培养pythonic思维第7条 尽量用enumerate取代range&#xff08;移位操作、位掩码&#xff09;要点enumerate函数可以用简洁的代码选代iterator&#xff0c;而且可以指出当前这轮循环的序号。不要先通过range指定下标的取值范围&#xff0c;然后用下标…