index.vue 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. <script setup>
  2. import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
  3. import tinymce from 'tinymce'
  4. import { REQUEST } from '~/utils/request'
  5. const props = defineProps({
  6. id: {
  7. type: String,
  8. default: `tiny-${Date.now()}-${~~(Math.random() * 10000)}`,
  9. },
  10. htmlClass: { default: '', type: String },
  11. modelValue: String,
  12. o: {
  13. default() {
  14. return {}
  15. },
  16. type: Object,
  17. },
  18. })
  19. const emits = defineEmits(['update:modelValue', 'click:audio', 'click:video'])
  20. const content = ref('')
  21. let editor = null
  22. let checkerTimeout = null
  23. let isTyping = false
  24. function init() {
  25. const options = {
  26. selector: `#${props.id}`,
  27. base_url: '/tiny',
  28. language: 'zh-Hans',
  29. height: 130,
  30. menubar: false,
  31. statusbar: false,
  32. body_class: 'tinymce-area',
  33. toolbar1: 'image emoticons',
  34. plugins: 'image emoticons',
  35. autosave_restore_when_empty: false,
  36. content_style: 'body { margin: 0.4rem; line-height: 1; } p { margin: 0; } ',
  37. init_instance_callback: (_editor) => {
  38. _editor.on('KeyUp', (e) => {
  39. submitNewContent()
  40. })
  41. _editor.on('Change', (e) => {
  42. if (_editor.getContent() !== props.modelValue)
  43. submitNewContent()
  44. })
  45. _editor.on('init', (e) => {
  46. _editor.setContent(content.value)
  47. // emits('input', content.value);
  48. })
  49. editor = _editor
  50. },
  51. automatic_uploads: true,
  52. images_upload_handler: (blobInfo, progress) => {
  53. const file = blobInfo.blob()
  54. return REQUEST.upload({
  55. url: '/upload/main/file',
  56. data: {
  57. filedata: file,
  58. },
  59. onUploadProgress(progressEvent) {
  60. progress(~~(progressEvent.loaded / progressEvent.total * 100 | 0))
  61. },
  62. }).then((res) => {
  63. if (res.code === '1')
  64. return window.GLOBAL_CONFIG.oss + (res.data.url)
  65. else
  66. return ('')
  67. })
  68. },
  69. // to solve a third party URL
  70. // paste_preprocess: (editor, args) => {
  71. // if (/^https?\:\/\/.+(png|jpg)$/.test(args.content)) {
  72. // args.content = uploadByThirdPartyUrl(args.content);
  73. // }
  74. // }
  75. }
  76. tinymce.init(Object.assign(options, props.o))
  77. }
  78. function submitNewContent() {
  79. isTyping = true
  80. if (checkerTimeout !== null)
  81. clearTimeout(checkerTimeout)
  82. checkerTimeout = setTimeout(() => {
  83. isTyping = false
  84. }, 300)
  85. emits('update:modelValue', editor.getContent())
  86. }
  87. onMounted(() => {
  88. content.value = props.modelValue
  89. init()
  90. })
  91. onBeforeUnmount(() => {
  92. editor.destroy()
  93. })
  94. defineExpose({
  95. clear(v = '') {
  96. editor.resetContent(v)
  97. },
  98. })
  99. function handleClickAudioCall() {
  100. emits('click:audio')
  101. }
  102. function handleClickVideoCall() {
  103. emits('click:video')
  104. }
  105. </script>
  106. <template>
  107. <div class="relative">
  108. <textarea :id="id" class="tinyarea w-full h-full" v-model="content"></textarea>
  109. <div class="absolute right-0 top-0 h-28px px-13px flex z-1000 text-hex-666">
  110. <div @click="handleClickAudioCall"
  111. class="cursor-pointer flex_center box-content w-28px px-3px h-full rounded-sm hover:bg-hex-cce2fa">
  112. <i:mingcute:phone-call-fill class=" w flex_center h-6" />
  113. </div>
  114. <div @click="handleClickVideoCall"
  115. class="cursor-pointer flex_center box-content w-28px px-3px h-full rounded-sm hover:bg-hex-cce2fa">
  116. <i:wpf:video-call class=" w-6 h-6" />
  117. </div>
  118. </div>
  119. </div>
  120. </template>
  121. <style lang="scss">
  122. .tinyarea+.tox-tinymce {
  123. border: none;
  124. &.tox:not(.tox-tinymce-inline) .tox-editor-header {
  125. box-shadow: none;
  126. padding: 0 0;
  127. }
  128. .tox-tbtn {
  129. margin-top: 0;
  130. margin-bottom: 0;
  131. cursor: pointer;
  132. }
  133. }
  134. </style>