index.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <!-- 富文本编辑器 -->
  2. <template>
  3. <div>
  4. <div v-if="props.item?.label" class="ml-2 mb-2 color959595">{{ props.item.label }}</div>
  5. <div class="z-10 box mb-5">
  6. <!-- 工具栏 -->
  7. <Toolbar
  8. :editor="editorRef"
  9. :editorId="editorId"
  10. class="border-0 b-b-1 border-solid border-[var(--tags-view-border-color)]"
  11. />
  12. <!-- 编辑器 -->
  13. <Editor
  14. v-model="valueHtml"
  15. :defaultConfig="editorConfig"
  16. :editorId="editorId"
  17. :style="editorStyle"
  18. @on-maxLength="onMaxLengthFun"
  19. @on-blur="onBlurFun"
  20. @on-change="handleChange"
  21. @on-created="handleCreated"
  22. />
  23. </div>
  24. </div>
  25. </template>
  26. <script setup>
  27. defineOptions({ name: 'wangEditor-index' })
  28. import '@wangeditor/editor/dist/css/style.css' // 引入 css
  29. import { onBeforeUnmount, ref, shallowRef, unref, computed, nextTick, watch } from 'vue'
  30. import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
  31. import { i18nChangeLanguage } from '@wangeditor/editor'
  32. import { isNumber } from '@/utils/is'
  33. import Snackbar from '@/plugins/snackbar'
  34. // import { getAccessToken, getTenantId } from '@/utils/auth'
  35. i18nChangeLanguage('zh-CN')
  36. const props = defineProps({
  37. modelValue: {
  38. type: String,
  39. default: '',
  40. },
  41. item: { // 表单配置数据
  42. type: Object,
  43. default: () => {}
  44. },
  45. editorId: {
  46. type: String,
  47. default: 'wangEditor-1',
  48. },
  49. height: {
  50. type: [Number, String],
  51. default: '300px'
  52. },
  53. editorConfig: {
  54. type: Object,
  55. default: () => {}
  56. },
  57. readonly: {
  58. type: Boolean,
  59. default: false
  60. },
  61. })
  62. const emit = defineEmits(['change', 'update:modelValue'])
  63. // 编辑器实例,必须用 shallowRef
  64. const editorRef = shallowRef()
  65. const valueHtml = ref('')
  66. watch(
  67. () => props.modelValue,
  68. (val) => {
  69. if (val === null) val = ''
  70. if (val === unref(valueHtml)) return
  71. valueHtml.value = val
  72. },
  73. {
  74. immediate: true
  75. }
  76. )
  77. // 监听
  78. watch(
  79. () => valueHtml.value,
  80. (val) => {
  81. emit('update:modelValue', val === '<p><br></p>' ? '' : val)
  82. }
  83. )
  84. const handleCreated = (editor) => {
  85. editorRef.value = editor
  86. }
  87. const onMaxLengthFun = (editor) => {
  88. const maxLength = editor?.getConfig()?.maxLength || null
  89. if (maxLength) Snackbar.info(`最多可输入${maxLength}个字符`)
  90. }
  91. const onBlurFun = (editor) => {
  92. const text = editor.getText()?.replace(/\s+/g, '')
  93. if (!text) valueHtml.value = ''
  94. }
  95. // 编辑器配置
  96. const editorConfig = computed(() => {
  97. return Object.assign(
  98. {
  99. placeholder: '请输入内容...',
  100. readOnly: props.item.readonly || props.readonly,
  101. maxLength: Number(props.item.maxLength) || Number(props.maxLength) || null,
  102. customAlert: (s, t) => {
  103. switch (t) {
  104. case 'success':
  105. Snackbar.success(s)
  106. break
  107. case 'info':
  108. Snackbar.info(s)
  109. break
  110. case 'warning':
  111. Snackbar.warning(s)
  112. break
  113. case 'error':
  114. Snackbar.error(s)
  115. break
  116. default:
  117. Snackbar.info(s)
  118. break
  119. }
  120. },
  121. autoFocus: false,
  122. scroll: true,
  123. MENU_CONF: {
  124. ['uploadImage']: {
  125. server:`${import.meta.env.VITE_BASE_URL}/commons/upload`,
  126. // 单个文件的最大体积限制,默认为 2M
  127. // maxFileSize: 5 * 1024 * 1024,
  128. // 最多可上传几个文件,默认为 100
  129. // maxNumberOfFiles: 10,
  130. // 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
  131. // allowedFileTypes: ['image/*'],
  132. // 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
  133. // meta: { updateSupport: 0 },
  134. // 将 meta 拼接到 url 参数中,默认 false
  135. // metaWithUrl: true,
  136. // 自定义增加 http header
  137. // headers: {
  138. // Accept: '*',
  139. // Authorization: 'Bearer ' + getAccessToken(),
  140. // 'tenant-id': getTenantId()
  141. // },
  142. // 跨域是否传递 cookie ,默认为 false
  143. // withCredentials: true,
  144. // 超时时间,默认为 10 秒
  145. timeout: 5 * 1000, // 5 秒
  146. // form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
  147. fieldName: 'file',
  148. // 上传之前触发
  149. onBeforeUpload(file) {
  150. console.log(file)
  151. return file
  152. },
  153. // 上传进度的回调函数
  154. onProgress(progress) {
  155. // progress 是 0-100 的数字
  156. console.log('progress', progress)
  157. },
  158. onSuccess(file, res) {
  159. console.log('onSuccess', file, res)
  160. },
  161. onFailed(file, res) {
  162. alert(res.message)
  163. console.log('onFailed', file, res)
  164. },
  165. onError(file, err, res) {
  166. alert(err.message)
  167. console.error('onError', file, err, res)
  168. },
  169. // 自定义插入图片
  170. // customInsert(res, insertFn: InsertFnType) {
  171. // insertFn(res.data, 'image', res.data)
  172. // }
  173. }
  174. },
  175. uploadImgShowBase64: true,
  176. },
  177. props.editorConfig || props.item.editorConfig || {}
  178. )
  179. })
  180. const editorStyle = computed(() => {
  181. const h = props.item.height || props.height
  182. return {
  183. height: isNumber(h) ? `${h}px` : h
  184. }
  185. })
  186. // 回调函数
  187. const handleChange = (editor) => {
  188. emit('change', editor)
  189. }
  190. // 组件销毁时,及时销毁编辑器
  191. onBeforeUnmount(() => {
  192. const editor = unref(editorRef.value)
  193. // 销毁,并移除 editor
  194. editor?.destroy()
  195. })
  196. const getEditorRef = async () => {
  197. await nextTick()
  198. return unref(editorRef.value)
  199. }
  200. defineExpose({
  201. getEditorRef
  202. })
  203. </script>
  204. <style lang="scss" scoped>
  205. .box {
  206. border: 1px solid #a2a2a2;
  207. border-radius: 5px;
  208. padding: 1px;
  209. &:hover {
  210. border: 1px solid #2b2b2b;
  211. }
  212. }
  213. .color959595 {
  214. color: #959595;
  215. }
  216. </style>