编辑器组件封装

编辑器组件封装
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> </el-form-item>
<br> <br>
<el-form-item label="作品集介绍"> <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>
<el-form-item class="text-right"> <el-form-item class="text-right">
<el-button :loading="loading" @click="deletePortfolio" v-if="isEdit">删除</el-button> <el-button :loading="loading" @click="deletePortfolio" v-if="isEdit">删除</el-button>
@ -35,11 +37,11 @@
<div style="display: block;"> <div style="display: block;">
<div class="cropperBox"> <div class="cropperBox">
<vue-cropper <vue-cropper
:autoCrop="autoCrop" :autoCrop="autoCrop"
:img="headImgUrl" :img="headImgUrl"
ref="cropper" ref="cropper"
:fixed="true" :fixed="true"
@realTime="realTime" @realTime="realTime"
/> />
</div> </div>
<div class="cropperBox"> <div class="cropperBox">
@ -57,11 +59,11 @@
<div class="button_box"> <div class="button_box">
<el-upload <el-upload
:before-upload="beforeAvatarUpload" :before-upload="beforeAvatarUpload"
:multiple="true" :multiple="true"
:show-file-list="false" :show-file-list="false"
action="" action=""
class="avatar-uploader"> class="avatar-uploader">
<div> <div>
<el-button plain round type="primary">上传</el-button> <el-button plain round type="primary">上传</el-button>
</div> </div>
@ -78,20 +80,19 @@
</div> </div>
<el-col class="text-center" v-else> <el-col class="text-center" v-else>
<el-alert <el-alert
:closable="false" :closable="false"
center center
show-icon show-icon
title="用户无权限" title="用户无权限"
type="warning"> type="warning">
</el-alert> </el-alert>
</el-col> </el-col>
</div> </div>
</template> </template>
<script> <script>
import Vue from 'vue';
import {mapState} from 'vuex'; import {mapState} from 'vuex';
import apiConfig from '~/config/api.config'; import ContentEditor from "@/components/ContentEditor.vue";
export default { export default {
name: "PortfolioPost", name: "PortfolioPost",
@ -104,10 +105,12 @@ export default {
asyncData({store, params, error}) { asyncData({store, params, error}) {
return Promise.all([ return Promise.all([
store.dispatch('portfolio/fetchPostDetail', params) store.dispatch('portfolio/fetchPostDetail', params)
.catch(err => error({statusCode: 404})) .catch(err => error({statusCode: 404}))
]) ])
}, },
components: {
ContentEditor
},
computed: { computed: {
...mapState({ ...mapState({
portfolioDetail: state => state.portfolio.detail.data, portfolioDetail: state => state.portfolio.detail.data,
@ -134,19 +137,10 @@ export default {
}, },
data() { data() {
return { return {
contentEditor: null,
portfolio: { portfolio: {
idPortfolio: 0, idPortfolio: 0,
portfolioDescription: '' portfolioDescription: ''
}, },
// rules: {
// portfolioTitle: [
// {required: true, message: '', trigger: 'blur'}
// ],
// portfolioDescription: [
// {required: true, message: '', trigger: 'blur'}
// ]
// },
loading: false, loading: false,
tokenURL: { tokenURL: {
URL: '', URL: '',
@ -158,104 +152,17 @@ export default {
isEdit: false, isEdit: false,
autoCrop: true, autoCrop: true,
notificationFlag: true, notificationFlag: true,
contentHtml: {} contentValue: {
portfolioDescription: '',
portfolioDescriptionHtml: ''
},
isLoading: false
} }
}, },
methods: { methods: {
realTime(data) { realTime(data) {
this.cropImg = 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) { handleAvatarSuccess(res) {
let _ts = this; let _ts = this;
if (res && res.data && res.data.url) { if (res && res.data && res.data.url) {
@ -291,22 +198,15 @@ export default {
// _ts.$refs.cropper?.replace(this.result); // _ts.$refs.cropper?.replace(this.result);
} }
}, },
handleSubmitData() { async handleSubmitData() {
let _ts = this; let _ts = this;
_ts.$set(_ts, 'loading', true); _ts.$set(_ts, 'loading', true);
let portfolioDescription = _ts.contentEditor.getValue();
let portfolioDescriptionHtml = _ts.contentEditor.getHTML();
let data = _ts.portfolio; let data = _ts.portfolio;
data.portfolioDescription = portfolioDescription; data.portfolioDescription = _ts.$refs.contentEditor.contentValue();
data.portfolioDescriptionHtml = portfolioDescriptionHtml; data.portfolioDescriptionHtml = await _ts.$refs.contentEditor.contentHtml();
data.headImgType = 0 data.headImgType = 0;
// if (_ts.isEdit) { data.headImgUrl = _ts.headImgUrl;
if ((data.portfolioDescription || undefined) === undefined || (data.portfolioDescriptionHtml || undefined) === undefined) {
//
// } else {
// data.headImgUrl = _ts.headImgUrl
// }
if ((data.portfolioDescription || undefined) == undefined || (data.portfolioDescriptionHtml || undefined) == undefined) {
this.$message.error('请输入必填信息'); this.$message.error('请输入必填信息');
return false return false
} }
@ -314,10 +214,8 @@ export default {
}, },
async submitData() { async submitData() {
let _ts = this let _ts = this
let data = this.handleSubmitData() let data = await this.handleSubmitData()
let id = _ts.idPortfolio; let id = _ts.idPortfolio;
data.headImgUrl = _ts.headImgUrl
let title = id ? '更新' : '添加'; let title = id ? '更新' : '添加';
_ts.$axios[id ? '$put' : '$post']('/api/portfolio/post', data).then(function (res) { _ts.$axios[id ? '$put' : '$post']('/api/portfolio/post', data).then(function (res) {
if (res && res.message) { if (res && res.message) {
@ -429,16 +327,6 @@ export default {
}); });
let _ts = this; let _ts = this;
_ts.$store.commit("setActiveMenu", "portfolio-post"); _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 = ''; let portfolioContent = '';
if (_ts.idPortfolio) { if (_ts.idPortfolio) {
@ -450,25 +338,15 @@ export default {
_ts.$refs?.cropper?.replace(_ts.portfolioDetail.headImgUrl); _ts.$refs?.cropper?.replace(_ts.portfolioDetail.headImgUrl);
portfolioContent = _ts.portfolioDetail.portfolioDescription; portfolioContent = _ts.portfolioDetail.portfolioDescription;
} }
} else { } else {
this.isEdit = false this.isEdit = false
} }
this.isLoading = true
this.contentEditor = this._initEditor({
id: 'contentEditor',
mode: 'both',
height: 480,
placeholder: '', //this.$t('inputContent', this.$store.state.locale)
resize: false,
value: this.portfolio.portfolioDescription
});
} }
} }
</script> </script>
<style lang="less"> <style lang="less">
@import "~vditor/src/assets/less/index.less";
.button_box { .button_box {
display: flex; display: flex;