sa-editor.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <template>
  2. <div class="sa-editor">
  3. <Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
  4. <Editor style="height: 400px; overflow-y: hidden" :defaultConfig="editorConfig" :mode="mode" v-model="valueHtml"
  5. @onCreated="handleCreated" @onChange="handleChange" />
  6. </div>
  7. </template>
  8. <script setup>
  9. import '@wangeditor/editor/dist/css/style.css';
  10. import { onBeforeUnmount, ref, shallowRef, onMounted, watch, nextTick } from 'vue';
  11. import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
  12. import { i18nChangeLanguage } from '@wangeditor/editor';
  13. import { useFile } from '@/sheep/hooks';
  14. import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
  15. import adminApi from '@/app/admin/api';
  16. import { ElMessage } from 'element-plus';
  17. import { useI18n } from 'vue-i18n';
  18. const { t, locale } = useI18n();
  19. // 根据当前语言设置编辑器语言
  20. const editorLang = locale.value === 'zh-CN' ? 'zh-CN' : 'en';
  21. i18nChangeLanguage(editorLang);
  22. const emits = defineEmits(['update:content']);
  23. const props = defineProps({
  24. content: {
  25. type: String,
  26. default: '',
  27. },
  28. directUpload: {
  29. type: Boolean,
  30. default: false,
  31. },
  32. });
  33. const mode = 'default';
  34. // 编辑器实例,必须用 shallowRef
  35. const editorRef = shallowRef();
  36. // 内容 HTML
  37. const valueHtml = ref(props.content);
  38. watch(
  39. () => props.content,
  40. () => {
  41. valueHtml.value = props.content;
  42. nextTick(() => {
  43. editorRef.value.focus();
  44. });
  45. },
  46. );
  47. const toolbarConfig = {};
  48. const editorConfig = { MENU_CONF: {} };
  49. // 根据 directUpload 参数选择上传方式
  50. if (props.directUpload) {
  51. // 直接上传模式
  52. editorConfig.MENU_CONF['uploadImage'] = {
  53. // 自定义上传
  54. async customUpload(file, insertFn) {
  55. try {
  56. const formData = new FormData();
  57. formData.append('file', file);
  58. const response = await adminApi.file.upload({}, formData);
  59. if (response.code == '200') {
  60. insertFn(checkUrl(response.data), '', '');
  61. } else {
  62. ElMessage.error(response.msg || t('message.imageUploadFailed'));
  63. }
  64. } catch (error) {
  65. console.error('Upload image failed:', error);
  66. ElMessage.error(t('message.imageUploadFailed'));
  67. }
  68. },
  69. };
  70. } else {
  71. // 文件管理器模式
  72. editorConfig.MENU_CONF['uploadImage'] = {
  73. // 自定义选择图片
  74. customBrowseAndUpload(insertFn) {
  75. useFile(
  76. {
  77. fileType: 'image',
  78. multiple: true,
  79. },
  80. {
  81. confirm: (data) => {
  82. data.forEach((item) => {
  83. insertFn(checkUrl(item.url), '', ''); // url alt href
  84. });
  85. },
  86. },
  87. );
  88. },
  89. };
  90. }
  91. editorConfig.MENU_CONF['uploadVideo'] = {
  92. // 自定义选择视频
  93. customBrowseAndUpload(insertFn) {
  94. useFile(
  95. {
  96. fileType: 'video',
  97. multiple: true,
  98. },
  99. {
  100. confirm: (data) => {
  101. data.forEach((item) => {
  102. insertFn(checkUrl(item.url), ''); // url poster
  103. });
  104. },
  105. },
  106. );
  107. },
  108. };
  109. editorConfig.MENU_CONF['lineHeight'] = {
  110. lineHeightList: ['0', '1', '1.5', '2', '2.5'],
  111. };
  112. // 编辑器回调函数
  113. const handleCreated = (editor) => {
  114. editorRef.value = editor; // 记录 editor 实例
  115. };
  116. const handleChange = (editor) => {
  117. emits('update:content', editor.getHtml());
  118. };
  119. onMounted(() => {
  120. nextTick(() => {
  121. editorRef.value.focus();
  122. });
  123. });
  124. onBeforeUnmount(() => {
  125. const editor = editorRef.value;
  126. if (editor == null) return;
  127. editor.destroy();
  128. });
  129. </script>
  130. <style lang="scss" scoped>
  131. .sa-editor {
  132. border: 1px solid var(--sa-border);
  133. :deep() {
  134. .w-e-bar {
  135. border-bottom: 1px solid var(--sa-border);
  136. }
  137. }
  138. }
  139. </style>
  140. <style lang="scss">
  141. // 全局样式覆盖,确保富文本编辑器的加粗等样式正常显示
  142. .w-e-text-container [data-slate-editor] {
  143. strong,
  144. strong.token.bold,
  145. b {
  146. font-weight: bold !important;
  147. }
  148. em,
  149. i {
  150. font-style: italic !important;
  151. }
  152. u {
  153. text-decoration: underline !important;
  154. }
  155. s,
  156. strike {
  157. text-decoration: line-through !important;
  158. }
  159. }
  160. </style>