文档管理功能开发

后端代码实现

文档表设计与代码生成
文档表设计
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 '顺序',
    `view_count` int default 0 comment '阅读数',
    `vote_count` int default 0 comment '点赞数',
    primary key (`id`)
)engine=innodb default charset=utf8mb4 comment='文档';

insert into doc values
                       (1,1,0,'文档1',1,0,0),
                       (2,1,1,'文档1.1',1,0,0),
                       (3,1,0,'文档2',2,0,0),
                       (4,1,3,'文档2.1',1,0,0),
                       (5,1,3,'文档2.2',2,0,0),
                       (6,1,5,'文档2.2.1',1,0,0);
Mybatis生成Model实体类、Mapper接口类和Mapper XML文件

generatorConfig.xml内容如下,

<?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="Mysql" targetRuntime="MyBatis3">
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/test-database"
                        userId="test-user"
                        password="test@123">
        </jdbcConnection>

        <javaModelGenerator targetPackage="com.jepcc.test.model" targetProject="src\main\java">
        </javaModelGenerator>

        <sqlMapGenerator targetPackage="mapper"  targetProject="src\main\resources">
        </sqlMapGenerator>

        <javaClientGenerator type="XMLMAPPER" targetPackage="com.jepcc.test.mapper"  targetProject="src\main\java">
        </javaClientGenerator>

<!--        <table tableName="ebook" domainObjectName="Ebook" ></table>-->
<!--        <table tableName="category" domainObjectName="Category" ></table>-->
        <table tableName="doc" domainObjectName="Doc" ></table>

    </context>
</generatorConfiguration>

执行命令:mybatis-generator:generate -e,生成如下文件:

  • Model实体类:com.jepcc.test.model.Doccom.jepcc.test.model.DocExample
  • Mapper接口类:com.jepcc.test.mapper.DocMapper
  • Mapper XML文件:src/main/resources/mapper/DocMapper.xml
完成文档表的增删改查功能
  • com.jepcc.test.controller.DocController
package com.jepcc.test.controller;

import com.jepcc.test.req.DocReq;
import com.jepcc.test.req.DocSaveReq;
import com.jepcc.test.resp.DocResp;
import com.jepcc.test.resp.CommonResp;
import com.jepcc.test.resp.PageResp;
import com.jepcc.test.service.DocService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/doc")
public class DocController {
    private final static Logger LOG = LoggerFactory.getLogger(DocController.class);
    @Resource
    private DocService docService;

    @GetMapping("/all")
    public CommonResp all(){
        CommonResp resp = new CommonResp();
        List<DocResp> list = docService.all();
        resp.setContent(list);
        return resp;
    }
    @GetMapping("/list")
    public CommonResp list(DocReq req){
        CommonResp resp = new CommonResp();
        PageResp<DocResp> list = docService.list(req);
        resp.setContent(list);
        return resp;
    }

    @PostMapping("/save")
    public CommonResp save(@RequestBody DocSaveReq req){
        CommonResp resp = new CommonResp();
        docService.save(req);
        return resp;
    }

    @DeleteMapping("/delete/{id}")
    public CommonResp delete(@PathVariable long id){
        CommonResp resp = new CommonResp();
        docService.delete(id);
        return resp;
    }
}
  • com.jepcc.test.service.DocService
package com.jepcc.test.service;


import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jepcc.test.mapper.DocMapper;
import com.jepcc.test.model.Doc;
import com.jepcc.test.model.DocExample;
import com.jepcc.test.req.DocReq;
import com.jepcc.test.req.DocSaveReq;
import com.jepcc.test.resp.DocResp;
import com.jepcc.test.resp.PageResp;
import com.jepcc.test.util.CopyUtil;
import com.jepcc.test.util.SnowFlake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.List;

@Service
public class DocService {
    @Resource
    private DocMapper docMapper;
    @Resource
    private SnowFlake snowFlake;
    private static final Logger LOG = LoggerFactory.getLogger(DocService.class);
    public List<DocResp> all(){
        DocExample docExample = new DocExample();
        //按照sort字段asc排序
        docExample.setOrderByClause("sort asc");

        List<Doc> list = docMapper.selectByExample(docExample);
        List<DocResp> result = CopyUtil.copyList(list,DocResp.class);

        return result;
    }

