首先需要安装富文本插件
npm add vue-ueditor-wrap
或
pnpm add vue-ueditor-wrap
这里我用的是我自己的包,不是官方的
然后下载压缩包解压至项目中的public文件夹里面
然后再main.js 里面配置,引入我们的富文本插件
import { createApp } from 'vue';
import pinia from '/@/stores/index';
import App from '/@/App.vue';
import router from '/@/router';
import { directive } from '/@/directive/index';
import { i18n } from '/@/i18n/index';
import other from '/@/utils/other';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import ElementPlus from 'element-plus';
import '/@/theme/index.scss';
import VueGridLayout from 'vue-grid-layout';
// 引入 UEditor 富文本插件
import '../public/UEditor/ueditor.config.js';
import '../public/UEditor/ueditor.all.min.js';
import '../public/UEditor/lang/zh-cn/zh-cn.js';
import '../public/UEditor/themes/default/css/ueditor.css'; // 确保引入 UEditor 的 CSS 文件
const app = createApp(App);
directive(app);
other.elSvg(app);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.use(pinia).use(router).use(ElementPlus).use(i18n).use(VueGridLayout).mount('#app');
然后再src目录下方的components文件夹里面创建文件UEditor.vue进行配置
<!-- UEditor 富文本编辑器组件,支持图片上传及多图上传 -->
<template>
<div ref="editor"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
declare global {
interface Window {
UE: {
getEditor: (element: HTMLElement | string, options?: Record<string, any>) => any;
delEditor: (element: HTMLElement | string) => void;
};
loader: {
load: (resources: string | string[], callback?: () => void, errorCallback?: () => void) => void;
loadJs: (url: string, callback?: () => void, errorCallback?: () => void) => void;
loadCss: (url: string, callback?: () => void, errorCallback?: () => void) => void;
loadImg: (url: string, callback?: () => void, errorCallback?: () => void) => void;
};
}
}
// 完整实现loader对象
if (!window.loader) {
window.loader = {
load: (resources: string | string[], callback?: () => void, errorCallback?: () => void) => {
console.log('加载资源:', resources);
// 处理单个资源或资源数组
const resourceArray = Array.isArray(resources) ? resources : [resources];
let loadedCount = 0;
resourceArray.forEach((resource, index) => {
if (resource.endsWith('.js')) {
// 加载JS
loadScript(resource, () => {
if (++loadedCount === resourceArray.length && callback) {
callback();
}
}, errorCallback);
} else if (resource.endsWith('.css')) {
// 加载CSS
loadStyle(resource, () => {
if (++loadedCount === resourceArray.length && callback) {
callback();
}
}, errorCallback);
} else {
// 未知类型,直接回调
if (++loadedCount === resourceArray.length && callback) {
callback();
}
}
});
},
loadJs: (url: string, callback?: () => void, errorCallback?: () => void) => {
console.log('加载JS:', url);
loadScript(url, callback, errorCallback);
},
loadCss: (url: string, callback?: () => void, errorCallback?: () => void) => {
console.log('加载CSS:', url);
loadStyle(url, callback, errorCallback);
},
loadImg: (url: string, callback?: () => void, errorCallback?: () => void) => {
console.log('加载图片:', url);
const img = new Image();
img.onload = () => callback?.();
img.onerror = () => errorCallback?.();
img.src = url;
}
};
}
// 辅助函数:加载JS脚本
const loadScript = (url: string, callback?: () => void, errorCallback?: () => void) => {
const script = document.createElement('script');
script.src = url;
script.onload = () => callback?.();
script.onerror = () => errorCallback?.();
document.head.appendChild(script);
};
// 辅助函数:加载CSS样式
const loadStyle = (url: string, callback?: () => void, errorCallback?: () => void) => {
const link = document.createElement('link');
link.href = url;
link.rel = 'stylesheet';
link.onload = () => callback?.();
link.onerror = () => errorCallback?.();
document.head.appendChild(link);
};
const props = defineProps({
modelValue: {
type: String,
default: '',
},
});
const emit = defineEmits(['update:modelValue', 'content-change']);
const editor = ref<HTMLElement | null>(null);
let ueditorInstance: any = null;
const ueditorHomeUrl = `${window.location.origin}/UEditor/`;
onMounted(() => {
nextTick(() => {
initUEditor();
});
});
onBeforeUnmount(() => {
destroyUEditor();
});
const initUEditor = () => {
if (!editor.value || !window.UE) return;
try {
ueditorInstance = window.UE.getEditor(editor.value, {
initialFrameWidth: "100%",
initialFrameHeight: 200,
autoHeightEnabled: false,
serverUrl: "/pc/file/index",//改成自己的富文本上传接口
imageUrl: "/pc/file/index",//改成自己的富文本上传接口
UEDITOR_HOME_URL: ueditorHomeUrl,
imageFieldName: "upfile",
});
ueditorInstance.ready(() => {
const processedContent = processContent(props.modelValue);
ueditorInstance.setContent(processedContent);
ueditorInstance.addListener('contentChange', () => {
const content = ueditorInstance.getContent();
emit('update:modelValue', content);
emit('content-change', content);
});
ueditorInstance.addListener('afterInsertImage', () => {
setTimeout(() => {
replaceImagesInEditor();
}, 100);
});
ueditorInstance.addListener('aftersetcontent', () => {
setTimeout(() => {
replaceImagesInEditor();
}, 100);
});
});
} catch (error) {
console.error('UEditor初始化失败:', error);
}
};
const processContent = (content: string) => {
if (!content) return '';
return content.replace(/<img[^>]+>/g, (imgTag) => {
const srcMatch = imgTag.match(/src="([^"]+)"/);
const src = srcMatch ? srcMatch[1] : '';
if (!src) return imgTag;
return `
<img
src="${src}"
style="
width: 200px !important;
height: 200px !important;
object-fit: contain !important;
display: inline-block !important;
margin: 5px !important;
vertical-align: middle !important;
border: 1px solid #eee !important;
"
>
`;
});
};
const replaceImagesInEditor = () => {
if (!ueditorInstance) return;
try {
const body = ueditorInstance.document.body;
const images = body.querySelectorAll('img');
images.forEach((img:any) => {
const src = img.getAttribute('src') || '';
if (src) {
const newImg = document.createElement('img');
newImg.src = src;
newImg.style.width = '200px';
newImg.style.height = '200px';
newImg.style.objectFit = 'contain';
newImg.style.display = 'inline-block';
newImg.style.margin = '5px';
newImg.style.verticalAlign = 'middle';
newImg.style.border = '1px solid #eee';
img.parentNode?.replaceChild(newImg, img);
}
});
} catch (error) {
console.error('替换图片失败:', error);
}
};
const destroyUEditor = () => {
if (ueditorInstance && editor.value && window.UE) {
window.UE.delEditor(editor.value);
ueditorInstance = null;
}
};
watch(
() => props.modelValue,
(newValue) => {
if (ueditorInstance && newValue !== ueditorInstance.getContent()) {
const processedContent = processContent(newValue);
ueditorInstance.setContent(processedContent);
}
}
);
</script>
<style>
.edui-container {
width: 100% !important;
}
</style>
(该配置里面也包含图片上传及多图上传)配置完成之后,在你所需页面引入使用
import UEditor from "/@/components/UEditor.vue";// 富文本引入
在所需要使用的地方写
<UEditor v-model="form.content" @content-change="handleContentChange" />
<!-- 富文本使用 -->
// 富文本手动触发校验
const handleContentChange = () => {
nextTick(() => {
formRef.value?.validateField('content'); // 手动触发验证
});
};
如果你需要上传图片,上传图片报错,说未配置
serverUrl:"/pcapi/editor/index",//根据实际情况配置,serverUrl修改成自己的富文本编辑的接口即可。
如果还有其他地方不懂的或者有什么问题,可以上网搜索自行查找🌹