|
@@ -2,226 +2,231 @@
|
|
<div v-if="state.mounted" :style="style" class="ba-editor wangeditor" v-loading="state.loading">
|
|
<div v-if="state.mounted" :style="style" class="ba-editor wangeditor" v-loading="state.loading">
|
|
<Toolbar class="wangeditor-toolbar" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
|
|
<Toolbar class="wangeditor-toolbar" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
|
|
<Editor ref="editor" :style="state.editorStyle" v-model="state.value" :defaultConfig="state.editorConfig"
|
|
<Editor ref="editor" :style="state.editorStyle" v-model="state.value" :defaultConfig="state.editorConfig"
|
|
- :mode="mode" @onCreated="handleCreated" @onChange="handleChange" v-bind="$attrs"/>
|
|
|
|
|
|
+ :mode="mode" @onCreated="handleCreated" @onChange="handleChange" v-bind="$attrs" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
-import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
|
|
|
-import { onBeforeUnmount, reactive, shallowRef, onMounted, CSSProperties, watch, nextTick, ref } from 'vue'
|
|
|
|
-import { IEditorConfig, IToolbarConfig, i18nChangeLanguage, IDomEditor } 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'
|
|
|
|
-import { stat } from 'fs'
|
|
|
|
|
|
+import "@wangeditor/editor/dist/css/style.css"; // 引入 css
|
|
|
|
+import { onBeforeUnmount, reactive, shallowRef, onMounted, CSSProperties, watch, nextTick, ref } from "vue";
|
|
|
|
+import { IEditorConfig, IToolbarConfig, i18nChangeLanguage, IDomEditor } 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";
|
|
|
|
+import { stat } from "fs";
|
|
|
|
|
|
interface Props {
|
|
interface Props {
|
|
- // 编辑区高度
|
|
|
|
- height?: string
|
|
|
|
- mode?: 'default' | 'simple'
|
|
|
|
- placeholder?: string
|
|
|
|
- modelValue: string
|
|
|
|
- // 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
|
|
|
|
- disable?: boolean
|
|
|
|
|
|
+ // 编辑区高度
|
|
|
|
+ height?: string;
|
|
|
|
+ mode?: "default" | "simple";
|
|
|
|
+ placeholder?: string;
|
|
|
|
+ modelValue: string;
|
|
|
|
+ // 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;
|
|
|
|
+ disable?: boolean;
|
|
}
|
|
}
|
|
|
|
|
|
type VideoInsertFnType = (url: string, poster: string) => void
|
|
type VideoInsertFnType = (url: string, poster: string) => void
|
|
type ImgInsertFnType = (url: string, alt: string, href: string) => void
|
|
type ImgInsertFnType = (url: string, alt: string, href: string) => void
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
- height: '320px',
|
|
|
|
- mode: 'default',
|
|
|
|
- placeholder: '请输入内容...',
|
|
|
|
- modelValue: '',
|
|
|
|
- disable: false,
|
|
|
|
- toolbarConfig: () => {
|
|
|
|
- return {}
|
|
|
|
- },
|
|
|
|
- editorConfig: () => {
|
|
|
|
- return {}
|
|
|
|
- },
|
|
|
|
- editorStyle: () => {
|
|
|
|
- {
|
|
|
|
- return {}
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- style: () => {
|
|
|
|
- return {}
|
|
|
|
- },
|
|
|
|
-})
|
|
|
|
-
|
|
|
|
-const config = useConfig()
|
|
|
|
-const editorRef = shallowRef()
|
|
|
|
|
|
+ height: "320px",
|
|
|
|
+ mode: "default",
|
|
|
|
+ placeholder: "请输入内容...",
|
|
|
|
+ modelValue: "",
|
|
|
|
+ disable: false,
|
|
|
|
+ toolbarConfig: () => {
|
|
|
|
+ return {};
|
|
|
|
+ },
|
|
|
|
+ editorConfig: () => {
|
|
|
|
+ return {};
|
|
|
|
+ },
|
|
|
|
+ editorStyle: () => {
|
|
|
|
+ {
|
|
|
|
+ return {};
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ style: () => {
|
|
|
|
+ return {};
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const config = useConfig();
|
|
|
|
+const editorRef = shallowRef();
|
|
const emits = defineEmits<{
|
|
const emits = defineEmits<{
|
|
- (e: 'update:modelValue', value: string): void
|
|
|
|
-}>()
|
|
|
|
|
|
+ (e: "update:modelValue", value: string): void
|
|
|
|
+}>();
|
|
|
|
|
|
const state: {
|
|
const state: {
|
|
- mounted: boolean
|
|
|
|
- value: string
|
|
|
|
- editorConfig: Partial<IEditorConfig>
|
|
|
|
- editorStyle: CSSProperties
|
|
|
|
- loading: boolean
|
|
|
|
|
|
+ mounted: boolean
|
|
|
|
+ value: string
|
|
|
|
+ editorConfig: Partial<IEditorConfig>
|
|
|
|
+ editorStyle: CSSProperties
|
|
|
|
+ loading: boolean
|
|
} = reactive({
|
|
} = reactive({
|
|
- mounted: false,
|
|
|
|
- value: props.modelValue,
|
|
|
|
- editorConfig: props.editorConfig,
|
|
|
|
- editorStyle: props.editorStyle,
|
|
|
|
- loading: false,
|
|
|
|
-})
|
|
|
|
|
|
+ mounted: false,
|
|
|
|
+ value: props.modelValue,
|
|
|
|
+ editorConfig: props.editorConfig,
|
|
|
|
+ editorStyle: props.editorStyle,
|
|
|
|
+ loading: false
|
|
|
|
+});
|
|
|
|
|
|
onMounted(() => {
|
|
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
|
|
|
|
- onProgress(progress: number) {
|
|
|
|
- NProgress.inc()
|
|
|
|
- },
|
|
|
|
- async customUpload(file: File, insertFn: ImgInsertFnType) {
|
|
|
|
- NProgress.configure({ showSpinner: true })
|
|
|
|
- NProgress.start()
|
|
|
|
- let fd = new FormData()
|
|
|
|
- fd.append('file', file)
|
|
|
|
- state.loading = true
|
|
|
|
- fileUpload(fd)
|
|
|
|
- .then((res) => {
|
|
|
|
- if (res.code == 1) {
|
|
|
|
- insertFn(res.data.previewUrl, res.data.originalFilename, res.data.previewUrl)
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
- NProgress.done()
|
|
|
|
- state.loading = false
|
|
|
|
- })
|
|
|
|
- .catch((err) => {
|
|
|
|
- NProgress.done()
|
|
|
|
- state.loading = false
|
|
|
|
- })
|
|
|
|
- },
|
|
|
|
|
|
+ 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
|
|
|
|
+ onProgress(progress: number) {
|
|
|
|
+ NProgress.inc();
|
|
|
|
+ },
|
|
|
|
+ async customUpload(file: File, insertFn: ImgInsertFnType) {
|
|
|
|
+ NProgress.configure({ showSpinner: true });
|
|
|
|
+ NProgress.start();
|
|
|
|
+ let fd = new FormData();
|
|
|
|
+ fd.append("file", file);
|
|
|
|
+ state.loading = true;
|
|
|
|
+ fileUpload(fd)
|
|
|
|
+ .then((res) => {
|
|
|
|
+ if (res.code == 1) {
|
|
|
|
+ insertFn(res.data.previewUrl, res.data.originalFilename, res.data.previewUrl);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ NProgress.done();
|
|
|
|
+ state.loading = false;
|
|
|
|
+ })
|
|
|
|
+ .catch((err) => {
|
|
|
|
+ NProgress.done();
|
|
|
|
+ state.loading = false;
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
+ };
|
|
|
|
|
|
- // 视频上传配置
|
|
|
|
- state.editorConfig.MENU_CONF['uploadVideo'] = {
|
|
|
|
- fieldName: 'file',
|
|
|
|
- onProgress(progress: number) {
|
|
|
|
- NProgress.inc()
|
|
|
|
- },
|
|
|
|
- async customUpload(file: File, insertFn: VideoInsertFnType) {
|
|
|
|
- NProgress.configure({ showSpinner: true })
|
|
|
|
- NProgress.start()
|
|
|
|
- let fd = new FormData()
|
|
|
|
- fd.append('file', file)
|
|
|
|
- state.loading = true
|
|
|
|
- fileUpload(fd)
|
|
|
|
- .then((res) => {
|
|
|
|
- console.log('上传视频', res.data)
|
|
|
|
- if (res.code == 1) {
|
|
|
|
- let poster = 'http://admin.baoxiaojia.dingsenhulian.com/uploadFile/1596051044679421952.jpg'
|
|
|
|
- insertFn(res.data.previewUrl, poster)
|
|
|
|
- }
|
|
|
|
- NProgress.done()
|
|
|
|
- state.loading = false
|
|
|
|
- })
|
|
|
|
- .catch((err) => {
|
|
|
|
- NProgress.done()
|
|
|
|
- state.loading = false
|
|
|
|
- })
|
|
|
|
- },
|
|
|
|
|
|
+ // 视频上传配置
|
|
|
|
+ state.editorConfig.MENU_CONF["uploadVideo"] = {
|
|
|
|
+ fieldName: "file",
|
|
|
|
+ onProgress(progress: number) {
|
|
|
|
+ NProgress.inc();
|
|
|
|
+ },
|
|
|
|
+ async customUpload(file: File, insertFn: VideoInsertFnType) {
|
|
|
|
+ NProgress.configure({ showSpinner: true });
|
|
|
|
+ NProgress.start();
|
|
|
|
+ let fd = new FormData();
|
|
|
|
+ fd.append("file", file);
|
|
|
|
+ state.loading = true;
|
|
|
|
+ fileUpload(fd)
|
|
|
|
+ .then((res) => {
|
|
|
|
+ console.log("上传视频", res.data);
|
|
|
|
+ if (res.code == 1) {
|
|
|
|
+ let poster = "http://admin.baoxiaojia.dingsenhulian.com/uploadFile/1596051044679421952.jpg";
|
|
|
|
+ insertFn(res.data.previewUrl, poster);
|
|
|
|
+ }
|
|
|
|
+ NProgress.done();
|
|
|
|
+ state.loading = false;
|
|
|
|
+ })
|
|
|
|
+ .catch((err) => {
|
|
|
|
+ NProgress.done();
|
|
|
|
+ state.loading = false;
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
+ };
|
|
|
|
|
|
- state.editorStyle.height = props.height
|
|
|
|
- state.editorStyle['overflow-y'] = 'hidden'
|
|
|
|
- state.mounted = true
|
|
|
|
|
|
+ state.editorStyle.height = props.height;
|
|
|
|
+ state.editorStyle["overflow-y"] = "hidden";
|
|
|
|
+ state.mounted = true;
|
|
|
|
|
|
- //设置只读
|
|
|
|
- nextTick(() => {
|
|
|
|
- if (props.disable) {
|
|
|
|
- editorRef.value.disable()
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
-})
|
|
|
|
|
|
+ //设置只读
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ if (props.disable) {
|
|
|
|
+ editorRef.value.disable();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+});
|
|
|
|
|
|
-const editor = ref()
|
|
|
|
|
|
+const editor = ref();
|
|
|
|
|
|
// 自定义粘贴事件
|
|
// 自定义粘贴事件
|
|
const handlePaste = (
|
|
const handlePaste = (
|
|
- editor: IDomEditor,
|
|
|
|
- event: ClipboardEvent,
|
|
|
|
- callback: (val: boolean) => void
|
|
|
|
- ) => {
|
|
|
|
- // editor.insertText('test')
|
|
|
|
- callback(false)
|
|
|
|
- }
|
|
|
|
|
|
+ editor: IDomEditor,
|
|
|
|
+ event: ClipboardEvent,
|
|
|
|
+ callback: (val: boolean) => void
|
|
|
|
+) => {
|
|
|
|
+ // editor.insertText('test')
|
|
|
|
+ callback(false);
|
|
|
|
+};
|
|
|
|
|
|
// 组件销毁时,也及时销毁编辑器
|
|
// 组件销毁时,也及时销毁编辑器
|
|
onBeforeUnmount(() => {
|
|
onBeforeUnmount(() => {
|
|
- if (editorRef.value == null) return
|
|
|
|
- editorRef.value.destroy()
|
|
|
|
-})
|
|
|
|
|
|
+ if (editorRef.value == null) return;
|
|
|
|
+ editorRef.value.destroy();
|
|
|
|
+});
|
|
|
|
|
|
const handleCreated = (editor: any) => {
|
|
const handleCreated = (editor: any) => {
|
|
- editorRef.value = editor // 记录 editor 实例
|
|
|
|
-}
|
|
|
|
|
|
+ editorRef.value = editor; // 记录 editor 实例
|
|
|
|
+};
|
|
|
|
|
|
const handleChange = () => {
|
|
const handleChange = () => {
|
|
- emits('update:modelValue', editorRef.value.getHtml())
|
|
|
|
-}
|
|
|
|
|
|
+ emits("update:modelValue", editorRef.value.getHtml());
|
|
|
|
+};
|
|
|
|
|
|
const getRef = () => {
|
|
const getRef = () => {
|
|
- return editorRef.value
|
|
|
|
-}
|
|
|
|
|
|
+ return editorRef.value;
|
|
|
|
+};
|
|
|
|
|
|
defineExpose({
|
|
defineExpose({
|
|
- getRef,
|
|
|
|
-})
|
|
|
|
|
|
+ getRef
|
|
|
|
+});
|
|
|
|
|
|
watch(
|
|
watch(
|
|
- () => props.modelValue,
|
|
|
|
- (newVal) => {
|
|
|
|
- state.value = newVal
|
|
|
|
- }
|
|
|
|
-)
|
|
|
|
|
|
+ () => props.modelValue,
|
|
|
|
+ (newVal) => {
|
|
|
|
+ state.value = newVal;
|
|
|
|
+ }
|
|
|
|
+);
|
|
</script>
|
|
</script>
|
|
|
|
|
|
-<style lang="scss">
|
|
|
|
|
|
+<style lang="scss">
|
|
.ba-editor {
|
|
.ba-editor {
|
|
- border: 1px solid var(--color-sub-3);
|
|
|
|
- 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;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ border: 1px solid var(--color-sub-3);
|
|
|
|
+ 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 {
|
|
.wangeditor-toolbar {
|
|
- border-bottom: 1px solid var(--color-sub-3);
|
|
|
|
|
|
+ border-bottom: 1px solid var(--color-sub-3);
|
|
}
|
|
}
|
|
|
|
|
|
video {
|
|
video {
|
|
- width: 100%;
|
|
|
|
|
|
+ width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
</style>
|