当前位置: 首页 > news >正文

【wiki知识库】06.文档管理页面的添加--前端Vue部分

  📝个人主页:哈__

期待您的关注 

目录

一、🔥今日目标

 二、🐻前端Vue模块的改造

BUG修改

1.wangeditor无法展示问题

2.弹窗无法正常关闭问题

2.1 添加admin-doc.vue

2.1.1 点击admin-ebook中的路由跳转到admin-doc

 2.2.2 进入到admin-doc中调用初始化查询方法

2.2.3 文档编辑中的一些细节

2.2.4 文档的删除

2.2.5 admin-doc.vue全部代码

2.2 添加DocView.vue

2.3 添加新的路由规则


一、🔥今日目标

上一次带大家把前端的分类管理模块做了出来,我们可以实现网站的分类功能,以及分类的树形结构展示功能。到此为止已经带大家做了电子书管理模块、分类模块,那么只要再把文档管理模块也做出来,我们就可以初步实现电子书这整个一套流程了。我们可以编辑添加电子书,实现分类,并且真正的往电子书的文档模块中添加内容。【wiki知识库】05.分类管理实现--前端Vue模块-CSDN博客

我们今天就要实现下方图片中箭头指向的功能

 除此之外,还有主页的展示功能。

 二、🐻前端Vue模块的改造

在此之前我要要说一件事情,我在做这个模块的时候出现了问题,一个是我们之后要使用的文本编辑器wangeditor无法正常展示,还有一个是弹窗无法关闭的问题。这里我把解决方法告诉大家。

BUG修改

1.wangeditor无法展示问题

出现这个问题可能是版本的问题,进入到我们的web目录中,打开终端窗口然后输入下方指令。重新安装wangeditor。

npm i wangeditor@4.6.3 --save

2.弹窗无法正常关闭问题

这个问题是wangeditor和vue版本兼容的问题。我们需要修改一下package.json。将vue版本改成下方图中所示,注意前边的符号。


2.1 添加admin-doc.vue

2.1.1 点击admin-ebook中的路由跳转到admin-doc

还记得我当初在admin-ebook.vue中写的一个router吗?在我们点击文档管理跳转到对应的组件的时候,我们是有传一个ebookId进来的,我们这里使用的是路由传参。


 2.2.2 进入到admin-doc中调用初始化查询方法

进入到这个页面呢调用了两个方法,一个是editor.create(),用于加载我们的文本编辑器,另外一个方法调用的是handleQuery()方法,向后端发送查询请求。

onMounted(() => {editor.create();handleQuery();});

 这个方法进入的时候,修改了一个变量loading,我们在进行信息查询的时候会给用户一个反馈,告诉用户稍等,我们修改为true之后就会有页面数据加载的效果。

level1变量我们之前也说过,用于保存树形结构的数据。之后呢就会发送一个ajax请求,等我们的数据返回来之后就要把loading改为false了。

至于下边的treeSelectData,在我们修改一个文档的父文档的时候,如果我们要把这个文档作为根文档,也就是一级文档,我们需要把这个文档的父文档设置为无,但是我们的level中存储的是数据库中查出来的数据,没有无这个选项,所以我们新加一个变量来存储level1的结果和无,这样不会影响我们查出来的数据。

