feat(editor): 集成富文本编辑器并优化响应式布局
1. 添加wangEditor富文本编辑器替换原有简易编辑器 2. 新增编辑器相关CSS样式和功能按钮 3. 优化详情页和编辑页的响应式布局 4. 调整评论区域样式结构 5. 添加移动端适配样式
This commit is contained in:
170
js/edit.js
170
js/edit.js
@@ -1,6 +1,8 @@
|
||||
// 简单版本的论坛编辑器,确保图片插入功能正常
|
||||
const { createApp, ref, computed, onMounted, nextTick, onUnmounted } = Vue;
|
||||
import { headTop } from "../component/head-top/head-top.js";
|
||||
const { createEditor, createToolbar } = window.wangEditor;
|
||||
console.log("createEditor", createEditor);
|
||||
|
||||
const editApp = createApp({
|
||||
setup() {
|
||||
@@ -12,11 +14,9 @@ const editApp = createApp({
|
||||
uniqid.value = params.uniqid || "";
|
||||
|
||||
getUserInfoWin();
|
||||
checkWConfig();
|
||||
|
||||
cUpload();
|
||||
init();
|
||||
|
||||
checkWConfig();
|
||||
|
||||
// 添加selectionchange事件监听,当鼠标选中区域内容时更新lastSelection
|
||||
document.addEventListener("selectionchange", handleSelectionChange);
|
||||
@@ -107,6 +107,10 @@ const editApp = createApp({
|
||||
ajaxGet(`/v2/api/config/upload?type=topic`).then((res) => {
|
||||
const data = res.data;
|
||||
uConfigData = data;
|
||||
|
||||
console.log("uConfigData", uConfigData);
|
||||
|
||||
init();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -132,15 +136,153 @@ const editApp = createApp({
|
||||
info.value = infoTarget;
|
||||
token.value = data.token;
|
||||
|
||||
nextTick(() => {
|
||||
judgeIsEmpty();
|
||||
});
|
||||
// nextTick(() => {
|
||||
// judgeIsEmpty();
|
||||
// });
|
||||
|
||||
initEditor();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("err", err);
|
||||
});
|
||||
};
|
||||
|
||||
let editor = null;
|
||||
|
||||
const initEditor = () => {
|
||||
let infoTarget = info.value || {};
|
||||
|
||||
console.log("infoTarget", infoTarget);
|
||||
|
||||
const editorConfig = {
|
||||
placeholder: "Type here...",
|
||||
enabledMenus: [],
|
||||
MENU_CONF: {
|
||||
["emotion"]: {
|
||||
emotions: optionEmoji.value,
|
||||
},
|
||||
|
||||
["uploadImage"]: {
|
||||
server: uConfigData.url,
|
||||
|
||||
// form-data fieldName ,默认值 'wangeditor-uploaded-image'
|
||||
fieldName: uConfigData.requestName,
|
||||
|
||||
// 单个文件的最大体积限制,默认为 2M
|
||||
maxFileSize: maxSize, // 1M
|
||||
|
||||
// 最多可上传几个文件,默认为 100
|
||||
maxNumberOfFiles: imageLength,
|
||||
|
||||
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
|
||||
allowedFileTypes: ["image/*", ".png", ".jpg", ".jpeg"],
|
||||
|
||||
// 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
|
||||
meta: { ...uConfigData.params },
|
||||
|
||||
// 将 meta 拼接到 url 参数中,默认 false
|
||||
metaWithUrl: false,
|
||||
|
||||
// 自定义增加 http header
|
||||
headers: { accept: "application/json, text/plain, */*", ...uConfigData.headers },
|
||||
|
||||
// 跨域是否传递 cookie ,默认为 false
|
||||
withCredentials: true,
|
||||
|
||||
// 超时时间,默认为 10 秒
|
||||
|
||||
async customUpload(file, insertFn) {
|
||||
try {
|
||||
let config = uConfigData;
|
||||
// 1. 构造 FormData(包含你的接口所需字段)
|
||||
const formData = new FormData();
|
||||
formData.append(config.requestName, file); // 文件数据
|
||||
formData.append("name", file.name); // 文件名
|
||||
formData.append("type", "image"); // 文件名
|
||||
formData.append("data", config.params.data); // 文件名
|
||||
|
||||
ajax(config.url, formData).then((res) => {
|
||||
const data = res.data;
|
||||
insertFn(data.url); // 传入图片的可访问 URL
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("上传出错:", err);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// 4. 链接菜单:显式启用(默认启用,补充配置防止被过滤)
|
||||
link: {
|
||||
disabled: false, // 确保不禁用
|
||||
showTarget: true, // 显示「是否新窗口打开」选项
|
||||
showRel: true, // 显示「rel 属性」选项
|
||||
},
|
||||
// 5. 对齐菜单:显式启用(默认启用,兜底配置)
|
||||
justify: {
|
||||
disabled: false,
|
||||
},
|
||||
|
||||
onChange(editor) {
|
||||
const html = editor.getHtml();
|
||||
console.log("editor content", html);
|
||||
// 也可以同步到 <textarea>
|
||||
},
|
||||
};
|
||||
|
||||
editor = createEditor({
|
||||
selector: "#editor-container",
|
||||
html: "<p><br>555</p>",
|
||||
config: editorConfig,
|
||||
mode: "default",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("editor", editor);
|
||||
editor.addMark("bold", true); // 加粗
|
||||
}, 1000);
|
||||
// const toolbar = DomEditor.getToolbar(editor)
|
||||
|
||||
const toolbarConfig = {
|
||||
// toolbarKeys: ["bold", "italic", "list"],
|
||||
toolbarKeys: [
|
||||
"headerSelect", // 标题
|
||||
"bold", // 粗体
|
||||
"italic", // 斜体
|
||||
// "justify", // 对齐方式
|
||||
{
|
||||
key: "justifyCenter",
|
||||
title: "对齐",
|
||||
iconSvg: '<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>',
|
||||
menuKeys: ["justifyLeft", "justifyRight", "justifyCenter", "justifyJustify"],
|
||||
},
|
||||
"emotion", // 表情
|
||||
"insertLink", // 插入链接
|
||||
"uploadImage", // 插入图片
|
||||
"uploadVideo", // 插入视频
|
||||
"undo", // 撤销
|
||||
"redo", // 重做
|
||||
"fullScreen", // 全屏
|
||||
],
|
||||
};
|
||||
|
||||
const toolbar = createToolbar({
|
||||
editor,
|
||||
selector: "#toolbar-container",
|
||||
config: toolbarConfig,
|
||||
mode: "default",
|
||||
});
|
||||
|
||||
console.log("toolbar", toolbar);
|
||||
|
||||
// setTimeout(() => {
|
||||
// const el = document.querySelector("#toolbar-container");
|
||||
// if (el && el.children.length === 0) {
|
||||
// createToolbar({ editor, selector: "#toolbar-container", config: toolbarConfig, mode: "default" });
|
||||
// }
|
||||
// }, 0);
|
||||
};
|
||||
|
||||
const restoreHtml = (formattedText, attachments) => {
|
||||
const imageList = attachments?.images || [];
|
||||
|
||||
@@ -196,7 +338,7 @@ const editApp = createApp({
|
||||
html = html.replace(/(<span class="blue">[^<]+<\/span>)\s+/gi, '$1 <span class="fill"></span> ');
|
||||
|
||||
// 7. 清理多余的<br>标签
|
||||
html = html.replace(/<br><br>/g, "<br>");
|
||||
// html = html.replace(/<br><br>/g, "<br>");
|
||||
|
||||
imageList.forEach((element) => {
|
||||
html += `<img src="${element.url}" data-aid="${element.aid}"><br/>`;
|
||||
@@ -218,6 +360,7 @@ const editApp = createApp({
|
||||
const editorRef = ref(null);
|
||||
|
||||
const focusLastNode = () => {
|
||||
return;
|
||||
const newRange = document.createRange();
|
||||
const textNode = document.createTextNode("");
|
||||
editorRef.value.appendChild(textNode);
|
||||
@@ -408,6 +551,7 @@ const editApp = createApp({
|
||||
|
||||
// 处理选中文本变化的函数
|
||||
const handleSelectionChange = () => {
|
||||
return;
|
||||
const selection = window.getSelection();
|
||||
// 确保有选中内容且选中区域在编辑器内
|
||||
if (selection.rangeCount > 0) {
|
||||
@@ -560,7 +704,7 @@ const editApp = createApp({
|
||||
html = html.replace(/<(?!(a\b|\/a\b))[^>]+>/gi, "");
|
||||
|
||||
// 8. 清理连续换行(最多保留两个空行,避免过多空行)
|
||||
html = html.replace(/\n{3,}/g, "\n\n");
|
||||
// html = html.replace(/\n{3,}/g, "\n\n");
|
||||
// 去除首尾空白
|
||||
html = html.trim();
|
||||
|
||||
@@ -745,7 +889,15 @@ const editApp = createApp({
|
||||
|
||||
const linkClick = () => {};
|
||||
|
||||
return { linkClick, insertVideo, insertLink, linkUrl, linkText, linkState, openLink, closeLink, handleClick, uniqid, userInfoWin, titleLength, submit, emojiState, openEmoji, closeEmoji, selectEmoji, optionEmoji, isPTitle, onEditorInput, onEditorFocus, onEditorBlur, paragraphTitle, info, tagList, token, cutAnonymity, editorRef, insertImage, judgeIsEmpty, isEmpty };
|
||||
const overstriking = () => {
|
||||
console.log("加粗");
|
||||
|
||||
editor.addMark("bold", true); // 加粗
|
||||
editor.addMark("color", "#999"); // 文本颜色
|
||||
console.log("editor", editor.addMark);
|
||||
};
|
||||
|
||||
return { overstriking, linkClick, insertVideo, insertLink, linkUrl, linkText, linkState, openLink, closeLink, handleClick, uniqid, userInfoWin, titleLength, submit, emojiState, openEmoji, closeEmoji, selectEmoji, optionEmoji, isPTitle, onEditorInput, onEditorFocus, onEditorBlur, paragraphTitle, info, tagList, token, cutAnonymity, editorRef, insertImage, judgeIsEmpty, isEmpty };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user