编辑器组件封装

编辑器组件封装
This commit is contained in:
ronger 2024-02-26 14:58:37 +08:00 committed by GitHub
commit 6f9a4f5a46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 205 additions and 158 deletions

View File

@ -0,0 +1,169 @@
<template>
<div>
<div id="contentEditor"></div>
</div>
</template>
<script>
import Vue from "vue";
import apiConfig from "@/config/api.config";
export default {
name: "contentEditor",
data() {
return {
contentEditor: null,
tokenURL: {}
}
},
props: {
initValue: {
type: String,
required: true
},
cacheId: {
type: String,
required: true
},
mode: {
type: String,
required: true
}
},
methods: {
_initEditor(data) {
//
let _ts = this;
let toolbar = [
'emoji',
'headings',
'bold',
'italic',
'strike',
'link',
'|',
'list',
'ordered-list',
'check',
'outdent',
'indent',
'|',
'quote',
'line',
'code',
'inline-code',
'insert-before',
'insert-after',
'|',
'upload',
// 'record',
'table',
'|',
'undo',
'redo',
'|',
'edit-mode',
{
name: 'more',
toolbar: [
'fullscreen',
'both',
'preview',
'info'
],
}]
return new Vue.Vditor(data.id, {
toolbar,
mode: _ts.mode,
tab: '\t',
cdn: apiConfig.VDITOR,
cache: {
enable: !_ts.cacheId,
id: _ts.cacheId,
},
after() {
_ts.contentEditor.setValue(data.value ? data.value : '');
},
typewriterMode: true,
hint: {
emoji: Vue.emoji
},
preview: {
hljs: {
enable: true,
lineNumber: true,
style: 'github'
},
markdown: {
toc: true,
autoSpace: true
},
math: {
inlineDigit: true
},
delay: 500,
mode: data.mode,
/*url: `${process.env.Server}/api/console/markdown`,*/
parse: (element) => {
if (element.style.display === 'none') {
return false
}
// LazyLoadImage();
// Vue.Vditor.highlightRender({style: 'github'}, element, this.contentEditor);
},
theme: {
cdn: apiConfig.VDITOR_CSS
}
},
upload: {
max: 10 * 1024 * 1024,
url: _ts.tokenURL.URL,
linkToImgUrl: _ts.tokenURL.linkToImageURL,
token: _ts.tokenURL.token,
filename: name => name.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5\.)]/g, '').
replace(/[\?\\/:|<>\*\[\]\(\)\$%\{\}@~]/g, '').
replace('/\\s/g', '')
},
height: data.height,
counter: 102400,
resize: {
enable: data.resize,
},
lang: this.$store.state.locale,
placeholder: data.placeholder,
})
},
contentValue() {
return this.contentEditor.getValue();
},
async contentHtml() {
return await this.contentEditor.getHTML()
}
},
async mounted() {
let _ts = this;
const responseData = await _ts.$axios.$get('/api/upload/token');
if (responseData) {
_ts.$store.commit('setUploadHeaders', responseData.uploadToken);
_ts.$set(_ts, 'tokenURL', {
token: responseData.uploadToken || '',
URL: responseData.uploadURL || '',
linkToImageURL: responseData.linkToImageURL || ''
})
}
_ts.contentEditor = _ts._initEditor({
id: 'contentEditor',
mode: 'both',
height: 480,
placeholder: '', //this.$t('inputContent', this.$store.state.locale)
resize: false,
value: _ts.initValue
});
}
}
</script>
<style lang="less">
@import "~vditor/src/assets/less/index.less";
</style>

View File