const handleQuery = () => {loading.value = true;// 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据level1.value = [];axios.get("/doc/all/" + route.query.ebookId).then((response) => {loading.value = false;const data = response.data;if (data.success) {docs.value = data.content;console.log("原始数组:", docs.value);level1.value = [];level1.value = Tool.array2Tree(docs.value, 0);console.log("树形结构:", level1);// 父文档下拉框初始化,相当于点击新增treeSelectData.value = Tool.copy(level1.value) || [];// 为选择树添加一个"无"treeSelectData.value.unshift({id: 0, name: '无'});} else {message.error(data.message);}});};

2.2.3 文档编辑中的一些细节

在我们进行文档编辑的时候,我们不可能把该文档的父文档改为自己,或者改为它的子文档,这是一个循环错误。所以我们在修改一个文档的时候要把这个文档的子文档和自己设置为不可选中。

 来看看这个方法,我们把树形结构的数据,还有我们要编辑的文档的id传进来,首先进行for循环去找到这个结点,然后我们把这个节点设置为不可见,然后我们去遍历这个节点的子节点,递归调用。

/*** 将某节点及其子孙节点全部置为disabled*/const setDisable = (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("disabled", node);// 将目标节点设置为disablednode.disabled = true;// 遍历所有子节点,将所有子节点全部都加上disabledconst children = node.children;if (Tool.isNotEmpty(children)) {for (let j = 0; j < children.length; j++) {setDisable(children, children[j].id)}}} else {// 如果当前节点不是目标节点,则到其子节点再找找看。const children = node.children;if (Tool.isNotEmpty(children)) {setDisable(children, id);}}}};

2.2.4 文档的删除

文档的删除并不只是该文档简单删除就完了,这个文档删掉之后,这个文档的所有子文档都要删除。我们之前分类管理模块也处理过这样的删除,但是我们是后端处理的删除逻辑,后端处理起来呢比较麻烦,这里我们可以使用前端处理一下。既然要删除子分支,我们就把这个要删除的文档的子文档的id都查出来一起传到后端。

/*** 查找整根树枝*/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);}}}};

