diff --git a/src/components.d.ts b/src/components.d.ts
index 0b3820a..207249a 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -7,7 +7,6 @@ export {}
declare module "vue" {
export interface GlobalComponents {
- Editor: typeof import("./components/Editor/index.vue")["default"];
ElAside: typeof import("element-plus/es")["ElAside"];
ElAutocomplete: typeof import("element-plus/es")["ElAutocomplete"];
ElBreadcrumb: typeof import("element-plus/es")["ElBreadcrumb"];
@@ -61,10 +60,6 @@ declare module "vue" {
IEpRemove: typeof import("~icons/ep/remove")["default"];
IEpSearch: typeof import("~icons/ep/search")["default"];
IEpSwitchButton: typeof import("~icons/ep/switch-button")["default"];
- Index2: typeof import("./components/Editor/index2.vue")["default"];
- Index3333: typeof import("./components/Editor/index3333.vue")["default"];
- Index444: typeof import("./components/Editor/index444.vue")["default"];
- Index5555: typeof import("./components/Editor/index5555.vue")["default"];
RouterLink: typeof import("vue-router")["RouterLink"];
RouterView: typeof import("vue-router")["RouterView"];
}
diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue
index 740bcbd..7af50fe 100644
--- a/src/components/Editor/index.vue
+++ b/src/components/Editor/index.vue
@@ -38,7 +38,14 @@
-
+
-
-
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
- 取消
+ 取消
确认
@@ -83,7 +119,7 @@ import { titleConfig } from "./titleConfig.js";
import { uploadVideo, uploadImg } from "@/api/modules/upload";
import { ElNotification } from "element-plus";
import { useRouter } from "vue-router";
-
+import { useMsg } from "@/hooks/useMsg";
// 字体配置
let fontSizeStyle = Quill.import("attributors/style/size");
fontSizeStyle.whitelist = ["12px", "14px", "16px", "18px", "20px", "22px", "24px", "26px", "28px", "30px", "32px"];
@@ -108,13 +144,16 @@ const uploadFileVideo = ref(null);
const outerVisible = ref(false);
const imageList = ref([]);
const imageListDb = ref([]);
-const tabsData = ref([]);
-const activeName = ref(null);
+const activeName = ref(null); // 跟踪当前激活的标签页key
const activeEditor = ref("main"); // 跟踪当前活跃编辑器:main/tab-索引
+// 标签页数据(新增key作为唯一标识,isEditing控制编辑状态)
+const tabsData = ref([]);
// 标签页编辑器ref数组
const tabEditors = ref([]);
-
+// 标题编辑输入框的ref
+const editInputRefs = ref([]);
+const currentEditingTabsRef = ref(null);
// Props
const props = defineProps({
content: { type: String, default: "" },
@@ -131,7 +170,7 @@ const editorContent = computed({
});
const myQuillEditor = ref(null); // 主编辑器ref
-// 主编辑器配置
+// 主编辑器配置(保持不变)
const options = reactive({
theme: "snow",
debug: "warn",
@@ -172,7 +211,7 @@ const options = reactive({
readOnly: props.readOnly
});
-// 标签页编辑器配置
+// 标签页编辑器配置(保持不变)
const options1 = reactive({
theme: "snow",
debug: "warn",
@@ -193,14 +232,14 @@ const options1 = reactive({
handlers: {
image: function (value) {
if (value) {
- const currentIndex = tabsData.value.findIndex(item => item.title === activeName.value);
+ const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
activeEditor.value = `tab-${currentIndex}`;
proxy.$refs.uploadRef.click();
} else Quill.format("customImage", true);
},
video: function (value) {
if (value) {
- const currentIndex = tabsData.value.findIndex(item => item.title === activeName.value);
+ const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
activeEditor.value = `tab-${currentIndex}`;
document.querySelector("#uploadFileVideo")?.click();
} else Quill.format("customVideo", true);
@@ -212,7 +251,7 @@ const options1 = reactive({
readOnly: props.readOnly
});
-// 上传前校验
+// 上传前校验(保持不变)
const handleBeforeUpload = file => {
const fileType = file.type;
file.customUid = generateUUID();
@@ -234,7 +273,7 @@ const handleBeforeUpload = file => {
return true;
};
-// 图片上传(仅修复最后一张删除问题)
+// 图片上传(保持不变)
const handleHttpUpload = async options => {
let formData = new FormData();
formData.append("image", options.file);
@@ -246,14 +285,12 @@ const handleHttpUpload = async options => {
const { data } = result.data;
const { imgId } = result;
- // 原有文件匹配逻辑保留
const fileItem = imageListDb.value.find(item => item.customUid === imgId);
if (fileItem) {
fileItem.serverImgId = imgId;
fileItem.path = data.path;
}
- // 检查所有文件上传完成
const allFilesUploaded = imageListDb.value.every(item => item.path);
if (allFilesUploaded) {
let rawQuillEditor = "";
@@ -267,7 +304,6 @@ const handleHttpUpload = async options => {
quill = rawQuillEditor.getQuill();
}
- // 关键修复:插入后强制刷新编辑器选区
imageListDb.value.forEach(item => {
const length = quill.getLength() - 1;
quill.insertEmbed(length, "customImage", {
@@ -277,9 +313,8 @@ const handleHttpUpload = async options => {
quill.setSelection(length + 1);
});
- // 修复:清空数组前先保存最后一个光标位置
const finalLength = quill.getLength();
- quill.setSelection(finalLength); // 确保光标在最后
+ quill.setSelection(finalLength);
imageList.value = [];
imageListDb.value = [];
@@ -287,11 +322,12 @@ const handleHttpUpload = async options => {
}
} catch (error) {
console.error("图片上传失败:", error);
- // 异常时清理当前文件
imageList.value = imageList.value.filter(item => item.customUid !== options.file.customUid);
imageListDb.value = imageListDb.value.filter(item => item.customUid !== options.file.customUid);
}
};
+
+// 视频上传(保持不变)
const handleVideoUpload = async evt => {
if (evt.target.files.length === 0) return;
const formData = new FormData();
@@ -318,85 +354,131 @@ const handleVideoUpload = async evt => {
console.log(error);
}
};
-// // 视频上传(区分tab和主富文本)
-// const handleVideoUpload = async evt => {
-// if (evt.target.files.length === 0) return;
-// const formData = new FormData();
-// formData.append("video", evt.target.files[0]);
-// try {
-// const quill = await getCurrentQuillInstance();
-// if (!quill) return;
-
-// // 记录当前活跃编辑器类型,用于日志
-// const editorType = activeEditor.value === "main" ? "主编辑器" : `标签页${activeEditor.value.split("-")[1]}`;
-// console.log(`视频将插入到: ${editorType}`);
-
-// const { data } = await uploadVideo(formData);
-// const length = quill.selection.savedRange?.index || quill.getLength();
-// quill.insertEmbed(length, "customVideo", {
-// url: h + data.path,
-// id: generateUUID()
-// });
-// evt.target.value = "";
-// } catch (error) {
-// console.error("视频上传失败:", error);
-// ElNotification({ title: "上传失败", message: "视频上传出错", type: "error" });
-// }
-// };
-
-// 标签页切换事件
-const handleTabChange = tabTitle => {
- const tabIndex = tabsData.value.findIndex(item => item.title === tabTitle);
+// 标签页切换事件(基于key切换)
+const handleTabChange = key => {
+ const tabIndex = tabsData.value.findIndex(item => item.key === key);
+ activeName.value = key;
activeEditor.value = `tab-${tabIndex}`;
- console.log(`切换到标签页: ${tabTitle} (索引: ${tabIndex})`);
};
-// 标签页编辑事件
-const handleTabsEdit = (targetName, action) => {
- console.log(action, "===action====");
+// 标签页增删事件
+const handleTabsEdit = (targetKey, action) => {
if (action === "add") {
+ if (tabsData.value.length > 5) {
+ return useMsg("error", "标签页已达上限 !");
+ }
+ // 新增标签页:生成唯一key,默认标题,初始不处于编辑状态
+ const newKey = `tab_${generateUUID()}`;
const newIndex = tabsData.value.length;
- tabsData.value.push({ title: `标签${newIndex + 1}`, content: "" });
+ tabsData.value.push({
+ key: newKey,
+ title: `标签${newIndex + 1}`,
+ content: "",
+ isEditing: false // 新增时默认不编辑
+ });
nextTick(() => {
- activeName.value = `标签${newIndex + 1}`;
+ activeName.value = newKey;
activeEditor.value = `tab-${newIndex}`;
+ // 新增后自动进入编辑状态
+ setTimeout(() => {
+ startEditTitle(newIndex);
+ }, 100);
});
} else if (action === "remove") {
- const index = tabsData.value.findIndex(item => item.title === targetName);
+ // 删除标签页
+ const index = tabsData.value.findIndex(item => item.key === targetKey);
tabsData.value.splice(index, 1);
tabEditors.value.splice(index, 1);
+ editInputRefs.value.splice(index, 1);
- // 修正 activeEditor 索引
+ // 调整活跃编辑器索引
if (activeEditor.value.startsWith("tab-")) {
const currentTabIndex = parseInt(activeEditor.value.split("-")[1]);
if (currentTabIndex > index) {
- // 若当前活跃标签页在删除项之后,索引减1
activeEditor.value = `tab-${currentTabIndex - 1}`;
} else if (currentTabIndex === index) {
- // 若删除的是当前活跃标签页,切换到第一个标签页
+ // 若删除当前活跃标签,切换到第一个或主编辑器
activeEditor.value = tabsData.value.length > 0 ? "tab-0" : "main";
+ activeName.value = tabsData.value.length > 0 ? tabsData.value[0].key : null;
}
}
}
};
-// 其他方法
+// 开始编辑标签页标题
+const startEditTitle = index => {
+ const tab = tabsData.value[index];
+ if (!tab) return;
+ // 记录原始标题(用于取消编辑时恢复)
+ tab.originalTitle = tab.title;
+ tab.isEditing = true;
+ // 延迟获取焦点,确保输入框已渲染
+ nextTick(() => {
+ editInputRefs.value[index]?.focus();
+ });
+};
+
+// 完成编辑(失去焦点或回车)
+const finishEditTitle = index => {
+ const tab = tabsData.value[index];
+ if (!tab) return;
+ // 校验标题(不能为空)
+ if (!tab.title.trim()) {
+ tab.title = tab.originalTitle || `标签${index + 1}`;
+ ElNotification({ title: "提示", message: "标签标题不能为空", type: "info" });
+ }
+ tab.isEditing = false;
+ // 更新activeName(如果当前编辑的是活跃标签)
+ if (tab.key === activeName.value) {
+ activeName.value = tab.key; // 触发重绘
+ }
+};
+
+// 其他方法(保持不变)
const onContentChange = content => {
emit("handleRichTextContentChange", content);
emit("update:content", content);
};
-
+const setTabsInfo = () => {
+ outerVisible.value = false;
+ //清空
+ tabsData.value = [];
+ activeName.value = null;
+ activeEditor.value = "main";
+};
+//弹窗关闭前的钩子
+const handleBeforeClose = () => {
+ setTabsInfo();
+};
+// 确认按钮点击事件(修改后)
const handleQR = () => {
const quill = toRaw(myQuillEditor.value)?.getQuill();
- if (quill) {
- const range = quill.getSelection(true);
+ if (!quill) return;
+ if (!tabsData.value.length) {
+ return useMsg("error", "标签页内容为空 !");
+ }
+ const range = quill.getSelection(true);
+ // 判断是否是编辑已有标签页(通过 currentEditingTabsRef 是否有值)
+ if (currentEditingTabsRef.value) {
+ // 1. 编辑模式:更新原有标签页组件
+ const blot = currentEditingTabsRef.value;
+ // 更新 blot 的数据(触发 DOM 更新)
+ blot.updateContents(tabsData.value); // 需要在 TabsBlot 中添加 updateContents 方法
+ // 清除编辑状态标记
+ currentEditingTabsRef.value = null;
+ } else {
+ // 2. 新增模式:插入新的标签页组件
quill.insertEmbed(range.index, "tabs", tabsData.value);
quill.setSelection(range.index + 1);
- outerVisible.value = false;
}
+ // 关闭弹窗并清空临时数据
+ setTabsInfo();
+};
+//取消
+const handleQX = () => {
+ setTabsInfo();
};
-
const initTitle = () => {
const editor = document.querySelector(".ql-editor");
if (editor) editor.dataset.placeholder = "";
@@ -405,9 +487,49 @@ const initTitle = () => {
if (tip) tip.setAttribute("title", item.title);
});
};
+// 定义 loadTabsDataToEditor 函数
+const loadTabsDataToEditor = tabs => {
+ // 清空现有数据
+ tabsData.value = [];
+ // 转换原始标签数据为编辑所需格式(添加key和编辑状态)
+ tabs.forEach((tab, index) => {
+ tabsData.value.push({
+ key: `tab_${generateUUID()}`, // 生成唯一key
+ title: tab.title || `标签${index + 1}`, // 避免空标题
+ content: tab.content || "", // 标签页内容
+ isEditing: false // 编辑状态标记
+ });
+ });
+ // 激活第一个标签页(如果有数据)
+ nextTick(() => {
+ if (tabsData.value.length > 0) {
+ activeName.value = tabsData.value[0].key;
+ activeEditor.value = "tab-0";
+ }
+ });
+};
onMounted(() => {
initTitle();
+ // 监听编辑按钮点击事件
+ const editorEl = document.querySelector(".ql-editor");
+ if (editorEl) {
+ editorEl.addEventListener("edit-tabs", e => {
+ console.log(1232, "测试");
+ console.log(e.detail.blot, "=e.detail=");
+ const tabsData = TabsBlot.value(e.detail.blot.domNode);
+ console.log(tabsData, "=tabsData=");
+ if (tabsData.length > 0) {
+ // 保存当前编辑的标签页引用
+ currentEditingTabsRef.value = e.detail.blot;
+ console.log(currentEditingTabsRef.value, "=currentEditingTabsRef.value =");
+ // 加载数据到弹窗
+ loadTabsDataToEditor(tabsData);
+ // 显示弹窗
+ outerVisible.value = true;
+ }
+ });
+ }
});
defineExpose({
@@ -420,6 +542,7 @@ defineExpose({
}
});
+
diff --git a/src/components/Editor/index2.vue b/src/components/Editor/index2.vue
index 506b25d..7a2cec4 100644
--- a/src/components/Editor/index2.vue
+++ b/src/components/Editor/index2.vue
@@ -38,7 +38,14 @@
-
+
-
-
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
- 取消
+ 取消
确认
@@ -108,12 +136,15 @@ const uploadFileVideo = ref(null);
const outerVisible = ref(false);
const imageList = ref([]);
const imageListDb = ref([]);
-const tabsData = ref([]);
-const activeName = ref(null);
+const activeName = ref(null); // 跟踪当前激活的标签页key
const activeEditor = ref("main"); // 跟踪当前活跃编辑器:main/tab-索引
+// 标签页数据(新增key作为唯一标识,isEditing控制编辑状态)
+const tabsData = ref([]);
// 标签页编辑器ref数组
const tabEditors = ref([]);
+// 标题编辑输入框的ref
+const editInputRefs = ref([]);
// Props
const props = defineProps({
@@ -131,7 +162,7 @@ const editorContent = computed({
});
const myQuillEditor = ref(null); // 主编辑器ref
-// 主编辑器配置
+// 主编辑器配置(保持不变)
const options = reactive({
theme: "snow",
debug: "warn",
@@ -172,7 +203,7 @@ const options = reactive({
readOnly: props.readOnly
});
-// 标签页编辑器配置
+// 标签页编辑器配置(保持不变)
const options1 = reactive({
theme: "snow",
debug: "warn",
@@ -193,14 +224,14 @@ const options1 = reactive({
handlers: {
image: function (value) {
if (value) {
- const currentIndex = tabsData.value.findIndex(item => item.title === activeName.value);
+ const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
activeEditor.value = `tab-${currentIndex}`;
proxy.$refs.uploadRef.click();
} else Quill.format("customImage", true);
},
video: function (value) {
if (value) {
- const currentIndex = tabsData.value.findIndex(item => item.title === activeName.value);
+ const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
activeEditor.value = `tab-${currentIndex}`;
document.querySelector("#uploadFileVideo")?.click();
} else Quill.format("customVideo", true);
@@ -212,7 +243,7 @@ const options1 = reactive({
readOnly: props.readOnly
});
-// 上传前校验
+// 上传前校验(保持不变)
const handleBeforeUpload = file => {
const fileType = file.type;
file.customUid = generateUUID();
@@ -234,7 +265,7 @@ const handleBeforeUpload = file => {
return true;
};
-// 图片上传(仅修复最后一张删除问题)
+// 图片上传(保持不变)
const handleHttpUpload = async options => {
let formData = new FormData();
formData.append("image", options.file);
@@ -246,14 +277,12 @@ const handleHttpUpload = async options => {
const { data } = result.data;
const { imgId } = result;
- // 原有文件匹配逻辑保留
const fileItem = imageListDb.value.find(item => item.customUid === imgId);
if (fileItem) {
fileItem.serverImgId = imgId;
fileItem.path = data.path;
}
- // 检查所有文件上传完成
const allFilesUploaded = imageListDb.value.every(item => item.path);
if (allFilesUploaded) {
let rawQuillEditor = "";
@@ -267,7 +296,6 @@ const handleHttpUpload = async options => {
quill = rawQuillEditor.getQuill();
}
- // 关键修复:插入后强制刷新编辑器选区
imageListDb.value.forEach(item => {
const length = quill.getLength() - 1;
quill.insertEmbed(length, "customImage", {
@@ -277,9 +305,8 @@ const handleHttpUpload = async options => {
quill.setSelection(length + 1);
});
- // 修复:清空数组前先保存最后一个光标位置
const finalLength = quill.getLength();
- quill.setSelection(finalLength); // 确保光标在最后
+ quill.setSelection(finalLength);
imageList.value = [];
imageListDb.value = [];
@@ -287,11 +314,12 @@ const handleHttpUpload = async options => {
}
} catch (error) {
console.error("图片上传失败:", error);
- // 异常时清理当前文件
imageList.value = imageList.value.filter(item => item.customUid !== options.file.customUid);
imageListDb.value = imageListDb.value.filter(item => item.customUid !== options.file.customUid);
}
};
+
+// 视频上传(保持不变)
const handleVideoUpload = async evt => {
if (evt.target.files.length === 0) return;
const formData = new FormData();
@@ -318,84 +346,114 @@ const handleVideoUpload = async evt => {
console.log(error);
}
};
-// // 视频上传(区分tab和主富文本)
-// const handleVideoUpload = async evt => {
-// if (evt.target.files.length === 0) return;
-// const formData = new FormData();
-// formData.append("video", evt.target.files[0]);
-// try {
-// const quill = await getCurrentQuillInstance();
-// if (!quill) return;
-
-// // 记录当前活跃编辑器类型,用于日志
-// const editorType = activeEditor.value === "main" ? "主编辑器" : `标签页${activeEditor.value.split("-")[1]}`;
-// console.log(`视频将插入到: ${editorType}`);
-
-// const { data } = await uploadVideo(formData);
-// const length = quill.selection.savedRange?.index || quill.getLength();
-// quill.insertEmbed(length, "customVideo", {
-// url: h + data.path,
-// id: generateUUID()
-// });
-// evt.target.value = "";
-// } catch (error) {
-// console.error("视频上传失败:", error);
-// ElNotification({ title: "上传失败", message: "视频上传出错", type: "error" });
-// }
-// };
-
-// 标签页切换事件
-const handleTabChange = tabTitle => {
- const tabIndex = tabsData.value.findIndex(item => item.title === tabTitle);
+// 标签页切换事件(基于key切换)
+const handleTabChange = key => {
+ const tabIndex = tabsData.value.findIndex(item => item.key === key);
+ activeName.value = key;
activeEditor.value = `tab-${tabIndex}`;
- console.log(`切换到标签页: ${tabTitle} (索引: ${tabIndex})`);
};
-// 标签页编辑事件
-const handleTabsEdit = (targetName, action) => {
+// 标签页增删事件
+const handleTabsEdit = (targetKey, action) => {
if (action === "add") {
+ // 新增标签页:生成唯一key,默认标题,初始不处于编辑状态
+ const newKey = `tab_${generateUUID()}`;
const newIndex = tabsData.value.length;
- tabsData.value.push({ title: `标签${newIndex + 1}`, content: "" });
+ tabsData.value.push({
+ key: newKey,
+ title: `标签${newIndex + 1}`,
+ content: "",
+ isEditing: false // 新增时默认不编辑
+ });
nextTick(() => {
- activeName.value = `标签${newIndex + 1}`;
+ activeName.value = newKey;
activeEditor.value = `tab-${newIndex}`;
+ // 新增后自动进入编辑状态
+ setTimeout(() => {
+ startEditTitle(newIndex);
+ }, 100);
});
} else if (action === "remove") {
- const index = tabsData.value.findIndex(item => item.title === targetName);
+ // 删除标签页
+ const index = tabsData.value.findIndex(item => item.key === targetKey);
tabsData.value.splice(index, 1);
tabEditors.value.splice(index, 1);
+ editInputRefs.value.splice(index, 1);
- // 修正 activeEditor 索引
+ // 调整活跃编辑器索引
if (activeEditor.value.startsWith("tab-")) {
const currentTabIndex = parseInt(activeEditor.value.split("-")[1]);
if (currentTabIndex > index) {
- // 若当前活跃标签页在删除项之后,索引减1
activeEditor.value = `tab-${currentTabIndex - 1}`;
} else if (currentTabIndex === index) {
- // 若删除的是当前活跃标签页,切换到第一个标签页
+ // 若删除当前活跃标签,切换到第一个或主编辑器
activeEditor.value = tabsData.value.length > 0 ? "tab-0" : "main";
+ activeName.value = tabsData.value.length > 0 ? tabsData.value[0].key : null;
}
}
}
};
-// 其他方法
+// 开始编辑标签页标题
+const startEditTitle = index => {
+ const tab = tabsData.value[index];
+ if (!tab) return;
+ // 记录原始标题(用于取消编辑时恢复)
+ tab.originalTitle = tab.title;
+ tab.isEditing = true;
+ // 延迟获取焦点,确保输入框已渲染
+ nextTick(() => {
+ editInputRefs.value[index]?.focus();
+ });
+};
+
+// 完成编辑(失去焦点或回车)
+const finishEditTitle = index => {
+ const tab = tabsData.value[index];
+ if (!tab) return;
+ // 校验标题(不能为空)
+ if (!tab.title.trim()) {
+ tab.title = tab.originalTitle || `标签${index + 1}`;
+ ElNotification({ title: "提示", message: "标签标题不能为空", type: "info" });
+ }
+ tab.isEditing = false;
+ // 更新activeName(如果当前编辑的是活跃标签)
+ if (tab.key === activeName.value) {
+ activeName.value = tab.key; // 触发重绘
+ }
+};
+
+// 其他方法(保持不变)
const onContentChange = content => {
emit("handleRichTextContentChange", content);
emit("update:content", content);
};
-
+const setTabsInfo = () => {
+ outerVisible.value = false;
+ //清空
+ tabsData.value = [];
+ activeName.value = null;
+ activeEditor.value = "main";
+};
+//弹窗关闭前的钩子
+const handleBeforeClose = () => {
+ setTabsInfo();
+};
+//确认
const handleQR = () => {
const quill = toRaw(myQuillEditor.value)?.getQuill();
if (quill) {
const range = quill.getSelection(true);
quill.insertEmbed(range.index, "tabs", tabsData.value);
quill.setSelection(range.index + 1);
- outerVisible.value = false;
+ setTabsInfo();
}
};
-
+//取消
+const handleQX = () => {
+ setTabsInfo();
+};
const initTitle = () => {
const editor = document.querySelector(".ql-editor");
if (editor) editor.dataset.placeholder = "";
@@ -419,6 +477,7 @@ defineExpose({
}
});
+
diff --git a/src/components/Editor/index3333.vue b/src/components/Editor/index3333.vue
deleted file mode 100644
index ff28764..0000000
--- a/src/components/Editor/index3333.vue
+++ /dev/null
@@ -1,370 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/Editor/index444.vue b/src/components/Editor/index444.vue
deleted file mode 100644
index 6640f73..0000000
--- a/src/components/Editor/index444.vue
+++ /dev/null
@@ -1,446 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- +添加标签
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/Editor/index5555.vue b/src/components/Editor/index5555.vue
deleted file mode 100644
index 5ad6288..0000000
--- a/src/components/Editor/index5555.vue
+++ /dev/null
@@ -1,517 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/components/Editor/quill-tabs.js b/src/components/Editor/quill-tabs.js
index da6ec9c..8cd45c1 100644
--- a/src/components/Editor/quill-tabs.js
+++ b/src/components/Editor/quill-tabs.js
@@ -5,79 +5,370 @@ const BlockEmbed = Quill.import("blots/block/embed");
class TabsBlot extends BlockEmbed {
static blotName = "tabs";
static tagName = "div";
- static className = "quill-tabs";
+ static className = "m-quill-tabs";
constructor(domNode) {
super(domNode);
this.bindEvents();
+ this.bindDeleteKeyEvent(); // 绑定删除键事件
}
static create(value) {
const node = super.create(value);
- const tabs = value;
+ const tabs = value || [];
+
+ // 主容器样式
+ node.setAttribute(
+ "style",
+ `
+ margin: 15px 0;
+ overflow: hidden;
+ border-radius: 4px;
+ position: relative;
+ `
+ );
+
// 标签栏
const tabList = document.createElement("div");
- tabList.className = "quill-tab-list";
+ tabList.className = "m-quill-tab-list";
+ tabList.setAttribute(
+ "style",
+ `
+ display: flex;
+ border-bottom: 1px solid #dddddd;
+ `
+ );
+
// 内容区
const contentList = document.createElement("div");
- contentList.className = "quill-tab-content-list";
- // 生成标签和内容
+ contentList.className = "m-quill-tab-content-list";
+ contentList.setAttribute(
+ "style",
+ `
+ padding: 15px;
+ `
+ );
+
+ // 生成标签按钮和内容面板
tabs.forEach((tab, index) => {
// 标签按钮
const btn = document.createElement("button");
- btn.className = `quill-tab-button ${index === 0 ? "active" : ""}`;
+ btn.className = `m-quill-tab-button`;
btn.setAttribute("data-index", index);
btn.textContent = tab.title;
+ btn.setAttribute(
+ "style",
+ `
+ padding: 10px 15px;
+ font-weight: 900;
+ color: #8f9099;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ margin-right: 60px;
+ cursor: pointer;
+ font-size:16px;
+ ${index === 0 ? "color: #1f2635; border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
+ `
+ );
tabList.appendChild(btn);
- // 内容面板
+ // 内容面板 - 关键修改:设置为可编辑
const panel = document.createElement("div");
- panel.className = `quill-tab-content ${index === 0 ? "active" : ""}`;
+ panel.className = `m-quill-tab-content`;
panel.setAttribute("data-index", index);
panel.innerHTML = tab.content;
+ panel.setAttribute(
+ "style",
+ `
+ display: ${index === 0 ? "block" : "none"};
+ min-height: 50px;
+ `
+ );
+ panel.contentEditable = "false"; // 内容面板可编辑
contentList.appendChild(panel);
});
+ // 编辑按钮
+ const editBtn = document.createElement("button");
+ editBtn.className = "m-quill-tab-edit-btn";
+ editBtn.innerHTML = "编辑";
+ editBtn.setAttribute("data-action", "edit");
+ editBtn.setAttribute(
+ "style",
+ `
+
+ padding: 10px;
+ margin-left: auto;
+ color: #606266;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ display:block;
+ `
+ );
+ // display: flex;
+ //align-items: center;
+ // editBtn.onmouseover = () => {
+ // editBtn.style.color = "#007bff";
+ // };
+ // editBtn.onmouseout = () => {
+ // editBtn.style.color = "#606266";
+ // };
+ tabList.appendChild(editBtn);
+
+ // 标签页切换逻辑
+ const scriptTag = document.createElement("script");
+ // 修改 scriptTag.textContent 中的逻辑
+ scriptTag.textContent = `
+ (function() {
+ const container = document.currentScript.parentElement;
+ const isAdmin = window.location.pathname.includes('/admin');
+ const editBtn1 = container.querySelector('.m-quill-tab-edit-btn');
+
+ // 仅在非管理系统(文章网站)隐藏编辑按钮,管理系统保持显示
+ if (!isAdmin && editBtn1) {
+ editBtn1.style.display = 'none'; // 文章网站隐藏按钮
+ } else if (isAdmin && editBtn1) {
+ editBtn1.style.display = 'block'; // 管理系统强制显示按钮
+ }
+
+ // 非管理系统才执行标签切换逻辑(管理系统不执行)
+ if (!isAdmin) {
+ const tabButtons = container.querySelectorAll('.m-quill-tab-button:not([data-action])');
+ const contentPanels = container.querySelectorAll('.m-quill-tab-content');
+ tabButtons.forEach(btn => {
+ btn.addEventListener('click', function() {
+ const index = parseInt(this.dataset.index);
+ tabButtons.forEach((b, i) => {
+ b.setAttribute('style', \`
+ padding: 10px 15px;
+ font-weight: 900;
+ cursor: pointer;
+ background: transparent;
+ font-size:16px;
+ margin-right: 60px;
+ color: #8f9099;
+ border: none;
+ \${i === index ?
+ 'color: #1f2635;border-bottom: 3px solid #537CD8;font-size:16px;' :
+ ''
+ }
+ \`);
+ });
+ contentPanels.forEach((panel, i) => {
+ panel.style.display = i === index ? 'block' : 'none';
+ });
+ });
+ });
+ }
+ })();
+ `;
+
node.appendChild(tabList);
node.appendChild(contentList);
- node.setAttribute("contenteditable", "false"); // 禁止直接编辑容器
+ node.appendChild(scriptTag);
+ node.setAttribute("contenteditable", "false");
return node;
}
bindEvents() {
- // 事件委托,确保动态生成的元素也能触发
- this.domNode.addEventListener("click", e => {
- const btn = e.target.closest(".quill-tab-button");
- if (btn) {
+ if (!this.eventBoundElements) {
+ this.eventBoundElements = new WeakMap();
+ }
+
+ // 移除 eventBoundElements 依赖,直接重新绑定事件(避免弱映射导致的问题)
+ const editBtn = this.domNode.querySelector(".m-quill-tab-edit-btn");
+ if (editBtn) {
+ // 先移除旧事件,避免重复绑定
+ editBtn.removeEventListener("click", this.handleEditClick);
+ // 绑定新事件(使用箭头函数确保 this 指向正确)
+ this.handleEditClick = e => {
e.stopPropagation();
- const index = parseInt(btn.dataset.index, 10);
- this.selectTab(index);
+ console.log("1232323");
+ this.domNode.dispatchEvent(
+ new CustomEvent("edit-tabs", {
+ bubbles: true,
+ detail: { blot: this }
+ })
+ );
+ };
+ editBtn.addEventListener("click", this.handleEditClick);
+ }
+
+ // 标签切换事件
+ const tabButtons = this.domNode.querySelectorAll(".m-quill-tab-button:not([data-action])");
+ tabButtons.forEach(btn => {
+ if (!this.eventBoundElements.has(btn)) {
+ btn.addEventListener("click", () => {
+ const index = parseInt(btn.dataset.index, 10);
+ this.selectTab(index);
+ });
+ this.eventBoundElements.set(btn, true);
}
});
}
- selectTab(index) {
- const buttons = this.domNode.querySelectorAll(".quill-tab-button");
- const panels = this.domNode.querySelectorAll(".quill-tab-content");
+ // 增强版删除键处理
+ bindDeleteKeyEvent() {
+ // 阻止从外部删除整个组件
+ this.domNode.addEventListener(
+ "keydown",
+ e => {
+ if (e.key === "Backspace" || e.key === "Delete") {
+ const selection = window.getSelection();
+ if (!selection.rangeCount) return;
- buttons.forEach((btn, i) => btn.classList.toggle("active", i === index));
- panels.forEach((panel, i) => panel.classList.toggle("active", i === index));
+ const range = selection.getRangeAt(0);
+ const parentBlock = this.domNode;
+
+ // 判断光标是否在组件内部
+ const isInside = parentBlock.contains(range.commonAncestorContainer);
+
+ // 如果光标在组件外部,阻止删除
+ if (!isInside) {
+ e.preventDefault();
+ return;
+ }
+
+ // 检查是否选中了整个组件
+ if (
+ range.startContainer === parentBlock &&
+ range.endContainer === parentBlock &&
+ range.startOffset === 0 &&
+ range.endOffset >= parentBlock.childNodes.length
+ ) {
+ e.preventDefault(); // 阻止删除整个组件
+ }
+ }
+ },
+ true
+ ); // 使用捕获阶段
+
+ // 标签栏禁止编辑
+ const tabList = this.domNode.querySelector(".m-quill-tab-list");
+ if (tabList) {
+ tabList.querySelectorAll("*").forEach(el => {
+ el.contentEditable = "false";
+ });
+ }
+ }
+
+ selectTab(index) {
+ const buttons = this.domNode.querySelectorAll(".m-quill-tab-button:not([data-action])");
+ const panels = this.domNode.querySelectorAll(".m-quill-tab-content");
+
+ buttons.forEach((btn, i) => {
+ btn.setAttribute(
+ "style",
+ `
+ padding: 10px 15px;
+ font-weight: 900;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ font-size:16px;
+ margin-right: 60px;
+ color: #8f9099;
+ border-bottom: 3px solid transparent;
+ ${i === index ? "color: #1f2635;border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
+ `
+ );
+ });
+
+ panels.forEach((panel, i) => {
+ panel.style.display = i === index ? "block" : "none";
+ });
}
static value(node) {
const tabs = [];
- node.querySelectorAll(".quill-tab-button").forEach((btn, i) => {
+ const buttons = node.querySelectorAll(".m-quill-tab-button:not([data-action])");
+ const panels = node.querySelectorAll(".m-quill-tab-content");
+
+ buttons.forEach((btn, i) => {
tabs.push({
title: btn.textContent,
- content: node.querySelectorAll(".quill-tab-content")[i].innerHTML
+ content: panels[i]?.innerHTML || ""
});
});
- return { tabs };
+
+ return tabs;
}
update(mutations, context) {
super.update(mutations, context);
- this.bindEvents(); // 重新绑定事件
+ const scriptTag = this.domNode.querySelector("script");
+ if (scriptTag) {
+ const newScript = document.createElement("script");
+
+ newScript.textContent = scriptTag.textContent;
+ scriptTag.parentNode.replaceChild(newScript, scriptTag);
+ }
+ this.bindEvents();
+ this.bindDeleteKeyEvent(); // 重新绑定删除事件
+ }
+
+ getValue() {
+ return TabsBlot.value(this.domNode);
+ }
+
+ // 更新标签页数据(编辑后更新 DOM)
+ updateContents(tabs) {
+ // 清空原有内容
+ const contentList = this.domNode.querySelector(".m-quill-tab-content-list");
+ const tabList = this.domNode.querySelector(".m-quill-tab-list");
+ // 保留编辑按钮(如果需要)
+ const editBtn = this.domNode.querySelector(".m-quill-tab-edit-btn");
+ // 清空标签栏和内容区(保留编辑按钮)
+ Array.from(tabList.children).forEach(child => {
+ if (!child.classList.contains("m-quill-tab-edit-btn")) {
+ child.remove();
+ }
+ });
+ contentList.innerHTML = "";
+
+ // 重新渲染标签页(复用 create 方法中的逻辑)
+ tabs.forEach((tab, index) => {
+ // 重建标签按钮
+ const btn = document.createElement("button");
+ btn.className = "m-quill-tab-button";
+ btn.setAttribute("data-index", index);
+ btn.textContent = tab.title;
+ btn.setAttribute(
+ "style",
+ `
+ padding: 10px 15px;
+ font-weight: 900;
+ color: #8f9099;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ margin-right: 60px;
+ font-size:16px;
+ ${index === 0 ? "color: #1f2635; border-bottom: 3px solid #537CD8;font-size:16px;" : ""}
+ `
+ );
+ tabList.insertBefore(btn, editBtn); // 插入到编辑按钮前
+
+ // 重建内容面板
+ const panel = document.createElement("div");
+ panel.className = "m-quill-tab-content";
+ panel.setAttribute("data-index", index);
+ panel.innerHTML = tab.content;
+ panel.setAttribute(
+ "style",
+ `
+ display: ${index === 0 ? "block" : "none"};
+ min-height: 50px;
+ `
+ );
+ panel.contentEditable = "false";
+ contentList.appendChild(panel);
+ });
+
+ // 重新绑定事件(确保切换功能正常)
+ this.bindEvents();
}
}
diff --git a/src/components/Editor/quill-tabs1.js b/src/components/Editor/quill-tabs1.js
index da6ec9c..814269f 100644
--- a/src/components/Editor/quill-tabs1.js
+++ b/src/components/Editor/quill-tabs1.js
@@ -10,74 +10,277 @@ class TabsBlot extends BlockEmbed {
constructor(domNode) {
super(domNode);
this.bindEvents();
+ this.bindDeleteKeyEvent(); // 绑定删除键事件
}
static create(value) {
const node = super.create(value);
- const tabs = value;
+ const tabs = value || [];
+
+ // 主容器样式
+ node.setAttribute(
+ "style",
+ `
+ margin: 15px 0;
+ overflow: hidden;
+ border: 1px solid #dddddd;
+ border-radius: 4px;
+ position: relative;
+ `
+ );
+
// 标签栏
const tabList = document.createElement("div");
tabList.className = "quill-tab-list";
+ tabList.setAttribute(
+ "style",
+ `
+ display: flex;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #dddddd;
+ `
+ );
+
// 内容区
const contentList = document.createElement("div");
contentList.className = "quill-tab-content-list";
- // 生成标签和内容
+ contentList.setAttribute(
+ "style",
+ `
+ padding: 15px;
+ `
+ );
+
+ // 生成标签按钮和内容面板
tabs.forEach((tab, index) => {
// 标签按钮
const btn = document.createElement("button");
btn.className = `quill-tab-button ${index === 0 ? "active" : ""}`;
btn.setAttribute("data-index", index);
btn.textContent = tab.title;
+ btn.setAttribute(
+ "style",
+ `
+ padding: 10px 15px;
+ font-weight: 500;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ transition: background-color 0.2s;
+ ${index === 0 ? "color: #007bff; background-color: white; border-bottom: 2px solid #007bff;" : ""}
+ `
+ );
tabList.appendChild(btn);
- // 内容面板
+ // 内容面板 - 关键修改:设置为可编辑
const panel = document.createElement("div");
panel.className = `quill-tab-content ${index === 0 ? "active" : ""}`;
panel.setAttribute("data-index", index);
panel.innerHTML = tab.content;
+ panel.setAttribute(
+ "style",
+ `
+ display: ${index === 0 ? "block" : "none"};
+ min-height: 50px; /* 确保有编辑区域 */
+ `
+ );
+ panel.contentEditable = "true"; // 内容面板可编辑
contentList.appendChild(panel);
});
+ // 编辑按钮
+ const editBtn = document.createElement("button");
+ editBtn.className = "quill-tab-edit-btn";
+ editBtn.innerHTML = "编辑";
+ editBtn.setAttribute("data-action", "edit");
+ editBtn.setAttribute(
+ "style",
+ `
+ display: flex;
+ align-items: center;
+ padding: 10px;
+ margin-left: auto;
+ color: #606266;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ `
+ );
+ editBtn.onmouseover = () => {
+ editBtn.style.color = "#007bff";
+ };
+ editBtn.onmouseout = () => {
+ editBtn.style.color = "#606266";
+ };
+ tabList.appendChild(editBtn);
+ // 标签页切换逻辑
+ const scriptTag = document.createElement("script");
+ scriptTag.textContent = `
+ (function() {
+ const container = document.currentScript.parentElement;
+ const tabButtons = container.querySelectorAll('.quill-tab-button:not([data-action])');
+ const contentPanels = container.querySelectorAll('.quill-tab-content');
+ tabButtons.forEach(btn => {
+ btn.addEventListener('click', function() {
+ const index = parseInt(this.dataset.index);
+ tabButtons.forEach((b, i) => {
+ b.setAttribute('style', \`
+ padding: 10px 15px;
+ font-weight: 500;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ transition: background-color 0.2s;
+ \${i === index ?
+ 'color: #007bff; background-color: white; border-bottom: 2px solid #007bff;' :
+ ''
+ }
+ \`);
+ });
+
+ contentPanels.forEach((panel, i) => {
+ panel.style.display = i === index ? 'block' : 'none';
+ });
+ });
+ });
+ })();
+ `;
+
+ // 组装结构 - 关键修改:移除contenteditable="false"
node.appendChild(tabList);
node.appendChild(contentList);
- node.setAttribute("contenteditable", "false"); // 禁止直接编辑容器
+ node.appendChild(scriptTag);
+
return node;
}
bindEvents() {
- // 事件委托,确保动态生成的元素也能触发
- this.domNode.addEventListener("click", e => {
- const btn = e.target.closest(".quill-tab-button");
- if (btn) {
+ if (!this.eventBoundElements) {
+ this.eventBoundElements = new WeakMap();
+ }
+
+ // 编辑按钮事件
+ const editBtn = this.domNode.querySelector(".quill-tab-edit-btn");
+ if (editBtn && !this.eventBoundElements.has(editBtn)) {
+ editBtn.addEventListener("click", e => {
e.stopPropagation();
- const index = parseInt(btn.dataset.index, 10);
- this.selectTab(index);
+ this.domNode.dispatchEvent(
+ new CustomEvent("edit-tabs", {
+ bubbles: true,
+ detail: { blot: this }
+ })
+ );
+ });
+ this.eventBoundElements.set(editBtn, true);
+ }
+
+ // 标签切换事件
+ const tabButtons = this.domNode.querySelectorAll(".quill-tab-button:not([data-action])");
+ tabButtons.forEach(btn => {
+ if (!this.eventBoundElements.has(btn)) {
+ btn.addEventListener("click", () => {
+ const index = parseInt(btn.dataset.index, 10);
+ this.selectTab(index);
+ });
+ this.eventBoundElements.set(btn, true);
}
});
}
+ // 增强版删除键处理
+ bindDeleteKeyEvent() {
+ // 阻止从外部删除整个组件
+ this.domNode.addEventListener(
+ "keydown",
+ e => {
+ if (e.key === "Backspace" || e.key === "Delete") {
+ const selection = window.getSelection();
+ if (!selection.rangeCount) return;
+
+ const range = selection.getRangeAt(0);
+ const parentBlock = this.domNode;
+
+ // 判断光标是否在组件内部
+ const isInside = parentBlock.contains(range.commonAncestorContainer);
+
+ // 如果光标在组件外部,阻止删除
+ if (!isInside) {
+ e.preventDefault();
+ return;
+ }
+
+ // 检查是否选中了整个组件
+ if (
+ range.startContainer === parentBlock &&
+ range.endContainer === parentBlock &&
+ range.startOffset === 0 &&
+ range.endOffset >= parentBlock.childNodes.length
+ ) {
+ e.preventDefault(); // 阻止删除整个组件
+ }
+ }
+ },
+ true
+ ); // 使用捕获阶段
+
+ // 标签栏禁止编辑
+ const tabList = this.domNode.querySelector(".quill-tab-list");
+ if (tabList) {
+ tabList.querySelectorAll("*").forEach(el => {
+ el.contentEditable = "false";
+ });
+ }
+ }
+
selectTab(index) {
- const buttons = this.domNode.querySelectorAll(".quill-tab-button");
+ const buttons = this.domNode.querySelectorAll(".quill-tab-button:not([data-action])");
const panels = this.domNode.querySelectorAll(".quill-tab-content");
- buttons.forEach((btn, i) => btn.classList.toggle("active", i === index));
- panels.forEach((panel, i) => panel.classList.toggle("active", i === index));
+ buttons.forEach((btn, i) => {
+ btn.setAttribute(
+ "style",
+ `
+ padding: 10px 15px;
+ font-weight: 500;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ transition: background-color 0.2s;
+ ${i === index ? "color: #007bff; background-color: white; border-bottom: 2px solid #007bff;" : ""}
+ `
+ );
+ });
+
+ panels.forEach((panel, i) => {
+ panel.style.display = i === index ? "block" : "none";
+ });
}
static value(node) {
const tabs = [];
- node.querySelectorAll(".quill-tab-button").forEach((btn, i) => {
+ const buttons = node.querySelectorAll(".quill-tab-button:not([data-action])");
+ const panels = node.querySelectorAll(".quill-tab-content");
+
+ buttons.forEach((btn, i) => {
tabs.push({
title: btn.textContent,
- content: node.querySelectorAll(".quill-tab-content")[i].innerHTML
+ content: panels[i].innerHTML
});
});
+
return { tabs };
}
update(mutations, context) {
super.update(mutations, context);
- this.bindEvents(); // 重新绑定事件
+ const scriptTag = this.domNode.querySelector("script");
+ if (scriptTag) {
+ const newScript = document.createElement("script");
+ newScript.textContent = scriptTag.textContent;
+ scriptTag.parentNode.replaceChild(newScript, scriptTag);
+ }
+ this.bindEvents();
+ this.bindDeleteKeyEvent(); // 重新绑定删除事件
}
}
diff --git a/src/components/Editor/quill-tabs222.js b/src/components/Editor/quill-tabs222.js
new file mode 100644
index 0000000..eb80593
--- /dev/null
+++ b/src/components/Editor/quill-tabs222.js
@@ -0,0 +1,180 @@
+import { Quill } from "@vueup/vue-quill";
+
+const BlockEmbed = Quill.import("blots/block/embed");
+
+class TabsBlot extends BlockEmbed {
+ static blotName = "tabs";
+ static tagName = "div";
+ static className = "quill-tabs";
+
+ constructor(domNode) {
+ super(domNode);
+ this.bindEvents();
+ }
+
+ static create(value) {
+ const tabs = Array.isArray(value) ? value : [];
+ const node = super.create(value);
+
+ // 主容器样式
+ node.setAttribute(
+ "style",
+ `
+ margin: 15px 0;
+ overflow: hidden;
+ border: 1px solid #dddddd;
+ border-radius: 4px;
+ position: relative;
+ `
+ );
+
+ // 标签栏容器
+ const tabList = document.createElement("div");
+ tabList.className = "quill-tab-list";
+ tabList.style.cssText = `
+ display: flex;
+ background-color: #f8f9fa;
+ border-bottom: 1px solid #dddddd;
+ `;
+
+ // 内容区容器
+ const contentList = document.createElement("div");
+ contentList.className = "quill-tab-content-list";
+ contentList.style.cssText = "padding: 15px;";
+
+ // 生成标签和内容
+ tabs.forEach((tab, index) => {
+ // 标签按钮
+ const btn = document.createElement("button");
+ btn.className = `quill-tab-button ${index === 0 ? "active" : ""}`;
+ btn.dataset.index = index;
+ btn.textContent = tab.title || `标签${index + 1}`;
+ btn.style.cssText = `
+ padding: 10px 15px;
+ font-weight: 500;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ ${index === 0 ? "color: #007bff; background-color: white; border-bottom: 2px solid #007bff;" : ""}
+ `;
+ tabList.appendChild(btn);
+
+ // 内容面板
+ const panel = document.createElement("div");
+ panel.className = `quill-tab-content ${index === 0 ? "active" : ""}`;
+ panel.dataset.index = index;
+ panel.innerHTML = tab.content || "";
+ panel.style.display = index === 0 ? "block" : "none";
+ contentList.appendChild(panel);
+ });
+
+ // 编辑按钮
+ const editBtn = document.createElement("button");
+ editBtn.className = "quill-tab-edit-btn";
+ editBtn.textContent = "编辑";
+ editBtn.dataset.action = "edit";
+ editBtn.style.cssText = `
+ display: flex;
+ align-items: center;
+ padding: 10px;
+ margin-left: auto;
+ color: #606266;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ `;
+ editBtn.onmouseover = () => (editBtn.style.color = "#007bff");
+ editBtn.onmouseout = () => (editBtn.style.color = "#606266");
+ tabList.appendChild(editBtn);
+
+ // 组装DOM
+ node.appendChild(tabList);
+ node.appendChild(contentList);
+ node.contentEditable = "false";
+
+ return node;
+ }
+
+ // 改进的事件绑定方法,避免重复绑定
+ bindEvents() {
+ // 使用WeakMap存储已绑定的元素,避免重复绑定
+ if (!this.eventBoundElements) {
+ this.eventBoundElements = new WeakMap();
+ }
+
+ // 编辑按钮事件
+ const editBtn = this.domNode.querySelector(".quill-tab-edit-btn");
+ if (editBtn && !this.eventBoundElements.has(editBtn)) {
+ editBtn.addEventListener("click", e => {
+ e.stopPropagation();
+ this.domNode.dispatchEvent(
+ new CustomEvent("edit-tabs", {
+ bubbles: true,
+ detail: { blot: this }
+ })
+ );
+ });
+ this.eventBoundElements.set(editBtn, true);
+ }
+
+ // 标签切换事件
+ const tabButtons = this.domNode.querySelectorAll(".quill-tab-button:not([data-action])");
+ tabButtons.forEach(btn => {
+ if (!this.eventBoundElements.has(btn)) {
+ btn.addEventListener("click", () => {
+ const index = parseInt(btn.dataset.index, 10);
+ this.selectTab(index);
+ });
+ this.eventBoundElements.set(btn, true);
+ }
+ });
+ }
+
+ // 移除了有问题的unbindEvents方法
+
+ selectTab(index) {
+ const buttons = this.domNode.querySelectorAll(".quill-tab-button:not([data-action])");
+ const panels = this.domNode.querySelectorAll(".quill-tab-content");
+
+ if (index < 0 || index >= buttons.length) return;
+
+ // 更新按钮样式
+ buttons.forEach((btn, i) => {
+ btn.style.cssText = `
+ padding: 10px 15px;
+ font-weight: 500;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ ${i === index ? "color: #007bff; background-color: white; border-bottom: 2px solid #007bff;" : ""}
+ `;
+ });
+
+ // 更新内容面板显示
+ panels.forEach((panel, i) => {
+ panel.style.display = i === index ? "block" : "none";
+ });
+ }
+
+ static value(node) {
+ const tabs = [];
+ const buttons = node.querySelectorAll(".quill-tab-button:not([data-action])");
+ const panels = node.querySelectorAll(".quill-tab-content");
+
+ buttons.forEach((btn, i) => {
+ tabs.push({
+ title: btn.textContent,
+ content: panels[i]?.innerHTML || ""
+ });
+ });
+
+ return tabs;
+ }
+
+ update(mutations, context) {
+ super.update(mutations, context);
+ this.bindEvents();
+ }
+}
+
+export default TabsBlot;