TinymceEditor.vue 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. <template>
  2. <div class="tinymce-editor">
  3. <textarea :id="id" style="visibility:hidden;"></textarea>
  4. </div>
  5. </template>
  6. <script>
  7. // Use self-hosted TinyMCE
  8. const loadTinyMCE = () => {
  9. if (window.tinymce) return Promise.resolve(window.tinymce)
  10. return new Promise((resolve, reject) => {
  11. const script = document.createElement('script')
  12. script.src = '/static/tinymce/tinymce.min.js'
  13. script.onload = () => {
  14. if (window.tinymce) {
  15. resolve(window.tinymce)
  16. } else {
  17. reject(new Error('Failed to load TinyMCE'))
  18. }
  19. }
  20. script.onerror = reject
  21. document.head.appendChild(script)
  22. })
  23. }
  24. export default {
  25. name: 'TinymceEditor',
  26. props: {
  27. value: {
  28. type: String,
  29. default: ''
  30. },
  31. init: {
  32. type: Object,
  33. default: () => ({})
  34. },
  35. placeholder: {
  36. type: String,
  37. default: ''
  38. }
  39. },
  40. data() {
  41. return {
  42. id: 'tinymce-' + Date.now() + Math.floor(Math.random() * 1000),
  43. editor: null,
  44. hasChange: false
  45. }
  46. },
  47. watch: {
  48. value(val) {
  49. if (!this.hasChange && this.editor) {
  50. this.editor.setContent(val)
  51. }
  52. }
  53. },
  54. mounted() {
  55. this.initEditor()
  56. },
  57. beforeDestroy() {
  58. if (this.editor) {
  59. this.editor.destroy()
  60. }
  61. },
  62. methods: {
  63. async initEditor() {
  64. try {
  65. const tinymce = await loadTinyMCE()
  66. const config = {
  67. selector: `#${this.id}`,
  68. language_url: this.init.language_url || '/static/tinymce/zh_CN.js',
  69. language: this.init.language || 'zh_CN',
  70. height: this.init.height || 300,
  71. plugins: this.init.plugins || 'link lists image code table wordcount',
  72. toolbar: this.init.toolbar || 'undo redo | formatselect | bold italic | alignleft aligncenter alignright | bullist numlist outdent indent | link image | code',
  73. images_upload_handler: this.init.images_upload_handler || null,
  74. placeholder: this.placeholder,
  75. skin_url: '/static/tinymce/skins/ui/oxide',
  76. content_css: '/static/tinymce/skins/content/default/content.css',
  77. setup: editor => {
  78. editor.on('init', () => {
  79. editor.setContent(this.value)
  80. })
  81. editor.on('change keyup', () => {
  82. this.hasChange = true
  83. this.$emit('input', editor.getContent())
  84. })
  85. }
  86. }
  87. tinymce.init(config).then(editors => {
  88. this.editor = editors[0]
  89. }).catch(error => {
  90. console.error('TinyMCE init error:', error)
  91. this.$message.error('富文本编辑器初始化失败')
  92. })
  93. } catch (error) {
  94. console.error('Failed to load TinyMCE:', error)
  95. this.$message.error('加载富文本编辑器失败')
  96. }
  97. }
  98. }
  99. }
  100. </script>
  101. <style>
  102. .tox-tinymce {
  103. border-radius: 4px;
  104. border: 1px solid #dcdfe6 !important;
  105. }
  106. </style>