2.2.5 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-form layout="inline" :model="param"><a-form-item><a-button type="primary" @click="handleQuery()">查询</a-button></a-form-item><a-form-item><a-button type="primary" @click="add()">新增</a-button></a-form-item></a-form></p><a-tablev-if="level1.length > 0":columns="columns":row-key="record => record.id":data-source="level1":loading="loading":pagination="false"size="small":defaultExpandAllRows="true"><template #name="{ text, record }">{{record.sort}} {{text}}</template><template v-slot:action="{ text, record }"><a-space size="small"><a-button type="primary" @click="edit(record)" size="small">编辑</a-button><a-popconfirmtitle="删除后不可恢复,确认删除?"ok-text="是"cancel-text="否"@confirm="handleDelete(record.id)"><a-button type="danger" size="small">删除</a-button></a-popconfirm></a-space></template></a-table></a-col><a-col :span="16"><p><a-form layout="inline" :model="param"><a-form-item><a-button type="primary" @click="handleSave()">保存</a-button></a-form-item></a-form></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-selectv-model:value="doc.parent"style="width: 100%":dropdown-style="{ maxHeight: '400px', overflow: 'auto' }":tree-data="treeSelectData"placeholder="请选择父文档"tree-default-expand-all:replaceFields="{title: 'name', key: 'id', value: 'id'}"></a-tree-select></a-form-item><a-form-item><a-input v-model:value="doc.sort" placeholder="顺序"/></a-form-item><a-form-item><a-button type="primary" @click="handlePreviewContent()"><EyeOutlined /> 内容预览</a-button></a-form-item><a-form-item><div id="content"></div></a-form-item></a-form></a-col></a-row><a-drawer width="900" placement="right" :closable="false" :visible="drawerVisible" @close="onDrawerClose"><div class="wangeditor" :innerHTML="previewHtml"></div></a-drawer></a-layout-content></a-layout></template><script lang="ts">import { defineComponent, onMounted, ref, createVNode } from 'vue';import axios from 'axios';import {message, Modal} from 'ant-design-vue';import {Tool} from "@/util/tool";import {useRoute} from "vue-router";import ExclamationCircleOutlined from "@ant-design/icons-vue/ExclamationCircleOutlined";import E from 'wangeditor'export default defineComponent({name: 'AdminDoc',setup() {const route = useRoute();console.log("路由:", route);console.log("route.path:", route.path);console.log("route.query:", route.query);console.log("route.param:", route.params);console.log("route.fullPath:", route.fullPath);console.log("route.name:", route.name);console.log("route.meta:", route.meta);const param = ref();param.value = {};const docs = ref();const loading = ref(false);// 因为树选择组件的属性状态,会随当前编辑的节点而变化,所以单独声明一个响应式变量const treeSelectData = ref();treeSelectData.value = [];const columns = [{title: '名称',dataIndex: 'name',slots: { customRender: 'name' }},{title: 'Action',key: 'action',slots: { customRender: 'action' }}];const level1 = ref(); // 一级文档树,children属性就是二级文档level1.value = [];/*** 数据查询**/const handleQuery = () => {loading.value = true;// 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据level1.value = [];axios.get("/doc/all/" + route.query.ebookId).then((response) => {loading.value = false;const data = response.data;if (data.success) {docs.value = data.content;console.log("原始数组:", docs.value);level1.value = [];level1.value = Tool.array2Tree(docs.value, 0);console.log("树形结构:", level1);// 父文档下拉框初始化,相当于点击新增treeSelectData.value = Tool.copy(level1.value) || [];// 为选择树添加一个"无"treeSelectData.value.unshift({id: 0, name: '无'});} else {message.error(data.message);}});};// -------- 表单 ---------const doc = ref();doc.value = {ebookId: route.query.ebookId};const modalVisible = ref(false);const modalLoading = ref(false);const editor = new E('#content');editor.config.zIndex = 0;// 显示上传图片按钮,转成Base64存储,同时也支持拖拽图片editor.config.uploadImgShowBase64 = true;const handleSave = () => {modalLoading.value = true;doc.value.content = editor.txt.html();axios.post("/doc/save", doc.value).then((response) => {modalLoading.value = false;const data = response.data; // data = commonRespif (data.success) {// modalVisible.value = false;message.success("保存成功!");// 重新加载列表handleQuery();} else {message.error(data.message);}});};/*** 将某节点及其子孙节点全部置为disabled*/const setDisable = (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("disabled", node);// 将目标节点设置为disablednode.disabled = true;// 遍历所有子节点,将所有子节点全部都加上disabledconst children = node.children;if (Tool.isNotEmpty(children)) {for (let j = 0; j < children.length; j++) {setDisable(children, children[j].id)}}} else {// 如果当前节点不是目标节点,则到其子节点再找找看。const children = node.children;if (Tool.isNotEmpty(children)) {setDisable(children, id);}}}};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);}}}};/*** 内容查询**/const handleQueryContent = () => {axios.get("/doc/find-content/" + doc.value.id).then((response) => {const data = response.data;if (data.success) {editor.txt.html(data.content)} else {message.error(data.message);}});};/*** 编辑*/const edit = (record: any) => {// 清空富文本框editor.txt.html("");modalVisible.value = true;doc.value = Tool.copy(record);handleQueryContent();// 不能选择当前节点及其所有子孙节点,作为父节点,会使树断开treeSelectData.value = Tool.copy(level1.value);setDisable(treeSelectData.value, record.id);// 为选择树添加一个"无"treeSelectData.value.unshift({id: 0, name: '无'});};/*** 新增*/const add = () => {// 清空富文本框editor.txt.html("");modalVisible.value = true;doc.value = {ebookId: route.query.ebookId};treeSelectData.value = Tool.copy(level1.value) || [];// 为选择树添加一个"无"treeSelectData.value.unshift({id: 0, name: '无'});};const handleDelete = (id: number) => {// console.log(level1, level1.value, id)// 清空数组,否则多次删除时,数组会一直增加deleteIds.length = 0;deleteNames.length = 0;getDeleteIds(level1.value, id);Modal.confirm({title: '重要提醒',icon: createVNode(ExclamationCircleOutlined),content: '将删除:【' + deleteNames.join(",") + "】删除后不可恢复,确认删除?",onOk() {// console.log(ids)axios.delete("/doc/delete/" + deleteIds.join(",")).then((response) => {const data = response.data; // data = commonRespif (data.success) {// 重新加载列表handleQuery();} else {message.error(data.message);}});},});};// ----------------富文本预览--------------const drawerVisible = ref(false);const previewHtml = ref();const handlePreviewContent = () => {const html = editor.txt.html();previewHtml.value = html;drawerVisible.value = true;};const onDrawerClose = () => {drawerVisible.value = false;};onMounted(() => {editor.create();handleQuery();});return {param,// docs,level1,columns,loading,handleQuery,edit,add,doc,modalVisible,modalLoading,handleSave,handleDelete,treeSelectData,drawerVisible,previewHtml,handlePreviewContent,onDrawerClose,}}});
</script><style scoped>img {width: 50px;height: 50px;}
</style>

