Vue3+Vite實(shí)現(xiàn)一個(gè)Markdown編輯器組件
一、項(xiàng)目背景與需求分析
在現(xiàn)代前端開(kāi)發(fā)中,Markdown 編輯器廣泛應(yīng)用于博客、文檔、Wiki、代碼注釋等場(chǎng)景。一個(gè)優(yōu)秀的 Markdown 編輯器需要具備以下功能:
- 實(shí)時(shí)預(yù)覽:用戶輸入時(shí),能夠看到實(shí)時(shí)的渲染效果。
- 語(yǔ)法高亮:支持 Markdown 語(yǔ)法的實(shí)時(shí)高亮顯示。
- 導(dǎo)出功能:支持將編輯的內(nèi)容導(dǎo)出為 Markdown 或 HTML 格式,方便分享和存檔。
- 自動(dòng)保存:避免用戶丟失未保存內(nèi)容。
- 文件上傳:支持上傳文件(如圖片),并在 Markdown 內(nèi)容中嵌入顯示。
本文將使用 Vue 3 構(gòu)建一個(gè)簡(jiǎn)單的 Markdown 編輯器組件,并實(shí)現(xiàn)上述基本功能。
二、搭建基礎(chǔ)項(xiàng)目
1. 初始化 Vue 3 項(xiàng)目
我們使用 Vite 來(lái)初始化 Vue 3 項(xiàng)目,并選擇 TypeScript 模板:
npm create vite@latest markdown-editor --template vue-ts cd markdown-editor
2. 安裝依賴(lài)
接下來(lái),我們安裝實(shí)現(xiàn) Markdown 渲染和語(yǔ)法高亮所需要的依賴(lài):
marked:用于將 Markdown 轉(zhuǎn)換為 HTML。
highlight.js:用于提供 Markdown 語(yǔ)法的高亮顯示。
npm install marked highlight.js
三、實(shí)現(xiàn) Markdown 編輯器組件
1. 創(chuàng)建 Markdown 編輯器組件
在 src/components 目錄下創(chuàng)建 MarkdownEditor.vue 文件,封裝 Markdown 編輯器功能。
<template>
<div class="markdown-editor">
<textarea
v-model="content"
class="editor-textarea"
placeholder="請(qǐng)輸入 Markdown 內(nèi)容..."
@input="saveToLocalStorage"
></textarea>
<div class="preview">
<div v-html="renderedContent" class="preview-content"></div>
</div>
<div class="export-buttons">
<button @click="exportMarkdown">導(dǎo)出 Markdown</button>
<button @click="exportHTML">導(dǎo)出 HTML</button>
</div>
<div class="file-upload">
<input type="file" @change="handleFileUpload" />
<p>上傳圖片并在 Markdown 中顯示</p>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, onMounted } from 'vue';
import { marked } from 'marked';
import hljs from 'highlight.js';
export default defineComponent({
name: 'MarkdownEditor',
setup() {
const content = ref('');
const fileUrl = ref<string | null>(null);
// 從本地存儲(chǔ)加載內(nèi)容
onMounted(() => {
const savedContent = localStorage.getItem('markdown-content');
if (savedContent) {
content.value = savedContent;
}
});
// 將 Markdown 轉(zhuǎn)換為 HTML 并加上語(yǔ)法高亮
const renderedContent = computed(() => {
marked.setOptions({
highlight: (code: string) => {
return hljs.highlightAuto(code).value;
},
});
return marked(content.value);
});
// 保存內(nèi)容到本地存儲(chǔ)(自動(dòng)保存)
const saveToLocalStorage = () => {
localStorage.setItem('markdown-content', content.value);
};
// 導(dǎo)出 Markdown 內(nèi)容
const exportMarkdown = () => {
const blob = new Blob([content.value], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'document.md';
link.click();
};
// 導(dǎo)出 HTML 內(nèi)容
const exportHTML = () => {
const blob = new Blob([renderedContent.value], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'document.html';
link.click();
};
// 文件上傳功能
const handleFileUpload = (event: Event) => {
const fileInput = event.target as HTMLInputElement;
const file = fileInput.files ? fileInput.files[0] : null;
if (file) {
const reader = new FileReader();
reader.onload = () => {
const imageUrl = reader.result as string;
fileUrl.value = imageUrl; // 存儲(chǔ)圖片的 URL
const markdownImage = ``; // 將圖片路徑轉(zhuǎn)化為 Markdown 格式
content.value += `\n\n${markdownImage}\n`; // 插入到 Markdown 內(nèi)容中
};
reader.readAsDataURL(file); // 將圖片讀取為 Data URL
}
};
return {
content,
renderedContent,
exportMarkdown,
exportHTML,
handleFileUpload,
};
},
});
</script>
<style scoped>
.markdown-editor {
display: flex;
flex-direction: column;
gap: 20px;
padding: 20px;
}
.editor-textarea {
width: 100%;
height: 300px;
padding: 10px;
font-size: 16px;
line-height: 1.5;
border: 1px solid #ccc;
border-radius: 8px;
resize: none;
}
.preview {
background-color: #f8f8f8;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
min-height: 300px;
}
.preview-content {
max-width: 100%;
word-wrap: break-word;
}
.export-buttons {
display: flex;
gap: 10px;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.file-upload {
display: flex;
flex-direction: column;
gap: 10px;
}
input[type="file"] {
padding: 5px;
}
</style>
2. 組件說(shuō)明
Markdown 渲染:通過(guò) marked 將用戶輸入的 Markdown 內(nèi)容轉(zhuǎn)換為 HTML。
語(yǔ)法高亮:使用 highlight.js 對(duì)代碼塊進(jìn)行高亮顯示,增強(qiáng)代碼閱讀性。
導(dǎo)出功能:支持將 Markdown 或 HTML 內(nèi)容導(dǎo)出為文件,方便用戶保存或分享。
自動(dòng)保存功能:使用 localStorage 自動(dòng)保存 Markdown 內(nèi)容,避免用戶丟失未保存的輸入。
文件上傳支持:允許用戶上傳圖片文件,并將圖片插入到 Markdown 內(nèi)容中,生成合適的 Markdown 格式。
四、優(yōu)化與拓展
1. 自動(dòng)保存功能
通過(guò) localStorage 實(shí)現(xiàn)了自動(dòng)保存功能。每次用戶輸入內(nèi)容時(shí),Markdown 編輯器的內(nèi)容會(huì)自動(dòng)存儲(chǔ)到瀏覽器的本地存儲(chǔ)中。當(dāng)用戶刷新頁(yè)面或重新加載時(shí),保存的內(nèi)容會(huì)自動(dòng)恢復(fù)。這樣能有效避免用戶因刷新或?yàn)g覽器崩潰導(dǎo)致的數(shù)據(jù)丟失。
// 保存內(nèi)容到本地存儲(chǔ)(自動(dòng)保存)
const saveToLocalStorage = () => {
localStorage.setItem('markdown-content', content.value);
};
2. 文件上傳功能
我們通過(guò) HTML5 的 FileReader API 實(shí)現(xiàn)了文件上傳支持。當(dāng)用戶上傳圖片文件時(shí),圖片會(huì)轉(zhuǎn)換為 Data URL,并自動(dòng)插入到 Markdown 內(nèi)容中。Markdown 內(nèi)容將以  格式顯示上傳的圖片。
// 文件上傳功能
const handleFileUpload = (event: Event) => {
const fileInput = event.target as HTMLInputElement;
const file = fileInput.files ? fileInput.files[0] : null;
if (file) {
const reader = new FileReader();
reader.onload = () => {
const imageUrl = reader.result as string;
fileUrl.value = imageUrl; // 存儲(chǔ)圖片的 URL
const markdownImage = ``; // 將圖片路徑轉(zhuǎn)化為 Markdown 格式
content.value += `\n\n${markdownImage}\n`; // 插入到 Markdown 內(nèi)容中
};
reader.readAsDataURL(file); // 將圖片讀取為 Data URL
}
};
五、總結(jié)
通過(guò) Vue 3 和 marked、highlight.js,我們實(shí)現(xiàn)了一個(gè)功能完善的 Markdown 編輯器組件。這個(gè)組件不僅支持實(shí)時(shí)預(yù)覽,還具備了語(yǔ)法高亮與導(dǎo)出功能,極大地方便了用戶的文檔編輯與分享需求。同時(shí),自動(dòng)保存功能和文件上傳功能進(jìn)一步提升了編輯器的可用性和用戶體驗(yàn)。
該組件可以輕松地集成到任何 Vue 3 項(xiàng)目中,為你的應(yīng)用增添強(qiáng)大的編輯能力。
到此這篇關(guān)于Vue3+Vite實(shí)現(xiàn)一個(gè)Markdown編輯器組件的文章就介紹到這了,更多相關(guān)Vue3 Markdown編輯器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3配置axios跨域?qū)崿F(xiàn)過(guò)程解析
這篇文章主要介紹了Vue3配置axios跨域?qū)崿F(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
vue學(xué)習(xí)之Vue-Router用法實(shí)例分析
這篇文章主要介紹了vue學(xué)習(xí)之Vue-Router用法,結(jié)合實(shí)例形式分析了Vue-Router路由原理與常見(jiàn)操作技巧,需要的朋友可以參考下2020-01-01
vue實(shí)現(xiàn)的多頁(yè)面項(xiàng)目如何優(yōu)化打包的步驟詳解
這篇文章主要介紹了vue實(shí)現(xiàn)的多頁(yè)面項(xiàng)目如何優(yōu)化打包的步驟詳解,文中通過(guò)示例代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
vue-cli2打包前和打包后的css前綴不一致的問(wèn)題解決
這篇文章主要介紹了vue-cli2打包前和打包后的css前綴不一致的問(wèn)題解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
一個(gè)因@click.stop引發(fā)的bug的解決
這篇文章主要介紹了一個(gè)因@click.stop引發(fā)的bug的解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01

