上一节:手把手撸一个wiki系统(5):分类管理 我们完成的分类功能开发,这节完成文档页面开发
表设计
drop table if exists `doc`;
create table `doc`(
`id` bigint not null comment 'id',
`ebook_id` bigint not null default 0 comment '电子书id',
`parent` bigint not null default 0 comment '父id',
`name` varchar(50) not null comment '名称',
`sort` int comment '顺序',
`viw_count` int default 0 comment '阅读数',
`vote_count` int default 0 comment '点赞数',
primary key (`id`)
) engine=innodb default charset=utf8mb4 comment '文档';
insert into `doc` (id, ebook_id, parent, name, sort, viw_count, vote_count) VALUES (1,1,0,'文档1',1,0,0);
insert into `doc` (id, ebook_id, parent, name, sort, viw_count, vote_count) VALUES (2,1,1,'文档1.1',1,0,0);
insert into `doc` (id, ebook_id, parent, name, sort, viw_count, vote_count) VALUES (3,1,0,'文档2',2,0,0);
insert into `doc` (id, ebook_id, parent, name, sort, viw_count, vote_count) VALUES (4,1,3,'文档2.1',1,0,0);
insert into `doc` (id, ebook_id, parent, name, sort, viw_count, vote_count) VALUES (5,1,3,'文档2.2',2,0,0);
insert into `doc` (id, ebook_id, parent, name, sort, viw_count, vote_count) VALUES (6,1,5,'文档2.2.1',1,0,0);
生成表,再使用mybatisX插件生成代码。
文档管理功能完成
后台复制改造controller、service、req、resp,前台DocAdmin
见6-2
完善文档新增功能
进入文档新增页面时使之带上文档的id
增加select树形选择组件
增加递归算法获取多级分类
见6-3
文档删除
关键是删除父文档时,将子文档一并删除
前端:
const deleteIds: Array<string> = [];
const deleteNames: Array<string> = [];
/**
* 查找整根树枝
*/
const getDeleteIds = (treeSelectData: any, id: any) => {
// console.log(treeSelectData, id);
// 遍历数组,即遍历某一层节点
for (let i = 0; i < treeSelectData.length; i++) {
const node = treeSelectData[i];
if (node.id === id) {
// 如果当前节点就是目标节点
console.log("delete", node);
// 将目标ID放入结果集ids
// node.disabled = true;
deleteIds.push(id);
deleteNames.push(node.name);
// 遍历所有子节点
const children = node.children;
if (Tool.isNotEmpty(children)) {
for (let j = 0; j < children.length; j++) {
getDeleteIds(children, children[j].id)
}
}
} else {
// 如果当前节点不是目标节点,则到其子节点再找找看。
const children = node.children;
if (Tool.isNotEmpty(children)) {
getDeleteIds(children, id);
}
}
}
};
后端:
@DeleteMapping("/delete/{idsStr}")
public CommonResp delete(@PathVariable String idsStr){
CommonResp commonResp = new CommonResp<>();
List<String> list = Arrays.asList(idsStr.split(","));
docService.delete(list);
return commonResp;
}
/**
* 批量id删除
* @param ids
*/
public void delete(List<String> ids){
docMapper.deleteBatchIds(ids);
}
集成富文本编辑器
wangeditor:https://www.wangeditor.com/
web目录下:npm i wangeditor@4.6.3 --save
<div id="content"></div>
import E from 'wangeditor'
const editor = new E('#content')
setTimeout(function (){
editor.create();
},100)
注意,这是4.6.3的版本,最新版(4.7)测试并不能这么写,这里不做深究
具体见提交6-5
文档内容表
#文档内容表
drop table if exists `content`;
create table `content`(
`id` bigint not null comment '文档id',
`content` mediumtext not null comment '内容',
primary key (`id`)
) engine=innodb default charset=utf8mb4 comment '文档内容';
生成对应的代码。
文档左右栅格布局
<a-row>
<a-col :span="8">
</a-col>
<a-col :span="16">
</a-col>
</a-row>
将p、a-table放入上一个col,将modal的内容放在下一个col里。
注意初始化内容,避免异步导致浏览器无法显示:
const doc = ref();
doc.value = {
ebookId: route.query.ebookId
};
见6-9.
初始让表格展开
<a-table
v-if="level1.length > 0"
:columns="columns"
:row-key="record => record.id"
:data-source="level1"
:loading="loading"
:pagination="false"
size="small"
:default-expand-all-rows="true"
>
见6-11
文档内容保存功能
请求参数:
/**
* 文档内容html字符串
*/
@NotNull(message = "内容不能为空")
private String content;
@Resource
private ContentMapper contentMapper;
public void save(DocSaveReq saveReq){
Doc doc = CopyUtil.copy(saveReq, Doc.class);
Content content = CopyUtil.copy(saveReq, Content.class);
if (ObjectUtils.isEmpty(saveReq.getId())){
//生成id
Long id = snowFlake.nextId();
//新增id、doc、content
doc.setId(id);
content.setId(id);
docMapper.insert(doc);
contentMapper.insert(content);
}else {
//更新doc、content
docMapper.updateById(doc);
contentMapper.updateById(content);
}
}
后端保存好后前端要再次获取内容
doc.value.content = editor.txt.html();
获取文档内容
点编辑时,富文本框内能显示出对应的内容
controller:
/**
* 查询文本内容
* @param id
* @return
*/
@GetMapping("/getContent/{id}")
public CommonResp getContent(@Valid Long id){
CommonResp<String> commonResp = new CommonResp<>();
String content = docService.getContent(id);
commonResp.setContent(content);
return commonResp;
}
service:
/**
* 获取文本content
* @param id
* @return
*/
public String getContent(Long id){
String content = contentMapper.selectById(id).getContent();
return content != null? content:null;
}
前端:
/**
* 内容content查询
**/
const handleQueryContent = () => {
axios.get("/doc/getContent/" + doc.value.id).then((response) => {
const data = response.data;
if (data.success) {
//赋值
editor.txt.html(data.content)
} else {
message.error(data.message);
}
});
};
还有一些细节优化,见提交6-12
文档页面功能开发
首页点击文档
1、新增路由
import Doc from '../views/doc.vue'
...
{
path: '/doc',
name: 'Doc',
component: Doc
},
2、增加doc.vue页面
<template>
<a-layout>
<a-layout-content :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }">
<div>
<h1>欢迎来到文档页面</h1>
</div>
</a-layout-content>
</a-layout>
</template>
3、home里添加点击进入功能
<router-link :to="'/doc?ebookId=' + item.id">
{{ item.name }}
</router-link>
4、改造doc controller,增加参数,只查当前页面的文档分类
/**
* 一次查全部分类,用search的话多了一次分页sql查询
*/
@GetMapping("/all/{ebookId}")
public CommonResp all(@PathVariable Long ebookId){
CommonResp commonResp = new CommonResp<>();
List<DocQueryResp> doc = docService.all();
commonResp.setContent(doc);
return commonResp;
}
5、service:
/**
* Doc一次查全部文档分类,根据ebookid
* @param
* @return
*/
public List<DocQueryResp> all(Long ebookId) {
QueryWrapper<Doc> docQueryWrapper = new QueryWrapper<Doc>()
.eq("ebook_id",ebookId);
List<Doc> docList = docMapper.selectList(docQueryWrapper);
//将List<Doc>转换为List<DocResp>
List<DocQueryResp> respList = CopyUtil.copyList(docList, DocQueryResp.class);
return respList;
}
7、改造doc页面,内容较多,具体看提交6-13
右侧显示文档内容
<div :innerHTML = "html"></div>
···
const html = ref();
···
/**
* 内容content查询
**/
const handleQueryContent = () => {
axios.get("/doc/getContent/" + doc.value.id).then((response) => {
const data = response.data;
if (data.success) {
//赋值
html.value = data.content;
} else {
message.error(data.message);
}
});
};
···
通过onselect方法获取content:
const onSelect = (selectedKeys: any, info: any) => {
console.log('selected', selectedKeys, info);
if (Tool.isNotEmpty(selectedKeys)) {
// 选中某一节点时,加载该节点的文档信息
doc.value = info.selectedNodes[0].props;
// 加载内容
handleQueryContent(selectedKeys[0]);
}
};
见6-14.
增加内容预览
<a-form-item>
<a-button type="primary" @click="handlePreviewContent()">
<EyeOutlined /> 预览
</a-button>
</a-form-item>
增加抽屉组件,存放预览内容:
<a-drawer width="900" placement="right" :closable="false" :visible="drawerVisible" @close="onDrawerClose">
<div class="wangeditor" :innerHTML="previewHtml"></div>
</a-drawer>
实现富文本预览:
/**
* 富文本预览,获取content->html->previewHtml
* drawerVisible是组件可见性
*/
const drawerVisible = ref(false);
const previewHtml = ref();
const handlePreviewContent = () => {
const html = editor.txt.html();
previewHtml.value = html;
drawerVisible.value = true;
};
再全部return出去。
见commit6-15.
doc页初始显示第一篇文档
:defaultSelectedKeys="defaultSelectedKeys"
//默认选中的节点,数组格式
const defaultSelectedKeys = ref();
defaultSelectedKeys.value = [];
在handleQuery里添加
if (Tool.isNotEmpty(level1)){
defaultSelectedKeys.value = [level1.value[0].id];
handleQueryContent(level1.value[0].id);
}
修改handleQueryContent:
/**
* 查content
*/
const handleQueryContent = (id: number) => {
axios.get("/doc/getContent/" + id).then((response) => {
const data = response.data;
if (data.success) {
html.value = data.content;
} else {
message.error(data.message);
}
});
};
注意此处和docAdmin的方法是不一样的,一个是获取ebookId,一个是获取文档树数组的第一个文档的id
无文档时增加提示
//初始展开第一个文档内容在右侧
if (Tool.isNotEmpty(level1.value)){
defaultSelectedKeys.value = [level1.value[0].id];
handleQueryContent(level1.value[0].id);
}else {
message.warning("暂无文档,请移步电子书管理页添加文档",2);
}
见6-16