    public PageResp<DocResp> list(DocReq req){

        DocExample docExample = new DocExample();
        DocExample.Criteria criteria = docExample.or();
        if(!ObjectUtils.isEmpty(req.getName())){
            criteria.andNameLike("%"+req.getName()+"%");
        }
        PageHelper.startPage(req.getPage(),req.getSize());

        List<Doc> list = docMapper.selectByExample(docExample);

        PageInfo<Doc> pageInfo = new PageInfo<>(list);

        List<DocResp> result = CopyUtil.copyList(list,DocResp.class);

        PageResp<DocResp> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(result);
        return pageResp;

    }

    public void save(DocSaveReq req){
        Doc doc = CopyUtil.copy(req,Doc.class);
        if(!ObjectUtils.isEmpty(doc.getId())){
//            更新
            docMapper.updateByPrimaryKey(doc);
        }else{
//            插入
            doc.setId(snowFlake.nextId());
            docMapper.insert(doc);
        }

    }

    public void delete(long id){
        docMapper.deleteByPrimaryKey(id);
    }
}
  • com.jepcc.test.req.DocReq
package com.jepcc.test.req;

public class DocReq extends PageReq{
    private Long id;

    private Long ebookId;

    private Long parent;

    private String name;

    private Integer sort;

    private Integer viewCount;

    private Integer voteCount;

    public Long getId() {
        return id;
    }

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

    public Long getEbookId() {
        return ebookId;
    }

    public void setEbookId(Long ebookId) {
        this.ebookId = ebookId;
    }

    public Long getParent() {
        return parent;
    }

    public void setParent(Long parent) {
        this.parent = parent;
    }

    public String getName() {
        return name;
    }

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

    public Integer getSort() {
        return sort;
    }

    public void setSort(Integer sort) {
        this.sort = sort;
    }

    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;
    }
}
  • com.jepcc.test.req.DocSaveReq
package com.jepcc.test.req;

public class DocSaveReq {
    private Long id;

    private Long ebookId;

    private Long parent;

    private String name;

    private Integer sort;

    private Integer viewCount;

    private Integer voteCount;

    public Long getId() {
        return id;
    }

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

    public Long getEbookId() {
        return ebookId;
    }

    public void setEbookId(Long ebookId) {
        this.ebookId = ebookId;
    }

    public Long getParent() {
        return parent;
    }

    public void setParent(Long parent) {
        this.parent = parent;
    }

    public String getName() {
        return name;
    }

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

    public Integer getSort() {
        return sort;
    }

    public void setSort(Integer sort) {
        this.sort = sort;
    }

    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;
    }
}
  • com.jepcc.test.resp.DocResp
package com.jepcc.test.resp;

public class DocResp {
    private Long id;

    private Long ebookId;

    private Long parent;

    private String name;

    private Integer sort;

    private Integer viewCount;

    private Integer voteCount;

    public Long getId() {
        return id;
    }

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

    public Long getEbookId() {
        return ebookId;
    }

    public void setEbookId(Long ebookId) {
        this.ebookId = ebookId;
    }

    public Long getParent() {
        return parent;
    }

    public void setParent(Long parent) {
        this.parent = parent;
    }

    public String getName() {
        return name;
    }

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

    public Integer getSort() {
        return sort;
    }

    public void setSort(Integer sort) {
        this.sort = sort;
    }

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

前端代码实现

