feature:新增富文本编辑器插件功能入口,提供导出图片功能,全局loading配置
This commit is contained in:
parent
f14805d0cd
commit
d48ea83378
|
@ -89,6 +89,7 @@
|
|||
"eslint": "5.15.3",
|
||||
"eslint-plugin-vue": "5.2.2",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"html2canvas": "^1.0.0-rc.5",
|
||||
"husky": "1.3.1",
|
||||
"lint-staged": "8.1.5",
|
||||
"mockjs": "1.0.1-beta3",
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<template>
|
||||
<div class="upload-container">
|
||||
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
|
||||
<el-button
|
||||
:style="{background:color,borderColor:color}"
|
||||
icon="el-icon-upload"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click=" dialogVisible=true"
|
||||
>
|
||||
upload
|
||||
</el-button>
|
||||
<el-dialog :visible.sync="dialogVisible">
|
||||
|
@ -15,14 +21,20 @@
|
|||
action="https://httpbin.org/post"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<el-button size="small" type="primary">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
>
|
||||
Click upload
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">
|
||||
Cancel
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
Confirm
|
||||
</el-button>
|
||||
</el-dialog>
|
||||
|
@ -49,12 +61,16 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
checkAllSuccess() {
|
||||
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
|
||||
return Object.keys(this.listObj).every(
|
||||
item => this.listObj[item].hasSuccess
|
||||
)
|
||||
},
|
||||
handleSubmit() {
|
||||
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
|
||||
if (!this.checkAllSuccess()) {
|
||||
this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
|
||||
this.$message(
|
||||
'Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!'
|
||||
)
|
||||
return
|
||||
}
|
||||
this.$emit('successCBK', arr)
|
||||
|
@ -92,7 +108,12 @@ export default {
|
|||
const img = new Image()
|
||||
img.src = _URL.createObjectURL(file)
|
||||
img.onload = function() {
|
||||
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
|
||||
_self.listObj[fileName] = {
|
||||
hasSuccess: false,
|
||||
uid: file.uid,
|
||||
width: this.width,
|
||||
height: this.height
|
||||
}
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
<template>
|
||||
<div class="layout-container">
|
||||
<div ref="list" class="layout-body">
|
||||
<tinymce
|
||||
v-model="content"
|
||||
:menu="menu"
|
||||
:height="height"
|
||||
:new-event-list="eventList"
|
||||
/>
|
||||
</div>
|
||||
<div class="util-component" style="display:none;">
|
||||
<el-upload
|
||||
:multiple="true"
|
||||
:file-list="dataList.list"
|
||||
:on-progress="uploadProgress"
|
||||
:on-success="handleSuccess"
|
||||
class="editor-slide-upload"
|
||||
action="https://httpbin.org/post"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<el-button id="editorUploader" size="small" type="primary">
|
||||
Click upload
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div ref="imageWrapper" class="show-html" v-html="content" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tinymce from './index'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { Loading } from 'element-ui'
|
||||
export default {
|
||||
name: 'TinymceDemo',
|
||||
components: { Tinymce },
|
||||
data() {
|
||||
return {
|
||||
showPreviewDialog: false,
|
||||
dataurl: '',
|
||||
eventList: [
|
||||
{
|
||||
toolbarName: 'uploadImg',
|
||||
tooltip: 'uploadImg',
|
||||
text: 'uploadImg',
|
||||
fn: (e, callback) => {
|
||||
this.handleAdd('uploadImg', data => {
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// {
|
||||
// toolbarName: 'uploadVideo',
|
||||
// tooltip: 'uploadVideo',
|
||||
// text: 'uploadVideo',
|
||||
// fn: (e, callback) => {
|
||||
// this.handleAdd('uploadVideo', data => {
|
||||
// if (callback && typeof callback === 'function') {
|
||||
// callback(data)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
{
|
||||
toolbarName: 'spreview',
|
||||
tooltip: 'spreview',
|
||||
text: 'spreview',
|
||||
fn: (e, callback) => {
|
||||
this.toImage()
|
||||
}
|
||||
}
|
||||
],
|
||||
isUpload: false,
|
||||
dataList: {
|
||||
list: [
|
||||
{
|
||||
url: ''
|
||||
}
|
||||
],
|
||||
type: 'image',
|
||||
setDom: v => `<a>${v.dom}</a>`
|
||||
},
|
||||
height: 700,
|
||||
menu: {
|
||||
file: { title: '文件', items: 'newdocument' },
|
||||
edit: {
|
||||
title: '编辑',
|
||||
items: 'undo redo | cut copy paste pastetext | selectall'
|
||||
},
|
||||
insert: { title: '插入', items: 'link media | image hr' },
|
||||
view: { title: '查看', items: 'visualaid' },
|
||||
format: {
|
||||
title: '格式',
|
||||
items:
|
||||
'bold italic underline strikethrough superscript subscript | formats | removeformat'
|
||||
},
|
||||
table: {
|
||||
title: '表格',
|
||||
items: 'inserttable tableprops deletetable | cell row column'
|
||||
},
|
||||
tools: { title: '工具', items: 'spellchecker code' }
|
||||
},
|
||||
loadingInstance: null,
|
||||
content: `<h1 style="text-align: center;">测试demo!</h1>
|
||||
<p>嗨</p>
|
||||
<img class="img" src="https://ali-image-test.dabanjia.com/image/20200508/1588911589606_5140%24ban.jpg" width="200"/>
|
||||
<p>枕着幸福在梦中微笑,清零生活的压力烦恼,阳光透过窗与你拥抱,伸伸懒腰迎接周末来到,问候源于无法按捺的心跳,愿你周末乐逍遥,生活更美妙!
|
||||
周末来到,让烦恼跑光,让忧愁找不到,跟寂寞说再见,跟压力说拜拜,跟快乐说你好,牵着幸福的手,携上平安,开开心心的过周末!
|
||||
每个人,都有一个世界;每首歌,都有一个故事;每一周,都有一个周末;每个人,都要一个愿望;我的愿望很现实:周末清晨,搅黄你的美梦!周末快乐!
|
||||
</p>
|
||||
`
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fileType() {
|
||||
// 自定义配置上传组件支持的文件,需要对上传组件进行封装,制定文件规格
|
||||
if (this.dataList.type === 'image') {
|
||||
return 'png|jpe?g|gif|svg'
|
||||
}
|
||||
if (this.dataList.type === 'video') {
|
||||
// 暂时只支持mp4
|
||||
return 'mp4'
|
||||
// return "mp4|webm|ogg|mp3|wav|flac|aac";
|
||||
}
|
||||
return ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 实时获取高度,并减去padding值
|
||||
this.height = this.$refs.list.clientHeight - 32
|
||||
},
|
||||
methods: {
|
||||
toImage() {
|
||||
var width = 330 // 获取dom 宽度
|
||||
var height = this.$refs.imageWrapper.scrollHeight // 获取dom 高度
|
||||
var canvas = document.createElement('canvas') // 创建一个canvas节点
|
||||
var scale = 2 // 定义任意放大倍数 支持小数默认设置为2,确保图片清晰度
|
||||
canvas.width = width * scale * scale // 定义canvas 宽度 * 缩放
|
||||
canvas.height = height * scale * scale // 定义canvas高度 *缩放
|
||||
canvas.getContext('2d').scale(scale, scale) // 获取context,设置scale
|
||||
var opts = {
|
||||
tainttest: true, // 检测每张图片都已经加载完成
|
||||
scale: scale, // 添加的scale 参数
|
||||
useCORS: true,
|
||||
canvas: canvas, // 自定义 canvas
|
||||
logging: true, // 日志开关
|
||||
width: width, // dom 原始宽度
|
||||
height: height // dom 原始高度
|
||||
}
|
||||
html2canvas(this.$refs.imageWrapper, opts).then(canvas => {
|
||||
const dataurl = canvas.toDataURL('image/png')
|
||||
this.dataurl = dataurl
|
||||
if (this.dataurl !== '') {
|
||||
console.log(this.dataurl, '生成base64')
|
||||
}
|
||||
})
|
||||
},
|
||||
error(e) {
|
||||
console.log(e)
|
||||
},
|
||||
handleAdd(key, callback) {
|
||||
this.dataList.list[0].url = ''
|
||||
this.isUpload = false
|
||||
switch (key) {
|
||||
case 'uploadImg':
|
||||
this.dataList.type = 'image'
|
||||
document.getElementById('editorUploader').click()
|
||||
|
||||
break
|
||||
case 'uploadVideo':
|
||||
this.dataList.type = 'video'
|
||||
document.getElementById('editorUploader').click()
|
||||
|
||||
break
|
||||
// 批量操作,可自定义所有操作
|
||||
case 'selectImg':
|
||||
break
|
||||
case 'selectVideo':
|
||||
break
|
||||
}
|
||||
const unWatch = this.$watch('isUpload', val => {
|
||||
if (val && callback && typeof callback === 'function') {
|
||||
callback(this.dataList)
|
||||
// 取消监听
|
||||
unWatch()
|
||||
}
|
||||
})
|
||||
},
|
||||
uploadProgress() {
|
||||
this.loadingInstance = Loading.service({
|
||||
lock: true,
|
||||
text: '数据加载中,请稍后...',
|
||||
background: 'rgba(0, 0, 0, 0.1)'
|
||||
})
|
||||
},
|
||||
// 定义模板
|
||||
handleSuccess(data, file) {
|
||||
this.loadingInstance.close()
|
||||
this.dataList.list[0].url = file.url
|
||||
const { type } = this.dataList
|
||||
if (type === 'image') {
|
||||
this.dataList.setDom = v =>
|
||||
`<img class="img-scale" src="${v.url}" width="200">`
|
||||
}
|
||||
if (type === 'video') {
|
||||
// 自定义配置模板信息,宽高后期都可变成输入
|
||||
this.dataList.setDom = v =>
|
||||
`<p >
|
||||
<video controls="controls" width="200" height="350" >
|
||||
<source src="https://dev-saas.oss-cn-beijing.aliyuncs.com/056eb234fa9844b0b7ec3e13bd39faf7.mp4" type="video/mp4" />
|
||||
<source src="${v.url}" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
</p>
|
||||
`
|
||||
}
|
||||
this.isUpload = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.show-html {
|
||||
position: fixed;
|
||||
right: 40px;
|
||||
top: 80px;
|
||||
width: 330px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border: 1px solid goldenrod;
|
||||
}
|
||||
</style>
|
|
@ -1,8 +1,16 @@
|
|||
<template>
|
||||
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
|
||||
<div
|
||||
:class="{ fullscreen: fullscreen }"
|
||||
class="tinymce-container"
|
||||
:style="{ width: containerWidth }"
|
||||
>
|
||||
<textarea :id="tinymceId" class="tinymce-textarea" />
|
||||
<div class="editor-custom-btn-container">
|
||||
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
|
||||
<editorImage
|
||||
color="#1890ff"
|
||||
class="editor-upload-btn"
|
||||
@successCBK="imageSuccessCBK"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -18,7 +26,8 @@ import toolbar from './toolbar'
|
|||
import load from './dynamicLoadScript'
|
||||
|
||||
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
|
||||
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
|
||||
const tinymceCDN =
|
||||
'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
|
||||
|
||||
export default {
|
||||
name: 'Tinymce',
|
||||
|
@ -27,7 +36,11 @@ export default {
|
|||
id: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
return (
|
||||
'vue-tinymce-' +
|
||||
+new Date() +
|
||||
((Math.random() * 1000).toFixed(0) + '')
|
||||
)
|
||||
}
|
||||
},
|
||||
value: {
|
||||
|
@ -45,6 +58,13 @@ export default {
|
|||
type: String,
|
||||
default: 'file edit insert view format table'
|
||||
},
|
||||
newEventList: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
required: false,
|
||||
|
@ -63,17 +83,18 @@ export default {
|
|||
tinymceId: this.id,
|
||||
fullscreen: false,
|
||||
languageTypeList: {
|
||||
'en': 'en',
|
||||
'zh': 'zh_CN',
|
||||
'es': 'es_MX',
|
||||
'ja': 'ja'
|
||||
en: 'en',
|
||||
zh: 'zh_CN',
|
||||
es: 'es_MX',
|
||||
ja: 'ja'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerWidth() {
|
||||
const width = this.width
|
||||
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
|
||||
if (/^[\d]+(\.[\d]+)?$/.test(width)) {
|
||||
// matches `100`, `'100'`
|
||||
return `${width}px`
|
||||
}
|
||||
return width
|
||||
|
@ -83,7 +104,8 @@ export default {
|
|||
value(val) {
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
this.$nextTick(() =>
|
||||
window.tinymce.get(this.tinymceId).setContent(val || ''))
|
||||
window.tinymce.get(this.tinymceId).setContent(val || '')
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -104,7 +126,7 @@ export default {
|
|||
methods: {
|
||||
init() {
|
||||
// dynamic load tinymce from cdn
|
||||
load(tinymceCDN, (err) => {
|
||||
load(tinymceCDN, err => {
|
||||
if (err) {
|
||||
this.$message.error(err.message)
|
||||
return
|
||||
|
@ -114,13 +136,39 @@ export default {
|
|||
},
|
||||
initTinymce() {
|
||||
const _this = this
|
||||
// 自定义事件列表
|
||||
let newToolbar = []
|
||||
let barStr = ''
|
||||
const eventData = []
|
||||
this.newEventList.map(itemData => {
|
||||
const { toolbarName, tooltip, text, fn } = itemData
|
||||
eventData.push({
|
||||
data: {
|
||||
tooltip,
|
||||
text,
|
||||
onclick: e => {
|
||||
fn(e, data => {
|
||||
// 当事件成功后触发回调函数
|
||||
_this.dataSuccessCBK(data)
|
||||
})
|
||||
}
|
||||
},
|
||||
name: toolbarName
|
||||
})
|
||||
barStr += toolbarName + ' '
|
||||
})
|
||||
if (this.toolbar.length > 0) {
|
||||
newToolbar = this.toolbar.concat(barStr)
|
||||
} else {
|
||||
newToolbar = toolbar.concat(barStr)
|
||||
}
|
||||
window.tinymce.init({
|
||||
selector: `#${this.tinymceId}`,
|
||||
language: this.languageTypeList['en'],
|
||||
height: this.height,
|
||||
body_class: 'panel-body ',
|
||||
object_resizing: false,
|
||||
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
|
||||
toolbar: newToolbar,
|
||||
menubar: this.menubar,
|
||||
plugins: plugins,
|
||||
end_container_on_empty_block: true,
|
||||
|
@ -144,8 +192,9 @@ export default {
|
|||
})
|
||||
},
|
||||
setup(editor) {
|
||||
editor.on('FullscreenStateChanged', (e) => {
|
||||
_this.fullscreen = e.state
|
||||
eventData.map(item => {
|
||||
console.log('-----', item)
|
||||
editor.addButton(item.name, item.data)
|
||||
})
|
||||
},
|
||||
// it will try to keep these URLs intact
|
||||
|
@ -206,8 +255,27 @@ export default {
|
|||
imageSuccessCBK(arr) {
|
||||
const _this = this
|
||||
arr.forEach(v => {
|
||||
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
|
||||
window.tinymce
|
||||
.get(_this.tinymceId)
|
||||
.insertContent(`<img class="wscnph" src="${v.url}" >`)
|
||||
})
|
||||
},
|
||||
dataSuccessCBK(dataList) {
|
||||
const { type, list, setDom } = dataList
|
||||
let newDom = ''
|
||||
console.log(dataList)
|
||||
const self = this
|
||||
if (type === 'image') {
|
||||
list.forEach(v => {
|
||||
newDom = setDom(v)
|
||||
window.tinymce.get(self.tinymceId).insertContent(newDom)
|
||||
})
|
||||
} else if (type === 'video') {
|
||||
list.forEach(v => {
|
||||
newDom = setDom(v)
|
||||
window.tinymce.get(self.tinymceId).insertContent(newDom)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import axios from 'axios'
|
||||
import { MessageBox, Message } from 'element-ui'
|
||||
import {
|
||||
Message,
|
||||
MessageBox,
|
||||
Loading
|
||||
} from 'element-ui'
|
||||
import store from '@/store'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
var loadingInstance = null
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
|
||||
|
@ -21,6 +25,12 @@ service.interceptors.request.use(
|
|||
// please modify it according to the actual situation
|
||||
config.headers['X-Token'] = getToken()
|
||||
}
|
||||
console.log('------')
|
||||
loadingInstance = Loading.service({
|
||||
lock: true,
|
||||
text: '数据加载中,请稍后...',
|
||||
background: 'rgba(0, 0, 0, 0.1)'
|
||||
})
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
|
@ -43,6 +53,7 @@ service.interceptors.response.use(
|
|||
* You can also judge the status by HTTP Status Code
|
||||
*/
|
||||
response => {
|
||||
loadingInstance.close()
|
||||
const res = response.data
|
||||
|
||||
// if the custom code is not 20000, it is judged as an error.
|
||||
|
@ -72,6 +83,7 @@ service.interceptors.response.use(
|
|||
}
|
||||
},
|
||||
error => {
|
||||
loadingInstance.close()
|
||||
console.log('err' + error) // for debug
|
||||
Message({
|
||||
message: error.message,
|
||||
|
|
|
@ -2,34 +2,44 @@
|
|||
<div class="components-container">
|
||||
<aside>
|
||||
Rich text is a core feature of the management backend, but at the same time it is a place with lots of pits. In the process of selecting rich texts, I also took a lot of detours. The common rich texts on the market have been basically used, and I finally chose Tinymce. See the more detailed rich text comparison and introduction.
|
||||
<a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/component/rich-editor.html">Documentation</a>
|
||||
<a
|
||||
target="_blank"
|
||||
class="link-type"
|
||||
href="https://panjiachen.github.io/vue-element-admin-site/component/rich-editor.html"
|
||||
>Documentation</a>
|
||||
</aside>
|
||||
<div>
|
||||
<tinymce v-model="content" :height="300" />
|
||||
<tinymce
|
||||
v-model="content"
|
||||
:height="300"
|
||||
/>
|
||||
</div>
|
||||
<div class="editor-content" v-html="content" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tinymce from '@/components/Tinymce'
|
||||
import Tinymce from '@/components/Tinymce/example'
|
||||
|
||||
export default {
|
||||
name: 'TinymceDemo',
|
||||
components: { Tinymce },
|
||||
data() {
|
||||
return {
|
||||
content:
|
||||
`<h1 style="text-align: center;">Welcome to the TinyMCE demo!</h1><p style="text-align: center; font-size: 15px;"><img title="TinyMCE Logo" src="//www.tinymce.com/images/glyph-tinymce@2x.png" alt="TinyMCE Logo" width="110" height="97" /><ul>
|
||||
<li>Our <a href="//www.tinymce.com/docs/">documentation</a> is a great resource for learning how to configure TinyMCE.</li><li>Have a specific question? Visit the <a href="https://community.tinymce.com/forum/">Community Forum</a>.</li><li>We also offer enterprise grade support as part of <a href="https://tinymce.com/pricing">TinyMCE premium subscriptions</a>.</li>
|
||||
</ul>`
|
||||
content: `<h1 style="text-align: center;">测试demo!</h1>
|
||||
<p>嗨</p>
|
||||
<img class="img" src="https://ali-image-test.dabanjia.com/image/20200508/1588911589606_5140%24ban.jpg" width="200"/>
|
||||
<p>枕着幸福在梦中微笑,清零生活的压力烦恼,阳光透过窗与你拥抱,伸伸懒腰迎接周末来到,问候源于无法按捺的心跳,愿你周末乐逍遥,生活更美妙!
|
||||
周末来到,让烦恼跑光,让忧愁找不到,跟寂寞说再见,跟压力说拜拜,跟快乐说你好,牵着幸福的手,携上平安,开开心心的过周末!
|
||||
每个人,都有一个世界;每首歌,都有一个故事;每一周,都有一个周末;每个人,都要一个愿望;我的愿望很现实:周末清晨,搅黄你的美梦!周末快乐!
|
||||
</p>
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor-content{
|
||||
.editor-content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue