Editor.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <script lang="ts" setup>
  2. import { PropType } from 'vue'
  3. import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
  4. import { i18nChangeLanguage, IDomEditor, IEditorConfig } from '@wangeditor/editor'
  5. import { propTypes } from '@/utils/propTypes'
  6. import { isNumber } from '@/utils/is'
  7. import { ElMessage } from 'element-plus'
  8. import { useLocaleStore } from '@/store/modules/locale'
  9. import { getRefreshToken, getTenantId } from '@/utils/auth'
  10. import { getUploadUrl } from '@/components/UploadFile/src/useUpload'
  11. defineOptions({ name: 'Editor' })
  12. type InsertFnType = (url: string, alt: string, href: string) => void
  13. const localeStore = useLocaleStore()
  14. const currentLocale = computed(() => localeStore.getCurrentLocale)
  15. i18nChangeLanguage(unref(currentLocale).lang)
  16. const props = defineProps({
  17. editorId: propTypes.string.def('wangeEditor-1'),
  18. height: propTypes.oneOfType([Number, String]).def('500px'),
  19. editorConfig: {
  20. type: Object as PropType<Partial<IEditorConfig>>,
  21. default: () => undefined
  22. },
  23. readonly: propTypes.bool.def(false),
  24. modelValue: propTypes.string.def('')
  25. })
  26. const emit = defineEmits(['change', 'update:modelValue'])
  27. // 编辑器实例,必须用 shallowRef
  28. const editorRef = shallowRef<IDomEditor>()
  29. const valueHtml = ref('')
  30. watch(
  31. () => props.modelValue,
  32. (val: string) => {
  33. if (!val) {
  34. val = ''
  35. }
  36. if (val === unref(valueHtml)) return
  37. valueHtml.value = val
  38. },
  39. {
  40. immediate: true
  41. }
  42. )
  43. // 监听
  44. watch(
  45. () => valueHtml.value,
  46. (val: string) => {
  47. emit('update:modelValue', val)
  48. }
  49. )
  50. const handleCreated = (editor: IDomEditor) => {
  51. editorRef.value = editor
  52. }
  53. // 编辑器配置
  54. const editorConfig = computed((): IEditorConfig => {
  55. return Object.assign(
  56. {
  57. placeholder: '请输入内容...',
  58. readOnly: props.readonly,
  59. customAlert: (s: string, t: string) => {
  60. switch (t) {
  61. case 'success':
  62. ElMessage.success(s)
  63. break
  64. case 'info':
  65. ElMessage.info(s)
  66. break
  67. case 'warning':
  68. ElMessage.warning(s)
  69. break
  70. case 'error':
  71. ElMessage.error(s)
  72. break
  73. default:
  74. ElMessage.info(s)
  75. break
  76. }
  77. },
  78. autoFocus: false,
  79. scroll: true,
  80. MENU_CONF: {
  81. ['uploadImage']: {
  82. server: getUploadUrl(),
  83. // 单个文件的最大体积限制,默认为 2M
  84. maxFileSize: 5 * 1024 * 1024,
  85. // 最多可上传几个文件,默认为 100
  86. maxNumberOfFiles: 10,
  87. // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
  88. allowedFileTypes: ['image/*'],
  89. // 自定义增加 http header
  90. headers: {
  91. Accept: '*',
  92. Authorization: 'Bearer ' + getRefreshToken(), // 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:Editor 无法方便的刷新访问令牌
  93. 'tenant-id': getTenantId()
  94. },
  95. // 超时时间,默认为 10 秒
  96. timeout: 15 * 1000, // 15 秒
  97. // form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
  98. fieldName: 'file',
  99. // 上传之前触发
  100. onBeforeUpload(file: File) {
  101. // console.log(file)
  102. return file
  103. },
  104. // 上传进度的回调函数
  105. onProgress(progress: number) {
  106. // progress 是 0-100 的数字
  107. console.log('progress', progress)
  108. },
  109. onSuccess(file: File, res: any) {
  110. console.log('onSuccess', file, res)
  111. },
  112. onFailed(file: File, res: any) {
  113. alert(res.message)
  114. console.log('onFailed', file, res)
  115. },
  116. onError(file: File, err: any, res: any) {
  117. alert(err.message)
  118. console.error('onError', file, err, res)
  119. },
  120. // 自定义插入图片
  121. customInsert(res: any, insertFn: InsertFnType) {
  122. insertFn(res.data, 'image', res.data)
  123. }
  124. },
  125. ['uploadVideo']: {
  126. server: getUploadUrl(),
  127. // 单个文件的最大体积限制,默认为 10M
  128. maxFileSize: 10 * 1024 * 1024,
  129. // 最多可上传几个文件,默认为 100
  130. maxNumberOfFiles: 10,
  131. // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
  132. allowedFileTypes: ['video/*'],
  133. // 自定义增加 http header
  134. headers: {
  135. Accept: '*',
  136. Authorization: 'Bearer ' + getRefreshToken(), // 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:Editor 无法方便的刷新访问令牌
  137. 'tenant-id': getTenantId()
  138. },
  139. // 超时时间,默认为 30 秒
  140. timeout: 15 * 1000, // 15 秒
  141. // form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
  142. fieldName: 'file',
  143. // 上传之前触发
  144. onBeforeUpload(file: File) {
  145. // console.log(file)
  146. return file
  147. },
  148. // 上传进度的回调函数
  149. onProgress(progress: number) {
  150. // progress 是 0-100 的数字
  151. console.log('progress', progress)
  152. },
  153. onSuccess(file: File, res: any) {
  154. console.log('onSuccess', file, res)
  155. },
  156. onFailed(file: File, res: any) {
  157. alert(res.message)
  158. console.log('onFailed', file, res)
  159. },
  160. onError(file: File, err: any, res: any) {
  161. alert(err.message)
  162. console.error('onError', file, err, res)
  163. },
  164. // 自定义插入图片
  165. customInsert(res: any, insertFn: InsertFnType) {
  166. insertFn(res.data, 'mp4', res.data)
  167. }
  168. }
  169. },
  170. uploadImgShowBase64: true
  171. },
  172. props.editorConfig || {}
  173. )
  174. })
  175. const editorStyle = computed(() => {
  176. return {
  177. height: isNumber(props.height) ? `${props.height}px` : props.height
  178. }
  179. })
  180. // 回调函数
  181. const handleChange = (editor: IDomEditor) => {
  182. emit('change', editor)
  183. }
  184. // 组件销毁时,及时销毁编辑器
  185. onBeforeUnmount(() => {
  186. const editor = unref(editorRef.value)
  187. // 销毁,并移除 editor
  188. editor?.destroy()
  189. })
  190. const getEditorRef = async (): Promise<IDomEditor> => {
  191. await nextTick()
  192. return unref(editorRef.value) as IDomEditor
  193. }
  194. defineExpose({
  195. getEditorRef
  196. })
  197. </script>
  198. <template>
  199. <div class="border-1 border-solid border-[var(--tags-view-border-color)] z-10">
  200. <!-- 工具栏 -->
  201. <Toolbar
  202. :editor="editorRef"
  203. :editorId="editorId"
  204. class="border-0 b-b-1 border-solid border-[var(--tags-view-border-color)]"
  205. />
  206. <!-- 编辑器 -->
  207. <Editor
  208. v-model="valueHtml"
  209. :defaultConfig="editorConfig"
  210. :editorId="editorId"
  211. :style="editorStyle"
  212. @on-change="handleChange"
  213. @on-created="handleCreated"
  214. />
  215. </div>
  216. </template>
  217. <style src="@wangeditor/editor/dist/css/style.css"></style>