0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第五章 电子书管理功能开发

Last updated at Posted at 2025-01-09

1.增加电子书管理页面

⭐增加一个页面需要增加三个部分的内容:
①页面 ②菜单(点击菜单才会跳到页面) ③路由(路由跟页面是绑定起来的)
⭐增加页面,是在views下
改页面名称:对应的路由也要改router

①增加电子书管理页面(登录之后才可以操作):views-admin-admin-ebook

<template>
  <div class="about">
    <h1>电子书管理</h1>
  </div>
</template>

②增加路由:router -index文件中

import AdminEbook from '../views/admin/admin-ebook.vue'

    path: '/admin/ebook',
    name: 'AdminEbook',
    component: AdminEbook

③增加菜单:components-the.header.vue中

<a-menu-item key="/admin/ebook">
        <router-link to="/admin/ebook">电子书管理</router-link>
</a-menu-item>

跳转用router-link to ,点击电子书管理就会跳到/admin/ebook
key和to的值是一样的

2.电子书表格展示(admin-ebook.vue)

①改造页面,修改布局:

image.png

<template>
  <a-layout>
    <a-layout-content
      :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
    >
      <div class="about">
        <h1>电子书管理</h1>
      </div>
    </a-layout-content>
  </a-layout>
</template>

②改造管理页面

image.png
在image下保存两张作为封面的测试,在数据库中写死路径
image.png

<template>
  <a-layout>
    <a-layout-content
      :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
    >
      <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:action="{ text, record }">
          <a-space size="small">
            <a-button type="primary">
              编辑
            </a-button>
            <a-button type="danger">
              删除
            </a-button>
          </a-space>
        </template>
      </a-table>
    </a-layout-content>
  </a-layout>
</template>

<script lang="ts">
  import { defineComponent, onMounted, ref } from 'vue';
  import axios from 'axios';

  export default defineComponent({
    name: 'AdminEbook',
    setup() {
      const ebooks = ref();
      const pagination = ref({
        current: 1,
        pageSize: 2,
        total: 0
      });
      const loading = ref(false);

      const columns = [
        {
          title: '封面',
          dataIndex: 'cover',
          slots: { customRender: 'cover' }
        },
        {
          title: '名称',
          dataIndex: 'name'
        },
        {
          title: '分类一',
          key: 'category1Id',
          dataIndex: 'category1Id'
        },
        {
          title: '分类二',
          dataIndex: 'category2Id'
        },
        {
          title: '文档数',
          dataIndex: 'docCount'
        },
        {
          title: '阅读数',
          dataIndex: 'viewCount'
        },
        {
          title: '点赞数',
          dataIndex: 'voteCount'
        },
        {
          title: 'Action',
          key: 'action',
          slots: { customRender: 'action' }
        }
      ];

      /**
       * 数据查询
       **/
      const handleQuery = (params: any) => {
        loading.value = true;
        axios.get("/ebook/list", params).then((response) => {
          loading.value = false;
          const data = response.data;
          ebooks.value = data.content;

          // 重置分页按钮
          pagination.value.current = params.page;
        });
      };

      /**
       * 表格点击页码时触发
       */
      const handleTableChange = (pagination: any) => {
        console.log("看看自带的分页参数都有啥:" + pagination);
        handleQuery({
          page: pagination.current,
          size: pagination.pageSize
        });
      };

      onMounted(() => {
        handleQuery({});
      });

      return {
        ebooks,
        pagination,
        columns,
        loading,
        handleTableChange
      }
    }
  });
</script>

<style scoped>
  img {
    width: 50px;
    height: 50px;
  }
</style>

在eslintrc中

'vue/no-unused-vars': 0,

3.使用PageHelper实现后端分页

①集成PageHelper插件

在pom文件中 增加依赖

<!--pagehelper 插件-->
		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper-spring-boot-starter</artifactId>
			<version>1.2.13</version>
		</dependency>

②修改电子书列表接口,支持分页(假分页数据)

⭐分页最重要的四个元素:两个请求参数,两个返回参数
前端传过来的参数(要查第几页 第几条), 返回给前端的参数(列表 total)
⭐分页最基本要查两次:一次是总数据,一次是当前页的列表数据
⭐⭐在EbookService中

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger LOG = LoggerFactory.getLogger(EbookService.class);

PageHelper.startPage(1, 3);

PageInfo<Ebook> pageInfo = new PageInfo<>(ebookList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());

