You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
6.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="components-tiny-mce">
<div
class="tinymce-container"
:class="{ 'tox-fullscreen': toxFullscreen }"
>
<Editor
:id="props.id"
:init="init"
api-key="i6mv006qcwsfu1t7ebisntg5w261vpowkwirnx9cnse3ho5o"
/>
<!-- 增加图片区域 -->
<div
class="add-or-upload"
>
<el-upload
class="upload-demo"
list-type="picture"
:action="uploadAction"
:headers="uploadHeaders"
:on-success="imageSuccessCBK"
:show-file-list="false"
:before-upload="beforeAvatarUpload"
>
<el-button
size="small"
type="primary"
>
点击上传图片
</el-button>
</el-upload>
</div>
</div>
</div>
</template>
<script setup>
import Editor from '@tinymce/tinymce-vue'
import $cookie from 'vue-cookies'
const uploadHeaders = { Authorization: $cookie.get('Authorization') }
const uploadAction = http.adornUrl('/admin/file/upload/element')
const props = defineProps({
modelValue: {
type: String,
default: ''
},
id: {
type: String,
default: function () {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
height: {
type: [Number, String],
required: false,
default: 360
},
width: {
type: [Number, String],
required: false,
default: 'auto'
}
})
const toxFullscreen = ref(false)
const hasChange = ref(false)
const hasInit = ref(false)
watch(
() => props.modelValue,
val => {
if (!hasChange.value && hasInit.value) {
nextTick(() => window.tinymce.get(props.id).setContent(val || ''))
}
}
)
const language = computed(() => {
return localStorage.getItem('b2cLang') || 'zh_CN'
})
watch(language, () => {
destroyTinymce()
})
const emit = defineEmits(['update:modelValue'])
const isLoaded = ref(false)
const init = reactive({
plugins: 'preview anchor autolink codesample emoticons image link lists media searchreplace table visualblocks pagebreak insertdatetime fullscreen', // 插件 preview anchor autolink codesample emoticons image link lists media searchreplace table visualblocks wordcount pagebreak insertdatetime fullscreen
content_css: 'default', // 主题tinymce-5-dark || tinymce-5 || default || writer || document || dark
custom_undo_redo_levels: 50, // 回退数量
end_container_on_empty_block: true, // 块级文本是否换行
keep_styles: false, // 回车是否保存原有样式例如code块回车插件初始化失败: tinycomments是否截断
menubar: true, // 是否开启顶部菜单 > false 关闭菜单 | 'edit insert view format table tools help' 菜单按照这里排序 | 参考:https://www.tiny.cloud/docs/tinymce/6/menus-configuration-options/
toolbar_mode: 'wrap', // 功能栏是否换行 > | wrap 换行 | scrolling 滚动 | sliding 省略
toolbar: ' searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample hr numlist link image preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen',
powerpaste_word_import: 'clean',
toolbar_location: 'top', // 菜单栏位置 > bottom 底部 | top 顶部
style_formats_merge: true, // 是否开启默认功能
elementpath: false, // 是否展示编辑层级 > p span
advlist_bullet_styles: 'square',
resize: true, // 调整宽高 > true 调整高 | false 不可调整宽高 | both 宽高可调
language, // 中文
init_instance_callback: editor => {
isLoaded.value = true
if (props.modelValue) {
editor.setContent(props.modelValue)
}
hasInit.value = true
editor.on('NodeChange Change KeyUp SetContent', () => {
hasChange.value = true
emit('update:modelValue', editor.getContent())
})
},
setup (editor) {
const addOrupload = document.querySelectorAll('.components-tiny-mce .tinymce-container .add-or-upload')
addOrupload.forEach(v => {
v.style.zIndex = 10
})
editor.on('FullscreenStateChanged', e => {
toxFullscreen.value = e.state
handleFullScreenStateChange(e.state)
})
}
})
const handleFullScreenStateChange = (isFullscreen) => {
const addOrupload = document.querySelectorAll('.components-tiny-mce .tinymce-container .add-or-upload')
if (isFullscreen) {
// 进入全屏模式时执行的代码
addOrupload.forEach(v => {
v.style.zIndex = 0
})
} else {
// 退出全屏模式时执行的代码
addOrupload.forEach(v => {
v.style.zIndex = 10
})
}
}
const destroyTinymce = () => {
const tinymce = window.tinymce?.get(props.id)
if (toxFullscreen.value) {
tinymce.execCommand('mceFullScreen')
}
onUnmounted(() => {
destroyTinymce()
})
if (tinymce) {
tinymce.destroy()
}
}
const resourcesUrl = import.meta.env.VITE_APP_RESOURCES_URL
// eslint-disable-next-line no-unused-vars
const imageSuccessCBK = (response, file, fileList) => {
window.tinymce.get(props.id).insertContent(`<img alt="" src="${resourcesUrl + file.response.data}" >`)
}
/**
* 限制图片上传大小
*/
const beforeAvatarUpload = (file) => {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/jpg'
if (!isJPG) {
this.$message.error('上传图片只能是jpeg/jpg/png/gif 格式!')
}
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('上传图片大小不能超过 2MB!')
}
return isLt2M && isJPG
}
</script>
<!--eslint-disable-next-line vue-scoped-css/enforce-style-type -->
<style lang="scss">
.components-tiny-mce {
.tox-fullscreen .add-or-upload {
z-index: 9999 !important;
position:fixed !important;
right: 0;
top: 0;
}
.tinymce-container {
position: relative;
.add-or-upload {
position: absolute;
top: 10px;
right: 10px;
}
}
}
.tox-tinymce-aux {
z-index: 9999 !important;
}
</style>