【mavoneditor使用教程】基于Vue的markdown文本编辑器

729 阅读3分钟

介绍

技术栈:TypeScript+Vue+Element-plus

最近在练项目的时候突发奇想,想使用一下markdown编辑器做,本教程使用的是mavoneditor。出来的效果就是可以使用markdown语法写文章,也可以使用上面的菜单栏编辑,还支持图片上传等,具体的可以参考官网,下面有传送门,引入其实挺简单的,容易上手。

其次我还模仿了掘金的发文章页面,做出来效果还蛮好看的,分享给大家。

传送门: mavon-editor - npm

效果图

本教程使用的是Vue3 setup版本,如果用vue2版本请参考上面传送门的Vue2写法!!! image.png

步骤

1. 安装

npm install mavon-editor@next --save

2. Vue注册

// 全局注册
// import with ES6
import { createApp } from 'vue'
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
// use
createApp(App).use(mavonEditor).mount('#app')

3. 编辑页面编写

<mavon-editor v-model="value" @imgAdd="imgAdd" @change="change" />

4. JavaScript代码

import { ref, computed } from 'vue';
const value = ref('');

到这里基本上就有个效果了,下面是扩展代码,供大家参考!如有不不适的地方请大佬指点!

页面参考代码

这里的代码主要是监听了文本编辑器的字符数 行数 正文字数 加了一些样式让页面好看。本教程没有提供后端。

<template>
    <div class="editor-view">
        <div class="editor-header">
            <el-row justify="space-between" align="middle" :gutter="10">
                <el-col :span="14">
                    <el-input v-model="title" placeholder="输入文章标题..." maxlength="35"></el-input>
                </el-col>
                <el-col :span="4">
                    <Text :text="'文章将自动保存至草稿箱'" color="#8a919f" :size="15" />
                </el-col>
                <el-col :span="5">
                    <div class="flex-box gap-5">
                        <el-button type="primary" @click="" plain>草稿箱</el-button>
                        <el-button type="primary" @click="">发布</el-button>
                        <el-avatar style="width: 40px; height: 40px;" src="avator.jpg" />
                    </div>
                </el-col>
            </el-row>
        </div>
        <mavon-editor v-model="value" @imgAdd="imgAdd" @change="change" />
        <div class="editor-footer">
            <el-row align="middle">
                <el-col :span="6">
                    <div class="flex-box gap-10">
                        <Text :left="10" :size="13" color="#24292e" :text="'字符数:' + getCharacterCount" />
                        <Text :size="13" color="#24292e" :text="'行数:' + getCharacterRowsCount" />
                        <Text :size="13" color="#24292e" :text="'正文字数' + getCharacterWordsCount" />
                    </div>
                </el-col>
            </el-row>
        </div>
    </div>
</template>

这里使用computed计算正文字数、行数、字符数。监听图片上传与文本改变。在图片上传和文本改变的时候就可以走接口或者暂时存储一些数据。

<script lang="ts" setup>
import { ref, computed } from 'vue';
const value = ref('');
const title = ref('');

const getCharacterCount = computed(() => {
    return value.value.length;
});

const getCharacterRowsCount = computed(() => {
    return value.value.split('\n').length;
});
// 正文字数 使用正则匹配去掉空格和换行符
const getCharacterWordsCount = computed(() => {
    return value.value.replace(/\s+/g, '').length;
})
const imgAdd = (pos: Number, $file: File) => {
    console.log(pos, $file);
};
const change = (value: string, render: string) => {
    console.log(value, render);
};

css部分大致分为三个区域,第一个是头部 .editor-header 其次.markdown-body 最后是.editor-footer类名。被.editor-view类名盒子包裹的。.editor-view使用flex布局,从上往下按。顶部和底部都设计了宽高,使用 flex:1 的样式将中间撑高。

这里遇到的一个小难题就是 我想把顶部的el-input的外边框给去掉,花了我挺久时间哈哈。这里使用important!修改优先级改不了,后面查资料发现可以使用穿透(::v-deep)。


<style lang="scss" scoped>
.editor-view {
    overflow: hidden;
    height: 100%;
    display: flex;
    flex-direction: column;
}

.editor-header {
    background-color: white;
    padding: 10px;
    height: fit-content;

    &::v-deep(.el-input__wrapper) {
        border: none;
        box-shadow: none;
        font-size: 20px;
    }
}

.markdown-body {
    flex: 1;
}

.editor-footer {
    background-color: white;
    height: 20px;
}
</style>

封装的组件

<template>
    <template v-if="type">
        <el-text
            :style="{ 'fontSize': (size || types[type].size) + 'px', 'color': color || types[type].color, 'marginTop': top + 'px', 'marginLeft': left + 'px' }"
            :tag="types[type].tag" :line-clamp="1">{{ text }}</el-text>
    </template>
</template>

<script lang="ts" setup>
defineProps({
    text: {
        type: String,
        required: true
    },
    color: {
        type: String,
        default: ''
    },
    size: {
        type: Number,
        // default: 16
    },
    type: {
        type: String as () => 'normal' | 'title' | 'active',
        default: 'normal'
    },
    left: {
        type: Number,
        default: 0
    },
    top: {
        type: Number,
        default: 0

    }
})

interface TypeConfig {
    color: string
    size: number
    tag?: string
}

const types: Record<'normal' | 'title' | 'active', TypeConfig> = {
    normal: {
        color: '#666a77',
        size: 14
    },
    title: {
        color: '#252933',
        size: 16,
        tag: 'b'
    },
    active: {
        color: '#1e80ff',
        size: 16
    }
}
</script>

<style lang="scss" scoped></style>

扩展css小知识

在vue因项目的开发过程,使用了ElementUl组件且样式style使用了scoped属性,当想要修改组件样式,发现直接修改不了,需去掉scoped属性或者使用深度选择器才能修改成功。去掉scoped的话又会影响全局样式,针对这种情况,可以使用深度作用选择器(即样式穿透)。

sass 和 less的样式穿透 使用 /deep/

>>>一般用于原生CSS

/deep/ 一般用于less

::v-deep 一般用于scss

如果没有写scoped属性,那么改变第三方组件样式的时候就不用写上面的穿透了哦。

深度选择参考: 深度(穿透)选择器 ::v-deep /deep/ 及 >>>_deep穿透-CSDN博客