手把手撸一个wiki系统(6):文档管理

崩天的勾玉 2021年6月30日20:36:59
评论
79 7213字

上一节:手把手撸一个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

您可能感兴趣的文章

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

发表评论

匿名网友