Jelajahi Sumber

feat: Add Editor component and add editor demo

陈凯龙 3 tahun lalu
induk
melakukan
3fb3e8da39

+ 3 - 0
package.json

@@ -27,6 +27,8 @@
   "dependencies": {
     "@iconify/iconify": "^2.1.2",
     "@vueuse/core": "^7.6.0",
+    "@wangeditor/editor": "^0.14.3",
+    "@wangeditor/editor-for-vue": "^5.1.8-7",
     "@zxcvbn-ts/core": "^1.2.0",
     "animate.css": "^4.1.1",
     "axios": "^0.25.0",
@@ -65,6 +67,7 @@
     "autoprefixer": "^10.4.2",
     "commitizen": "^4.2.4",
     "consola": "^2.15.3",
+    "console": "npm:Console@^0.7.2",
     "eslint": "^8.8.0",
     "eslint-config-prettier": "^8.3.0",
     "eslint-define-config": "^1.2.4",

File diff ditekan karena terlalu besar
+ 774 - 2
pnpm-lock.yaml


+ 8 - 0
src/components/Editor/index.ts

@@ -0,0 +1,8 @@
+import Editor from './src/Editor.vue'
+import { IDomEditor } from '@wangeditor/editor'
+
+export interface EditorExpose {
+  getEditorRef: () => Promise<IDomEditor>
+}
+
+export { Editor }

+ 121 - 0
src/components/Editor/src/Editor.vue

@@ -0,0 +1,121 @@
+<script setup lang="ts">
+import { onBeforeUnmount, computed, PropType, unref, nextTick, ref, watch } from 'vue'
+import { Editor, Toolbar, getEditor, removeEditor } from '@wangeditor/editor-for-vue'
+import { IDomEditor, IEditorConfig, i18nChangeLanguage } from '@wangeditor/editor'
+import { propTypes } from '@/utils/propTypes'
+import { isNumber } from '@/utils/is'
+import { ElMessage } from 'element-plus'
+import { useLocaleStore } from '@/store/modules/locale'
+
+const localeStore = useLocaleStore()
+
+const currentLocale = computed(() => localeStore.getCurrentLocale)
+
+i18nChangeLanguage(unref(currentLocale).lang)
+
+const props = defineProps({
+  editorId: propTypes.string.def('wangeEditor-1'),
+  height: propTypes.oneOfType([Number, String]).def('500px'),
+  editorConfig: {
+    type: Object as PropType<IEditorConfig>,
+    default: () => undefined
+  },
+  defaultHtml: propTypes.string.def('')
+})
+
+const emit = defineEmits(['change'])
+
+// 编辑器配置
+const editorConfig = computed((): IEditorConfig => {
+  return Object.assign(
+    {
+      readOnly: false,
+      customAlert: (s: string, t: string) => {
+        switch (t) {
+          case 'success':
+            ElMessage.success(s)
+            break
+          case 'info':
+            ElMessage.info(s)
+            break
+          case 'warning':
+            ElMessage.warning(s)
+            break
+          case 'error':
+            ElMessage.error(s)
+            break
+          default:
+            ElMessage.info(s)
+            break
+        }
+      },
+      autoFocus: false,
+      scroll: true,
+      uploadImgShowBase64: true
+    },
+    props.editorConfig || {}
+  )
+})
+
+const editorStyle = computed(() => {
+  return {
+    height: isNumber(props.height) ? `${props.height}px` : props.height
+  }
+})
+
+// 回调函数
+const handleChange = (editor: IDomEditor) => {
+  emit('change', editor)
+}
+
+// 组件销毁时,及时销毁编辑器
+onBeforeUnmount(() => {
+  const editor = getEditor(props.editorId)
+  if (editor == null) return
+
+  // 销毁,并移除 editor
+  editor.destroy()
+  removeEditor(props.editorId)
+})
+
+const getEditorRef = async (): Promise<IDomEditor> => {
+  await nextTick()
+  return getEditor(props.editorId) as IDomEditor
+}
+
+defineExpose({
+  getEditorRef
+})
+
+const show = ref(true)
+
+watch(
+  () => props.defaultHtml,
+  () => {
+    show.value = false
+    setTimeout(() => {
+      show.value = true
+    }, 0)
+  }
+)
+</script>
+
+<template>
+  <div v-if="show" class="border-1 border-solid border-[var(--tags-view-border-color)]">
+    <!-- 工具栏 -->
+    <Toolbar
+      :editorId="editorId"
+      class="border-bottom-1 border-solid border-[var(--tags-view-border-color)]"
+    />
+    <!-- 编辑器 -->
+    <Editor
+      :editorId="editorId"
+      :defaultConfig="editorConfig"
+      :defaultHtml="defaultHtml"
+      :style="editorStyle"
+      @on-change="handleChange"
+    />
+  </div>
+</template>
+
+<style src="@wangeditor/editor/dist/css/style.css"></style>

+ 7 - 1
src/locales/en.ts

@@ -102,7 +102,9 @@ export default {
     defaultForm: 'All examples',
     search: 'Search',
     table: 'Table',
-    defaultTable: 'Basic example'
+    defaultTable: 'Basic example',
+    editor: 'Editor',
+    richText: 'Rich text'
   },
   analysis: {
     newUser: 'New user',
@@ -330,5 +332,9 @@ export default {
     hiddenExpandedRows: 'Hidden expanded rows',
     changeTitle: 'Change title',
     header: 'Header'
+  },
+  richText: {
+    richText: 'Rich text',
+    richTextDes: 'Secondary packaging based on wangeditor'
   }
 }

+ 7 - 1
src/locales/zh-CN.ts

@@ -102,7 +102,9 @@ export default {
     defaultForm: '全部示例',
     search: '查询',
     table: '表格',
-    defaultTable: '基础示例'
+    defaultTable: '基础示例',
+    editor: '编辑器',
+    richText: '富文本'
   },
   analysis: {
     newUser: '新增用户',
@@ -327,5 +329,9 @@ export default {
     hiddenExpandedRows: '隐藏展开行',
     changeTitle: '修改标题',
     header: '头部'
+  },
+  richText: {
+    richText: '富文本',
+    richTextDes: '基于 wangeditor 二次封装'
   }
 }

+ 19 - 0
src/router/index.ts

@@ -166,6 +166,25 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
           }
         ]
       },
