【基于 WangEditor v5 + Vue2 封装 CSDN 风格富文本组件】
基于 WangEditor v5 + Vue2 封装 CSDN 风格富文本组件,
一、wangEditor封装及使用技术文章大纲
引言
- 介绍wangEditor的基本信息(轻量级富文本编辑器、开源、功能特点)
- 封装的目的和意义(提高复用性、统一配置、简化调用)
wangEditor基础集成
- 通过npm或CDN引入wangEditor
- 初始化编辑器的基本代码示例
- 核心配置项说明(如菜单配置、上传图片配置)
封装策略设计
- 创建独立Vue/React组件封装编辑器
- 通过props传递配置参数(如工具栏选项、初始内容)
- 暴露编辑器实例方法(如获取内容、清空内容)
关键功能封装实现
- 图片/视频上传功能统一处理(对接OSS或后端API)
- 自定义扩展菜单项的实现方法
- 内容变化监听与双向数据绑定处理
主题样式定制
- 覆盖默认样式实现UI主题定制
- 响应式布局适配方案
- 暗黑模式等主题切换实现
典型应用场景
- 表单中的富文本输入场景
- 与Markdown的双向转换实现
- 协同编辑场景下的封装注意事项
性能优化建议
- 懒加载实现方案
- 大文档编辑时的性能处理
- 销毁实例避免内存泄漏
常见问题解决方案
- XSS安全防护配置
- 粘贴内容格式处理
- 移动端适配问题修复
测试与部署
- 单元测试编写要点
- 构建为独立npm包的方法
- 版本更新与维护策略
结语
- 封装带来的收益总结
- 未来可扩展方向(插件系统、AI集成等)
- 官方资源与社区支持信息CSDN 风格富文本组件封装(Vue2)
<template><div class="csdn-editor-container"><!-- 编辑器工具栏(CSDN 风格:简洁+核心功能) --><Toolbar:editor="editor":defaultConfig="toolbarConfig":mode="mode"style="border-bottom: 1px solid #e5e7eb; background: #f8f9fa; padding: 4px 8px;"/><!-- 富文本编辑区(CSDN 经典白色背景+代码高亮适配) --><Editorv-model="html":defaultConfig="editorConfig":mode="mode"style="height: 600px; overflow-y: auto; background: #fff; font-size: 15px; line-height: 1.8;"@onCreated="onCreated"@onChange="onChange"@customPaste="customPaste"/></div>
</template><script>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css'
// 引入代码高亮插件(CSDN 风格高亮)
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css' // CSDN 常用深色代码主题export default Vue.extend({name: 'CsdnWangEditor',components: { Editor, Toolbar },props: {// 双向绑定内容value: {type: String,default: '<p>请输入博客内容...</p>'},// 编辑模式(default:富文本 / simple:精简模式)mode: {type: String,default: 'default'},// 最大字数限制(CSDN 默认无限制,可自定义)maxLength: {type: Number,default: 0}},data() {return {editor: null,html: this.value,// 工具栏配置(复刻 CSDN 核心功能)toolbarConfig: {excludeKeys: ['insertVideo', // 隐藏视频插入(CSDN 需单独配置)'fullScreen', // 可选:保留/隐藏全屏按钮'codeBlock', // 替换为自定义代码块(支持高亮)'fontSize', 'fontFamily' // CSDN 默认不显示字体字号工具栏],// 自定义工具栏顺序(CSDN 风格)order: ['bold', 'italic', 'underline', 'strikeThrough', 'sub', 'sup','|', 'fontColor', 'bgColor', 'clearStyle','|', 'header', 'blockquote', 'code','|', 'list', 'todo', 'align', 'lineHeight','|', 'link', 'image', 'table', 'hr','|', 'undo', 'redo', 'preview', 'print']},// 编辑器配置(适配 CSDN 场景)editorConfig: {placeholder: '在这里写下你的技术博客...',// 图片上传(CSDN 风格:支持拖拽/粘贴/点击上传)MENU_CONF: {uploadImage: {// 最大文件大小(CSDN 限制 5MB)maxFileSize: 5 * 1024 * 1024,// 支持的图片格式accept: ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'],// 自定义上传逻辑(对接 CSDN 图片接口或自己的 OSS)async customUpload(file, insertFn) {// 示例:模拟 CSDN 图片上传(实际需替换为真实接口)const formData = new FormData()formData.append('file', file)// 这里替换为 CSDN 开放接口或自建上传接口// const res = await axios.post('CSDN_UPLOAD_API', formData)// insertFn(res.data.url) // 上传成功后插入图片// 模拟上传成功(测试用)setTimeout(() => {const mockUrl = `https://picsum.photos/800/400?random=${Math.random()}`insertFn(mockUrl)}, 1000)},// 显示上传进度onProgress(progress) {console.log('图片上传进度:', progress)}},// 代码块配置(支持 CSDN 常用语言)codeBlock: {languages: [{ value: 'html', name: 'HTML' },{ value: 'css', name: 'CSS' },{ value: 'javascript', name: 'JavaScript' },{ value: 'vue', name: 'Vue' },{ value: 'react', name: 'React' },{ value: 'java', name: 'Java' },{ value: 'python', name: 'Python' },{ value: 'sql', name: 'SQL' },{ value: 'shell', name: 'Shell' }]}},// 字数统计(CSDN 风格:实时显示)maxLength: this.maxLength,// 粘贴处理(保留 CSDN 格式,过滤无用样式)pasteFilterStyle: true,pasteIgnoreImg: false, // 允许粘贴图片// 自定义粘贴逻辑(如粘贴 Markdown 自动转换)customPaste: (editor, event, callback) => {const text = event.clipboardData.getData('text/plain')// 若粘贴的是 Markdown 文本,可自动转换为 HTML(需引入 markdown-it)// const html = markdownit().render(text)// editor.insertHtml(html)// callback(false) // 阻止默认粘贴callback(true) // 保留默认粘贴行为}}}},watch: {value(newVal) {this.html = newVal // 双向绑定同步}},methods: {onCreated(editor) {this.editor = Object.seal(editor) // 必须用 Object.seal() 避免报错// 初始化代码高亮(CSDN 风格)this.initCodeHighlight(editor)},onChange(editor) {this.html = editor.getHtml()this.$emit('input', this.html) // 同步给父组件this.$emit('change', editor)},// 自定义粘贴处理(适配 CSDN 粘贴逻辑)customPaste(editor, event, callback) {// 过滤 Word 粘贴的冗余样式(CSDN 常用优化)const html = event.clipboardData.getData('text/html')if (html.includes('msword')) {// 清除 Word 样式const cleanHtml = html.replace(/<style[\s\S]*?<\/style>/gi, '').replace(/class="[^"]*"/gi, '').replace(/style="[^"]*"/gi, '')editor.insertHtml(cleanHtml)event.preventDefault()callback(false)return}callback(true)},// 初始化代码高亮(适配 CSDN 代码块风格)initCodeHighlight(editor) {// 监听代码块变化,触发高亮editor.on('codeBlockChange', () => {this.$nextTick(() => {document.querySelectorAll('pre code').forEach(block => {hljs.highlightElement(block)})})})// 初始渲染时高亮this.$nextTick(() => {document.querySelectorAll('pre code').forEach(block => {hljs.highlightElement(block)})})}},beforeDestroy() {// 销毁编辑器(避免内存泄漏)if (this.editor) {this.editor.destroy()this.editor = null}}
})
</script><style scoped>
/* CSDN 风格样式优化 */
.csdn-editor-container {border: 1px solid #e5e7eb;border-radius: 4px;overflow: hidden;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
/* 代码块样式优化(贴近 CSDN) */
::v-deep pre {background: #282c34 !important;border-radius: 4px !important;padding: 16px !important;margin: 16px 0 !important;overflow-x: auto !important;
}
::v-deep code {font-family: 'Consolas', 'Monaco', 'Menlo', monospace !important;font-size: 14px !important;
}
/* 图片样式(CSDN 居中+边框) */
::v-deep img {max-width: 100% !important;margin: 16px auto !important;display: block !important;border: 1px solid #e5e7eb !important;border-radius: 4px !important;padding: 4px !important;
}
</style>
二、组件使用示例(CSDN 博客编辑页)
<template><div class="csdn-blog-edit"><!-- CSDN 博客编辑头部(模拟) --><div class="edit-header"><inputv-model="blogTitle"placeholder="请输入博客标题(不超过100字)"class="blog-title-input"maxlength="100"><div class="edit-toolbar"><button @click="saveDraft" class="btn draft-btn">保存草稿</button><button @click="publishBlog" class="btn publish-btn">发布博客</button></div></div><!-- 分类/标签(模拟 CSDN 侧边栏) --><div class="edit-sidebar"><div class="sidebar-item"><label>博客分类:</label><select v-model="blogCategory" class="category-select"><option value="frontend">前端开发</option><option value="backend">后端开发</option><option value="mobile">移动开发</option><option value="ai">人工智能</option><option value="other">其他分类</option></select></div><div class="sidebar-item"><label>博客标签:</label><input v-model="blogTags" placeholder="输入标签,用逗号分隔" class="tags-input"></div></div><!-- 核心:CSDN 风格富文本编辑器 --><div class="editor-wrapper"><CsdnWangEditorv-model="blogContent":maxLength="50000"@change="handleContentChange"/></div></div>
</template><script>
import Vue from 'vue'
import CsdnWangEditor from './CsdnWangEditor.vue'export default Vue.extend({components: { CsdnWangEditor },data() {return {blogTitle: '',blogContent: '<p>请输入博客内容...</p>',blogCategory: 'frontend',blogTags: '',draftTimer: null // 自动保存草稿定时器}},methods: {// 保存草稿(CSDN 自动保存逻辑)saveDraft() {if (!this.blogTitle.trim()) {alert('请先输入博客标题')return}// 模拟保存到本地存储或后端localStorage.setItem('csdn_draft', JSON.stringify({title: this.blogTitle,content: this.blogContent,category: this.blogCategory,tags: this.blogTags,saveTime: new Date().toLocaleString()}))alert('草稿保存成功!')},// 发布博客(对接 CSDN 发布接口)publishBlog() {if (!this.blogTitle.trim() || !this.blogContent.trim()) {alert('标题和内容不能为空')return}// 模拟发布逻辑const blogData = {title: this.blogTitle,content: this.blogContent,category: this.blogCategory,tags: this.blogTags.split(',').map(tag => tag.trim()),publishTime: new Date().toLocaleString()}console.log('发布博客数据:', blogData)alert('博客发布成功!')// 实际项目中对接 CSDN 开放接口或自己的后端// axios.post('CSDN_PUBLISH_API', blogData)},// 内容变化时触发(自动保存草稿)handleContentChange() {// 10秒自动保存一次草稿(CSDN 逻辑)clearTimeout(this.draftTimer)this.draftTimer = setTimeout(() => {this.saveDraft()}, 10000)}},mounted() {// 加载本地草稿(CSDN 自动恢复草稿)const draft = localStorage.getItem('csdn_draft')if (draft) {const draftData = JSON.parse(draft)this.blogTitle = draftData.titlethis.blogContent = draftData.contentthis.blogCategory = draftData.categorythis.blogTags = draftData.tags}},beforeDestroy() {clearTimeout(this.draftTimer)}
})
</script><style scoped>
.csdn-blog-edit {max-width: 1400px;margin: 0 auto;padding: 20px;display: flex;flex-direction: column;gap: 20px;
}
.edit-header {display: flex;justify-content: space-between;align-items: center;gap: 20px;
}
.blog-title-input {flex: 1;padding: 12px 16px;font-size: 20px;border: 1px solid #e5e7eb;border-radius: 4px;outline: none;
}
.blog-title-input:focus {border-color: #1890ff;box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.btn {padding: 8px 16px;border-radius: 4px;cursor: pointer;font-size: 14px;border: none;
}
.draft-btn {background: #f8f9fa;color: #333;border: 1px solid #e5e7eb;
}
.publish-btn {background: #1890ff;color: #fff;margin-left: 8px;
}
.edit-sidebar {width: 260px;align-self: flex-start;border: 1px solid #e5e7eb;border-radius: 4px;padding: 16px;
}
.sidebar-item {margin-bottom: 16px;
}
.sidebar-item label {display: block;margin-bottom: 8px;font-weight: 500;font-size: 14px;
}
.category-select, .tags-input {width: 100%;padding: 8px 12px;border: 1px solid #e5e7eb;border-radius: 4px;font-size: 14px;
}
.editor-wrapper {flex: 1;margin-left: 280px;margin-top: -240px;
}
</style>
三、关键适配 CSDN 特性说明
-
功能适配
- 核心工具栏:复刻 CSDN 常用功能(代码块、图片上传、表格、公式等)
- 代码高亮:使用
highlight.js实现 CSDN 经典深色代码主题 - 图片上传:支持拖拽/粘贴/点击上传,适配 CSDN 5MB 大小限制
- 自动保存:10秒自动保存草稿,支持本地存储恢复
-
样式适配
- 编辑器背景:白色背景+15px 字体,贴近 CSDN 阅读体验
- 代码块:深色背景+ monospace 字体,优化代码可读性
- 图片样式:居中显示+边框+内边距,符合 CSDN 图片展示规范
- 整体布局:模拟 CSDN 编辑页(标题栏+侧边分类+编辑区)
-
交互适配
- 粘贴优化:过滤 Word 冗余样式,支持 Markdown 粘贴转换
- 字数限制:默认 5 万字,可自定义调整
- 草稿恢复:加载本地存储的草稿内容,避免内容丢失
四、使用前准备
- 安装依赖
# 核心依赖
npm install @wangeditor/editor @wangeditor/editor-for-vue --save
# 代码高亮依赖
npm install highlight.js --save
# 可选:Markdown 粘贴转换依赖
npm install markdown-it --save
- 接口替换
- 图片上传:将
customUpload中的模拟接口替换为 CSDN 开放接口或自建 OSS 接口 - 博客发布:将
publishBlog中的模拟逻辑替换为 CSDN 发布接口 - 草稿保存:可对接 CSDN 草稿接口,替换本地存储逻辑
- 图片上传:将
