手把手撸一个wiki系统(5):分类管理

崩天的勾玉 2021年6月25日22:55:44
评论
86 7935字

上节手把手撸一个wiki系统(4):电子书列表讲了管理页面列表的开发,这节讲分类、文档管理的相关开发。

创建分类表

drop table if exists `category`;
create table `category`(
    `id` bigint not null comment 'id',
    `parent` bigint not null comment '父id',
    `name` varchar(50) not null comment '名字',
    `sort` int comment '顺序',
    primary key (`id`)
) engine=innodb default charset=utf8mb4 comment='分类';

insert into `category` (id, parent, name, sort) VALUES (100,000,'编程语言',100);
insert into `category` (id, parent, name, sort) VALUES (101,100,'java',101);
insert into `category` (id, parent, name, sort) VALUES (102,100,'golang',102);

insert into `category` (id, parent, name, sort) VALUES (200,000,'开发工具',200);
insert into `category` (id, parent, name, sort) VALUES (201,200,'IDEA',201);
insert into `category` (id, parent, name, sort) VALUES (202,200,'eclipse',202);

insert into `category` (id, parent, name, sort) VALUES (300,000,'数据库',300);
insert into `category` (id, parent, name, sort) VALUES (301,300,'redis',301);
insert into `category` (id, parent, name, sort) VALUES (302,300,'mysql',302);

insert into `category` (id, parent, name, sort) VALUES (400,000,'框架',400);
insert into `category` (id, parent, name, sort) VALUES (401,400,'mybatis',401);
insert into `category` (id, parent, name, sort) VALUES (402,400,'spring',402);

400是一级分类,401,402是二级分类

生成相应类

修改一下generator的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!--
    context:生成一组对象的环境
    id:必选,上下文id,用于在生成错误时提示
    defaultModelType:指定生成对象的样式
        1,conditional:类似hierarchical;
        2,flat:所有内容(主键,blob)等全部生成在一个对象中;
        3,hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,其他简单属性在一个对象中(record class)
    targetRuntime:
        1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
        2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
    introspectedColumnImpl:类全限定名,用于扩展MBG
    -->
    <context id="my" targetRuntime="MyBatis3">

        <!-- 自动检查关键字,为关键字增加反引号 -->
        <property name="autoDelimitKeywords" value="true"/>
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!--覆盖生成XML文件-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />
        <!-- 生成的实体类添加toString()方法 -->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>

        <!--不生成注释  -->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!--2.加载连接数据库信息  -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://127.0.0.1:3306/mywiki?serverTimezone=Asia/Shanghai"
                        userId="root"
                        password="密码">
            <!--防止生成其他库同名表-->
            <property name="nullCatalogMeansCurrent" value="true"/>
        </jdbcConnection>

        <!--3.生成Model类的包名和位置 -->
        <!--targetProject=  指定包所在的位置,只需要到src/main/java  -->
        <javaModelGenerator targetPackage="com.example.mywiki.domain"
                            targetProject="src/main/java">

        </javaModelGenerator>

        <!--4.生成映射文件包名和位置 -->
        <!-- 指定包的位置,只需到上一级 -->
        <sqlMapGenerator targetPackage="mapping"
                         targetProject="src/main/resources">
        </sqlMapGenerator>

        <!--5.生成dao层(mapper层)的包和位置  -->
        <javaClientGenerator targetPackage="com.example.mywiki.mapper" type="XMLMAPPER"
                             targetProject="src/main/java">
        </javaClientGenerator>

        <!--6. 我要生成的表, 可以一次性生成多张表.
        tableName是表名
        domainObjectName是实体名, 默认表名首字母大写
        -->
        <table tableName="ebook" domainObjectName="Ebook"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
        <table tableName="category" domainObjectName="Category"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>

    </context>
</generatorConfiguration>

生成好后进行修改

具体大概是复制原来EBook的那些,包括前端和后端,路由等等再稍作修改。

内容较多,见commit 5-1

增加all方法,用于一次查全部分类,无需进行分页(多了一条sql查询)

/**
 * 一次查全部分类,用search的话多了一次分页sql查询
 */
@GetMapping("/all")
public CommonResp all(){
    CommonResp commonResp = new CommonResp<>();
    List<CategoryQueryResp> category = categoryService.all();
    commonResp.setContent(category);
    return commonResp;
}

再修改前端,去除分页相关内容。见5-22

分类树形结构展示

tool.ts:

/**
 * 使用递归将数组转为树形结构
 * 父ID属性为parent
 */
public static array2Tree (array: any, parentId: number) {
    if (Tool.isEmpty(array)) {
        return [];
    }

    const result = [];
    for (let i = 0; i < array.length; i++) {
        const c = array[i];
        // console.log(Number(c.parent), Number(parentId));
        if (Number(c.parent) === Number(parentId)) {
            result.push(c);

            // 递归查看当前节点对应的子节点
            const children = Tool.array2Tree(array, c.id);
            if (Tool.isNotEmpty(children)) {
                c.children = children;
            }
        }
    }
    return result;
}

