Official data

  • WangEditor website
  • WangEditor document
  • Lot of wangEditor

Add attached demo

  • Add the attached demo address
  • Add the attachment demo effect

The simplest use

Packaging components

<! -- Editor.vue --> <template lang="pug"> div div(ref="editor") </template> <script> import E from "wangeditor"; Export default {name: "editor", data() {return {// rich text content editorContent: ""}; }, mounted() {var editor = new E(this.$refs.editor); / / / / preconfigured content when the content changes, flung the content editor. CustomConfig. Onchange = HTML = > {enclosing editorContent = HTML; this.$emit("update:content", html); }; // Create the editor editor.create(); }}; </script>Copy the code

The calling component

<! -- Edit.vue --> <template lang="pug"> div editor(:content.sync='content') div {{content}} </template> <script type="text/ecmascript-6"> import Editor from '@/components/Editor' export default { name: 'edit', components:{Editor}, data(){ return { content:'' } } } </script> <style scoped></style>Copy the code

Setting the initial state has content

In general, the creation and editing of the page are always the same, so when editing, the content area will start with data, here to change the component writing, add Settings content, and slightly improve the original writing method.

<! -- Editor.vue --> <template lang="pug"> div div(ref="editor") </template> <script> import E from "wangeditor"; Export default {name: "editor", props: {content: {type: String, default() {return ""; }}, data() {return {// rich text content editorContent: ""}; }, mounted() {// editorContent = this.content; // editorContent = this.content; // Create an editor this.createEditor(); // Set the content this._setinitContent (this.editorContent); }, methods: {// configure parameters to createEditor createEditor() {// initialize container let editor = new E(this.$refs.editor); This. Editor = editor; // The change event assigns the value to editorContent this._syncContent(); // The change event assigns the value to editorContent this._syncContent(); // Create the editor editor.create(); }, _syncContent() {// set before create to throw the content when it changes, Synchronization parent component content this. Editor. CustomConfig. Onchange = HTML = > {enclosing editorContent = HTML; this.$emit("update:content", html); }; }, _setInitContent(content) { this.editor.txt.html(content); }}}; </script>Copy the code

Adding local image uploads

By default, the uploaded images are only connected to the network. If you want to upload local images, you need to add additional parameters. This editor implements the function of dragging and dropping pictures, super convenient!!

See here for detailed documentation on uploading images. For the project, I used custom uploads:

editor.customConfig.customUploadImg = async (files, insert) => {
  // Files is a list of selected files from input, traversing the upload
  for (let i = 0; i < files.length; i++) {
    let file = files[i]
    // Upload server to get the image link
    let imgUrl = (await this._uploadSingleFile(file))
    // After getting the image URL, insert it into the editor
    insert(imgUrl)
  }
}
Copy the code
<! -- Editor.vue --> <template lang="pug"> div div(ref="editor") </template> <script> import E from "wangeditor"; Export default {name: "editor", props: {content: {type: String, default() {return ""; }}, data() {return {// rich text content editorContent: ""}; }, mounted() {// editorContent = this.content; // editorContent = this.content; // Create an editor this.createEditor(); // Set the content this._setinitContent (this.editorContent); }, methods: {// configure parameters to createEditor createEditor() {// initialize container let editor = new E(this.$refs.editor); This. Editor = editor; // The change event assigns the value to editorContent this._syncContent(); // The change event assigns the value to editorContent this._syncContent(); This._setuploadlocalimg (); // Create the editor editor.create(); }, _syncContent() {// set before create to throw the content when it changes, Synchronization parent component content this. Editor. CustomConfig. Onchange = HTML = > {enclosing editorContent = HTML; this.$emit("update:content", html); }; }, _setInitContent(content) { this.editor.txt.html(content); }, _setUploadLocalImg() { this.editor.customConfig.customUploadImg = async (files, Insert) => {// for (let I = 0; i < files.length; i++) { let file = files[i]; // Let imgUrl = await this._uploadSingleFile(file); // Let uploadurl = await this. // Insert (imgUrl) into the editor; }}; Async _uploadSingleFile(file) {let options = {url: "/xx/yy", method: "POST", data: {sign: "XXX ", file}, // Convert formData form transformRequest: [ function(data) { let formData = new FormData(); for (const key in data) { const value = data[key]; formData.append(key, value); } return formData; } ] }; let url = (await "axios"(options)).data.url; return url; }}}; </script>Copy the code

Add attachments that are not pictures to upload

It’s a little bit more complicated.

Now, the edit area and the edit bar can be separated, and they can write their own styles, so it’s very flexible.

So when you take it apart, you can do thisthis.editor = new E(this.$refs.editorBar, this.$refs.editorText)

The reason for this is because I want to put the attached icon in the menu bar as well, so I need to use a bit of positioning here.

And because upload attachments, so need a display of attachment area, called the attachment area

The processing logic here is that clicking the attachment icon triggers clickinginput(type='file')Get files, upload them, and display them in the file area

Attention here!!

  1. The edit area is usually set to the maximum height, beyond which the inner scroll bar is set, so wrap the edit area with a container, and set the height of the inner and outer containers
  2. The file size changes units dynamically
  3. Different file types are displayed with different suffixes
  4. After the file is uploaded, it can be downloaded or previewed. Of course, the parent component can also be passed into files
  5. In the case of editing, if the request comes back with a delay, remember to add v-if and render the editor when the content comes back, because the editor can only initialize the content once
<! -- editor. vue --> <template lang="pug"> div //- edit bar div.editorbar (ref='editorBar') div.file-icon //- point img triggers the actual input img(@click='$refs.inputFile.click()' src='https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/file_icon.png' Alt = ") //- The actual input,click event is to solve the problem of the same file being uploaded twice, because the file can be deleted, Input (ref='inputFile' hidden type='file' multiple Accept ='.docx,.pptx,.xlsx,.pdf') @click='$event.target.value = null' @change='uploadFile') //- Edit area The edit area is usually set to a maximum height. Div. Editor-text-wrap div. Editor-text (ref='editorText') //- attachment area ul.file-list(v-if=' EditorFiles.length ') li.file-item(v-for='(item,index) in editorFiles' :key='index' is='file-item' :file='item' @delFile='delFile(index)') </template> <script> import E from "wangeditor"; import FileItem from "./FileItem"; Export default {name: "editor", components: {FileItem}, props: {content: {type: String, default() { return ""; }}, // files: {type: Array, default() {return []; }}, data() {return {// Rich text content editorContent: "", // Attachment editorFiles: []}; {editorContent(newValue) {this.$emit("update:content", newValue); }, editorFiles(newValue) { this.$emit("update:files", newValue); } // editorContent = this.content; // editorContent = this.content; // editorContent = this.content; Name: file.name, size: file.size, isnext. size: false, url: "The next day there are no matches. files.length && files.forEach(item => (item.isUploaded = true)); this.editorFiles = [...files]; // Create an editor this.createEditor(); // Set the content this._setinitContent (this.editorContent); }, methods: { async uploadFile(e) { let files = e.target.files; for (let i = 0; i < files.length; i++) { await this._handleSingleFile(files[i]); }}, async _handleSingleFile(file) {// At the end of the event the name is the same as the download size. file.name, size: file.size, isUploaded: false, url: "" }; this.editorFiles.push(this.curFile); // Let res = await this._uploadSingleFile(file) this.curfile. isfile = true at the end. This.curfile. url = "Address returned by the server "; }, // delete the file when delFile(index) {this.editorFiles.splice(index, 1); }, // configure the parameter to create an editor createEditor() {// initialize the container let Editor = new E(this.$refs.editorBar, this.$refs.editorText); This. Editor = editor; // The change event assigns the value to editorContent this._syncContent(); // The change event assigns the value to editorContent this._syncContent(); This._setuploadlocalimg (); // Create the editor editor.create(); }, _syncContent() {// set before create, when the content changes, Synchronous editorContent enclosing editor. CustomConfig. Onchange = HTML = > {enclosing editorContent = HTML; }; }, _setInitContent(content) { this.editor.txt.html(content); }, _setUploadLocalImg() { this.editor.customConfig.customUploadImg = async (files, Insert) => {// for (let I = 0; i < files.length; i++) { let file = files[i]; // Let imgUrl = await this._uploadSingleFile(file); // Let uploadurl = await this. // Insert (imgUrl) into the editor; }}; Async _uploadSingleFile(file) {let options = {url: "/xx/yy", method: "POST", data: {sign: "XXX ", file}, // Upload the file requires the parameter to be converted to formData form transformRequest: [ function(data) { let formData = new FormData(); for (const key in data) { const value = data[key]; formData.append(key, value); } return formData; } ] }; let url = (await "axios"(options)).data.url; return url; }}}; < span style = "box-sizing: border-box! Important; word-wrap: break-word! Important;" border: 1px solid #eee; } /* attachment icon */. File-icon {position: absolute; top: 12px; width: 36px; height: 36px; z-index: 3; cursor: pointer; left: 1480px; } / /.file-icon img {width: 100%; height: 100%; display: block; } / /.editor-text-wrap {height: 600px; margin-top: -1px; } .editor-text { border: 1px solid #eee; height: 100%; } /* file */. File-list {padding: 20px 0 0 20px; display: flex; border: 1px solid #eee; flex-wrap: wrap; } .file-item { margin: 0 20px 20px 0; } </style>Copy the code
<! -- fileitem.vue --> <template lang="pug"> div.file-item(@click='clickFile') // -- close button img.icon-delete(Alt ='') src='https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/icon_close.png' @click.stop='clickDelete') div.file-item-left img.icon-file(alt='' :src="'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/icon_'+suffix+'.png'") div.file-item-right h3.file-name {{nameShow}} div.file-other span.file-size {{sizeShow}} span.file-upload-status {{file.isUploaded at the end? '}} </template> <script> export default { name: "file-item", props: { file: { default() { return {}; }}}, computed: {suffix() {// this.name for example, 1.2.pptx // [1,2, PPTX] let arr = this.file.name.split("."); // pptx let suffix = arr.slice(-1)[0]; return suffix; }, // name = xx... Xx nameShow() {// 1.2 let name = this.file.name.slice(0, this.file.name.indexof (this.suffix) -1); console.log(name); let pre = name.length < 10 ? name : `${name.slice(0, 8)}... ${name.slice(-2)}`; return `${pre}.${this.suffix}`; }, sizeShow() { let nBytes = this.file.size; let sOutput = nBytes + " bytes"; // optional code for multiples approximation const aMultiples = ["k", "m", "g", "t", "p", "e", "z", "y"]; for ( let nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++ ) { sOutput = nApprox.toFixed(0) + aMultiples[nMultiple]; } return sOutput; } }, data() { return { x: "close" }; }, methods: { clickDelete() { this.$emit("delFile"); }, clickFile() { window.open(this.file.url); }}}; </script> <style scoped> h1, h2, h3 { padding: 0; margin: 0; } /* prettier-ignore */ .file-item { background-color: #f0f1f1; padding: 13PX 10PX; display: flex; position: relative; width: 260PX; } /* prettier-ignore */ .icon-file { width: 26PX; height: 34PX; } /* prettier-ignore */ .file-item-right { margin-left: 10PX; } /* prettier-ignore */ .file-name { font-size: 14PX; color: #222; } /* prettier-ignore */ .file-other { margin-top: 1PX; color: #999; font-size: 12PX; } /* prettier-ignore */ .file-upload-status { margin-left: 5PX; } /* prettier-ignore */ .icon-delete { position: absolute; width: 24PX; height: 24PX; right: 0; top: 0; z-index: 2; } /* PXtorem-disable-next-line */ </style>Copy the code

Secondary data

  • List all the editors, very good, thanks for the comments

  • 5 ways to download files on the front end

  • Summary of the whole process from select to upload the thumbnail preview to upload the file

  • There are two ways to write Accept, one to filter the suffix, one to filter the MIME type, and multiple conditions separated by commas. = ‘image / *, such as accept. Docx’

    • .docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
    • .pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
    • .xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    • .pdf application/pdf
    • GIF image / *