  • web/src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'
import About from "../views/About.vue";
import AdminEbook from "../views/admin/admin-ebook.vue";
import AdminCategory from "../views/admin/admin-category.vue";
import AdminDoc from "../views/admin/admin-doc.vue";

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component:About
  },
  {
    path:"/admin/ebook",
    name:"AdminEbook",
    component:AdminEbook
  },
  {
    path:"/admin/category",
    name:"AdminCategory",
    component:AdminCategory
  },
  {
    path:"/admin/doc",
    name:"AdminDoc",
    component:AdminDoc
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router
  • web/src/views/admin/admin-ebook.vue
<template>
  <a-layout>
    <a-layout-content
        :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
    >
      <p>
        <a-form :model="param" layout="inline">
          <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>
      </p>
      <a-table :columns="columns"
               :dataSource="ebooks"
               :row-key="record=>record.id"
               :pagination="pagination"
               :loading="loading"
               @change="handleTableChange">
        <template #cover="{text:cover}">
          <img v-if="cover" :src="cover" alt="avatar">
        </template>
        <template #category="{text,record}">
          <text>{{getCategoryName(record.category1Id)}}/{{getCategoryName(record.category2Id)}}</text>
        </template>
        <template #action="{text,record}">
          <a-space>
            <router-link to="/admin/doc">
              <a-button type="primary">文档管理</a-button>
            </router-link>
            <a-button type="primary" @click="edit(record)">编辑</a-button>
            <a-popconfirm
                title="删除后不可恢复,确认删除?"
                ok-text="确定"
                cancel-text="取消"
                @confirm="handleDelete(record)"
            >
              <a-button type="danger">删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </a-table>
    </a-layout-content>

  </a-layout>

  <a-modal
      title="电子书表单"
      v-model:visible="visible"
      :confirm-loading="confirmLoading"
      @ok="handleOk"
  >
    <a-form :model="ebook" :label-col="{span:4}" :wrapper-col="{span:16}">
      <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-cascader v-model:value="categoryIds" :options="options" @change="handleCascaderChange"
                    :field-names="{label:'name',value:'id',children:'children'}"/>
      </a-form-item>
      <a-form-item label="描述">
        <a-input v-model:value="ebook.description" />
      </a-form-item>
    </a-form>
  </a-modal>

</template>


<script lang="ts">

import {defineComponent, ref, onMounted, reactive} from 'vue';
import axios from "axios";
import {message} from "ant-design-vue";
import {arrayToTree} from "@/tools/tool";

export default defineComponent({
  name:"AdminEbook",
  setup: function () {
    const ebooks = ref();
    const pagination = ref({
      current: 1,
      pageSize: 4,
      total: 0
    });
    const loading = ref(false);
    const columns = [
      {
        title: "封面",
        dataIndex: "cover",
        key: "cover",
        slots: {customRender: 'cover'}
      },
      {
        title: "名称",
        key: "name",
        dataIndex: "name"
      },
      {
        title: "分类",
        key: "categoryId",
        slots:{customRender: "category"}
      },
      {
        title: "文档数",
        key: "docCount",
        dataIndex: "docCount"
      },
      {
        title: "阅读数",
        key: "viewCount",
        dataIndex: "viewCount"
      },
      {
        title: "点赞数",
        key: "vouteCount",
        dataIndex: "voteCount"
      },
      {
        title: "操作",
        key: "action",
        dataIndex: "action",
        slots: {customRender: 'action'}

      }
    ];

    let categorys:any = [];
    const categoryIds = ref();
    const options = ref();
    const handleCategoryQuery = () => {
      axios.get("/category/all").then(response => {
        const data = response.data;
        if(data.success){
          categorys = data.content;
          const result = arrayToTree(data.content,0);
          console.log("get options successfully,",result);
          options.value = result;
        }else{
          console.log("get options failed");
        }
      })
    }
    const getCategoryName = (categoryId:number) => {
      let name = "";
      for(let i=0;i<categorys.length;i++){
        let item = categorys[i]
        if(Number(item.id) === Number(categoryId)){
          name = item.name;
          break;
        }
      }
      return name;
    }
    const handleCascaderChange = (value:any) => {
    }

    const visible = ref(false);
    const modalText = ref('test');
    const confirmLoading = ref(false);
    const ebook = ref();
    const param = reactive({
      name:""
    });
    const handleQuery = (params: any) => {
      loading.value = true;
      axios.get("/ebook/list", {params: {
          page:params.page,
          size:params.size,
          name:param.name
        }}).then(response => {
        loading.value = false;
        const data = response.data;
        const content = data.content;
        if (data.success) {
          console.log(content.list);
          ebooks.value = content.list;
          pagination.value.total = content.total;
          pagination.value.current = params.page;
        } else {
          message.warn(data.message);
        }
      })
    };

    const handleTableChange = (pagination: any) => {
      handleQuery({
        page: pagination.current,
        size: pagination.pageSize
      });
    }

    const edit = (record: any) => {
      visible.value = true;
      ebook.value = JSON.parse(JSON.stringify(record));
      categoryIds.value = [ebook.value.category1Id,ebook.value.category2Id];
    }
    const add = () => {
      visible.value = true;
      ebook.value = {};
    }
    const handleDelete = (record: any) => {
      axios.delete("ebook/delete/" + record.id).then(response => {
        const data = response.data;
        if (data.success) {
          handleQuery({
            page: pagination.value.current,
            size: pagination.value.pageSize
          })
        }
      })
    }
    const handleOk = () => {
      ebook.value.category1Id = categoryIds.value[0];
      ebook.value.category2Id = categoryIds.value[1];
      axios.post("/ebook/save", ebook.value).then(response => {
        const data = response.data;
        if (data.success) {
          visible.value = false;
          confirmLoading.value = false;

          handleQuery({
            page: pagination.value.current,
            size: pagination.value.pageSize
          })
        } else {
          message.warn(data.message);
        }

      })
    };

    onMounted(() => {
      handleCategoryQuery();
      handleQuery({
        page: 1,
        size: pagination.value.pageSize
      });

    })

    return {
      columns,
      ebooks,
      loading,
      pagination,
      visible,
      modalText,
      confirmLoading,
      ebook,
      param,
      categoryIds,
      options,
      handleTableChange,
      edit,
      add,
      handleDelete,
      handleOk,
      handleQuery,
      handleCascaderChange,
      getCategoryName
    }
  }
})
</script>


<style scoped>
img{
  width: 48px;
  height: 48px;
}
</style>
  • web/src/views/admin/admin-doc.vue
<template>
  <a-layout>
    <a-layout-content :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }">
      <p>
        <a-button type="primary" @click="handleAdd">新增</a-button>
      </p>
      <a-table :columns="columns" :data-source="docs" :row-key="record=>record.id"
               :loading="loading" :pagination="false" @change="handleChange">
        <template #action="{text,record}">
          <a-space>
            <a-button type="primary" @click="edit(record)">编辑</a-button>
            <a-popconfirm
                title="删除后不可恢复,确认删除?"
                ok-text="确定"
                cancel-text="取消"
                @confirm="confirm(record)"
            >
              <a-button type="danger">删除</a-button>
            </a-popconfirm>
          </a-space>
        </template>
      </a-table>
    </a-layout-content>
  </a-layout>
    <a-modal :visible="visible" title="文档管理" @ok="handleOk" @cancel="handleCancel">
      <a-form :model="doc" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
        <a-form-item label="名称">
          <a-input v-model:value="doc.name" />
        </a-form-item>
        <a-form-item label="文档">
          <a-input v-model:value="doc.parent" />
          <a-tree-select
              v-model:value="doc.parent"
              :replace-fields="{children:'children', title:'name', key:'id', value: 'id' }"
              style="width: 100%"
              :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
              :tree-data="treeData"
              placeholder="请选择父文档"
              tree-default-expand-all
          >
          </a-tree-select>
        </a-form-item>
        <a-form-item label="排序">
          <a-input v-model:value="doc.sort" />
        </a-form-item>
      </a-form>
    </a-modal>
