上节手把手撸一个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.
本文来自凡蜕博客(https://blog.ysboke.cn), 转载请带上地址.。