文档管理布局优化
这里主要涉及到antd的Grid栅格。
<a-row>
<a-col :span="12">col-12</a-col>
<a-col :span="12">col-12</a-col>
</a-row>
主要对admin-doc.vue
进行修改,如下,
<template>
<a-layout>
<a-layout-content :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }">
<a-row :gutter="24">
<a-col :span="8">
<p>
<a-button type="primary" @click="handleAdd">新增</a-button>
</p>
<a-table :columns="columns" :data-source="docs" :row-key="record=>record.id" size="small"
:loading="loading" :pagination="false" @change="handleChange"
v-if="docs.length" default-expand-all-rows
>
<template #customeTitle="{text,record}">
{{record.sort}} {{text}}
</template>
<template #action="{text,record}">
<a-space>
<a-button type="primary" @click="edit(record)" size="small">编辑</a-button>
<a-popconfirm
title="删除后不可恢复,确认删除?"
ok-text="确定"
cancel-text="取消"
@confirm="confirm(record)"
>
<a-button type="danger" size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
</a-col>
<a-col :span="16">
<p>
<a-button type="primary" @click="handleSave">保存</a-button>
</p>
<a-form :model="doc" layout="vertical">
<a-form-item>
<a-input v-model:value="doc.name" placeholder="名称" />
</a-form-item>
<a-form-item>
<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"
tree-default-expand-all
placeholder="请选择父文档"
>
</a-tree-select>
</a-form-item>
<a-form-item>
<a-input v-model:value="doc.sort" placeholder="排序"/>
</a-form-item>
<a-form-item>
<div id="content">
</div>
</a-form-item>
</a-form>
</a-col>
</a-row>
</a-layout-content>
</a-layout>
</template>
<script lang="ts">
import {createVNode, defineComponent, onMounted, ref} from 'vue';
import axios from "axios";
import {arrayToTree} from "@/tools/tool";
import {useRoute} from "vue-router";
import {Modal} from "ant-design-vue";
import {ExclamationCircleOutlined} from '@ant-design/icons-vue';
import E from "wangeditor";
interface docItem {
id:number,
name:string,
parent?:number,
sort?:number,
children?:docItem[],
ebookId?:number,
viewCount?: number,
voteCount?: number,
disabled?:boolean,
}
const setDisable = (arr:docItem[],targetId:number|string) => {
if(arr.length === 0) return ;
for(let i=0;i<arr.length;i++){
let node = arr[i];
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);
}
}
}
}
let deleteIds:any = [];
let deleteDocs:any = [];
const getDeleteIds = (arr:docItem[],targetId:number|string) => {
if(arr.length === 0) return ;
for(let i=0;i<arr.length;i++){
let node = arr[i];
if(Number(node.id)=== Number(targetId)){
deleteIds.push(node.id);
deleteDocs.push(node.name);
let children = node.children;
if(children && children.length){
for(let j=0;j<children.length;j++){
getDeleteIds(children,children[j].id);
}
}
}else{
let children = node.children;
if(children && children.length){
getDeleteIds(children,targetId);
}
}
}
}
export default defineComponent({
name:"AdminDoc",
setup: function () {
const columns = [
{
dataIndex: 'name',
key: 'name',
title: "名称",
slots: {customRender: 'customeTitle'}
},
{
title: 'Action',
key: 'action',
slots: {customRender: 'action'},
},
];
const loading = ref(false);
const docs = ref();
docs.value = [];
const doc = ref();
doc.value = {
name:"",
parent:"",
sort:""
}
const treeData = ref();
const route = useRoute();
let editor:any= null;
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);
docs.value = result;
}
})
}
const handleSave = () => {
axios.post("/doc/save", doc.value).then(response => {
const data = response.data;
if (data.success) {
handleQuery();
} else {
console.log("save failed");
}
})
}
const handleDelete = (record: any) => {
axios.delete("/doc/delete/" + deleteIds.join(",")).then(response => {
const data = response.data;
if (data.success) {
handleQuery();
} else {
console.log("delete failed");
}
})
}
const showConfirm = (record:any) => {
Modal.confirm({
title: '重要提醒',
icon: createVNode(ExclamationCircleOutlined),
content: createVNode('div', { style: 'color:red;' }, '将删除【'+deleteDocs.join(",")+'】,删除后不可恢复,确认删除?'),
onOk() {
console.log(deleteDocs.join(","));
handleDelete(record);
},
onCancel() {
console.log('Cancel');
}
});
};
const handleChange = (p: any) => {
handleQuery();
}
const edit = (record: any) => {
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 confirm = (record: any) => {
deleteIds = [];
deleteDocs = []
getDeleteIds(docs.value,record.id);
console.log(deleteDocs);
showConfirm(record);
}
const handleSearch = () => {
handleQuery();
}
const handleAdd = () => {
doc.value = {
id: "",
name: "",
sort: "",
ebookId:route.params.ebookId
}
treeData.value = JSON.parse(JSON.stringify(docs.value));
treeData.value.unshift({id: 0, name: "无"});
}
onMounted(() => {
handleQuery();
editor = new E("#content");
editor.config.zIndex = 0;
editor.create();
})
return {
docs,
columns,
loading,
doc,
treeData,
handleQuery,
handleChange,
edit,
showConfirm,
handleSave,
confirm,
handleSearch,
handleAdd
};
}
});
</script>
<style scoped>
</style>
文档内容表设计
文档内容表设计如下,
drop table if exists `content`;
create table `content` (
`id` bigint not null comment '文档id',
`txt` mediumtext not null comment '内容',
primary key (`id`)
)engine=innodb default charset=utf8mb4 comment='文档内容';
content表包含两个字段:文档唯一标识id
和文档内容txt
。
现在,回过头来看下之前的文档表设计,doc表也包含文档唯一标识id
。
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='文档';
文档内容是文档的一部分,也就是说,文档内容字段txt
本可以放到doc表中。
但txt
的数据类型是mediumtext
,是大字段,如果将它放到doc表里,可能会出现这么一种情况:假如每次查询doc表,将返回10条数据,每条数据里都包含txt
这个大字段,由此就会造成较大的数据库压力。所以为了避免这种情况,就采用了分表。
分表有纵向分表和横向分表。
- 纵向分表 ,将本来可以在同一个表的内容,人为划分为多个表,本例就是纵向分表。
- 横向分表,就是把大的表结构,横向切割为相同结构的不同表。
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>-->
<table tableName="content" domainObjectName="Content" ></table>
</context>
</generatorConfiguration>
执行命令:mybatis-generator:generate -e
,生成如下文件,
- Model实体类:
com.jepcc.test.model.Content
和com.jepcc.test.model.ContentExample
- Mapper接口类:
com.jepcc.test.mapper.ContentMapper
- Mapper XML文件:
src/main/resources/mapper/ContentMapper.xml