</template>


<script lang="ts">
import {defineComponent, onMounted, ref} from 'vue';
import axios from "axios";
import {arrayToTree} from "@/tools/tool";

interface docItem {
  id:number,
  name:string,
  parent?:number,
  sort?:number,
  children?:docItem[],
  ebookId?:number,
  viewCount?: number,
  voteCount?: number,
  disabled?:boolean,
}
// const setChildrenDisable = (children:docItem[]) => {
//   for(let i=0;i<children.length;i++){
//     let child = children[i];
//     child.disabled = true;
//     if(child.children && child.children.length){
//       setChildrenDisable(child.children);
//     }
//   }
// }
// const setDisable = (arr:docItem[],targetId:number) => {
//   const len = arr.length;
//   if(len === 0) return;
//   for(let i=0;i<len;i++){
//     let item = arr[i];
//     if(Number(item["id"]) === Number(targetId)){
//       item.disabled = true;
//       if(item.children && item.children.length){
//         setChildrenDisable(item.children);
//       }
//     }
//     if(item.children && item.children.length){
//       setDisable(item.children,targetId);
//     }
//   }
// }

const setDisable = (arr:docItem[],targetId:number|string) => {
  if(arr.length === 0) return ;
  for(let i=0;i<arr.length;i++){
    let node = arr[i];
    console.log("typeof node.id",typeof node.id);
    console.log("typeof targetId",typeof targetId);
    if(Number(node.id)=== Number(targetId)){
      node.disabled = true;
      let children = node.children;
      if(children && children.length){
        for(let j=0;j<children.length;j++){
          setDisable(children,children[j].id);
        }
      }
    }else{
      let children = node.children;
      if(children && children.length){
        setDisable(children,targetId);
      }
    }
  }
}
export default defineComponent({
  name:"AdminDoc",
  setup() {
    const columns = [
      {
        dataIndex: 'name',
        key: 'name',
        title:"名称"
      },
      {
        dataIndex: 'parent',
        key: 'parent',
        title:"父文档"
      },
      {
        dataIndex: 'sort',
        key: 'sort',
        title:"排序"
      },
      {
        title: 'Action',
        key: 'action',
        slots: { customRender: 'action' },
      },
    ];
    const loading = ref(false);
    const docs = ref();
    const visible = ref(false);
    const doc = ref({
      id:"",
      parent:"",
      name:"",
      sort:""
    });
    const treeData = ref();
    const handleQuery = () => {
      loading.value = true;
      axios.get("/doc/all").then(response => {
        loading.value = false;
        const data = response.data;
        if(data.success){
          let result = arrayToTree(data.content,0);
          console.log("result of arrayToTree:",result);
          docs.value = result;
        }
      })
    }
    const handleSave = (params:any) => {
      axios.post("/doc/save",params).then(response => {
        visible.value = false;
        const data = response.data;
        if(data.success){
          handleQuery();
        }else{
          console.log("save failed");
        }
      })
    }
    const handleDelete = (record:any) => {
      axios.delete("/doc/delete/"+record.id).then(response => {
        const data = response.data;
        if(data.success){
          handleQuery();
        }else{
          console.log("delete failed");
        }
      })
    }

    const handleChange = (p:any) => {
      handleQuery();
    }
    const edit = (record:any) => {
      visible.value = true;
      doc.value = JSON.parse(JSON.stringify(record));
      treeData.value = JSON.parse(JSON.stringify(docs.value));
      treeData.value.unshift({id:0,name:"无"});
      setDisable(treeData.value,record.id);
    }
    const handleOk = () => {
      handleSave(doc.value);
    }
    const handleCancel = () => {
      visible.value = false;
    }
    const confirm = (record:any) => {
      handleDelete(record)
    }
    const handleSearch = () => {
      handleQuery();
    }
    const handleAdd = () => {
      doc.value = {
        id:"",
        parent:"",
        name:"",
        sort:""
      }
      visible.value = true;
      treeData.value = JSON.parse(JSON.stringify(docs.value));
      treeData.value.unshift({id:0,name:"无"});
    }
    onMounted(() => {
      handleQuery();
    })

    return {
      docs,
      columns,
      loading,
      visible,
      doc,
      treeData,
      handleQuery,
      handleChange,
      edit,
      handleOk,
      handleCancel,
      confirm,
      handleSearch,
      handleAdd
    };

  }

});
</script>


<style scoped>

</style>

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值