yzx 6 mēneši atpakaļ
vecāks
revīzija
1574aeca59
1 mainītis faili ar 205 papildinājumiem un 0 dzēšanām
  1. 205 0
      web/src/components/mixins/editor/wang.vue

+ 205 - 0
web/src/components/mixins/editor/wang.vue

@@ -0,0 +1,205 @@
+<template>
+    <div v-if="state.mounted" :style="style" class="ba-editor wangeditor">
+        <Toolbar class="wangeditor-toolbar" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
+        <Editor
+            :style="state.editorStyle"
+            v-model="state.value"
+            :defaultConfig="state.editorConfig"
+            :mode="mode"
+            @onCreated="handleCreated"
+            @onChange="handleChange"
+            v-bind="$attrs"
+        />
+    </div>
+</template>
+
+<script setup lang="ts">
+import '@wangeditor/editor/dist/css/style.css' // 引入 css
+import { onBeforeUnmount, reactive, shallowRef, onMounted, CSSProperties, watch } from 'vue'
+import { IEditorConfig, IToolbarConfig, i18nChangeLanguage } from '@wangeditor/editor'
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+import { useConfig } from '/@/stores/config'
+import { fileUpload } from '/@/api/common'
+import NProgress from 'nprogress'
+
+interface Props {
+    // 编辑区高度
+    height?: string
+    mode?: 'default' | 'simple'
+    placeholder?: string
+    modelValue: string | null
+    // https://www.wangeditor.com/v5/toolbar-config.html#getconfig
+    toolbarConfig?: Partial<IToolbarConfig>
+    // https://www.wangeditor.com/v5/editor-config.html#placeholder
+    editorConfig?: Partial<IEditorConfig>
+    // 编辑区style
+    editorStyle?: CSSProperties
+    // 整体的style
+    style?: CSSProperties
+    // 图片和文件上传到服务器而不是云存储
+    fileForceLocal?: boolean
+}
+
+type VideoInsertFnType = (url: string) => void
+type ImgInsertFnType = (url: string, alt: string, href: string) => void
+
+const props = withDefaults(defineProps<Props>(), {
+    height: '320px',
+    mode: 'default',
+    placeholder: '请输入内容...',
+    modelValue: '',
+    toolbarConfig: () => {
+        return {
+            excludeKeys: ['|'],
+        }
+    },
+    editorConfig: () => {
+        return {}
+    },
+    editorStyle: () => {
+        {
+            return {}
+        }
+    },
+    style: () => {
+        return {}
+    },
+    fileForceLocal: false,
+})
+
+const config = useConfig()
+const editorRef = shallowRef()
+const emits = defineEmits<{
+    (e: 'update:modelValue', value: string): void
+}>()
+
+const state: {
+    mounted: boolean
+    value: string
+    editorConfig: Partial<IEditorConfig>
+    editorStyle: CSSProperties
+} = reactive({
+    mounted: false,
+    value: !props.modelValue ? '<p></p>' : props.modelValue,
+    editorConfig: props.editorConfig,
+    editorStyle: props.editorStyle,
+})
+
+onMounted(() => {
+    i18nChangeLanguage(config.lang.defaultLang == 'zh-cn' ? 'zh-CN' : config.lang.defaultLang)
+    state.editorConfig.placeholder = props.placeholder
+
+    // 图片上传配置
+    state.editorConfig.MENU_CONF = {}
+    state.editorConfig.MENU_CONF['uploadImage'] = {
+        fieldName: 'file',
+        maxFileSize: 10 * 1024 * 1024, // 10M
+        async customUpload(file: File, insertFn: ImgInsertFnType) {
+            NProgress.configure({ showSpinner: true, trickle: false })
+            NProgress.start()
+            let fd = new FormData()
+            fd.append('file', file)
+            fileUpload(fd, {}, props.fileForceLocal, {
+                onUploadProgress: (evt) => {
+                    NProgress.set(evt.progress!)
+                },
+            }).then((res) => {
+                if (res.code == 1) {
+                    insertFn(res.data.file.full_url, res.data.file.name, res.data.file.full_url)
+                }
+                NProgress.done()
+            })
+        },
+    }
+
+    // 视频上传配置
+    state.editorConfig.MENU_CONF['uploadVideo'] = {
+        fieldName: 'file',
+        async customUpload(file: File, insertFn: VideoInsertFnType) {
+            NProgress.configure({ showSpinner: true, trickle: false })
+            NProgress.start()
+            let fd = new FormData()
+            fd.append('file', file)
+            fileUpload(fd, {}, props.fileForceLocal, {
+                onUploadProgress: (evt) => {
+                    NProgress.set(evt.progress!)
+                },
+            }).then((res) => {
+                if (res.code == 1) {
+                    insertFn(res.data.file.full_url)
+                }
+                NProgress.done()
+            })
+        },
+    }
+
+    state.editorStyle.height = props.height
+    state.editorStyle['overflow-y'] = 'hidden'
+    state.mounted = true
+})
+
+// 组件销毁时,也及时销毁编辑器
+onBeforeUnmount(() => {
+    if (editorRef.value == null) return
+    editorRef.value.destroy()
+})
+
+const handleCreated = (editor: any) => {
+    editorRef.value = editor // 记录 editor 实例
+}
+
+const handleChange = () => {
+    emits('update:modelValue', editorRef.value.getHtml())
+}
+
+const getRef = () => {
+    return editorRef.value
+}
+
+defineExpose({
+    getRef,
+})
+
+watch(
+    () => props.modelValue,
+    (newVal) => {
+        state.value = !newVal ? '<p></p>' : newVal
+    }
+)
+</script>
+
+<style scoped lang="scss">
+.ba-editor {
+    border: 1px solid var(--el-border-color-lighter);
+    z-index: 9999;
+    :deep(.w-e-scroll) {
+        scrollbar-width: none;
+        &::-webkit-scrollbar {
+            width: 5px;
+        }
+        &::-webkit-scrollbar-thumb {
+            background: #eaeaea;
+            border-radius: var(--el-border-radius-base);
+            box-shadow: none;
+            -webkit-box-shadow: none;
+        }
+        &:hover {
+            &::-webkit-scrollbar-thumb:hover {
+                background: #c8c9cc;
+            }
+        }
+    }
+}
+.wangeditor-toolbar {
+    border-bottom: 1px solid var(--el-border-color-lighter);
+}
+
+// 暗黑样式
+@at-root .dark {
+    .ba-editor {
+        --w-e-textarea-bg-color: var(--ba-bg-color-overlay);
+        --w-e-toolbar-bg-color: var(--ba-bg-color-overlay);
+        --w-e-toolbar-border-color: var(--el-color-info-light-3);
+    }
+}
+</style>