vue3 markdown编辑器编辑,保存;后端存数据库(解析指定内容)并前端预览下载
背景
话接上篇,搞出来pdf文档展示,然后跟产品演示
产品:不行,这根本没法用,我要目录生成,并且跳转到指定内容,并且能搜索,能复制;
ps:提需求的时候不说,做完了演示的时候说了 :)
我:那pdf做不了。。。。;
产品:那你自己想想其他方案吧,文档格式不限;
我:????最开始不是要pdf么;
话不多说,换新的方案,我第一个想到的就是之前搞过的docsify,支持生成目录跳转,展示,搜索,能复制,就是图片是个问题,搜前端组件的时候,搜到了v-md-editor,大概看了官方文档,普通用法和高级用法,感觉可行,开搞!
实现思路: 产品、运维使用编辑器编辑文档(测试环境),保存本地,然后上传到服务器,然后v-md-preview展示
技术栈
主要的就是前端处理,后端都好弄(vue3+ts +springboot)
前端使用组件: v-md-editor
安装支持 vue3 的版本
# 使用 npm
npm i @kangc/v-md-editor@next -S
# 使用 yarn
yarn add @kangc/v-md-editor@next
在 vue3 中注册
import { createApp } from 'vue';
import VueMarkdownEditor from '@kangc/v-md-editor';
import '@kangc/v-md-editor/lib/style/base-editor.css';
import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js';
import '@kangc/v-md-editor/lib/theme/style/vuepress.css';
import Prism from 'prismjs';
VueMarkdownEditor.use(vuepressTheme, {
Prism,
});
const app = createApp(/*...*/);
app.use(VueMarkdownEditor);
使用
<template>
<v-md-editor v-model="text" height="400px"></v-md-editor>
</template>
<script>
export default {
data() {
return {
text: '',
};
},
};
</script>
实现过程
v-md-editor 编辑器实现线上编辑,然后写回调函数,增加v-md-editord的保存本地和上传本地图片的方法(组件支持的扩展)
v-md-editor 中添加@save=“mdEdit(text)” @upload-image=“handleUploadImage”
因为md文件上传到服务器的时候图片不会上传,只是携带者链接,所以转base64并将其放到md文件中,显示的会很乱,但是没办法(菜单都是固定一个文档,而且只有产品、运维上传操作,其他不开放权限,而且修改频率比较低,用户只是展示,文档还好,都不是很大)
本地图片上传
代码如下:
function handleUploadImage(event: Event,insertImage:any, files:Array<File>){
if(files.length>1){
return;
}else if(files.length == 0){
return;
}else{
const file = files[0];
const rader = new FileReader();
reader.readAsDataURL(File);
reader.onload = (e:any)=>{
const base64 = e.target.result;
insertImage({
url:base64,
desc:''
});
}
}
}
v-md-editor保存本地
代码如下:
const mdEdit = (text:any) =>{
const blob = new blob([text],{type:"text/plain;charset=utf-8"});
const fileName = "templare"+newData().getTime()+".md";
const link = window.URL.createObjectURL(blob);
//保存本地文件
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);//释放blob对象
}
获取导航及跳转
代码如下:
空巷一人
// 获取导航
function getAnchors() {
const anchors = (preview.value.$el as Element).querySelectorAll('h1,h2,h3,h4,h5,h6')
const titles = Array.from(anchors).filter((title: any) => !!title.innerText.trim())
if (!titles.length) {
t.titles = []
return
}
const hTags = Array.from(new Set(titles.map((title) => title.tagName))).sort()
t.titles = titles.map((el: any) => ({
title: el.innerText,
lineIndex: el.getAttribute('data-v-md-line'),
indent: hTags.indexOf(el.tagName)
}))
console.log( t.titles);
}
// 点击导航
function handleAnchorClick(anchor: { title: string; lineIndex: string; indent: number }) {
const { lineIndex } = anchor
const heading = preview.value.$el.querySelector(`[data-v-md-line="${lineIndex}"]`)
if (heading) {
preview.value.scrollToTarget({
target: heading,
scrollContainer: window,
top: 60
})
}
}
获取导航vue代码:
<template>
<div class="markdown">
<div class="tocNavigation">
<!-- 目录开始 -->
<div
:key="anchor"
v-for="anchor in menuTages.titles"
:style="{ padding: `10px 0 10px ${anchor.indent * 20}px`,fibtSuze: `15px`}"
@click="handleAnchorClick(anchor)"
>
<a style="cursor: pointer">{{ anchor.title }}</a>
</div>
<!-- 目录结束 -->
</div>
<div ref="scroll" style="height:100vh; overflow:auto;">
<v-md-preview class="previewContainer" :text="fileInfo" ref="preview"></v-md-preview>
</div>
</div>
</template>
<script setup>
import markdownText from "@/assets/data/introduce.md?raw";
const menuTages = reactive({
titles: [] as Array<{title:string; lineIndex:string; indent:number}>
})
const scroll = ref();
const preview = ref();
</script>
<style scoped>
.markdown {
width: 80vw;
height: 100vh;
left: 10vw;
position: fixed;//设置div固定可解决滚动时页面整体上移的问题
}
.tocNavigation {
width: 12vw;
height: 80vh;
float: left;
margin-top: 2vh;
overflow: auto;
}
.preview {
overflow: auto;
height: 90vh;
}
a:link,a:hover,a:visited,a:active {
color: blue;
}
</style>
官网上的跳转锚点怎么都不生效,后来在v-md-editor交流群(官网有)里有大神提供了方法,给preview单独加了个div,然后在此div中设置了样式overflow:auto,接着在点击导航的方法中将scrollContainer: window ----->替换为scrollContainer: scroll 解决了这个问题!感谢大佬!
展示中文乱码问题
最后还有一个下载,因为后端我是将文件以流的形式存的,展示返回给前端的时候,如果是流的花会有中文乱码,试了好多种的字符集解析都没有成功,后来发现在数据库里都能正常显示,然后就将后端返回由流转换为了字符串,很简单的处理
new String(byte[])
然后前端就正常展示了,也对应生成了目录,并且可以跳转,可以复制;
上传
上传很简单就是浏览文件,上传,很简单,就不贴了,一搜多得是
下载
下载也是后端返回的大字符串,因为可能存在流中文乱码,也是试过几种方法,都不可行,就懒得再弄了,也是后端返回的字符串
前端处理:因为不能保存为md文件(怕用户打不开文件),因此转换为html
//引入marked 自行安装
1.安装marked
npm install marked --save
复制
2.引用
var marked = require('marked')
复制
3.转换为html
var html = marked('### hello markdown') // <h3>hello markdown</h3>
前端代码:
const downloadFile = () =>{
axios({
url:xxx,
method:"get"
}).then(res ={
var marked = require('marked')
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true, //
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
const content = marked(res.data);
const blob = new Blob([content],{type:'text/plain;charset=utf-8'});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
cosnt fileName = "xx.html";
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
link.remove();
})
}
marked参数含义:
- –pedantic: 只解析符合markdown.pl定义的,不修正markdown的错误
- –gfm: 启动Github样式的Markdown
- –breaks: 支持Github换行符,必须打开gfm选项
- –tables: 支持Github表格,必须打开gfm选项
- –sanitize: 原始输出,忽略HTML标签
- –smart-lists: 优化列表输出
目前转换还是丢失了样式,还在解决中,大体能看。