vue3+ts markdown文件展示(图片上传、自定义锚点)、编辑、下载

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 Stringbyte[]

然后前端就正常展示了,也对应生成了目录,并且可以跳转,可以复制;

上传

上传很简单就是浏览文件,上传,很简单,就不贴了,一搜多得是

下载

下载也是后端返回的大字符串,因为可能存在流中文乱码,也是试过几种方法,都不可行,就懒得再弄了,也是后端返回的字符串

前端处理:因为不能保存为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: 优化列表输出

目前转换还是丢失了样式,还在解决中,大体能看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值