突发奇想,很想手搓一个文档网站,最好支持在线编写文档的功能,于是寻寻觅觅,找到了国内前端作者大佬做的wangeditor。于是开始琢磨。(叠甲:我技术一般,写的也不华丽,只是提供一个我能成功实践的思路,如果你觉得能用就看看,大佬轻喷)
<div class="write-box">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
/>
<Editor
style="height: 750px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
@onCreated="handleCreated"
@onChange="handleChange"
/>
</div>
(编辑器的前端代码)
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
}); // 当前菜单排序和分组
const toolbarConfig: Partial<IToolbarConfig> = {};
const handleChange = (editor: IDomEditor) => {
editor.getAllMenuKeys();
};
const handleCreated = (editor) => {
editorRef.value = editor;
};
toolbarConfig.toolbarKeys = [
"headerSelect",
"|",
"blockquote",
"|",
"bold",
"underline",
"italic",
{
key: "group-more-style",
title: "更多样式", // 必填
iconSvg: "<svg>....</svg>", // 可选
menuKeys: ["through", "code", "clearStyle", "sup", "sub"], // 下级菜单 key ,必填
},
"color",
"bgColor",
"|",
"fontSize",
"fontFamily",
"lineHeight",
"|",
"bulletedList",
"numberedList",
"todo",
{
key: "group-justify",
title: "对齐",
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M768 793.6v1…72.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>',
menuKeys: [
"justifyLeft",
"justifyRight",
"justifyCenter",
"justifyJustify",
],
},
{
key: "group-indent",
title: "缩进",
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>',
menuKeys: ["indent", "delIndent"],
},
"|",
"uploadImage",
"divider",
"|",
"undo",
"redo",
"|",
"fullScreen",
];
(这些是基础逻辑设置代码,也可以参考官方的)
注意:我使用的是vue3+vite,ts语法和直接声明了setup
下面,是向服务器发送图片数据并返回url链接的函数,也是将图片复制到编辑器内能显示出来的原因(基于你选用的方式为选择url显示图片)。
const editorConfig = {
placeholder: "请输入内容...",
MENU_CONF: {
uploadImage: {
customUpload(resultFile, insertImgFn) {
const headers = {
"Content-Type": "multipart/form-data",
Authorization: `Bearer ${localStorage.getItem("token")}`,
};//可以选择在请求头上携带token,增加安全性
const f1 = new FormData() as any;
f1.append("file", resultFile);
axios
.post(
`https://你的服务器地址/uploadFile/image?filename=${DocName.value}`,
f1,
headers
)
.then((res) => {
if (res.status === 200) {
insertImgFn(res.data);
}//如果返回的状态码是200,则在插入图片的方法赋值返回的url
})
.catch((res) => {
if (res.state != 200) {
ElMessageBox.alert("登陆信息过期,请重新登陆", "提示").then(
() => {
router.push("/login");
}
);
}
});//因为有其他需求,我重新用axios封装了发送请求的方式
},
},
},
};
如果不太理解上面的内容可以先看看官方的文档,官方文档对插入图片的方式及后续的删除图片后的操作做了解释。
https://www.wangeditor.com/v5/menu-config.html#base64-%E6%8F%92%E5%85%A5%E5%9B%BE%E7%89%87
后端返回数据的格式为图片的url,图片可以保存到某某云服务器厂商的云盘,或者你自己的服务器,反正最终返回的内容是图片的url就可以了,如果是公网环境,url起码需要在浏览器上面的网址栏能访问(初步测试)。
然后就是图片的删除和ctrl+z 的撤回问题了,删除图片但是服务器并不会同步删除,所以无用的图片会占资源,浪费空间。但是不可以在删除图片的时候做同步,因为ctrl+z撤回后,图片会重新添加,但是并不会再次发送到后端保存。所以只能留到最终提交的时候处理。
由于我最后保存的数据是html格式存储到数据库内的,图片也会以<img src="图片url地址">的方式存进数据库,所以我直接在最终提交的时候先做image url的提取
function getHtmlImageUrl(HtmlData) {
const imgSrcReg = /<img.*?src=["']([^'"]*)["'].*?>/g;
let match;
const images: any = [];
while ((match = imgSrcReg.exec(imageUrl))) {
const src = match[1];
const imagePath = src.replace(/^(?:\/\/|[^/]+)*\//, ""); // 提取图片路径
images.push(imagePath);
}
if (images.length != 0) {
return images;
} else {
return null;
}
}
最终返回一个参数,多个url会用逗号隔开。
因为我是自己写的服务器,但是文档也可能不插入图片,所以我同时声明一个布尔变量,用来确定文章内有没有插入图片,在发送数据到后端的时候同步把布尔值发到后端,让服务器拥有处理插入图片和不插入的图片的两种处理方式。