feat: 🚀 div标签解析
This commit is contained in:
@@ -112,7 +112,7 @@
|
||||
<script setup name="Editor">
|
||||
import { QuillEditor, Quill } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import { getCurrentInstance, reactive, ref, toRaw, computed, onMounted, nextTick } from "vue";
|
||||
import { getCurrentInstance, reactive, ref, toRaw, computed, onMounted, nextTick, watch } from "vue";
|
||||
import { generateUUID } from "@/utils";
|
||||
// import { h } from "@/utils/url";
|
||||
import { routerObj } from "./utils.js";
|
||||
@@ -123,17 +123,35 @@ 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"];
|
||||
fontSizeStyle.whitelist = [
|
||||
"12px",
|
||||
"14px",
|
||||
"16px",
|
||||
"18px",
|
||||
"20px",
|
||||
"22px",
|
||||
"24px",
|
||||
"26px",
|
||||
"28px",
|
||||
"30px",
|
||||
"32px",
|
||||
"34px",
|
||||
"36px",
|
||||
"38px",
|
||||
"40px",
|
||||
"42px"
|
||||
];
|
||||
Quill.register(fontSizeStyle, true);
|
||||
|
||||
// 自定义Blot
|
||||
import ImageBlot from "./quill-image";
|
||||
import Video from "./quill-video";
|
||||
import TabsBlot from "./quill-tabs";
|
||||
import DynamicDivBlot from "./quill-detail-div";
|
||||
Quill.register(Video);
|
||||
Quill.register(ImageBlot);
|
||||
Quill.register(TabsBlot);
|
||||
|
||||
Quill.register(DynamicDivBlot);
|
||||
// 基础变量
|
||||
const { proxy } = getCurrentInstance();
|
||||
const emit = defineEmits(["update:content", "handleRichTextContentChange"]);
|
||||
@@ -164,7 +182,10 @@ const props = defineProps({
|
||||
|
||||
// 主编辑器内容双向绑定
|
||||
const editorContent = computed({
|
||||
get: () => props.content,
|
||||
get: () => {
|
||||
if (!props.content) return "";
|
||||
return props.content;
|
||||
},
|
||||
set: val => {
|
||||
emit("update:content", val);
|
||||
}
|
||||
@@ -175,6 +196,7 @@ const myQuillEditor = ref(null); // 主编辑器ref
|
||||
const options = reactive({
|
||||
theme: "snow",
|
||||
debug: "warn",
|
||||
strict: false,
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: [
|
||||
@@ -206,8 +228,17 @@ const options = reactive({
|
||||
outerVisible.value = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
clipboard: {
|
||||
matchVisual: false // 禁用视觉匹配(减少空标签)
|
||||
}
|
||||
},
|
||||
formats: [
|
||||
// ... 原有格式
|
||||
"align",
|
||||
"bold",
|
||||
"italic" // 只保留必要格式,减少自动生成的空标签
|
||||
],
|
||||
placeholder: "请输入内容...",
|
||||
readOnly: props.readOnly
|
||||
});
|
||||
@@ -608,20 +639,54 @@ const loadTabsDataToEditor = tabs => {
|
||||
});
|
||||
};
|
||||
|
||||
// 清理空标签的专用函数(针对 Quill 生成的空 <p>)
|
||||
const cleanEmptyTags = container => {
|
||||
if (!container) return;
|
||||
|
||||
// 1. 获取所有 <p> 标签
|
||||
const allPTags = container.querySelectorAll("p");
|
||||
if (allPTags.length === 0) return;
|
||||
|
||||
// 2. 手动判断标签是否为空(处理隐形字符)
|
||||
Array.from(allPTags).forEach(pTag => {
|
||||
// 清除所有空白字符(包括 、换行、空格)
|
||||
const trimmedContent = pTag.innerHTML
|
||||
.replace(/ /g, "") // 替换 HTML 空格实体
|
||||
.replace(/\s+/g, "") // 替换所有空白字符(空格、换行等)
|
||||
.replace(/<br\s*\/?>/gi, ""); // 移除 <br> 标签
|
||||
|
||||
// 判断是否为空标签(清理后内容长度为 0)
|
||||
const isEmpty = trimmedContent.length === 0;
|
||||
|
||||
if (isEmpty) {
|
||||
// 保留首尾空标签,避免编辑器异常
|
||||
const isFirstOrLast = pTag === container.firstElementChild || pTag === container.lastElementChild;
|
||||
if (!isFirstOrLast) {
|
||||
pTag.remove();
|
||||
} else {
|
||||
pTag.innerHTML = ""; // 清空内容
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
// 触发清理的函数(针对所有编辑器)
|
||||
const handleCleanEmptyTags = () => {
|
||||
// 处理主编辑器
|
||||
const mainEditor = document.querySelector("#mainEditor .ql-editor");
|
||||
console.log(mainEditor, "=mainEditor=");
|
||||
if (mainEditor) cleanEmptyTags(mainEditor);
|
||||
};
|
||||
|
||||
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);
|
||||
// 显示弹窗
|
||||
@@ -629,8 +694,15 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
}
|
||||
// 等待编辑器首次渲染完成
|
||||
setTimeout(handleCleanEmptyTags, 300);
|
||||
});
|
||||
// 监听内容变化,重新清理空标签
|
||||
watch(editorContent, () => {
|
||||
nextTick(() => {
|
||||
setTimeout(handleCleanEmptyTags, 100); // 延迟确保 Quill 已重新渲染
|
||||
});
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
clearEditor: () => {
|
||||
const quill = toRaw(myQuillEditor.value)?.getQuill();
|
||||
@@ -668,4 +740,110 @@ defineExpose({
|
||||
width: 100px;
|
||||
margin: -2px 0; /* 与标签对齐 */
|
||||
}
|
||||
.o_detail_all {
|
||||
text-align: center;
|
||||
}
|
||||
.o_detail_title {
|
||||
margin-top: 3.125vw;
|
||||
margin-bottom: 1.25vw;
|
||||
font-size: 2.25em;
|
||||
font-weight: 600;
|
||||
line-height: 1.2em;
|
||||
color: #101010;
|
||||
}
|
||||
/* stylelint-disable-next-line no-duplicate-selectors */
|
||||
.o_detail_small {
|
||||
margin-bottom: 0.7vw;
|
||||
font-size: 1.5em;
|
||||
color: #333333;
|
||||
}
|
||||
/* stylelint-disable-next-line no-duplicate-selectors */
|
||||
.o_detail_text {
|
||||
width: 80%;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0.7vw;
|
||||
margin-left: auto;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
color: #737373;
|
||||
}
|
||||
/* stylelint-disable-next-line no-duplicate-selectors */
|
||||
.o_detail_title {
|
||||
padding: 4% 0 2.8%;
|
||||
font-size: 2.25em;
|
||||
color: #101010;
|
||||
}
|
||||
/* stylelint-disable-next-line no-duplicate-selectors */
|
||||
.o_detail_small {
|
||||
padding-bottom: 1.8%;
|
||||
font-size: 1.5em;
|
||||
color: #333333;
|
||||
}
|
||||
/* stylelint-disable-next-line no-duplicate-selectors */
|
||||
.o_detail_text {
|
||||
width: 80%;
|
||||
padding: 0 0 1.8%;
|
||||
margin: auto;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.875em;
|
||||
color: #737373;
|
||||
}
|
||||
.products_des {
|
||||
width: 100%;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
.products_des img {
|
||||
width: 100%;
|
||||
}
|
||||
.de_t_n {
|
||||
font-size: 1.5em;
|
||||
color: #333333;
|
||||
}
|
||||
.detail_title {
|
||||
padding: 2% 0;
|
||||
text-align: center;
|
||||
}
|
||||
.detail_title p {
|
||||
line-height: 2em;
|
||||
}
|
||||
.detail_con_a {
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.lj_detail_text,
|
||||
.lj_detail_texts {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
.lj_detail_text p {
|
||||
padding: 0.5% 0;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
/* seo-pro */
|
||||
.seo-pro h3 {
|
||||
margin: 2% 0 1%;
|
||||
font-size: 1.5em;
|
||||
font-weight: 400;
|
||||
line-height: 1.2;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
}
|
||||
.seo-pro p {
|
||||
margin: 0 0 11px;
|
||||
text-align: center;
|
||||
}
|
||||
.seo-pro a {
|
||||
color: #333333;
|
||||
text-decoration: none;
|
||||
}
|
||||
.sa_blue,
|
||||
.sa_blue a,
|
||||
.seo-pro a:hover {
|
||||
color: #009fdf;
|
||||
}
|
||||
/* stylelint-disable-next-line no-duplicate-selectors */
|
||||
// .o_detail_all {
|
||||
// text-align: center;
|
||||
// }
|
||||
/* stylelint-disable-next-line no-duplicate-selectors */
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user