| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- <template>
- <div>
- <Editor
- v-model="myValue"
- :init="init"
- :disabled="disabled"
- :placeholder="placeholder"
- :id="tinymceId"
- />
- </div>
- </template>
- <script setup lang="ts">
- import { reactive, ref, onMounted, watch } from 'vue'
- import Editor from '@tinymce/tinymce-vue'
- import tinymce from 'tinymce/tinymce'
- import 'tinymce/themes/silver'
- import 'tinymce/themes/silver/theme'
- import 'tinymce/models/dom'
- import 'tinymce/icons/default'
- import 'tinymce/icons/default/icons'
- // 引入编辑器插件 - 移除不存在的插件
- import 'tinymce/plugins/code'
- import 'tinymce/plugins/image'
- import 'tinymce/plugins/media'
- import 'tinymce/plugins/link'
- import 'tinymce/plugins/preview'
- import 'tinymce/plugins/table'
- import 'tinymce/plugins/pagebreak'
- import 'tinymce/plugins/lists'
- import 'tinymce/plugins/advlist'
- import 'tinymce/plugins/quickbars'
- import 'tinymce/plugins/wordcount'
- import '/public/langs/zh_CN'
- import 'tinymce/skins/content/default/content.css'
- import { getRefreshToken } from '@/utils/auth'
- // 定义接口
- interface Props {
- value?: string
- placeholder?: string
- height?: number
- disabled?: boolean
- plugins?: string | string[]
- toolbar?: string | string[]
- templates?: any[]
- options?: Record<string, any>
- }
- interface Emits {
- (e: 'update:value', value: string): void
- }
- // 定义props
- const props = withDefaults(defineProps<Props>(), {
- value: '',
- placeholder: '',
- height: 500,
- disabled: false,
- // 移除template插件
- plugins: 'code image media link preview table quickbars pagebreak lists advlist',
- // 移除template相关工具栏
- toolbar: 'undo redo codesample bold italic underline strikethrough link alignleft aligncenter alignright alignjustify bullist numlist outdent indent removeformat forecolor backcolor |formatselect fontselect fontsizeselect | blocks fontfamily fontsize pagebreak lists image media table preview | code selectall',
- templates: () => [],
- options: () => ({})
- })
- // 定义emits
- const emit = defineEmits<Emits>()
- // 响应式数据
- const myValue = ref<string>(props.value)
- const tinymceId = ref<string>(`vue-tinymce-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`)
- // 上传相关配置
- const HEADERS = { Authorization: 'Bearer ' + getRefreshToken() }
- const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent'
- // 文件上传处理函数
- const handleImageUpload = (blobInfo: any, progress: any): Promise<string> => {
- return new Promise((resolve, reject) => {
- const formData = new FormData()
- formData.append('file', blobInfo.blob(), blobInfo.filename())
- // 使用fetch进行上传
- fetch(UPLOAD_URL, {
- method: 'POST',
- headers: HEADERS,
- body: formData
- })
- .then(response => response.json())
- .then(res => {
- if (res.code === 0) {
- resolve(res.data.src || res.data.url)
- } else {
- reject(new Error(res.msg || '上传失败'))
- }
- })
- .catch(error => {
- console.error('上传错误:', error)
- reject(new Error('上传失败'))
- })
- })
- }
- // 文件选择回调
- const handleFilePicker = (callback: Function, value: string, meta: any) => {
- // 这里可以实现自定义的文件选择器
- if (meta.filetype === 'file') {
- callback('mypage.html', { text: 'My text' })
- }
- if (meta.filetype === 'image') {
- callback('myimage.jpg', { alt: 'My alt text' })
- }
- if (meta.filetype === 'media') {
- callback('movie.mp4', { source2: 'alt.ogg', poster: 'image.jpg' })
- }
- }
- // TinyMCE配置
- const init = reactive({
- selector: `#${tinymceId.value}`,
- language_url: '/tinymce/langs/zh-Hans.js',
- language: 'zh-Hans',
- skin_url: '/tinymce/skins/ui/oxide',
- content_css: '/tinymce/skins/content/default/content.css',
- menubar: true,
- statusbar: true,
- plugins: props.plugins,
- toolbar: props.toolbar,
- toolbar_mode: 'sliding',
- font_formats: 'Arial=arial,helvetica,sans-serif; 宋体=SimSun; 微软雅黑=Microsoft Yahei; Impact=impact,chicago;',
- paste_convert_word_fake_lists: false,
- font_size_formats: '12px 14px 16px 18px 22px 24px 36px 72px',
- height: props.height,
- placeholder: props.placeholder,
- branding: false,
- image_dimensions: false,
- paste_webkit_styles: 'all',
- paste_merge_formats: true,
- nonbreaking_force_tab: false,
- paste_auto_cleanup_on_paste: false,
- file_picker_types: 'file',
- resize: true,
- elementpath: true,
- content_style: '',
- // 移除templates配置
- quickbars_selection_toolbar: 'forecolor backcolor bold italic underline strikethrough link',
- quickbars_image_toolbar: 'alignleft aligncenter alignright',
- quickbars_insert_toolbar: false,
- image_caption: true,
- image_advtab: true,
- convert_urls: false,
- // 图片上传处理
- images_upload_handler: handleImageUpload,
- // 文件选择处理
- file_picker_callback: handleFilePicker,
- // 编辑器设置
- setup: (editor: any) => {
- editor.on('init', () => {
- editor.getBody().style.fontSize = '14px'
- })
- editor.on('OpenWindow', (e: any) => {
- // 修复编辑器在el-drawer中的焦点问题
- const drawer = document.querySelector('.el-drawer.open')
- const editorContainer = e.target.editorContainer
- if (drawer && drawer.contains(editorContainer)) {
- const nowActiveElement = document.activeElement
- setTimeout(() => {
- if (document.activeElement) {
- document.activeElement.blur()
- }
- if (nowActiveElement) {
- nowActiveElement.focus()
- }
- }, 0)
- }
- })
- },
- // 合并自定义选项
- ...props.options
- })
- // 监听外部数据变化
- watch(
- () => props.value,
- (newValue) => {
- myValue.value = newValue
- emit('update:value', myValue.value)
- }
- )
- // 监听内部数据变化
- watch(
- () => myValue.value,
- (newValue) => {
- emit('update:value', newValue)
- }
- )
- // 组件挂载时初始化
- onMounted(() => {
- tinymce.init({})
- })
- </script>
- <style scoped>
- /* 可以添加一些自定义样式 */
- </style>
|