⭐⭐在application.properties文件中
将mapper层的日志级别设置为trace,最低级别,所有日志都会打印出来

#打印所有的sql日志sql,参数结果
logging.level.com.jiawa.wiki.mapper=trace

4.封装分页请求参数和返回参数

⭐分页最重要的四个元素:两个请求参数,两个返回参数
前端传过来的参数(要查第几页 第几条), 返回给前端的参数(列表 total)
将这四个参数封装为两个类

请求参数封装PageReq

①req-PageReq:两个参数 页码和条数 get set tostring(打日志)方法
②EbookReq类继承PageReq类
③EbookService中:把参数变为动态的,而不是写死的

PageHelper.startPage(req.getPage(), req.getSize());

返回结果封装PageResp

①resp-PageResp:两个参数 total list列表(电子书,用户等等 不确定,用泛型,泛型的名字可以随意) get set tostring(打日志)方法

private long total;
private List<T> list;

②EbookService中:返回整个PageResp

import com.jiawa.wiki.resp.PageResp;

public PageResp<EbookResp> list(EbookReq req) {

PageResp<EbookResp> pageResp = new PageResp();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);

return pageResp;

③EbookController

import com.jiawa.wiki.resp.PageResp;

CommonResp<PageResp<EbookResp>> resp = new CommonResp<>();
PageResp<EbookResp> list = ebookService.list(req);

5.前后端分页功能整合

①电子书管理页面前后端分页整合

admin-ebook.vue中:

pageSize: 4,

