分享一個(gè)基于Ace的Markdown編輯器
我認(rèn)為的編輯器分成兩類,一種是分為左右兩邊實(shí)現(xiàn)即時(shí)渲染;一種是先寫語(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的,但是它支持重寫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-04
vue語(yǔ)法之render函數(shù)和jsx的基本使用
這篇文章主要介紹了vue語(yǔ)法之render函數(shù)和jsx的基本使用,在Vue中是支持jsx的,凡是我們是比較少在Vue中使用jsx的,jsx在react中使用的更加廣泛,因此在這里我簡(jiǎn)單介紹一下jsx的基本使用,需要的朋友可以參考下2022-08-08
vue2.0+elementui實(shí)現(xiàn)一個(gè)上門取件時(shí)間組件
這篇文章主要給大家介紹了關(guān)于vue2.0+elementui實(shí)現(xiàn)一個(gè)上門取件時(shí)間組件的相關(guān)資料,用于預(yù)約上門服務(wù)時(shí)間 看到網(wǎng)上有很多亂七八糟的代碼,看著頭疼,于是自己寫了一個(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-09
vue或css動(dòng)畫實(shí)現(xiàn)列表向上無(wú)縫滾動(dòng)
這篇文章主要為大家詳細(xì)介紹了vue或css動(dòng)畫實(shí)現(xiàn)列表向上無(wú)縫滾動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
基于Vue el-autocomplete 實(shí)現(xiàn)類似百度搜索框功能
本文通過(guò)代碼給大家介紹了Vue el-autocomplete 實(shí)現(xiàn)類似百度搜索框功能,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
vue?filters和directives訪問(wèn)this的問(wèn)題詳解
這篇文章主要介紹了vue?filters和directives訪問(wèn)this的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
vue3使用axios并封裝axios請(qǐng)求的詳細(xì)步驟
本篇文章分步驟給大家介紹了vue3使用axios并封裝axios請(qǐng)求的詳細(xì)步驟,結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友參考下吧2023-06-06
vue點(diǎn)擊項(xiàng)目唯一id生成器nanoid的使用方式
這篇文章主要介紹了vue點(diǎn)擊項(xiàng)目唯一id生成器nanoid的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05

