index.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <div class="editor-container">
  3. <Toolbar :editor="editorRef" :mode="mode" :defaultConfig="toolConfig" />
  4. <Editor
  5. :mode="mode"
  6. :defaultConfig="state.editorConfig"
  7. :style="{ height }"
  8. v-model="state.editorVal"
  9. @onCreated="handleCreated"
  10. @onChange="handleChange"
  11. @onBlur="handleBlur"
  12. :defaultContent="defaultContent"
  13. />
  14. </div>
  15. </template>
  16. <script lang="ts" setup name="wngEditor">
  17. import '@wangeditor-next/editor/dist/css/style.css' // 引入 css
  18. import { onBeforeUnmount, reactive, shallowRef, watch, nextTick, ref } from 'vue';
  19. import { IDomEditor } from '@wangeditor-next/editor'
  20. import { Editor, Toolbar } from '@wangeditor-next/editor-for-vue'
  21. import { Cookie } from '@/utils/storage';
  22. import { uploadFile } from '@/api/public/upload';
  23. import { ElMessage } from 'element-plus';
  24. // 全局配置
  25. // 定义父组件传过来的值
  26. const props = defineProps({
  27. // 是否禁用
  28. disable: {
  29. type: Boolean,
  30. default: () => false,
  31. },
  32. // 内容框默认 placeholder
  33. placeholder: {
  34. type: String,
  35. default: () => '请填写内容...',
  36. },
  37. // https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F
  38. // 模式,可选 <default|simple>,默认 default
  39. mode: {
  40. type: String,
  41. default: () => 'default',
  42. },
  43. // 高度
  44. height: {
  45. type: String,
  46. default: () => '310px',
  47. },
  48. // 双向绑定,用于获取 editor.getHtml()
  49. getHtml: String,
  50. // 双向绑定,用于获取 editor.getText()
  51. getText: String,
  52. defaultContent: {
  53. type: Array,
  54. default: () => [],
  55. },
  56. });
  57. // 定义子组件向父组件传值/事件
  58. const emit = defineEmits(['update:getHtml', 'update:getText', 'blur']);
  59. const editorRef = shallowRef<RefType>();
  60. const state = reactive<any>({
  61. editorConfig: {
  62. placeholder: props.placeholder,
  63. MENU_CONF: {},
  64. hoverbarKeys:{
  65. },
  66. },
  67. editorVal: props.getHtml,
  68. });
  69. const toolConfig = ref({
  70. excludeKeys: [
  71. // 排除掉视频上传
  72. 'group-video',
  73. ],
  74. });
  75. type InsertFnType = (url: string, alt: string, href: string) => void;
  76. // 图片上传
  77. state.editorConfig.MENU_CONF['uploadImage'] = {
  78. // 自定义上传
  79. async customUpload(file: File, insertFn: InsertFnType) {
  80. // 限制文件不能大于100M
  81. if (file.size > 10 * 1024 * 1024) {
  82. ElMessage.error('上传图片不能大于10M');
  83. return;
  84. }
  85. const formData = new FormData();
  86. formData.append('fileData', file);
  87. uploadFile(formData)
  88. .then((res) => {
  89. insertFn(import.meta.env.VITE_API_UPLOAD_URL + res.result.path, '加载失败', '');
  90. })
  91. .catch((err) => {
  92. ElMessage.error(err);
  93. });
  94. },
  95. };
  96. // //上传视频
  97. state.editorConfig.MENU_CONF['uploadVideo'] = {
  98. server: import.meta.env.VITE_API_UPLOAD_URL,
  99. // form-data fieldName ,默认值 'wangeditor-uploaded-video'
  100. fieldName: 'file',
  101. // 单个文件的最大体积限制,默认为 10M
  102. maxFileSize: 5 * 1024 * 1024, // 5M
  103. // 最多可上传几个文件,默认为 5
  104. maxNumberOfFiles: 3,
  105. // 选择文件时的类型限制,默认为 ['video/*'] 。如不想限制,则设置为 []
  106. allowedFileTypes: ['video/*'],
  107. // 将 meta 拼接到 url 参数中,默认 false
  108. metaWithUrl: false,
  109. // 自定义增加 http header
  110. headers: {
  111. Authorization: 'Bearer ' + Cookie.get('token'),
  112. userid: '',
  113. },
  114. // 跨域是否传递 cookie ,默认为 false
  115. withCredentials: true,
  116. // 超时时间,默认为 30 秒
  117. timeout: 15 * 1000, // 15 秒
  118. // 自定义插入视频
  119. customInsert(res: any, insertFn: any) {
  120. // 从 res 中找到 url alt href ,然后插图图片
  121. insertFn(res.data.url);
  122. },
  123. };
  124. // 行高
  125. /*state.editorConfig.MENU_CONF['lineHeight'] = {
  126. lineHeightList: ['1', '1.5', '2', '2.5'],
  127. };*/
  128. // 字体大小
  129. state.editorConfig.MENU_CONF['fontSize'] = {
  130. fontSizeList: [
  131. // 元素支持两种形式
  132. // 1. 字符串;
  133. // 2. { name: 'xxx', value: 'xxx' }
  134. '12px',
  135. '14px',
  136. '16px',
  137. '20px',
  138. '24px',
  139. '32px',
  140. '40px',
  141. '48px',
  142. ],
  143. };
  144. // 默认字体
  145. /*const defaultContent =ref([
  146. {
  147. type: "paragraph",
  148. children: [{ text: "" }],
  149. fontSize: "20px",
  150. fontFamily: "仿宋",
  151. },
  152. ],)*/
  153. // 编辑器回调函数
  154. const handleCreated = (editor: IDomEditor) => {
  155. editorRef.value = editor;
  156. };
  157. // 编辑器内容改变时
  158. const handleChange = (editor: IDomEditor) => {
  159. emit('update:getHtml', editor.getHtml());
  160. emit('update:getText', editor.getText());
  161. };
  162. // 页面销毁时
  163. onBeforeUnmount(() => {
  164. const editor = editorRef.value;
  165. if (editor == null) return;
  166. editor?.destroy();
  167. });
  168. nextTick(() => {
  169. // 监听双向绑定值改变,用于回显
  170. watch(
  171. () => props.getHtml,
  172. (val) => {
  173. state.editorVal = val;
  174. },
  175. {
  176. deep: true,
  177. }
  178. );
  179. // 监听是否禁用改变
  180. // https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
  181. watch(
  182. () => props.disable,
  183. (bool) => {
  184. const editor = editorRef.value;
  185. if (editor == null) return;
  186. bool ? editor.disable() : editor.enable();
  187. },
  188. {
  189. deep: true,
  190. immediate: true,
  191. }
  192. );
  193. });
  194. const handleBlur = (editor: IDomEditor) => {
  195. emit('blur', editor);
  196. };
  197. // 暴露变量
  198. defineExpose({
  199. editorRef,
  200. });
  201. </script>
  202. <style scoped lang="scss">
  203. .editor-container {
  204. z-index: 99;
  205. border-radius: 5px;
  206. width: 100%;
  207. height: 100%;
  208. }
  209. </style>