分享一個(gè)基于Ace的Markdown編輯器
我認(rèn)為的編輯器分成兩類,一種是分為左右兩邊實(shí)現(xiàn)即時(shí)渲染;一種是先寫(xiě)語(yǔ)法,然后通過(guò)按鈕實(shí)現(xiàn)渲染。
其實(shí)即時(shí)渲染也不難,共同需要考慮的問(wèn)題就是xss,因?yàn)殇秩編?kù)能自定義第三方的xss過(guò)濾(之前是通過(guò)設(shè)置來(lái)實(shí)現(xiàn),也就是本身自帶,不過(guò)在某個(gè)版本后被取消了),所以xss就用官方推薦的dompurify。即時(shí)渲染可以通過(guò)編輯器本身api實(shí)現(xiàn)文本變動(dòng)監(jiān)聽(tīng)來(lái)實(shí)現(xiàn),還有一個(gè)需要考慮的問(wèn)題就是代碼與渲染區(qū)域的對(duì)應(yīng)。但因?yàn)檫@與我的需求相悖,在這里就不介紹了,相信小老板們都能輕松實(shí)現(xiàn)
統(tǒng)一慣例,我們來(lái)看看效果圖
上面的工具欄其實(shí)就是添加事件然后往光標(biāo)插入對(duì)應(yīng)的語(yǔ)句而已,emoji暫時(shí)沒(méi)有實(shí)現(xiàn),貌似需要第三方庫(kù)支持。
整體來(lái)說(shuō)并沒(méi)有難點(diǎn),只不過(guò)對(duì)于這些東西來(lái)說(shuō),要么是文檔分散講得不清楚,要么就是找不到什么文檔。要是真沒(méi)有文檔的話,或者官方簡(jiǎn)陋的文檔,你可能真的想問(wèn)候一下他,哈哈哈。這個(gè)時(shí)候一個(gè)能用的代碼就顯得尤為重要,盡管它可能沒(méi)什么注釋,但相信聰明的你肯定能理解其中的意思。話不多說(shuō),上代碼吧~
<template> <div> <div class="section-ace"> <el-row> <el-col :span="6"> <el-row> <el-col :span="12"> <a class="editor-tab-content" :class="isEditActive" @click="showEdit"> <i class="fa fa-pencil-square-o" aria-hidden="true"></i> 編輯 </a> </el-col> <el-col :span="12"> <a class="preview-tab-content" :class="isPreviewActive" @click="showPreview"> <i class="fa fa-eye" aria-hidden="true"></i> 預(yù)覽 </a> </el-col> </el-row> </el-col> <el-col :push="8" :span="18"> <el-row> <div class="toolbar"> <el-col :span="1"> <div> <i @click="insertBoldCode" class="fa fa-bold" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <div> <i @click="insertItalicCode" class="fa fa-italic" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <div> <i @click="insertMinusCode" class="fa fa-minus" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <el-popover placement="bottom" width="125" transition="fade-in-linear" trigger="click" content=""> <i slot="reference" class="fa fa-header" aria-hidden="true"></i> <div> <div class="header1-btn" :class="isHeader1Active" @click="insertHeader1Code"> 標(biāo)題 1 (Ctrl+Alt+1) </div> <div class="header2-btn" :class="isHeader2Active" @click="insertHeader2Code"> 標(biāo)題 2 (Ctrl+Alt+2) </div> <div class="header3-btn" :class="isHeader3Active" @click="insertHeader3Code"> 標(biāo)題 3 (Ctrl+Alt+3) </div> </div> </el-popover> </el-col> <el-col :span="1"> <el-popover placement="bottom" width="125" transition="fade-in-linear" trigger="click" content=""> <i slot="reference" class="fa fa-code" aria-hidden="true"></i> <div> <div class="text-btn" :class="isTextActive" @click="insertText"> 文本 (Ctrl+Alt+P) </div> <div class="code-btn" :class="isCodeActive" @click="insertCode"> 代碼 (Ctrl+Alt+C) </div> </div> </el-popover> </el-col> <el-col :span="1"> <div> <i @click="insertQuoteCode" class="fa fa-quote-left" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <div> <i @click="insertUlCode" class="fa fa-list-ul" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <div> <i @click="insertOlCode" class="fa fa-list-ol" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <div> <i @click="insertLinkCode" class="fa fa-link" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <div> <i @click="insertImgCode" class="fa fa-picture-o" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <div> <el-upload class="upload-demo" action="https://jsonplaceholder.typicode.com/posts/" :limit="1"> <i class="fa fa-cloud-upload" aria-hidden="true"></i> </el-upload> </div> </el-col> <el-col :span="1"> <div> <i @click="selectEmoji" class="fa fa-smile-o" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <div> <i @click="toggleMaximize" class="fa fa-arrows-alt" aria-hidden="true"></i> </div> </el-col> <el-col :span="1"> <i @click="toggleHelp" class="fa fa-question-circle" aria-hidden="true"></i> <el-dialog :visible.sync="dialogHelpVisible" :show-close="false" top="5vh" width="60%" :append-to-body="true" :close-on-press-escape="true"> <el-card class="box-card" style="margin: -60px -20px -30px -20px"> <div slot="header" class="helpHeader"> <i class="fa fa-question-circle" aria-hidden="true"><span>Markdown Guide</span></i> </div> <p>This site is powered by Markdown. For full documentation, <a rel="external nofollow" target="_blank">click here</a> </p> <el-table :data="tableData" stripe border :highlight-current-row="true" style="width: 100%"> <el-table-column prop="code" label="Code" width="150"> <template slot-scope="scope"> <p v-html='scope.row.code'></p> </template> </el-table-column> <el-table-column prop="or" label="Or" width="180"> <template slot-scope="scope"> <p v-html='scope.row.or'></p> </template> </el-table-column> <el-table-column prop="devices" label="Linux/Windows"> </el-table-column> <el-table-column prop="device" label="Mac OS" width="180"> </el-table-column> <el-table-column prop="showOff" label="... to Get" width="200"> <template slot-scope="scope"> <p v-html='scope.row.showOff'></p> </template> </el-table-column> </el-table> </el-card> </el-dialog> </el-col> </div> </el-row> </el-col> </el-row> </div> <br> <div id="container"> <div class="show-panel"> <div ref="markdown" class="ace" v-show="!isShowPreview"></div> <div class="panel-preview" ref="preview" v-show="isShowPreview"></div> </div> </div> </div> </template> <script> import ace from 'ace-builds' // 在 webpack 環(huán)境中使用必須要導(dǎo)入 import 'ace-builds/webpack-resolver'; import marked from 'marked' import highlight from "highlight.js"; import "highlight.js/styles/foundation.css"; import katex from 'katex' import 'katex/dist/katex.css' import DOMPurify from 'dompurify'; const renderer = new marked.Renderer(); function toHtml(text){ let temp = document.createElement("div"); temp.innerHTML = text; let output = temp.innerText || temp.textContent; temp = null; return output; } function mathsExpression(expr) { if (expr.match(/^\$\$[\s\S]*\$\$$/)) { expr = expr.substr(2, expr.length - 4); return katex.renderToString(expr, { displayMode: true }); } else if (expr.match(/^\$[\s\S]*\$$/)) { expr = toHtml(expr); // temp solution expr = expr.substr(1, expr.length - 2); //Does that mean your text is getting dynamically added to the page? If so, someone must be calling KaTeX to render // it, and that call needs to have the strict flag set to false as well. 即控制臺(tái)警告,比如%為轉(zhuǎn)義或者中文 // link: https://katex.org/docs/options.html return katex.renderToString(expr, { displayMode: false , strict: false}); } } const unchanged = new marked.Renderer() renderer.code = function(code, language, escaped) { console.log(language); const isMarkup = ['c++', 'cpp', 'golang', 'java', 'js', 'javascript', 'python'].includes(language); let hled = ''; if (isMarkup) { const math = mathsExpression(code); if (math) { return math; } else { console.log("highlight"); hled = highlight.highlight(language, code).value; } } else { console.log("highlightAuto"); hled = highlight.highlightAuto(code).value; } return `<pre class="hljs ${language}"><code class="${language}">${hled}</code></pre>`; // return unchanged.code(code, language, escaped); }; renderer.codespan = function(text) { const math = mathsExpression(text); if (math) { return math; } return unchanged.codespan(text); }; export default { name: "abc", props: { value: { type: String, required: true } }, data() { return { tableData: [{ code: ':emoji_name:', or: '—', devices: '—', device: '—', showOff: '🧡' },{ code: '*Italic*', or: '_Italic_', devices: 'Ctrl+I', device: 'Command+I', showOff: '<em>Italic</em>' },{ code: '**Bold**', or: '__Bold__', devices: 'Ctrl+B', device: 'Command+B', showOff: '<em>Bold</em>' },{ code: '++Underscores++', or: '—', devices: 'Shift+U', device: 'Option+U', showOff: '<ins>Underscores</ins>' },{ code: '~~Strikethrough~~', or: '—', devices: 'Shift+S', device: 'Option+S', showOff: '<del>Strikethrough</del>' },{ code: '# Heading 1', or: 'Heading 1<br>=========', devices: 'Ctrl+Alt+1', device: 'Command+Option+1', showOff: '<h1>Heading 1</h1>' },{ code: '## Heading 2', or: 'Heading 2<br>-----------', devices: 'Ctrl+Alt+2', device: 'Command+Option+2', showOff: '<h2>Heading 1</h2>' },{ code: '[Link](https://a.com)', or: '[Link][1]<br>⁝<br>[1]: https://b.org', devices: 'Ctrl+L', device: 'Command+L', showOff: '<a rel="external nofollow" >Link</a>' },{ code: '', or: '![Image][1]<br>⁝<br>[1]: http://url/b.jpg', devices: 'Ctrl+Shift+I', device: 'Command+Option+I', showOff: '<img src="https://cdn.acwing.com/static/plugins/images/commonmark.png" width="36" height="36" alt="Markdown">' },{ code: '> Blockquote', or: '—', devices: 'Ctrl+Q', device: 'Command+Q', showOff: '<blockquote><p>Blockquote</p></blockquote>' },{ code: 'A paragraph.<br><br>A paragraph after 1 blank line.', or: '—', devices: '—', device: '—', showOff: '<p>A paragraph.</p><p>A paragraph after 1 blank line.</p>' },{ code: '<p>* List<br> * List<br> * List</p>', or: '<p> - List<br> - List<br> - List<br></p>', devices: 'Ctrl+U', device: 'Command+U', showOff: '<ul><li>List</li><li>List</li><li>List</li></ul>' },{ code: '<p> 1. One<br> 2. Two<br> 3. Three</p>', or: '<p> 1) One<br> 2) Two<br> 3) Three</p>', devices: 'Ctrl+Shift+O', device: 'Command+Option+O', showOff: '<ol><li>One</li><li>Two</li><li>Three</li></ol>' },{ code: 'Horizontal Rule<br><br>-----------', or: 'Horizontal Rule<br><br>***********', devices: 'Ctrl+H', device: 'Command+H', showOff: 'Horizontal Rule<hr>' },{ code: '`Inline code` with backticks', or: '—', devices: 'Ctrl+Alt+C', device: 'Command+Option+C', showOff: '<code>Inline code</code>with backticks' },{ code: '```<br> def whatever(foo):<br> return foo<br>```', or: '<b>with tab / 4 spaces</b><br>....def whatever(foo):<br>.... return foo', devices: 'Ctrl+Alt+P', device: 'Command+Option+P', showOff: '<pre class="hljs"><code class=""><span class="hljs-function"><span class="hljs-keyword">def</span>' + '<span class="hljs-title">whatever</span><span class="hljs-params">(foo)</span></span>:\n' + ' <span class="hljs-keyword">return</span> foo</code></pre>' }], dialogHelpVisible: false, isTextActive: '', isCodeActive: '', isHeader1Active: '', isHeader2Active: '', isHeader3Active: '', isShowPreview: false, isEditActive: "active", isPreviewActive: "", aceEditor: null, themePath: 'ace/theme/crimson_editor', // 不導(dǎo)入 webpack-resolver,該模塊路徑會(huì)報(bào)錯(cuò) modePath: 'ace/mode/markdown', // 同上 codeValue: this.value || '', }; }, methods: { insertBoldCode() { this.aceEditor.insert("****"); let cursorPosition = this.aceEditor.getCursorPosition(); this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column - 2); }, insertItalicCode() { this.aceEditor.insert("__"); let cursorPosition = this.aceEditor.getCursorPosition(); this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column - 1); }, insertMinusCode() { let cursorPosition = this.aceEditor.getCursorPosition(); this.aceEditor.insert("\n\n"); this.aceEditor.insert("----------"); this.aceEditor.insert("\n\n"); this.aceEditor.gotoLine(cursorPosition.row + 5, cursorPosition.column,true); }, insertHeader1Code() { this.isHeader2Active = this.isHeader3Active = ''; this.isHeader1Active = 'active'; this.aceEditor.insert("\n\n"); this.aceEditor.insert("#"); }, insertHeader2Code() { this.isHeader1Active = this.isHeader3Active = ''; this.isHeader2Active = 'active'; this.aceEditor.insert("\n\n"); this.aceEditor.insert("##"); }, insertHeader3Code() { this.isHeader1Active = this.isHeader2Active = ''; this.isHeader3Active = 'active'; this.aceEditor.insert("\n\n"); this.aceEditor.insert("###"); }, insertText() { let cursorPosition = this.aceEditor.getCursorPosition(); this.isCodeActive = ''; this.isTextActive = 'active'; this.aceEditor.insert("```\n\n```"); this.aceEditor.gotoLine(cursorPosition.row + 2, cursorPosition.column,true); }, insertCode() { let cursorPosition = this.aceEditor.getCursorPosition(); this.isTextActive = ''; this.isCodeActive = 'active'; this.aceEditor.insert("``"); this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column + 1); }, insertQuoteCode() { this.aceEditor.insert("\n>"); let cursorPosition = this.aceEditor.getCursorPosition(); this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column + 1); }, insertUlCode() { this.aceEditor.insert("\n*"); let cursorPosition = this.aceEditor.getCursorPosition(); this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column + 1); }, insertOlCode() { this.aceEditor.insert("\n1."); let cursorPosition = this.aceEditor.getCursorPosition(); this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column + 1); }, insertLinkCode() { this.aceEditor.insert("[]()"); let cursorPosition = this.aceEditor.getCursorPosition(); this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column - 3); }, insertImgCode() { this.aceEditor.insert("![]()"); let cursorPosition = this.aceEditor.getCursorPosition(); this.aceEditor.moveCursorTo(cursorPosition.row, cursorPosition.column - 3); }, uploadImg() { this.aceEditor.insert("![]()"); }, selectEmoji() { this.aceEditor.insert("****"); }, toggleMaximize() { this.aceEditor.insert("****"); }, toggleHelp() { this.dialogHelpVisible = !this.dialogHelpVisible; }, showEdit() { this.$refs.preview.innerHTML = ''; this.isEditActive = 'active'; this.isPreviewActive = ''; this.isShowPreview = false; }, showPreview() { this.show(); this.isEditActive = ''; this.isPreviewActive = 'active'; this.isShowPreview = true; }, show(data) { let value = this.aceEditor.session.getValue(); this.$refs.preview.innerHTML = DOMPurify.sanitize(marked(value)); console.log(DOMPurify.sanitize(marked(value))); }, }, mounted() { this.aceEditor = ace.edit(this.$refs.markdown,{ selectionStyle: 'line', //選中樣式 maxLines: 1000, // 最大行數(shù),超過(guò)會(huì)自動(dòng)出現(xiàn)滾動(dòng)條 minLines: 22, // 最小行數(shù),還未到最大行數(shù)時(shí),編輯器會(huì)自動(dòng)伸縮大小 fontSize: 14, // 編輯器內(nèi)字體大小 theme: this.themePath, // 默認(rèn)設(shè)置的主題 mode: this.modePath, // 默認(rèn)設(shè)置的語(yǔ)言模式 tabSize: 4, // 制表符設(shè)置為 4 個(gè)空格大小 readOnly: false, //只讀 wrap: true, highlightActiveLine: true, value: this.codeValue }); marked.setOptions({ renderer: renderer, // highlight: function (code) { // return highlight.highlightAuto(code).value; // }, gfm: true,//默認(rèn)為true。 允許 Git Hub標(biāo)準(zhǔn)的markdown. tables: true,//默認(rèn)為true。 允許支持表格語(yǔ)法。該選項(xiàng)要求 gfm 為true。 breaks: false,//默認(rèn)為false。 允許回車換行。該選項(xiàng)要求 gfm 為true。 pedantic: false,//默認(rèn)為false。 盡可能地兼容 markdown.pl的晦澀部分。不糾正原始模型任何的不良行為和錯(cuò)誤。 // sanitize: false,//對(duì)輸出進(jìn)行過(guò)濾(清理) 不支持了,用sanitizer 或者直接渲染的時(shí)候過(guò)濾 xhtml: true, // If true, emit self-closing HTML tags for void elements (<br/>, <img/>, etc.) with a "/" as required by XHTML. silent: true, //If true, the parser does not throw any exception. smartLists: true, smartypants: false//使用更為時(shí)髦的標(biāo)點(diǎn),比如在引用語(yǔ)法中加入破折號(hào)。 }); // this.aceEditor.session.on('change', this.show); // let that = this; // this.aceEditor.commands.addCommand({ // name: '復(fù)制', // bindKey: {win: 'Ctrl-C', mac: 'Command-M'}, // exec: function(editor) { // that.$message.success("復(fù)制成功"); // } // }); // this.aceEditor.commands.addCommand({ // name: '粘貼', // bindKey: {win: 'Ctrl-V', mac: 'Command-M'}, // exec: function(editor) { // that.$message.success("粘貼成功"); // } // }); }, watch: { value(newVal) { console.log(newVal); this.aceEditor.setValue(newVal); } } } </script> <style scoped lang="scss"> .toolbar { cursor: pointer;//鼠標(biāo)手型 } .show-panel { padding: 5px; border: 1px solid lightgray; .ace { position: relative !important; border-top: 1px solid lightgray; display: block; margin: auto; height: auto; width: 100%; } .panel-preview { padding: 1rem; margin: 0 0 0 0; width: auto; background-color: white; } } .editor-tab-content, .preview-tab-content, .header1-btn, .header2-btn, .header3-btn, .text-btn, .code-btn{ border-bottom-color: transparent; border-bottom-style: solid; border-radius: 0; padding: .85714286em 1.14285714em 1.29999714em 1.14285714em; border-bottom-width: 2px; transition: color .1s ease; cursor: pointer;//鼠標(biāo)手型 } .header1-btn, .header2-btn, .header3-btn, .code-btn, .text-btn { font-size: 5px; padding: .78571429em 1.14285714em!important; } .active { background-color: transparent; box-shadow: none; border-color: #1B1C1D; font-weight: 700; color: rgba(0,0,0,.95); } .header1-btn:hover, .header2-btn:hover, .header3-btn:hover, .text-btn:hover, .code-btn:hover { cursor: pointer;//鼠標(biāo)手型 background: rgba(0,0,0,.05)!important; color: rgba(0,0,0,.95)!important; } .helpHeader { font-size: 1.228571rem; line-height: 1.2857em; font-weight: 700; border-top-left-radius: .28571429rem; border-top-right-radius: .28571429rem; display: block; font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif; background: #FFF; box-shadow: none; color: rgba(0,0,0,.85); } </style>
這次的代碼同樣需要在引用時(shí)綁定value,也就是編輯框里的內(nèi)容
<MarkdownEditor v-bind:value="''"></MarkdownEditor>
哦,對(duì)了,忘記講一些東西了。關(guān)于代碼塊高亮以及l(fā)atex渲染的問(wèn)題。
高亮使用的是highlight.js,marked是支持這個(gè)庫(kù)的,直接使用就行,它能自動(dòng)識(shí)別語(yǔ)言,要是不想調(diào)用那個(gè)函數(shù),你也可以自行判斷用戶會(huì)使用到的語(yǔ)言。主題的使用,需要引用包下style對(duì)應(yīng)的css。還有一個(gè)最重要的就是渲染的標(biāo)簽必須要有class為hljs的屬性,不然你只能看到代碼是高亮的。至于class屬性怎么添加,如果你沒(méi)有l(wèi)etax需求,那么只需要在渲染的時(shí)候套一層標(biāo)簽,它的class屬性是這個(gè)即可。
剩下的就是latex了,因?yàn)閙arked本身是不支持latex的,但是它支持重寫(xiě)render函數(shù),通過(guò)這一方法來(lái)實(shí)現(xiàn)對(duì)latex的支持,在這里我使用的是katex,感興趣的小老板可以試試mathjax。不過(guò)有一個(gè)不太好的地方就是數(shù)學(xué)公式需要被代碼塊包住,即$a * b$
。不過(guò)這都不是大問(wèn)題,能好好渲染才是王道。
好了,本次的分享就到此為止吧,see you again~
到此這篇關(guān)于基于Ace的Markdown編輯器的文章就介紹到這了,更多相關(guān)Ace Markdown編輯器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
windows下vue-cli及webpack搭建安裝環(huán)境
這篇文章主要介紹了windows下vue-cli及webpack搭建安裝環(huán)境,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04vue語(yǔ)法之render函數(shù)和jsx的基本使用
這篇文章主要介紹了vue語(yǔ)法之render函數(shù)和jsx的基本使用,在Vue中是支持jsx的,凡是我們是比較少在Vue中使用jsx的,jsx在react中使用的更加廣泛,因此在這里我簡(jiǎn)單介紹一下jsx的基本使用,需要的朋友可以參考下2022-08-08vue2.0+elementui實(shí)現(xiàn)一個(gè)上門取件時(shí)間組件
這篇文章主要給大家介紹了關(guān)于vue2.0+elementui實(shí)現(xiàn)一個(gè)上門取件時(shí)間組件的相關(guān)資料,用于預(yù)約上門服務(wù)時(shí)間 看到網(wǎng)上有很多亂七八糟的代碼,看著頭疼,于是自己寫(xiě)了一個(gè)簡(jiǎn)單的,需要的朋友可以參考下2024-02-02在vue項(xiàng)目中使用axios發(fā)送post請(qǐng)求出現(xiàn)400錯(cuò)誤的解決
這篇文章主要介紹了在vue項(xiàng)目中使用axios發(fā)送post請(qǐng)求出現(xiàn)400錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09vue或css動(dòng)畫(huà)實(shí)現(xiàn)列表向上無(wú)縫滾動(dòng)
這篇文章主要為大家詳細(xì)介紹了vue或css動(dòng)畫(huà)實(shí)現(xiàn)列表向上無(wú)縫滾動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04簡(jiǎn)述vue路由打開(kāi)一個(gè)新的窗口的方法
這篇文章主要介紹了vue路由打開(kāi)一個(gè)新的窗口的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11基于Vue el-autocomplete 實(shí)現(xiàn)類似百度搜索框功能
本文通過(guò)代碼給大家介紹了Vue el-autocomplete 實(shí)現(xiàn)類似百度搜索框功能,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10vue?filters和directives訪問(wèn)this的問(wèn)題詳解
這篇文章主要介紹了vue?filters和directives訪問(wèn)this的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01vue3使用axios并封裝axios請(qǐng)求的詳細(xì)步驟
本篇文章分步驟給大家介紹了vue3使用axios并封裝axios請(qǐng)求的詳細(xì)步驟,結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友參考下吧2023-06-06vue點(diǎn)擊項(xiàng)目唯一id生成器nanoid的使用方式
這篇文章主要介紹了vue點(diǎn)擊項(xiàng)目唯一id生成器nanoid的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05