categoryAdmin:

    /**
     * 数据查询
     * 
     * 一级分类树,children属性就是二级分类
     * [{
     *   id: "",
     *   name: "",
     *   children: [{
     *     id: "",
     *     name: "",
     *   }]
     * }]
     */
    const level1 = ref(); // 一级分类树,children属性就是二级分类
    level1.value = [];
  const handleQuery = () => {
    loading.value = true;
    // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
    level1.value = [];
    axios.get("/category/all").then((response) => {
      loading.value = false;
      const data = response.data;
      if (data.success) {
        categorys.value = data.content;
        console.log("原始数组:", categorys.value);

        level1.value = [];
        level1.value = Tool.array2Tree(categorys.value, 0);
        console.log("树形结构:", level1);
      } else {
        message.error(data.message);
      }
    });
  };

再将level1给return出去,并在table里作数据源即可。

增加下拉框

<a-form-item label="父分类">
  <a-select
      v-model:value="category.parent"
      ref="select"
  >
    <a-select-option :value="0">
      无
    </a-select-option>
    <a-select-option v-for="c in level1" :key="c.id" :value="c.id" :disabled="category.id === c.id">
      {{c.name}}
    </a-select-option>
  </a-select>
</a-form-item>

<a-select是下拉组件,v-for遍历一级分类,:disabled="category.id === c.id"是排除选择自身为父分类。

见5-4

电子书管理页面修改

将modal的分类展示位级联:

<a-form-item label="分类">
  <a-cascader
      v-model:value="categoryIds"
      :field-names="{ label: 'name', value: 'id', children: 'children' }"
      :options="level1"
      expand-trigger="hover"
  />
</a-form-item>
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 edit = (record: any) => {  modalVisible.value = true;  ebook.value = Tool.copy(record); //对象复制  categoryIds.value = [ebook.value.category1Id, ebook.value.category2Id]};
//初始执行,应该先查第一页,获得的数据传入paramsonMounted(() => {  handleQueryCategory();  handleQuery({    page: 1,    size: pagination.value.pageSize,  });});

见5-5

问题:精度丢失

后端雪花算法生成的id是18位的,前端的number类型是16位的,传过去丢了2位精度,那么再返回来时就对不上了

我替换了雪花算法,将id变成了16位。如果在后端将Long改String也行,但是前端也要对应改。

见5-7

首页左侧多级分类

<a-menu    :style="{ height: '100%', borderRight: 0 }"    :mode="vertical">  <a-menu-item key="welcome">    <router-link :to="'/'"/>    <MailOutlined />    <span>欢迎</span>  </a-menu-item>  <a-sub-menu v-for="item in level1" :key="item.id" >    <template v-slot:title>      <span><user-outlined />{{item.name}}</span>    </template>    <a-menu-item v-for="child in item.children" :key="child.id">      <MailOutlined /><span>{{child.name}}</span>    </a-menu-item>  </a-sub-menu>  <a-menu-item key="tip" :disabled="true">    <span>以上菜单在分类管理配置</span>  </a-menu-item></a-menu>
const handleQueryCategory = () => {
  axios.get("/category/all").then((response) => {
    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);
    } else {
      message.error(data.message);
    }
  });
};

见5-8

引入MybatisPlus

代码大重构,引入mybatis-plus,替换所有mapper、xml,修改配置文件和依赖,剔除代码生成器部分。

重构的原因是自带的mybatis要操作数据库经常要手写mapper接口、xml的sql,效率比较低。

关于MybatisPlus的使用我也发在了博客上,欢迎参观。

见5-9!

根据categoryId2查二级分类

首页有二级分类菜单,那么实现点击二级分类菜单展示所有的分类

EbookQueryReq里添加字段:

private Long categoryId2;

service:

if (!ObjectUtils.isEmpty(req.getName())) {
    //有name则模糊查询
    wrapper.like("name", req.getName());
    IPage<Ebook> ebookIPage = ebookMapper.selectPage(page, wrapper);
    ebookList = ebookIPage.getRecords();
}else if (!ObjectUtils.isEmpty(req.getCategory2Id())) {
    //有category2Id则等值查,注意column不是实体属性
    wrapper.eq("category2_Id", req.getCategory2Id());
    ebookList= ebookMapper.selectList(wrapper);
}else {
    //无参时查全部
    ebookList = ebookMapper.selectPage(page, null).getRecords();
}

见5-10.

您可能感兴趣的文章

继续阅读
版权:文章来自凡蜕博客,转载请带上地址。微信公众号: 『崩天的勾玉』
匿名

发表评论

匿名网友