@ -19,7 +19,9 @@
</el-form-item>
<br>
<el-form-item label="作品集介绍">
<div id="contentEditor"></div>
<content-editor mode="sv" :cacheId="'portfolio-' + (portfolio.idPortfolio || '')"
:initValue="portfolio.portfolioDescription||''" v-if="isLoading"
ref="contentEditor"></content-editor>
</el-form-item>
<el-form-item class="text-right">
<el-button :loading="loading" @click="deletePortfolio" v-if="isEdit">删除</el-button>
@ -35,11 +37,11 @@
<div style="display: block;">
<div class="cropperBox">
<vue-cropper
:autoCrop="autoCrop"
:img="headImgUrl"
ref="cropper"
:fixed="true"
@realTime="realTime"
:autoCrop="autoCrop"
:img="headImgUrl"
ref="cropper"
:fixed="true"
@realTime="realTime"
/>
</div>
<div class="cropperBox">
@ -57,11 +59,11 @@
<div class="button_box">
<el-upload
:before-upload="beforeAvatarUpload"
:multiple="true"
:show-file-list="false"
action=""
class="avatar-uploader">
:before-upload="beforeAvatarUpload"
:multiple="true"
:show-file-list="false"
action=""
class="avatar-uploader">
<div>
<el-button plain round type="primary">上传</el-button>
</div>
@ -78,20 +80,19 @@
</div>
<el-col class="text-center" v-else>
<el-alert
:closable="false"
center
show-icon
title="用户无权限"
type="warning">
:closable="false"
center
show-icon
title="用户无权限"
type="warning">
</el-alert>
</el-col>
</div>
</template>
<script>
import Vue from 'vue';
import {mapState} from 'vuex';
import apiConfig from '~/config/api.config';
import ContentEditor from "@/components/ContentEditor.vue";
export default {
name: "PortfolioPost",
@ -104,10 +105,12 @@ export default {
asyncData({store, params, error}) {
return Promise.all([
store.dispatch('portfolio/fetchPostDetail', params)
.catch(err => error({statusCode: 404}))
.catch(err => error({statusCode: 404}))
])
},
components: {
ContentEditor
},
computed: {
...mapState({
portfolioDetail: state => state.portfolio.detail.data,
@ -134,19 +137,10 @@ export default {
},
data() {
return {
contentEditor: null,
portfolio: {
idPortfolio: 0,
portfolioDescription: ''
},
// rules: {
// portfolioTitle: [
// {required: true, message: '', trigger: 'blur'}
// ],
// portfolioDescription: [
// {required: true, message: '', trigger: 'blur'}
// ]
// },
loading: false,
tokenURL: {
URL: '',
@ -158,104 +152,17 @@ export default {
isEdit: false,
autoCrop: true,
notificationFlag: true,
contentHtml: {}
contentValue: {
portfolioDescription: '',
portfolioDescriptionHtml: ''
},
isLoading: false
}
},
methods: {
realTime(data) {
this.cropImg = data;
},
_initEditor(data) {
//
let _ts = this;
let toolbar = [
'emoji',
'headings',
'bold',
'italic',
'strike',
'link',
'|',
'list',
'ordered-list',
'check',
'outdent',
'indent',
'|',
'quote',
'line',
'code',
'inline-code',
'insert-before',
'insert-after',
'|',
// 'upload',
// 'record',
'table',
'|',
'undo',
'redo',
'|',
'edit-mode',
{
name: 'more',
toolbar: [
'fullscreen',
'both',
'preview',
'info'
],
}]
return new Vue.Vditor(data.id, {
toolbar,
mode: 'sv',
tab: '\t',
cdn: apiConfig.VDITOR,
cache: {
enable: this.$route.params.article_id ? false : true,
id: this.$route.params.article_id ? this.$route.params.article_id : '',
},
after() {
_ts.contentEditor.setValue(data.value ? data.value : '');
},
input: (val) => {
this.portfolio.portfolioDescription = val
},
typewriterMode: true,
hint: {
emoji: Vue.emoji
},
preview: {
markdown: {
toc: true,
},
math: {
inlineDigit: true
},
delay: 500,
mode: data.mode,
/*url: `${process.env.Server}/api/console/markdown`,*/
parse: (element) => {
if (element.style.display === 'none') {
return false
}
// LazyLoadImage();
// Vue.Vditor.highlightRender({style: 'github'}, element, this.contentEditor);
},
theme: {
cdn: apiConfig.VDITOR_CSS
}
},
height: data.height,
counter: 102400,
resize: {
enable: data.resize,
},
lang: this.$store.state.locale,
// placeholder: data.placeholder,
})
},
handleAvatarSuccess(res) {
let _ts = this;
if (res && res.data && res.data.url) {
@ -291,22 +198,15 @@ export default {
// _ts.$refs.cropper?.replace(this.result);
}
},
handleSubmitData() {
async handleSubmitData() {
let _ts = this;
_ts.$set(_ts, 'loading', true);
let portfolioDescription = _ts.contentEditor.getValue();
let portfolioDescriptionHtml = _ts.contentEditor.getHTML();
let data = _ts.portfolio;
data.portfolioDescription = portfolioDescription;
data.portfolioDescriptionHtml = portfolioDescriptionHtml;
data.headImgType = 0
// if (_ts.isEdit) {
//
// } else {
// data.headImgUrl = _ts.headImgUrl
// }
if ((data.portfolioDescription || undefined) == undefined || (data.portfolioDescriptionHtml || undefined) == undefined) {
data.portfolioDescription = _ts.$refs.contentEditor.contentValue();
data.portfolioDescriptionHtml = await _ts.$refs.contentEditor.contentHtml();
data.headImgType = 0;
data.headImgUrl = _ts.headImgUrl;
if ((data.portfolioDescription || undefined) === undefined || (data.portfolioDescriptionHtml || undefined) === undefined) {
this.$message.error('请输入必填信息');
return false
}
@ -314,10 +214,8 @@ export default {
},
async submitData() {
let _ts = this
let data = this.handleSubmitData()
let data = await this.handleSubmitData()
let id = _ts.idPortfolio;
data.headImgUrl = _ts.headImgUrl
let title = id ? '更新' : '添加';
_ts.$axios[id ? '$put' : '$post']('/api/portfolio/post', data).then(function (res) {
if (res && res.message) {
@ -429,16 +327,6 @@ export default {
});
let _ts = this;
_ts.$store.commit("setActiveMenu", "portfolio-post");
this.$axios.$get('/api/upload/simple/token').then(function (res) {
if (res) {
_ts.$store.commit('setUploadHeaders', res.uploadToken);
_ts.$set(_ts, 'tokenURL', {
token: res.uploadToken || '',
URL: res.uploadURL || '',
linkToImageURL: res.linkToImageURL || ''
})
}
});
let portfolioContent = '';
if (_ts.idPortfolio) {
@ -450,25 +338,15 @@ export default {
_ts.$refs?.cropper?.replace(_ts.portfolioDetail.headImgUrl);
portfolioContent = _ts.portfolioDetail.portfolioDescription;
}
} else {
this.isEdit = false
}
this.contentEditor = this._initEditor({
id: 'contentEditor',
mode: 'both',
height: 480,
placeholder: '', //this.$t('inputContent', this.$store.state.locale)
resize: false,
value: this.portfolio.portfolioDescription
});
this.isLoading = true
}
}
</script>
<style lang="less">
@import "~vditor/src/assets/less/index.less";
.button_box {
display: flex;