axios.get("/ebook/list", {
          params: {
            page: params.page,
            size: params.size
          }
        }).then((response) => {

ebooks.value = data.content.list;

pagination.value.total = data.content.total;

handleQuery({
          page: 1,
          size: pagination.value.pageSize,
        });

②首页显示电子书列表,使用固定分页参数,一次性查出所有的电子书

home.vue中:

     axios.get("/ebook/list", {
        params: {
          page: 1,
          size: 1000
        }
     }).then((response) => {


    ebooks.value = data.content.list;

6.制作电子书表单

⭐return:将用到的变量,方法返回给html

①点击每一行编辑按钮,弹出编辑框

image.png
在eslintrc.js中:

'@typescript-eslint/no-unused-vars'

在admin-ebook.vue中:

<a-button type="primary" @click="edit">

<a-modal
    title="电子书表单"
    v-model:visible="modalVisible"
    :confirm-loading="modalLoading"+    @ok="handleModalOk"
  >
    <p>test</p>
</a-modal>

 -------- 表单 ---------
      const modalVisible = ref(false);
      const modalLoading = ref(false);
      const handleModalOk = () => {
        modalLoading.value = true;
        setTimeout(() => {
          modalVisible.value = false;
          modalLoading.value = false;
        }, 2000);
      };

      /**
       * 编辑
      */
      const edit = () => {+        modalVisible.value = true;
      };


    handleTableChange,
    edit,
    modalVisible,
    modalLoading,
    handleModalOk

②编辑框显示电子书表单

image.png
在admin-ebook.vue中:

<a-button type="primary" @click="edit(record)">

<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-input v-model:value="ebook.category1Id" />
      </a-form-item>
      <a-form-item label="分类二">
        <a-input v-model:value="ebook.category2Id" />
      </a-form-item>
      <a-form-item label="描述">
        <a-input v-model:value="ebook.desc" type="textarea" />
      </a-form-item>
</a-form>

const ebook = ref({});

const edit = (record: any) => {

ebook.value = record

ebook,

7.完成电子书编辑功能

⭐保存类,更新类用Post,查询用get
RequestBody:json方式提交

①增加后端保存接口(ebook保存接口)

②点击保存时,调用保存接口

③保存成功刷新列表

在EbookController中:

com.jiawa.wiki.req.EbookQueryReq;
com.jiawa.wiki.req.EbookSaveReq;
com.jiawa.wiki.resp.EbookQueryResp;
org.springframework.web.bind.annotation.*;

public CommonResp list(EbookQueryReq req) {
        CommonResp<PageResp<EbookQueryResp>> resp = new CommonResp<>();
        PageResp<EbookQueryResp> list = ebookService.list(req);

 @PostMapping("/save")
 public CommonResp save(@RequestBody EbookSaveReq req) {
        CommonResp resp = new CommonResp<>();
        ebookService.save(req);
        return resp;

在EbookReq中:

public class EbookQueryReq extends PageReq {

新建类--EbookSaveReq:

package com.jiawa.wiki.req;

public class EbookSaveReq {
    private Long id;

    private String name;

    private Long category1Id;

    private Long category2Id;

    private String description;

    private String cover;

    private Integer docCount;

    private Integer viewCount;

    private Integer voteCount;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getCategory1Id() {
        return category1Id;
    }

    public void setCategory1Id(Long category1Id) {
        this.category1Id = category1Id;
    }

    public Long getCategory2Id() {
        return category2Id;
    }

    public void setCategory2Id(Long category2Id) {
        this.category2Id = category2Id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getCover() {
        return cover;
    }

    public void setCover(String cover) {
        this.cover = cover;
    }

    public Integer getDocCount() {
        return docCount;
    }

    public void setDocCount(Integer docCount) {
        this.docCount = docCount;
    }

    public Integer getViewCount() {
        return viewCount;
    }

    public void setViewCount(Integer viewCount) {
        this.viewCount = viewCount;
    }

    public Integer getVoteCount() {
        return voteCount;
    }

    public void setVoteCount(Integer voteCount) {
        this.voteCount = voteCount;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", name=").append(name);
        sb.append(", category1Id=").append(category1Id);
        sb.append(", category2Id=").append(category2Id);
        sb.append(", description=").append(description);
        sb.append(", cover=").append(cover);
        sb.append(", docCount=").append(docCount);
        sb.append(", viewCount=").append(viewCount);
        sb.append(", voteCount=").append(voteCount);
        sb.append("]");
        return sb.toString();
    }
}

在EbookResp中:

public class EbookQueryResp {

在EbookService中:

import com.jiawa.wiki.req.EbookQueryReq;
import com.jiawa.wiki.req.EbookSaveReq;
import com.jiawa.wiki.resp.EbookQueryResp;

public PageResp<EbookQueryResp> list(EbookQueryReq req) {

List<EbookQueryResp> list = CopyUtil.copyList(ebookList, EbookQueryResp.class);

PageResp<EbookQueryResp> pageResp = new PageResp();

/**
     * 保存
     */
    public void save(EbookSaveReq req) {
        Ebook ebook = CopyUtil.copy(req, Ebook.class);
        if (ObjectUtils.isEmpty(req.getId())) {
            // 新增
            ebookMapper.insert(ebook);
        } else {
            // 更新
            ebookMapper.updateByPrimaryKey(ebook);
        }
   }

在admin-ebook中:

   axios.post("/ebook/save", ebook.value).then((response) => {
          const data = response.data; // data = commonResp
          if (data.success) {
            modalVisible.value = false;
            modalLoading.value = false;

            // 重新加载列表
            handleQuery({
              page: pagination.value.current,
              size: pagination.value.pageSize,
            });
          }
        });

8.雪花算法与新增功能

⭐雪花算法:是一个工具类,用来生成数据库id
在EbookServic中:

import com.jiawa.wiki.util.SnowFlake;


@Resource 
private SnowFlake snowFlake;

ebook.setId(snowFlake.nextId());

在util下新建雪花类SnowFlake:

package com.jiawa.wiki.util;

import org.springframework.stereotype.Component;

import java.text.ParseException;

/**
 * Twitter的分布式自增ID雪花算法
 **/
@Component
public class SnowFlake {

    /**
     * 起始的时间戳
     */
    private final static long START_STMP = 1609459200000L; // 2021-01-01 00:00:00

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
    private final static long MACHINE_BIT = 5;   //机器标识占用的位数
    private final static long DATACENTER_BIT = 5;//数据中心占用的位数

    /**
     * 每一部分的最大值
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private long datacenterId = 1;  //数据中心
    private long machineId = 1;     //机器标识
    private long sequence = 0L; //序列号
    private long lastStmp = -1L;//上一次时间戳

    public SnowFlake() {
    }

    public SnowFlake(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     * 产生下一个ID
     *
     * @return
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStmp == lastStmp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            //不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStmp = currStmp;

        return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
                | datacenterId << DATACENTER_LEFT       //数据中心部分
                | machineId << MACHINE_LEFT             //机器标识部分
                | sequence;                             //序列号部分
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) throws ParseException {
        // 时间戳
        // System.out.println(System.currentTimeMillis());
        // System.out.println(new Date().getTime());
        //
        // String dateTime = "2021-01-01 08:00:00";
        // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        // System.out.println(sdf.parse(dateTime).getTime());

        SnowFlake snowFlake = new SnowFlake(1, 1);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 10; i++) {
            System.out.println(snowFlake.nextId());
            System.out.println(System.currentTimeMillis() - start);
        }
    }
}

在admin-ebook中:

<p>
        <a-button type="primary" @click="add()" size="large">
          新增
        </a-button>
</p>

/**
       * 新增
       */
      const add = () => {
        modalVisible.value = true;
        ebook.value = {};
      };

       add,

9.增加删除电子书功能

⭐电子书管理页面,点击某一行的删除按钮时,删除该行电子书
后端增加删除接口,前端点击删除按钮时调用后端删除接口,删除时需要有一个确认框
在EbookController中:

@DeleteMapping("/delete/{id}")
    public CommonResp delete(@PathVariable Long id) {
        CommonResp resp = new CommonResp<>();
        ebookService.delete(id);
        return resp;
    }

在EbookService中:

public void delete(Long id) {
        ebookMapper.deleteByPrimaryKey(id);
    }

在admin-ebook中:

 <a-popconfirm
              title="删除后不可恢复,确认删除?"
              ok-text="是"
              cancel-text="否"
              @confirm="handleDelete(record.id)"
            >
              <a-button type="danger">
                删除
              </a-button>
</a-popconfirm>

<a-input v-model:value="ebook.description" type="textarea" />

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


       handleModalOk,

       handleDelete

10.集成Validation做参数校验

对电子书查询和保存做参数校验
⭐集成spring-boot-starter-validation
⭐对保存接口和查询接口增加参数校验
⭐校验不通过时,前端弹出错误提示

①集成Validation,为列表接口增加参数校验

在pom文件中增加依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

在controller下新增ControllerExceptionHandler类

package com.jiawa.wiki.controller;

import com.jiawa.wiki.resp.CommonResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 统一异常处理、数据预处理等
 */
@ControllerAdvice
public class ControllerExceptionHandler {

    private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);

    /**
     * 校验异常统一处理
     * @param e
     * @return
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public CommonResp validExceptionHandler(BindException e) {
        CommonResp commonResp = new CommonResp();
        LOG.warn("参数校验失败:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        commonResp.setSuccess(false);
        commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return commonResp;
    }
}

在EbookController中:

import javax.validation.Valid;

public CommonResp list(@Valid EbookQueryReq req) {

在PageReq中:

import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;

@NotNull(message = "【页码】不能为空")

@NotNull(message = "【每页条数】不能为空")
@Max(value = 1000, message = "【每页条数】不能超过1000")

在admin-ebook中:

import { message } from 'ant-design-vue';

pageSize: 1001,

if (data.success) {
     ebooks.value = data.content.list;

// 重置分页按钮
           pagination.value.current = params.page;
           pagination.value.total = data.content.total;
          } else {
            message.error(data.message);
          }

②为保存接口增加参数校验

在EbookController中:

public CommonResp save(@Valid @RequestBody EbookSaveReq req) {

在EbookSaveReq中:

import javax.validation.constraints.NotNull;

@NotNull(message = "【名称】不能为空")

在admin-ebook中:

pageSize: 10,

modalLoading.value = false;

} else {
    message.error(data.message);

11.电子书管理功能优化

①增加按名字查询电子书功能

image.png
在admin-ebook中:

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


const param = ref();
param.value = {};

size: params.size,
name: param.value.name

param,
handleQuery,

②编辑时复制对象,修改表单时,不会影响列表数据

在util下新建tool.ts

export class Tool {
  /**
   * 空校验 null或""都返回true
   */
  public static isEmpty (obj: any) {
    if ((typeof obj === 'string')) {
      return !obj || obj.replace(/\s+/g, "") === ""
    } else {
      return (!obj || JSON.stringify(obj) === "{}" || obj.length === 0);
    }
  }

  /**
   * 非空校验
   */
  public static isNotEmpty (obj: any) {
    return !this.isEmpty(obj);
  }

  /**
   * 对象复制
   * @param obj
   */
  public static copy (obj: object) {
    if (Tool.isNotEmpty(obj)) {
      return JSON.parse(JSON.stringify(obj));
    }
  }
}

在admin-ebook中:

import {Tool} from "@/util/tool";

ebook.value = Tool.copy(record);
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?