Elvis's blogs

同一页面中使用多个 TinyMCE 富文本框的坑

2024年1月9日 • ☕️ 2 min read

1. textarea 标签 id 重复

在初始化 TinyMCE 时需要配置 textarea 的 selector,这里容易出现的一个问题就是直接将 textarea 的 id 写成固定值了,当同一个页面多次使用这个组件的时候,就会出现 id 重复的情况,导致只有一个富文本框能正常使用的问题

解决方法很简单,每次组件加载的时候动态设置textarea 标签的 id 就行了:

<template>
	<textarea :id="TINY_MCE_ID"></textarea>
</template>


<script setup>
    const TINY_MCE_ID = "editor_" + Date.now()
    
    ...
    window.tinyMCE.init({
        selector: '#' + TINY_MCE_ID
    })
    ...
</script>

2. 通过 cdn 或者本地资源方式使用时 onload 的回调问题

通过这两种方式使用时,我们需要通过 script 标签加载 js 代码:

<!-- TinyMCE.vue -->

<script setup>
    let tinyMCEEditor
    
	onMounted(() => {
        const script = document.createElement('script')
        script.src = BASE_URL + '/tinymce.min.js'
        script.onload = async () => {
            // 初始化 TinyMCE 并保存返回的 Editor 对象
            ;[tinyMCEEditor] = await window.tinyMCE.init(initOptions)
        }
        document.body.appendChild(script)
    })
</script>

当同一个页面多次使用这个组件时,由于已经存在这个 script 标签了,并且 tinymce.min.js 已经加载过了,所以 script 标签的 onload 方法不会再回调了

我们可以编写一个 Hook 统一执行 TinyMCE 资源的加载

UseTinyMCE Hook

// useTinyMCE.ts

import { RawEditorOptions, Editor } from '../../public/tinyMCE/tinymce'

let isScriptLoading = false
const initMethodList: Array<() => Promise<void>> = []

const isTinyMCENotReadyToInit = () =>
    isScriptLoading === true || !window.tinyMCE
const isBeforeTinyMCEInit = () => isScriptLoading === false && !window.tinyMCE

const useTinyMCE = () => {
    const init = (initOptions: RawEditorOptions) => {
        return new Promise<Editor[]>(async resolve => {
            const initMethod = async () =>
                resolve(await window.tinyMCE.init(initOptions))

            if (isTinyMCENotReadyToInit()) {
                initMethodList.push(initMethod)
            } else {
                initMethod()
            }

            if (isBeforeTinyMCEInit()) {
                isScriptLoading = true
                const script = document.createElement('script')
                script.src = '/tinyMCE/tinymce.min.js'
                script.onload = async () => {
                    isScriptLoading = false
                    while (initMethodList.length) {
                        initMethodList[0]()
                        initMethodList.splice(0, 1)
                    }
                }
                document.body.appendChild(script)
            }
        })
    }

    return {
        init
    }
}

export default useTinyMCE

在组件中使用

<script setup>
	import useTinyMCE from 'examplePath/useTinyMCE'

    const { init: initTinyMCE } = useTinyMCE()
    
    const initOptions = {...}
    let tinyMCEEditor

    onMounted(async () => {
        [tinyMCEEditor] = await initTinyMCE(initOptions)
    })
</script>