2.2 添加DocView.vue

这个组件呢就是为了在主页展示文档数据。但是其中的一些功能后端还未实现。

<template><a-layout><a-layout-content :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"><h3 v-if="level1.length === 0">对不起,找不到相关文档!</h3><a-row><a-col :span="6"><a-treev-if="level1.length > 0":tree-data="level1"@select="onSelect":replaceFields="{title: 'name', key: 'id', value: 'id'}":defaultExpandAll="true":defaultSelectedKeys="defaultSelectedKeys"></a-tree></a-col><a-col :span="18"><div><h2>{{doc.name}}</h2><div><span>阅读数:{{doc.viewCount}}</span> &nbsp; &nbsp;<span>点赞数:{{doc.voteCount}}</span></div><a-divider style="height: 2px; background-color: #9999cc"/></div><div class="wangeditor" :innerHTML="html"></div><div class="vote-div"><a-button type="primary" shape="round" :size="'large'" @click="vote"><template #icon><LikeOutlined /> &nbsp;点赞:{{doc.voteCount}} </template></a-button></div></a-col></a-row></a-layout-content></a-layout>
</template><script lang="ts">import { defineComponent, onMounted, ref, createVNode } from 'vue';import axios from 'axios';import {message} from 'ant-design-vue';import {Tool} from "@/util/tool";import {useRoute} from "vue-router";export default defineComponent({name: 'Doc',setup() {const route = useRoute();const docs = ref();const html = ref();const defaultSelectedKeys = ref();defaultSelectedKeys.value = [];// 当前选中的文档const doc = ref();doc.value = {};const level1 = ref(); // 一级文档树,children属性就是二级文档level1.value = [];/*** 内容查询**/const handleQueryContent = (id: number) => {axios.get("/doc/find-content/" + id).then((response) => {const data = response.data;if (data.success) {html.value = data.content;} else {message.error(data.message);}});};/*** 数据查询**/const handleQuery = () => {axios.get("/doc/all/" + route.query.ebookId).then((response) => {const data = response.data;if (data.success) {docs.value = data.content;level1.value = [];level1.value = Tool.array2Tree(docs.value, 0);if (Tool.isNotEmpty(level1)) {defaultSelectedKeys.value = [level1.value[0].id];handleQueryContent(level1.value[0].id);// 初始显示文档信息doc.value = level1.value[0];}} else {message.error(data.message);}});};const onSelect = (selectedKeys: any, info: any) => {console.log('selected', selectedKeys, info);if (Tool.isNotEmpty(selectedKeys)) {// 选中某一节点时,加载该节点的文档信息doc.value = info.selectedNodes[0].props;// 加载内容handleQueryContent(selectedKeys[0]);}};// 点赞const vote = () => {axios.get('/doc/vote/' + doc.value.id).then((response) => {const data = response.data;if (data.success) {doc.value.voteCount++;} else {message.error(data.message);}});};onMounted(() => {handleQuery();});return {level1,html,onSelect,defaultSelectedKeys,doc,vote}}});
</script><style>/* table 样式 */.wangeditor table {border-top: 1px solid #ccc;border-left: 1px solid #ccc;}.wangeditor table td,.wangeditor table th {border-bottom: 1px solid #ccc;border-right: 1px solid #ccc;padding: 3px 5px;}.wangeditor table th {border-bottom: 2px solid #ccc;text-align: center;}/* blockquote 样式 */.wangeditor blockquote {display: block;border-left: 8px solid #d0e5f2;padding: 5px 10px;margin: 10px 0;line-height: 1.4;font-size: 100%;background-color: #f1f1f1;}/* code 样式 */.wangeditor code {display: inline-block;*display: inline;*zoom: 1;background-color: #f1f1f1;border-radius: 3px;padding: 3px 5px;margin: 0 3px;}.wangeditor pre code {display: block;}/* ul ol 样式 */.wangeditor ul, ol {margin: 10px 0 10px 20px;}/* 和antdv p冲突,覆盖掉 */.wangeditor blockquote p {font-family:"YouYuan";margin: 20px 10px !important;font-size: 16px !important;font-weight:600;}/* 点赞 */.vote-div {padding: 15px;text-align: center;}/* 图片自适应 */.wangeditor img {max-width: 100%;height: auto;}/* 视频自适应 */.wangeditor iframe {width: 100%;height: 400px;}
</style>

2.3 添加新的路由规则

在router下的index.js中新增两个路由规则。

 {path: '/doc',name: 'doc',component:DocView},{path: '/admin/doc',name: 'AdminDoc',component: AdminDoc}

后续我会把后端的代码补上的。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 记录pytest中场景执行的token异常处理问题
  • 加油卡APP开发,汽车加油便捷新方式
  • C++:调整数组顺序使奇数位于偶数前面【面试】
  • 创新共享经济:探索Web3对新商业模式的启迪
  • 【Python入门与进阶】Python函数的定义与使用
  • 随手记:商品信息过多,展开收起功能
  • Java-集合类-Arrays.asList()使用需要注意的大坑
  • 综合数据分析及可视化实战
  • 力扣hot100:394. 字符串解码(递归/括号匹配,字符串之间相对顺序)
  • 放弃Venn-Upset-花瓣图,拥抱二分网络
  • 无公网IP与服务器完成企业微信网页应用开发远程调试详细流程
  • 36、matlab矩阵特征值、特征向量和奇异值
  • 【python】在【机器学习】与【数据挖掘】中的应用:从基础到【AI大模型】
  • 基于MCGS的双容水箱液位控制系统设计【MCGS+MATLAB+研华工控机】
  • 【第六篇】SpringSecurity的权限管理
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • C++类中的特殊成员函数
  • eclipse的离线汉化
  • GitUp, 你不可错过的秀外慧中的git工具
  • MySQL几个简单SQL的优化
  • Node + FFmpeg 实现Canvas动画导出视频
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Spring Boot MyBatis配置多种数据库
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • webpack入门学习手记(二)
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 当SetTimeout遇到了字符串
  • 服务器从安装到部署全过程(二)
  • 构造函数(constructor)与原型链(prototype)关系
  • 简析gRPC client 连接管理
  • 微信小程序实战练习(仿五洲到家微信版)
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • ​【经验分享】微机原理、指令判断、判断指令是否正确判断指令是否正确​
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • (~_~)
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (2)空速传感器
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (9)STL算法之逆转旋转
  • (delphi11最新学习资料) Object Pascal 学习笔记---第14章泛型第2节(泛型类的类构造函数)
  • (void) (_x == _y)的作用
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (苍穹外卖)day03菜品管理
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (论文阅读32/100)Flowing convnets for human pose estimation in videos
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (四) Graphivz 颜色选择
  • (贪心) LeetCode 45. 跳跃游戏 II
  • (已解决)什么是vue导航守卫
  • (转)全文检索技术学习(三)——Lucene支持中文分词