+      {
+        path: 'editor-demo',
+        component: getParentLayout(),
+        name: 'EditorDemo',
+        meta: {
+          title: t('router.editor'),
+          alwaysShow: true
+        },
+        children: [
+          {
+            path: 'editor',
+            component: () => import('@/views/Components/Editor/Editor.vue'),
+            name: 'Editor',
+            meta: {
+              title: t('router.richText')
+            }
+          }
+        ]
+      },
       {
         path: 'search',
         component: () => import('@/views/Components/Search.vue'),

+ 32 - 0
src/views/Components/Editor/Editor.vue

@@ -0,0 +1,32 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { Editor, EditorExpose } from '@/components/Editor'
+import { useI18n } from '@/hooks/web/useI18n'
+import { IDomEditor } from '@wangeditor/editor'
+import { ref, onMounted, unref } from 'vue'
+
+const { t } = useI18n()
+
+const change = (editor: IDomEditor) => {
+  console.log(editor.getHtml())
+}
+
+const editorRef = ref<typeof Editor & EditorExpose>()
+
+const defaultHtml = ref('')
+
+onMounted(async () => {
+  const editor = await unref(editorRef)?.getEditorRef()
+  console.log(editor)
+})
+
+setTimeout(() => {
+  defaultHtml.value = '<p>hello <strong>world</strong></p>'
+}, 3000)
+</script>
+
+<template>
+  <ContentWrap :title="t('richText.richText')" :message="t('richText.richTextDes')">
+    <Editor ref="editorRef" @change="change" :defaultHtml="defaultHtml" />
+  </ContentWrap>
+</template>

+ 27 - 0
types/custom-types.d.ts

@@ -0,0 +1,27 @@
+import { SlateDescendant } from '@wangeditor/editor'
+
+declare module 'slate' {
+  interface CustomTypes {
+    // 扩展 text
+    Text: {
+      text: string
+      bold?: boolean
+      italic?: boolean
+      code?: boolean
+      through?: boolean
+      underline?: boolean
+      sup?: boolean
+      sub?: boolean
+      color?: string
+      bgColor?: string
+      fontSize?: string
+      fontFamily?: string
+    }
+
+    // 扩展 Element 的 type 属性
+    Element: {
+      type: string
+      children: SlateDescendant[]
+    }
+  }
+}

+ 3 - 1
vite.config.ts

@@ -134,7 +134,9 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
         'echarts',
         'echarts-wordcloud',
         'intro.js',
-        'qrcode'
+        'qrcode',
+        '@wangeditor/editor',
+        '@wangeditor/editor-for-vue'
       ]
     }
   }

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini