170 lines
4.2 KiB
Vue
170 lines
4.2 KiB
Vue
|
|
<script setup lang="ts">
|
|||
|
|
import { computed, onBeforeUnmount, shallowRef, watch } from 'vue';
|
|||
|
|
import '@wangeditor/editor/dist/css/style.css';
|
|||
|
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
|
|||
|
|
import type { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor';
|
|||
|
|
import { uploadFile } from '@/service/api/file';
|
|||
|
|
|
|||
|
|
defineOptions({ name: 'BusinessRichTextEditor' });
|
|||
|
|
|
|||
|
|
interface Props {
|
|||
|
|
placeholder?: string;
|
|||
|
|
disabled?: boolean;
|
|||
|
|
height?: number | string;
|
|||
|
|
/** 上传目录,传给后端 directory 字段 */
|
|||
|
|
uploadDirectory?: string;
|
|||
|
|
/** 单张图片大小上限(MB),默认 5 */
|
|||
|
|
maxImageSizeMB?: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const props = withDefaults(defineProps<Props>(), {
|
|||
|
|
placeholder: '请输入内容',
|
|||
|
|
disabled: false,
|
|||
|
|
height: 320,
|
|||
|
|
uploadDirectory: undefined,
|
|||
|
|
maxImageSizeMB: 5
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const model = defineModel<string | null | undefined>({ default: '' });
|
|||
|
|
|
|||
|
|
const editorRef = shallowRef<IDomEditor>();
|
|||
|
|
|
|||
|
|
const toolbarConfig: Partial<IToolbarConfig> = {
|
|||
|
|
excludeKeys: [
|
|||
|
|
// 视频组
|
|||
|
|
'group-video',
|
|||
|
|
'insertVideo',
|
|||
|
|
'uploadVideo',
|
|||
|
|
// 更多样式分组
|
|||
|
|
'group-more-style',
|
|||
|
|
// 图片:只允许本地上传,不允许插入网络图片 URL
|
|||
|
|
'insertImage',
|
|||
|
|
// 超链接:业务暂不需要
|
|||
|
|
'insertLink',
|
|||
|
|
'editLink',
|
|||
|
|
'unLink',
|
|||
|
|
'viewLink'
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const editorConfig: Partial<IEditorConfig> = {
|
|||
|
|
placeholder: props.placeholder,
|
|||
|
|
readOnly: props.disabled,
|
|||
|
|
MENU_CONF: {
|
|||
|
|
uploadImage: {
|
|||
|
|
maxFileSize: props.maxImageSizeMB * 1024 * 1024,
|
|||
|
|
allowedFileTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/bmp'],
|
|||
|
|
async customUpload(file: File, insertFn: (url: string, alt?: string, href?: string) => void) {
|
|||
|
|
const result = await uploadFile(file, props.uploadDirectory);
|
|||
|
|
|
|||
|
|
if (result.error || !result.data) {
|
|||
|
|
const msg = result.error?.response?.data?.msg || '图片上传失败';
|
|||
|
|
window.$message?.error(msg);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const url = result.data;
|
|||
|
|
insertFn(url, file.name, url);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => props.disabled,
|
|||
|
|
value => {
|
|||
|
|
const editor = editorRef.value;
|
|||
|
|
if (!editor) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (value) {
|
|||
|
|
editor.disable();
|
|||
|
|
} else {
|
|||
|
|
editor.enable();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
function handleCreated(editor: IDomEditor) {
|
|||
|
|
editorRef.value = editor;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onBeforeUnmount(() => {
|
|||
|
|
editorRef.value?.destroy();
|
|||
|
|
editorRef.value = undefined;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/** 当 height 传 '100%' 或 'auto' 时启用「撑满父容器」模式 —— 父级必须有具体高度。 */
|
|||
|
|
const isAutoFill = computed(() => props.height === '100%' || props.height === 'auto');
|
|||
|
|
|
|||
|
|
const containerClass = computed(() => ({
|
|||
|
|
'business-rich-text-editor': true,
|
|||
|
|
'business-rich-text-editor--auto-fill': isAutoFill.value
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const editorStyle = computed(() => {
|
|||
|
|
if (isAutoFill.value) {
|
|||
|
|
return { flex: 1, minHeight: 0, overflowY: 'hidden' as const };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
height: typeof props.height === 'number' ? `${props.height}px` : props.height,
|
|||
|
|
overflowY: 'hidden' as const
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<div :class="containerClass">
|
|||
|
|
<Toolbar
|
|||
|
|
class="business-rich-text-editor__toolbar"
|
|||
|
|
:editor="editorRef"
|
|||
|
|
:default-config="toolbarConfig"
|
|||
|
|
mode="default"
|
|||
|
|
/>
|
|||
|
|
<Editor
|
|||
|
|
v-model="model"
|
|||
|
|
class="business-rich-text-editor__editor"
|
|||
|
|
:style="editorStyle"
|
|||
|
|
:default-config="editorConfig"
|
|||
|
|
mode="default"
|
|||
|
|
@on-created="handleCreated"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style scoped lang="scss">
|
|||
|
|
.business-rich-text-editor {
|
|||
|
|
width: 100%;
|
|||
|
|
border: 1px solid var(--el-border-color);
|
|||
|
|
border-radius: var(--el-border-radius-base);
|
|||
|
|
overflow: hidden;
|
|||
|
|
background: var(--el-bg-color);
|
|||
|
|
|
|||
|
|
&__toolbar {
|
|||
|
|
border-bottom: 1px solid var(--el-border-color);
|
|||
|
|
background: var(--el-fill-color-light);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&__editor {
|
|||
|
|
background: var(--el-bg-color);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&--auto-fill {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
height: 100%;
|
|||
|
|
min-height: 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* wangeditor 弹层(链接、图片菜单等)默认 z-index 偏低,提高一档避免被 ElDialog 遮挡 */
|
|||
|
|
:deep(.w-e-modal),
|
|||
|
|
:deep(.w-e-drop-panel),
|
|||
|
|
:deep(.w-e-bar-divider),
|
|||
|
|
:deep(.w-e-hover-bar) {
|
|||
|
|
z-index: 3000 !important;
|
|||
|
|
}
|
|||
|
|
</style>
|