Compare commits
8 Commits
41da6b1914
...
f7979b4e9b
| Author | SHA1 | Date | |
|---|---|---|---|
| f7979b4e9b | |||
| 9110df9711 | |||
| 1090351df7 | |||
| 0dab4cc524 | |||
| 29d6ba59c9 | |||
| 1566a72cb6 | |||
| 4e8f3e6564 | |||
| 5da9c11771 |
@@ -89,7 +89,6 @@ class RequestHttp {
|
||||
// 可以在这里更新用户的 token 信息
|
||||
const userStore = useUserStore();
|
||||
userStore.setToken(authorization);
|
||||
console.log("123232323");
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,22 +196,23 @@
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="32px"]::before {
|
||||
// content: "32px";
|
||||
// }
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="36px"]::before,
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="36px"]::before {
|
||||
// content: "36px";
|
||||
// }
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="38px"]::before,
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="38px"]::before {
|
||||
// content: "38px";
|
||||
// }
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="40px"]::before,
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="40px"]::before {
|
||||
// content: "40px";
|
||||
// }
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="42px"]::before,
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="42px"]::before {
|
||||
// content: "44px";
|
||||
// }
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="36px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="36px"]::before {
|
||||
content: "36px";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="38px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="38px"]::before {
|
||||
content: "38px";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="40px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="40px"]::before {
|
||||
content: "40px";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="42px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="42px"]::before {
|
||||
content: "44px";
|
||||
}
|
||||
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="44px"]::before,
|
||||
// .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="44px"]::before {
|
||||
// content: "44px";
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
<QuillEditor
|
||||
id="mainEditor"
|
||||
ref="myQuillEditor"
|
||||
contentType="html"
|
||||
v-model:content="editorContent"
|
||||
contentType="html"
|
||||
@update:content="onContentChange"
|
||||
:options="options"
|
||||
/>
|
||||
@@ -53,9 +53,7 @@
|
||||
editable
|
||||
@edit="handleTabsEdit"
|
||||
@tab-change="handleTabChange"
|
||||
v-if="tabsData.length"
|
||||
>
|
||||
<!-- 标签页:标题支持编辑 -->
|
||||
<el-tab-pane
|
||||
:label="item.title"
|
||||
:name="item.key"
|
||||
@@ -66,11 +64,9 @@
|
||||
>
|
||||
<template #label>
|
||||
<div class="tab-title-edit">
|
||||
<!-- 文字显示状态 -->
|
||||
<span v-if="!item.isEditing" @click="startEditTitle(index)" class="title-text">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<!-- 输入框编辑状态 -->
|
||||
<el-input
|
||||
@click.stop
|
||||
@keydown.delete.stop
|
||||
@@ -86,7 +82,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 标签页编辑器内容 -->
|
||||
<QuillEditor
|
||||
:id="`tabEditor_${item.key}`"
|
||||
:ref="
|
||||
@@ -108,33 +103,93 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<!-- 图片顺序调整弹窗 -->
|
||||
<el-dialog v-model="showImageSortDialog" title="调整图片顺序" width="800px" :before-close="handleImageDialogClose">
|
||||
<div class="image-sort-container">
|
||||
<draggable
|
||||
v-model="sortedImageList"
|
||||
:animation="200"
|
||||
class="image-grid"
|
||||
ghost-class="ghost"
|
||||
item-key="customUid"
|
||||
@update:modelValue="updateSortOrder"
|
||||
>
|
||||
<template #item="{ element: img }">
|
||||
<div class="image-item">
|
||||
<div class="image-preview-container">
|
||||
<div class="image-order-badge">{{ img.sortOrder + 1 }}</div>
|
||||
<img :src="img.tempUrl" :alt="`图片 ${img.sortOrder + 1}`" class="preview-img" />
|
||||
<div style="display: flex; justify-content: flex-end; padding: 6px">
|
||||
<el-button size="small" type="default" @click.stop="removeImage(img.customUid)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-info">
|
||||
<span class="image-name">{{ img.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<div v-if="sortedImageList.length === 0" class="empty-state">暂无图片,请先上传</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showImageSortDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmInsertImages" :disabled="sortedImageList.length === 0">
|
||||
确认插入
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="Editor">
|
||||
// import { Delete } from "@element-plus/icons-vue";
|
||||
import { QuillEditor, Quill } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
// /computed
|
||||
import { getCurrentInstance, reactive, ref, toRaw, onMounted, nextTick } from "vue";
|
||||
import { getCurrentInstance, reactive, ref, toRaw, computed, onMounted, nextTick } from "vue";
|
||||
import { generateUUID } from "@/utils";
|
||||
// import { h } from "@/utils/url";
|
||||
import { routerObj } from "./utils.js";
|
||||
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";
|
||||
import draggable from "vuedraggable";
|
||||
|
||||
// 字体配置
|
||||
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;
|
||||
// 自定义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();
|
||||
@@ -147,35 +202,40 @@ const uploadFileVideo = ref(null);
|
||||
const outerVisible = ref(false);
|
||||
const imageList = ref([]);
|
||||
const imageListDb = ref([]);
|
||||
const activeName = ref(null); // 跟踪当前激活的标签页key
|
||||
const activeEditor = ref("main"); // 跟踪当前活跃编辑器:main/tab-索引
|
||||
const activeName = ref(null);
|
||||
const activeEditor = ref("main");
|
||||
|
||||
// 标签页数据(新增key作为唯一标识,isEditing控制编辑状态)
|
||||
// 标签页数据
|
||||
const tabsData = ref([]);
|
||||
// 标签页编辑器ref数组
|
||||
const tabEditors = reactive([]);
|
||||
// 标题编辑输入框的ref
|
||||
const tabEditors = ref([]);
|
||||
const editInputRefs = ref([]);
|
||||
const currentEditingTabsRef = ref(null);
|
||||
|
||||
// 图片排序相关变量
|
||||
const showImageSortDialog = ref(false);
|
||||
const sortedImageList = ref([]);
|
||||
const uploadingCount = ref(0);
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
content: { type: String, default: "" },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
fileSizeLimit: { type: Number, default: 10 }
|
||||
fileSizeLimit: { type: Number, default: 5 }
|
||||
});
|
||||
|
||||
// 主编辑器内容双向绑定
|
||||
const editorContent = computed({
|
||||
get: () => {
|
||||
if (!props.content) return "";
|
||||
return props.content;
|
||||
},
|
||||
set: val => {
|
||||
emit("update:content", val);
|
||||
}
|
||||
});
|
||||
const myQuillEditor = ref(null); // 主编辑器ref
|
||||
const myQuillEditor = ref(null);
|
||||
|
||||
// 主编辑器配置(保持不变)
|
||||
// 主编辑器配置
|
||||
const options = reactive({
|
||||
theme: "snow",
|
||||
debug: "warn",
|
||||
@@ -198,6 +258,7 @@ const options = reactive({
|
||||
image: function (value) {
|
||||
if (value) {
|
||||
activeEditor.value = "main";
|
||||
sortedImageList.value = [];
|
||||
proxy.$refs.uploadRef.click();
|
||||
} else Quill.format("customImage", true);
|
||||
},
|
||||
@@ -217,10 +278,11 @@ const options = reactive({
|
||||
readOnly: props.readOnly
|
||||
});
|
||||
|
||||
// 标签页编辑器配置(保持不变)
|
||||
// 标签页编辑器配置
|
||||
const options1 = reactive({
|
||||
theme: "snow",
|
||||
debug: "warn",
|
||||
strict: false,
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: [
|
||||
@@ -240,6 +302,7 @@ const options1 = reactive({
|
||||
if (value) {
|
||||
const currentIndex = tabsData.value.findIndex(item => item.key === activeName.value);
|
||||
activeEditor.value = `tab-${currentIndex}`;
|
||||
sortedImageList.value = [];
|
||||
proxy.$refs.uploadRef.click();
|
||||
} else Quill.format("customImage", true);
|
||||
},
|
||||
@@ -257,33 +320,41 @@ const options1 = reactive({
|
||||
readOnly: props.readOnly
|
||||
});
|
||||
|
||||
// 上传前校验(保持不变)
|
||||
// 上传前校验
|
||||
const handleBeforeUpload = file => {
|
||||
const fileType = file.type;
|
||||
file.customUid = generateUUID();
|
||||
imageListDb.value.push(file);
|
||||
|
||||
const validTypes = ["image/jpeg", "image/png", "image/gif", "image/jpg", "image/bmp", "image/webp"];
|
||||
if (!validTypes.includes(fileType)) {
|
||||
ElNotification({ title: "格式错误", message: "仅支持图片格式", type: "warning" });
|
||||
imageListDb.value = imageListDb.value.filter(item => item.customUid !== file.customUid);
|
||||
return false;
|
||||
}
|
||||
|
||||
const isLt = file.size / 1024 / 1024 < props.fileSizeLimit;
|
||||
if (!isLt) {
|
||||
ElNotification({ title: "大小超限", message: `不能超过 ${props.fileSizeLimit} MB`, type: "warning" });
|
||||
imageListDb.value = imageListDb.value.filter(item => item.customUid !== file.customUid);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 生成临时URL用于预览
|
||||
const tempUrl = URL.createObjectURL(file);
|
||||
imageListDb.value.push({
|
||||
...file,
|
||||
tempUrl,
|
||||
sortOrder: imageListDb.value.length, // 初始化排序索引
|
||||
serverImgId: "",
|
||||
path: ""
|
||||
});
|
||||
|
||||
uploadingCount.value++;
|
||||
return true;
|
||||
};
|
||||
|
||||
// 图片上传(保持不变)
|
||||
// 图片上传处理
|
||||
const handleHttpUpload = async options => {
|
||||
let formData = new FormData();
|
||||
formData.append("image", options.file);
|
||||
imageList.value.push(options.file);
|
||||
|
||||
try {
|
||||
const result = await uploadImg(formData, routerName.value, options.file.customUid);
|
||||
@@ -291,171 +362,200 @@ const handleHttpUpload = async options => {
|
||||
const { data } = result.data;
|
||||
const { imgId } = result;
|
||||
|
||||
const fileItem = imageListDb.value.find(item => item.customUid === imgId);
|
||||
const fileItem = imageListDb.value.find(item => item.customUid === options.file.customUid);
|
||||
if (fileItem) {
|
||||
fileItem.serverImgId = imgId;
|
||||
fileItem.path = data.path;
|
||||
}
|
||||
|
||||
const allFilesUploaded = imageListDb.value.every(item => item.path);
|
||||
if (allFilesUploaded) {
|
||||
let rawQuillEditor = "";
|
||||
let quill = "";
|
||||
if (activeEditor.value === "main") {
|
||||
rawQuillEditor = toRaw(myQuillEditor.value);
|
||||
quill = rawQuillEditor.getQuill();
|
||||
} else {
|
||||
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
rawQuillEditor = toRaw(tabEditors[tabIndex]);
|
||||
quill = rawQuillEditor.getQuill();
|
||||
}
|
||||
uploadingCount.value--;
|
||||
|
||||
imageListDb.value.forEach(item => {
|
||||
const length = quill.getLength() - 1;
|
||||
quill.insertEmbed(length, "customImage", {
|
||||
url: item.path,
|
||||
id: item.serverImgId || generateUUID()
|
||||
// 所有图片上传完成后显示排序弹窗
|
||||
if (uploadingCount.value === 0) {
|
||||
sortedImageList.value = [...imageListDb.value];
|
||||
updateSortOrder(); // 确保排序索引正确
|
||||
nextTick(() => {
|
||||
showImageSortDialog.value = true;
|
||||
});
|
||||
quill.setSelection(length + 1);
|
||||
});
|
||||
|
||||
const finalLength = quill.getLength();
|
||||
quill.setSelection(finalLength);
|
||||
|
||||
imageList.value = [];
|
||||
imageListDb.value = [];
|
||||
}
|
||||
}
|
||||
} 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 failUid = options.file.customUid;
|
||||
imageListDb.value = imageListDb.value.filter(item => item.customUid !== failUid);
|
||||
uploadingCount.value = Math.max(0, uploadingCount.value - 1);
|
||||
|
||||
ElNotification({
|
||||
title: "上传失败",
|
||||
message: `图片 ${options.file.name} 上传失败`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 视频上传(保持不变)
|
||||
// const handleVideoUpload = async evt => {
|
||||
// if (evt.target.files.length === 0) return;
|
||||
// const formData = new FormData();
|
||||
// formData.append("video", evt.target.files[0]);
|
||||
// try {
|
||||
// let rawQuillEditor = "";
|
||||
// let quill = "";
|
||||
// if (activeEditor.value === "main") {
|
||||
// rawQuillEditor = toRaw(myQuillEditor.value);
|
||||
// quill = rawQuillEditor.getQuill();
|
||||
// } else {
|
||||
// const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
// rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
|
||||
// quill = rawQuillEditor.getQuill();
|
||||
// }
|
||||
// let length = quill.selection.savedRange.index;
|
||||
// const { data } = await uploadVideo(formData);
|
||||
// quill.insertEmbed(length, "customVideo", {
|
||||
// url: data.path,
|
||||
// id: generateUUID()
|
||||
// });
|
||||
// uploadFileVideo.value.value = "";
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// }
|
||||
// };
|
||||
// 在<script setup>中替换handleVideoUpload方法
|
||||
// 更新排序索引
|
||||
const updateSortOrder = () => {
|
||||
sortedImageList.value.forEach((item, index) => {
|
||||
item.sortOrder = index;
|
||||
});
|
||||
};
|
||||
|
||||
// 从排序列表移除图片
|
||||
const removeImage = customUid => {
|
||||
// 释放临时URL
|
||||
const removedImg = sortedImageList.value.find(img => img.customUid === customUid);
|
||||
if (removedImg?.tempUrl) {
|
||||
URL.revokeObjectURL(removedImg.tempUrl);
|
||||
}
|
||||
|
||||
// 从列表中移除
|
||||
sortedImageList.value = sortedImageList.value.filter(img => img.customUid !== customUid);
|
||||
|
||||
// 重新计算排序索引
|
||||
updateSortOrder();
|
||||
};
|
||||
|
||||
// 确认插入图片到编辑器
|
||||
const confirmInsertImages = () => {
|
||||
let quill;
|
||||
if (activeEditor.value === "main") {
|
||||
const rawQuillEditor = toRaw(myQuillEditor.value);
|
||||
quill = rawQuillEditor.getQuill();
|
||||
} else {
|
||||
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
const rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
|
||||
quill = rawQuillEditor.getQuill();
|
||||
}
|
||||
|
||||
if (!quill) {
|
||||
ElNotification({ title: "错误", message: "编辑器未加载完成", type: "error" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取光标位置
|
||||
const range = quill.getSelection() || { index: 0 };
|
||||
let currentInsertIndex = range.index;
|
||||
|
||||
// 按排序索引升序插入
|
||||
const sortedByOrder = [...sortedImageList.value].sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
//
|
||||
sortedByOrder.forEach(item => {
|
||||
quill.insertEmbed(currentInsertIndex, "customImage", {
|
||||
url: item.path,
|
||||
id: item.serverImgId || generateUUID()
|
||||
});
|
||||
currentInsertIndex++;
|
||||
});
|
||||
|
||||
// 调整光标位置
|
||||
quill.setSelection(currentInsertIndex);
|
||||
|
||||
// 清理资源
|
||||
sortedImageList.value.forEach(img => {
|
||||
if (img.tempUrl) {
|
||||
URL.revokeObjectURL(img.tempUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭弹窗并重置
|
||||
showImageSortDialog.value = false;
|
||||
imageList.value = [];
|
||||
imageListDb.value = [];
|
||||
sortedImageList.value = [];
|
||||
};
|
||||
|
||||
// 关闭图片排序弹窗时清理资源
|
||||
const handleImageDialogClose = () => {
|
||||
// 释放所有临时URL
|
||||
sortedImageList.value.forEach(img => {
|
||||
if (img.tempUrl) {
|
||||
URL.revokeObjectURL(img.tempUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// 重置状态
|
||||
showImageSortDialog.value = false;
|
||||
imageList.value = [];
|
||||
imageListDb.value = [];
|
||||
sortedImageList.value = [];
|
||||
};
|
||||
|
||||
// 视频上传
|
||||
const handleVideoUpload = async evt => {
|
||||
if (evt.target.files.length === 0) return;
|
||||
const file = evt.target.files[0];
|
||||
|
||||
// 1. 校验视频文件
|
||||
const maxSize = props.fileSizeLimit * 1024 * 1024 * 15;
|
||||
// 校验视频文件
|
||||
const maxSize = 150 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
ElNotification({
|
||||
title: "文件过大",
|
||||
message: `视频大小不能超过 ${props.fileSizeLimit}MB`,
|
||||
message: `视频大小不能超过 ${150}MB`,
|
||||
type: "warning"
|
||||
});
|
||||
evt.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 生成视频本地URL(用于生成封面,不上传)
|
||||
// 生成视频本地URL
|
||||
const localVideoUrl = URL.createObjectURL(file);
|
||||
|
||||
try {
|
||||
// 4. 并行处理:上传视频 + 生成并上传封面
|
||||
// 4.1 上传视频到视频服务器
|
||||
// 上传视频
|
||||
const videoFormData = new FormData();
|
||||
videoFormData.append("video", file);
|
||||
const videoRes = await uploadVideo(videoFormData); // 视频上传接口
|
||||
const videoRes = await uploadVideo(videoFormData);
|
||||
|
||||
// 校验视频上传结果(根据你的接口返回格式调整)
|
||||
if (videoRes?.code !== 0) {
|
||||
throw new Error(`视频上传失败: ${videoRes?.message || "未知错误"}`);
|
||||
}
|
||||
const videoUrl = videoRes.data.path; // 服务器返回的视频URL
|
||||
console.log(localVideoUrl, "=localVideoUrl=");
|
||||
// 4.2 生成封面图并上传到图片服务器
|
||||
const frameBlob = await Video.captureVideoFrame(localVideoUrl);
|
||||
console.log(frameBlob, "============frameBlob===========");
|
||||
let coverUrl = "";
|
||||
if (!frameBlob) return;
|
||||
const videoUrl = videoRes.data.path;
|
||||
|
||||
// 复用图片上传接口(与图片上传逻辑一致)
|
||||
// 生成封面图并上传
|
||||
const frameBlob = await Video.captureVideoFrame(localVideoUrl);
|
||||
let coverUrl = "";
|
||||
if (frameBlob) {
|
||||
const coverFormData = new FormData();
|
||||
const coverUid = generateUUID(); // 生成唯一ID
|
||||
// formData.append("image", options.file);
|
||||
const coverUid = generateUUID();
|
||||
coverFormData.append("image", frameBlob, `cover-${coverUid}.jpg`);
|
||||
console.log(coverFormData, "=coverFormData=");
|
||||
// 调用图片上传接口(和普通图片上传用同一个接口)
|
||||
const coverRes = await uploadImg(coverFormData, routerName.value, coverUid);
|
||||
|
||||
// 校验封面上传结果
|
||||
if (coverRes?.data?.code === 0) {
|
||||
coverUrl = coverRes.data.data.path; // 服务器返回的封面URL
|
||||
} else {
|
||||
console.warn("封面上传失败,使用默认封面");
|
||||
coverUrl = coverRes.data.data.path;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 将带封面的视频插入编辑器
|
||||
// 插入视频到编辑器
|
||||
insertVideoToEditor(videoUrl, coverUrl);
|
||||
|
||||
// 6. 上传成功提示
|
||||
// loading.close();
|
||||
// ElNotification({
|
||||
// title: "上传成功",
|
||||
// message: "视频已添加到编辑器",
|
||||
// type: "success"
|
||||
// });
|
||||
} catch (error) {
|
||||
console.log(error, "==============");
|
||||
console.log(error);
|
||||
} finally {
|
||||
console.log("======12323232========");
|
||||
// 清理资源
|
||||
URL.revokeObjectURL(localVideoUrl); // 释放本地视频URL
|
||||
evt.target.value = ""; // 重置文件输入框
|
||||
URL.revokeObjectURL(localVideoUrl);
|
||||
evt.target.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
// 辅助方法:插入视频到编辑器
|
||||
// 插入视频到编辑器
|
||||
const insertVideoToEditor = (videoUrl, coverUrl) => {
|
||||
// 获取当前活跃的编辑器(主编辑器或标签页编辑器)
|
||||
let quill;
|
||||
if (activeEditor.value === "main") {
|
||||
quill = toRaw(myQuillEditor.value)?.getQuill();
|
||||
} else {
|
||||
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
quill = toRaw(tabEditors[tabIndex])?.getQuill();
|
||||
quill = toRaw(tabEditors.value[tabIndex])?.getQuill();
|
||||
}
|
||||
|
||||
if (quill) {
|
||||
const range = quill.getSelection() || { index: 0 };
|
||||
// 插入自定义视频组件,携带服务器返回的视频URL和封面URL
|
||||
quill.insertEmbed(range.index, "customVideo", {
|
||||
url: videoUrl,
|
||||
poster: coverUrl // 这里使用图片服务器返回的封面URL
|
||||
poster: coverUrl
|
||||
});
|
||||
quill.setSelection(range.index + 1); // 移动光标到视频后
|
||||
quill.setSelection(range.index + 1);
|
||||
}
|
||||
};
|
||||
// 标签页切换事件(基于key切换)
|
||||
|
||||
// 标签页切换事件
|
||||
const handleTabChange = key => {
|
||||
const tabIndex = tabsData.value.findIndex(item => item.key === key);
|
||||
activeName.value = key;
|
||||
@@ -468,37 +568,32 @@ const handleTabsEdit = (targetKey, action) => {
|
||||
if (tabsData.value.length > 5) {
|
||||
return useMsg("error", "标签页已达上限 !");
|
||||
}
|
||||
// 新增标签页:生成唯一key,默认标题,初始不处于编辑状态
|
||||
const newKey = `tab_${generateUUID()}`;
|
||||
const newIndex = tabsData.value.length;
|
||||
tabsData.value.push({
|
||||
key: newKey,
|
||||
title: `标签${newIndex + 1}`,
|
||||
content: "",
|
||||
isEditing: false // 新增时默认不编辑
|
||||
isEditing: false
|
||||
});
|
||||
nextTick(() => {
|
||||
activeName.value = newKey;
|
||||
activeEditor.value = `tab-${newIndex}`;
|
||||
// 新增后自动进入编辑状态
|
||||
setTimeout(() => {
|
||||
startEditTitle(newIndex);
|
||||
}, 100);
|
||||
});
|
||||
} else if (action === "remove") {
|
||||
// 删除标签页
|
||||
const index = tabsData.value.findIndex(item => item.key === targetKey);
|
||||
tabsData.value.splice(index, 1);
|
||||
tabEditors.splice(index, 1);
|
||||
tabEditors.value.splice(index, 1);
|
||||
editInputRefs.value.splice(index, 1);
|
||||
|
||||
// 调整活跃编辑器索引
|
||||
if (activeEditor.value.startsWith("tab-")) {
|
||||
const currentTabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
if (currentTabIndex > index) {
|
||||
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;
|
||||
}
|
||||
@@ -510,49 +605,41 @@ const handleTabsEdit = (targetKey, action) => {
|
||||
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; // 触发重绘
|
||||
activeName.value = tab.key;
|
||||
}
|
||||
};
|
||||
|
||||
// 其他方法(保持不变)
|
||||
// 其他方法
|
||||
const onContentChange = content => {
|
||||
console.log(content, "=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) return;
|
||||
@@ -561,26 +648,16 @@ const handleQR = () => {
|
||||
}
|
||||
const range = quill.getSelection(true);
|
||||
|
||||
// 判断是否是编辑已有标签页(通过 currentEditingTabsRef 是否有值)
|
||||
if (currentEditingTabsRef.value) {
|
||||
// 1. 编辑模式:更新原有标签页组件
|
||||
const blot = currentEditingTabsRef.value;
|
||||
// 更新 blot 的数据(触发 DOM 更新)
|
||||
blot.updateContents(tabsData.value); // 需要在 TabsBlot 中添加 updateContents 方法
|
||||
// 清除编辑状态标记
|
||||
currentEditingTabsRef.value.updateContents(tabsData.value);
|
||||
currentEditingTabsRef.value = null;
|
||||
} else {
|
||||
// 2. 新增模式:插入新的标签页组件
|
||||
quill.insertEmbed(range.index, "tabs", tabsData.value);
|
||||
// 关键:在标签页前方插入一个空段落(确保顶部有空间)
|
||||
// quill.insertText(range.index, "\n"); // 插入换行
|
||||
quill.setSelection(range.index + 1);
|
||||
quill.insertText(range.index, "\n"); // 插入换行
|
||||
quill.insertText(range.index, "\n");
|
||||
}
|
||||
// 关闭弹窗并清空临时数据
|
||||
setTabsInfo();
|
||||
};
|
||||
//取消
|
||||
const handleQX = () => {
|
||||
setTabsInfo();
|
||||
};
|
||||
@@ -592,20 +669,16 @@ 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 // 编辑状态标记
|
||||
key: `tab_${generateUUID()}`,
|
||||
title: tab.title || `标签${index + 1}`,
|
||||
content: tab.content || "",
|
||||
isEditing: false
|
||||
});
|
||||
});
|
||||
// 激活第一个标签页(如果有数据)
|
||||
nextTick(() => {
|
||||
if (tabsData.value.length > 0) {
|
||||
activeName.value = tabsData.value[0].key;
|
||||
@@ -615,26 +688,19 @@ const loadTabsDataToEditor = tabs => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
initTitle();
|
||||
// 监听编辑按钮点击事件
|
||||
const editorEl = document.querySelector(".ql-editor");
|
||||
if (editorEl) {
|
||||
editorEl.addEventListener("edit-tabs", e => {
|
||||
console.log(e.detail.blot, "=e.detail=");
|
||||
const tabsData = TabsBlot.value(e.detail.blot.domNode);
|
||||
if (tabsData.length > 0) {
|
||||
// 保存当前编辑的标签页引用
|
||||
currentEditingTabsRef.value = e.detail.blot;
|
||||
// 加载数据到弹窗
|
||||
loadTabsDataToEditor(tabsData);
|
||||
// 显示弹窗
|
||||
outerVisible.value = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
clearEditor: () => {
|
||||
@@ -650,28 +716,226 @@ defineExpose({
|
||||
<style lang="scss">
|
||||
@import "./index.scss";
|
||||
|
||||
// 增加编辑器内容区交互性确保删除可用
|
||||
// 编辑器基础样式
|
||||
.ql-editor {
|
||||
min-height: 600px; // 确保空编辑器也有点击区域
|
||||
min-height: 600px;
|
||||
cursor: text !important;
|
||||
user-select: text !important;
|
||||
}
|
||||
|
||||
// /* 标签页样式 */
|
||||
// 图片排序弹窗样式
|
||||
.image-sort-container {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.image-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
padding: 10px;
|
||||
}
|
||||
.image-item {
|
||||
width: 150px;
|
||||
padding: 8px;
|
||||
cursor: move;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片预览容器
|
||||
.image-preview-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||
}
|
||||
.preview-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
// 排序序号徽章
|
||||
.image-order-badge {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
background: rgb(0 0 0 / 50%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// 圆形删除按钮
|
||||
// .circle-delete-btn {
|
||||
// position: absolute;
|
||||
// top: -8px;
|
||||
// right: -8px;
|
||||
// z-index: 500;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// width: 28px;
|
||||
// height: 28px;
|
||||
// padding: 0;
|
||||
// margin: 0;
|
||||
// color: white;
|
||||
// background-color: #ff4d4f;
|
||||
// border: none;
|
||||
// border-radius: 50%;
|
||||
// box-shadow: 0 2px 4px rgb(0 0 0 / 20%);
|
||||
// transition: all 0.2s;
|
||||
// &:hover {
|
||||
// color: white;
|
||||
// background-color: #d93025;
|
||||
// transform: scale(1.1);
|
||||
// }
|
||||
// .el-icon {
|
||||
// font-size: 14px;
|
||||
// }
|
||||
// }
|
||||
.image-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.image-name {
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.empty-state {
|
||||
padding: 30px 0;
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
text-align: center;
|
||||
}
|
||||
.ghost {
|
||||
background-color: #e9ecef;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
// 标签页样式
|
||||
.quill-tabs {
|
||||
margin: 15px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
// /* 用伪元素添加图标(可替换为自己的图标) */
|
||||
.ql-tabs::before {
|
||||
font-size: 16px;
|
||||
content: "T"; /* 用 emoji 或字体图标 */
|
||||
content: "T";
|
||||
}
|
||||
.title-input {
|
||||
width: 100px;
|
||||
margin: -2px 0; /* 与标签对齐 */
|
||||
margin: -2px 0;
|
||||
}
|
||||
|
||||
// 图片样式
|
||||
.ql-editor .quill-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 5px 0;
|
||||
&:focus {
|
||||
outline: 2px solid #4285f4;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// 详情样式
|
||||
.o_detail_all {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.o_detail_title {
|
||||
margin-top: 2vw;
|
||||
margin-bottom: 1.25vw;
|
||||
overflow: hidden;
|
||||
font-size: 2.25em;
|
||||
font-weight: 600;
|
||||
line-height: 1.2em;
|
||||
color: #101010;
|
||||
text-align: center;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.o_detail_small {
|
||||
margin-bottom: 0.7vw;
|
||||
font-size: 1.5em;
|
||||
color: #333333;
|
||||
}
|
||||
.o_detail_text {
|
||||
width: 80%;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0.7vw;
|
||||
margin-left: auto;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.5em;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
./quill-image1111
|
||||
|
||||
@@ -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"]);
|
||||
@@ -159,12 +177,15 @@ const currentEditingTabsRef = ref(null);
|
||||
const props = defineProps({
|
||||
content: { type: String, default: "" },
|
||||
readOnly: { type: Boolean, default: false },
|
||||
fileSizeLimit: { type: Number, default: 10 }
|
||||
fileSizeLimit: { type: Number, default: 5 }
|
||||
});
|
||||
|
||||
// 主编辑器内容双向绑定
|
||||
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: [
|
||||
@@ -208,6 +230,7 @@ const options = reactive({
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
placeholder: "请输入内容...",
|
||||
readOnly: props.readOnly
|
||||
});
|
||||
@@ -216,6 +239,7 @@ const options = reactive({
|
||||
const options1 = reactive({
|
||||
theme: "snow",
|
||||
debug: "warn",
|
||||
strict: false,
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: [
|
||||
@@ -256,7 +280,6 @@ const options1 = reactive({
|
||||
const handleBeforeUpload = file => {
|
||||
const fileType = file.type;
|
||||
file.customUid = generateUUID();
|
||||
imageListDb.value.push(file);
|
||||
|
||||
const validTypes = ["image/jpeg", "image/png", "image/gif", "image/jpg", "image/bmp", "image/webp"];
|
||||
if (!validTypes.includes(fileType)) {
|
||||
@@ -271,14 +294,20 @@ const handleBeforeUpload = file => {
|
||||
imageListDb.value = imageListDb.value.filter(item => item.customUid !== file.customUid);
|
||||
return false;
|
||||
}
|
||||
imageListDb.value.push({
|
||||
...file,
|
||||
order: imageListDb.value.length
|
||||
});
|
||||
|
||||
imageListDb.value = [...imageListDb.value].sort((a, b) => a.order - b.order);
|
||||
|
||||
console.log(imageListDb.value, "=================value==================");
|
||||
return true;
|
||||
};
|
||||
|
||||
// 图片上传(保持不变)
|
||||
const handleHttpUpload = async options => {
|
||||
let formData = new FormData();
|
||||
formData.append("image", options.file);
|
||||
imageList.value.push(options.file);
|
||||
|
||||
try {
|
||||
const result = await uploadImg(formData, routerName.value, options.file.customUid);
|
||||
@@ -287,12 +316,17 @@ const handleHttpUpload = async options => {
|
||||
const { imgId } = result;
|
||||
|
||||
const fileItem = imageListDb.value.find(item => item.customUid === imgId);
|
||||
console.log(fileItem, "=fileItem=");
|
||||
if (fileItem) {
|
||||
fileItem.serverImgId = imgId;
|
||||
fileItem.path = data.path;
|
||||
// 记录完成时间,用于排序
|
||||
fileItem.completeTime = Date.now();
|
||||
}
|
||||
|
||||
const allFilesUploaded = imageListDb.value.every(item => item.path);
|
||||
console.log(allFilesUploaded, "=allFilesUploaded=");
|
||||
|
||||
if (allFilesUploaded) {
|
||||
let rawQuillEditor = "";
|
||||
let quill = "";
|
||||
@@ -304,18 +338,26 @@ const handleHttpUpload = async options => {
|
||||
rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
|
||||
quill = rawQuillEditor.getQuill();
|
||||
}
|
||||
// 获取当前光标位置
|
||||
const selection = quill.getSelection();
|
||||
const insertPosition = selection ? selection.index : quill.getLength();
|
||||
|
||||
imageListDb.value.forEach(item => {
|
||||
const length = quill.getLength() - 1;
|
||||
quill.insertEmbed(length, "customImage", {
|
||||
url: item.path,
|
||||
// 关键修改:按照原始上传顺序排序
|
||||
// 假设imageListDb中的顺序就是上传顺序
|
||||
// 或者如果有order属性,可以按照order排序
|
||||
const sortedImages = [...imageListDb.value];
|
||||
|
||||
// 按顺序插入图片
|
||||
sortedImages.forEach((item, index) => {
|
||||
quill.insertEmbed(insertPosition + index, "customImage", {
|
||||
url: "https://dev.ow.f2b211.com" + item.path,
|
||||
id: item.serverImgId || generateUUID()
|
||||
});
|
||||
quill.setSelection(length + 1);
|
||||
});
|
||||
|
||||
const finalLength = quill.getLength();
|
||||
quill.setSelection(finalLength);
|
||||
// 最终光标定位到最后一张图片后面
|
||||
const finalPosition = insertPosition + sortedImages.length;
|
||||
quill.setSelection(finalPosition);
|
||||
|
||||
imageList.value = [];
|
||||
imageListDb.value = [];
|
||||
@@ -328,34 +370,254 @@ const handleHttpUpload = async options => {
|
||||
}
|
||||
};
|
||||
|
||||
// // 图片上传(保持不变)
|
||||
// const handleHttpUpload = async options => {
|
||||
// let formData = new FormData();
|
||||
// formData.append("image", options.file);
|
||||
// imageList.value.push(options.file);
|
||||
|
||||
// try {
|
||||
// const result = await uploadImg(formData, routerName.value, options.file.customUid);
|
||||
// if (result?.data?.code === 0) {
|
||||
// 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 = "";
|
||||
// let quill = "";
|
||||
// if (activeEditor.value === "main") {
|
||||
// rawQuillEditor = toRaw(myQuillEditor.value);
|
||||
// quill = rawQuillEditor.getQuill();
|
||||
// } else {
|
||||
// const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
// rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
|
||||
// quill = rawQuillEditor.getQuill();
|
||||
// }
|
||||
|
||||
// imageListDb.value.forEach(item => {
|
||||
// const length = quill.getLength() - 1;
|
||||
// quill.insertEmbed(length, "customImage", {
|
||||
// url: item.path,
|
||||
// id: item.serverImgId || generateUUID()
|
||||
// });
|
||||
// quill.setSelection(length + 1);
|
||||
// });
|
||||
|
||||
// const finalLength = quill.getLength();
|
||||
// quill.setSelection(finalLength);
|
||||
|
||||
// imageList.value = [];
|
||||
// imageListDb.value = [];
|
||||
// }
|
||||
// }
|
||||
// } 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 handleHttpUpload = async options => {
|
||||
// let formData = new FormData();
|
||||
// formData.append("image", options.file);
|
||||
// // imageList.value.push(options.file);
|
||||
|
||||
// try {
|
||||
// const result = await uploadImg(formData, routerName.value, options.file.customUid);
|
||||
// if (result?.data?.code === 0) {
|
||||
// const { data } = result.data;
|
||||
// const { imgId } = result;
|
||||
// // imageListDb.value.forEach(item => {
|
||||
// // if (item.customUid === imgId) {
|
||||
// // item.serverImgId = imgId;
|
||||
// // item.path = data.path;
|
||||
// // console.log(item.path, "================>");
|
||||
// // }
|
||||
// // });
|
||||
// const fileItem = imageListDb.value.find(item => item.customUid === imgId);
|
||||
// console.log(fileItem, "=fileItem=");
|
||||
// if (fileItem) {
|
||||
// fileItem.serverImgId = imgId;
|
||||
// fileItem.path = data.path;
|
||||
// }
|
||||
// const allFilesUploaded = imageListDb.value.every(item => item.path);
|
||||
// console.log(allFilesUploaded, "=allFilesUploaded=");
|
||||
// if (allFilesUploaded) {
|
||||
// let rawQuillEditor = "";
|
||||
// let quill = "";
|
||||
// if (activeEditor.value === "main") {
|
||||
// rawQuillEditor = toRaw(myQuillEditor.value);
|
||||
// quill = rawQuillEditor.getQuill();
|
||||
// } else {
|
||||
// const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
// rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
|
||||
// quill = rawQuillEditor.getQuill();
|
||||
// }
|
||||
|
||||
// // 关键修改:获取当前光标位置(选区起始索引)
|
||||
// const selection = quill.getSelection();
|
||||
// // 如果没有选区(光标未激活),默认插入到末尾
|
||||
// const insertPosition = selection ? selection.index : quill.getLength();
|
||||
// console.log(imageListDb, "=imageListDb=");
|
||||
// imageListDb?.value?.forEach(item => {
|
||||
// // 使用光标位置插入图片
|
||||
// setTimeout(() => {
|
||||
// quill.insertEmbed(insertPosition, "customImage", {
|
||||
// url: "https://dev.ow.f2b211.com" + item.path,
|
||||
// id: item.serverImgId || generateUUID()
|
||||
// });
|
||||
// }, 100);
|
||||
|
||||
// // 插入后光标后移一位(避免多张图片重叠插入)
|
||||
// quill.setSelection(insertPosition + 1);
|
||||
// });
|
||||
|
||||
// // 最终光标定位到最后一张图片后面
|
||||
// const finalPosition = insertPosition + imageListDb.value.length;
|
||||
// quill.setSelection(finalPosition);
|
||||
|
||||
// imageList.value = [];
|
||||
// imageListDb.value = [];
|
||||
// }
|
||||
// }
|
||||
// } 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();
|
||||
// formData.append("video", evt.target.files[0]);
|
||||
// try {
|
||||
// let rawQuillEditor = "";
|
||||
// let quill = "";
|
||||
// if (activeEditor.value === "main") {
|
||||
// rawQuillEditor = toRaw(myQuillEditor.value);
|
||||
// quill = rawQuillEditor.getQuill();
|
||||
// } else {
|
||||
// const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
// rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
|
||||
// quill = rawQuillEditor.getQuill();
|
||||
// }
|
||||
// let length = quill.selection.savedRange.index;
|
||||
// const { data } = await uploadVideo(formData);
|
||||
// quill.insertEmbed(length, "customVideo", {
|
||||
// url: data.path,
|
||||
// id: generateUUID()
|
||||
// });
|
||||
// uploadFileVideo.value.value = "";
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// }
|
||||
// };
|
||||
// 在<script setup>中替换handleVideoUpload方法
|
||||
const handleVideoUpload = async evt => {
|
||||
if (evt.target.files.length === 0) return;
|
||||
const formData = new FormData();
|
||||
formData.append("video", evt.target.files[0]);
|
||||
try {
|
||||
let rawQuillEditor = "";
|
||||
let quill = "";
|
||||
if (activeEditor.value === "main") {
|
||||
rawQuillEditor = toRaw(myQuillEditor.value);
|
||||
quill = rawQuillEditor.getQuill();
|
||||
} else {
|
||||
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
rawQuillEditor = toRaw(tabEditors.value[tabIndex]);
|
||||
quill = rawQuillEditor.getQuill();
|
||||
}
|
||||
let length = quill.selection.savedRange.index;
|
||||
const { data } = await uploadVideo(formData);
|
||||
quill.insertEmbed(length, "customVideo", {
|
||||
url: data.path,
|
||||
id: generateUUID()
|
||||
const file = evt.target.files[0];
|
||||
|
||||
// 1. 校验视频文件
|
||||
const maxSize = 150 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
ElNotification({
|
||||
title: "文件过大",
|
||||
message: `视频大小不能超过 ${150}MB`,
|
||||
type: "warning"
|
||||
});
|
||||
uploadFileVideo.value.value = "";
|
||||
evt.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 生成视频本地URL(用于生成封面,不上传)
|
||||
const localVideoUrl = URL.createObjectURL(file);
|
||||
|
||||
try {
|
||||
// 4. 并行处理:上传视频 + 生成并上传封面
|
||||
// 4.1 上传视频到视频服务器
|
||||
const videoFormData = new FormData();
|
||||
videoFormData.append("video", file);
|
||||
const videoRes = await uploadVideo(videoFormData); // 视频上传接口
|
||||
|
||||
// 校验视频上传结果(根据你的接口返回格式调整)
|
||||
if (videoRes?.code !== 0) {
|
||||
throw new Error(`视频上传失败: ${videoRes?.message || "未知错误"}`);
|
||||
}
|
||||
const videoUrl = videoRes.data.path; // 服务器返回的视频URL
|
||||
console.log(localVideoUrl, "=localVideoUrl=");
|
||||
// 4.2 生成封面图并上传到图片服务器
|
||||
const frameBlob = await Video.captureVideoFrame(localVideoUrl);
|
||||
console.log(frameBlob, "============frameBlob===========");
|
||||
let coverUrl = "";
|
||||
if (!frameBlob) return;
|
||||
|
||||
// 复用图片上传接口(与图片上传逻辑一致)
|
||||
const coverFormData = new FormData();
|
||||
const coverUid = generateUUID(); // 生成唯一ID
|
||||
// formData.append("image", options.file);
|
||||
coverFormData.append("image", frameBlob, `cover-${coverUid}.jpg`);
|
||||
console.log(coverFormData, "=coverFormData=");
|
||||
// 调用图片上传接口(和普通图片上传用同一个接口)
|
||||
const coverRes = await uploadImg(coverFormData, routerName.value, coverUid);
|
||||
|
||||
// 校验封面上传结果
|
||||
if (coverRes?.data?.code === 0) {
|
||||
coverUrl = coverRes.data.data.path; // 服务器返回的封面URL
|
||||
} else {
|
||||
console.warn("封面上传失败,使用默认封面");
|
||||
}
|
||||
|
||||
// 5. 将带封面的视频插入编辑器
|
||||
insertVideoToEditor(videoUrl, coverUrl);
|
||||
|
||||
// 6. 上传成功提示
|
||||
// loading.close();
|
||||
// ElNotification({
|
||||
// title: "上传成功",
|
||||
// message: "视频已添加到编辑器",
|
||||
// type: "success"
|
||||
// });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log(error, "==============");
|
||||
} finally {
|
||||
console.log("======12323232========");
|
||||
// 清理资源
|
||||
URL.revokeObjectURL(localVideoUrl); // 释放本地视频URL
|
||||
evt.target.value = ""; // 重置文件输入框
|
||||
}
|
||||
};
|
||||
|
||||
// 辅助方法:插入视频到编辑器
|
||||
const insertVideoToEditor = (videoUrl, coverUrl) => {
|
||||
// 获取当前活跃的编辑器(主编辑器或标签页编辑器)
|
||||
let quill;
|
||||
if (activeEditor.value === "main") {
|
||||
quill = toRaw(myQuillEditor.value)?.getQuill();
|
||||
} else {
|
||||
const tabIndex = parseInt(activeEditor.value.split("-")[1]);
|
||||
quill = toRaw(tabEditors.value[tabIndex])?.getQuill();
|
||||
}
|
||||
|
||||
if (quill) {
|
||||
const range = quill.getSelection() || { index: 0 };
|
||||
// 插入自定义视频组件,携带服务器返回的视频URL和封面URL
|
||||
quill.insertEmbed(range.index, "customVideo", {
|
||||
url: videoUrl,
|
||||
poster: coverUrl // 这里使用图片服务器返回的封面URL
|
||||
});
|
||||
quill.setSelection(range.index + 1); // 移动光标到视频后
|
||||
}
|
||||
};
|
||||
// 标签页切换事件(基于key切换)
|
||||
const handleTabChange = key => {
|
||||
const tabIndex = tabsData.value.findIndex(item => item.key === key);
|
||||
@@ -514,20 +776,49 @@ const loadTabsDataToEditor = tabs => {
|
||||
});
|
||||
};
|
||||
|
||||
// const cleanEmptyTags = container => {
|
||||
// if (!container) return;
|
||||
|
||||
// // 获取所有 <p> 标签
|
||||
// const pTags = container.querySelectorAll("p");
|
||||
|
||||
// // 只清理真正的空标签(不含任何内容,包括<br>)
|
||||
// Array.from(pTags).forEach(pTag => {
|
||||
// // 判断标准:
|
||||
// // 1. 完全没有子节点
|
||||
// // 2. 或innerHTML为空字符串
|
||||
// const isCompletelyEmpty = pTag.childNodes.length === 0 || pTag.innerHTML.trim() === "";
|
||||
|
||||
// if (isCompletelyEmpty) {
|
||||
// // 特殊处理:保留首尾空标签,避免编辑器异常
|
||||
// const isFirstOrLast = pTag === container.firstElementChild || pTag === container.lastElementChild;
|
||||
|
||||
// if (isFirstOrLast) {
|
||||
// pTag.innerHTML = ""; // 清空内容
|
||||
// } else {
|
||||
// pTag.remove(); // 移除中间的纯空标签
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
// // 触发清理的函数(针对所有编辑器)
|
||||
// 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);
|
||||
// 显示弹窗
|
||||
@@ -535,8 +826,15 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
}
|
||||
// 等待编辑器首次渲染完成
|
||||
// setTimeout(handleCleanEmptyTags, 300);
|
||||
});
|
||||
|
||||
// 监听内容变化,重新清理空标签
|
||||
// watch(editorContent, () => {
|
||||
// nextTick(() => {
|
||||
// setTimeout(handleCleanEmptyTags, 100); // 延迟确保 Quill 已重新渲染
|
||||
// });
|
||||
// });
|
||||
defineExpose({
|
||||
clearEditor: () => {
|
||||
const quill = toRaw(myQuillEditor.value)?.getQuill();
|
||||
@@ -574,5 +872,89 @@ defineExpose({
|
||||
width: 100px;
|
||||
margin: -2px 0; /* 与标签对齐 */
|
||||
}
|
||||
|
||||
/* 详情样式 */
|
||||
.o_detail_all {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.o_detail_title {
|
||||
margin-top: 2vw;
|
||||
margin-bottom: 1.25vw;
|
||||
overflow: hidden;
|
||||
font-size: 2.25em;
|
||||
font-weight: 600;
|
||||
line-height: 1.2em;
|
||||
color: #101010;
|
||||
text-align: center;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.o_detail_small {
|
||||
margin-bottom: 0.7vw;
|
||||
font-size: 1.5em;
|
||||
color: #333333;
|
||||
}
|
||||
.o_detail_text {
|
||||
width: 80%;
|
||||
margin-right: auto;
|
||||
margin-bottom: 0.7vw;
|
||||
margin-left: auto;
|
||||
font-size: 1.125em;
|
||||
line-height: 1.5em;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
./quill-image1111
|
||||
|
||||
49
src/components/Editor/quill-detail-div.js
Normal file
49
src/components/Editor/quill-detail-div.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// quill-dynamic-div.js
|
||||
import { Quill } from "@vueup/vue-quill";
|
||||
const Block = Quill.import("blots/block");
|
||||
|
||||
class DynamicDivBlot extends Block {
|
||||
// 从 DOM 节点创建 Blot 时,保留所有属性和类名
|
||||
static create(value) {
|
||||
const node = super.create();
|
||||
// 如果是从数据恢复(value 存在),还原类名
|
||||
if (value?.className) {
|
||||
node.className = value.className; // 直接设置完整类名字符串(支持多类名)
|
||||
}
|
||||
// 保留其他属性(如 id、data-* 等,按需添加)
|
||||
if (value?.attrs) {
|
||||
Object.keys(value.attrs).forEach(key => {
|
||||
node.setAttribute(key, value.attrs[key]);
|
||||
});
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// 从 Blot 提取值(序列化时用),保留所有类名和属性
|
||||
static value(node) {
|
||||
return {
|
||||
className: node.className, // 保留完整类名(如 "o_detail_all big")
|
||||
attrs: Array.from(node.attributes).reduce((attrs, attr) => {
|
||||
attrs[attr.name] = attr.value;
|
||||
return attrs;
|
||||
}, {}) // 保留所有属性
|
||||
};
|
||||
}
|
||||
|
||||
// 格式化时识别所有 div 标签
|
||||
static formats(domNode) {
|
||||
// 只要是 div 标签,就返回其类名和属性(确保 Quill 不清理)
|
||||
return {
|
||||
className: domNode.className,
|
||||
attrs: Array.from(domNode.attributes).reduce((attrs, attr) => {
|
||||
attrs[attr.name] = attr.value;
|
||||
return attrs;
|
||||
}, {})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
DynamicDivBlot.blotName = "dynamicDiv"; // 唯一标识
|
||||
DynamicDivBlot.tagName = "div"; // 处理所有 div 标签
|
||||
|
||||
export default DynamicDivBlot;
|
||||
@@ -53,7 +53,7 @@ const videoShowUrl = ref<any>(null);
|
||||
const props = withDefaults(defineProps<UploadFileProps>(), {
|
||||
videoUrl: "",
|
||||
disabled: false,
|
||||
fileSize: 200,
|
||||
fileSize: 150,
|
||||
width: "400px",
|
||||
fileType: () => [".mp4", ".avi", ".mov", "video/mp4", "video/mov", "video/avi"],
|
||||
borderRadius: "8px"
|
||||
|
||||
@@ -44,9 +44,16 @@ import { useMsg } from "@/hooks/useMsg";
|
||||
import { useUserStore } from "@/stores/modules/user";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { outLogin } from "@/utils/outLogin";
|
||||
import { useKeepAliveStore } from "@/stores/modules/keepAlive";
|
||||
import { getLanguageListApi, getLanguageCutoverApi } from "@/api/modules/global";
|
||||
import { HOME_URL } from "@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { useTabsStore } from "@/stores/modules/tabs";
|
||||
const tabStore = useTabsStore();
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const keepAliveStore = useKeepAliveStore();
|
||||
document.cookie = `lang=zh_cn`;
|
||||
const langs = ref<any>([]);
|
||||
const name = ref("");
|
||||
@@ -58,13 +65,13 @@ const getLanguageList = async () => {
|
||||
const { data } = result;
|
||||
langs.value = data;
|
||||
let id = userStore?.languageType ? userStore?.languageType : data[0]?.id;
|
||||
getLanguageCutover(id);
|
||||
getLanguageCutover(id, "noCLick");
|
||||
}
|
||||
};
|
||||
getLanguageList();
|
||||
|
||||
// 站点切换接口
|
||||
const getLanguageCutover = async (id: any) => {
|
||||
const getLanguageCutover = async (id: any, type: any) => {
|
||||
const result = await getLanguageCutoverApi(id);
|
||||
if (result?.code === 0) {
|
||||
userStore.setLanguageType(id);
|
||||
@@ -72,12 +79,35 @@ const getLanguageCutover = async (id: any) => {
|
||||
return item.id === id;
|
||||
});
|
||||
name.value = names[0]?.country_name;
|
||||
if (type === "click") {
|
||||
tabStore.setTabs([
|
||||
{
|
||||
icon: "",
|
||||
title: "首页",
|
||||
path: "/admin/index",
|
||||
name: "home",
|
||||
close: true
|
||||
}
|
||||
]);
|
||||
keepAliveStore.setKeepAliveName();
|
||||
setTimeout(() => {
|
||||
router.push(HOME_URL);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 站点切换事件
|
||||
const handleCommand = (val: string) => {
|
||||
getLanguageCutover(val);
|
||||
//切换语言将清空所有标签页面
|
||||
|
||||
ElMessageBox.confirm("切换语言将清空所有标签页面?", "温馨提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(async () => {
|
||||
await getLanguageCutover(val, "click");
|
||||
});
|
||||
};
|
||||
|
||||
// 退出登录
|
||||
|
||||
@@ -86,9 +86,23 @@ const closeOtherTab = () => {
|
||||
|
||||
// Close All
|
||||
const closeAllTab = () => {
|
||||
tabStore.closeMultipleTab();
|
||||
tabStore.setTabs([
|
||||
{
|
||||
icon: "",
|
||||
title: "首页",
|
||||
path: "/admin/index",
|
||||
name: "home",
|
||||
close: true
|
||||
}
|
||||
]);
|
||||
keepAliveStore.setKeepAliveName();
|
||||
setTimeout(() => {
|
||||
router.push(HOME_URL);
|
||||
}, 500);
|
||||
|
||||
// tabStore.closeMultipleTab();
|
||||
// keepAliveStore.setKeepAliveName();
|
||||
// router.push(HOME_URL);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import MoreButton from "./components/MoreButton.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const tabStore = useTabsStore();
|
||||
const authStore = useAuthStore();
|
||||
// const globalStore = useGlobalStore();
|
||||
@@ -44,7 +45,7 @@ const keepAliveStore = useKeepAliveStore();
|
||||
const tabsMenuValue = ref(route.fullPath);
|
||||
|
||||
const tabsMenuList = computed(() => tabStore.tabsMenuList);
|
||||
|
||||
console.log(tabStore.tabsMenuList, "===============value================");
|
||||
onMounted(() => {
|
||||
tabsDrop();
|
||||
initTabs();
|
||||
|
||||
@@ -30,12 +30,13 @@ export const useTabsStore = defineStore({
|
||||
},
|
||||
// Close MultipleTab
|
||||
async closeMultipleTab(tabsMenuValue?: string) {
|
||||
this.tabsMenuList = this.tabsMenuList.filter(item => {
|
||||
return item.path === tabsMenuValue || !item.close;
|
||||
this.tabsMenuList = this.tabsMenuList.filter((item: any) => {
|
||||
return item.path === tabsMenuValue || !item.hidden;
|
||||
});
|
||||
},
|
||||
// Set Tabs
|
||||
async setTabs(tabsMenuList: any[]) {
|
||||
console.log(tabsMenuList, "=tabsMenuList=");
|
||||
this.tabsMenuList = tabsMenuList;
|
||||
},
|
||||
// Set Tabs Title
|
||||
|
||||
@@ -15,6 +15,35 @@ const hasIdRecursive = (data: any, targetId: any) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const htmlDecode = (html: any) => {
|
||||
let e: any = document.createElement("div");
|
||||
e.innerHTML = html;
|
||||
|
||||
// 关键:在解码后添加样式处理
|
||||
const detailAllElements = e.querySelectorAll(".o_detail_all");
|
||||
detailAllElements.forEach((detailAll: HTMLElement) => {
|
||||
// 为文字类子元素添加居中样式
|
||||
let textElements: any = [
|
||||
...Array.from(detailAll.querySelectorAll<HTMLElement>(".o_detail_text")),
|
||||
...Array.from(detailAll.querySelectorAll<HTMLElement>(".o_detail_small")),
|
||||
...Array.from(detailAll.querySelectorAll<HTMLElement>(".o_detail_title"))
|
||||
];
|
||||
|
||||
textElements.forEach((el: any) => {
|
||||
// 保留原有样式,追加居中样式(避免覆盖已有样式)
|
||||
el.style.textAlign = "center";
|
||||
// 如果需要强制覆盖,可添加 !important
|
||||
// el.style.textAlign = 'center !important';
|
||||
});
|
||||
});
|
||||
|
||||
if (e.childNodes.length > 1) {
|
||||
return e.innerHTML;
|
||||
} else {
|
||||
return e.childNodes[0].innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
//将参数分离
|
||||
export const initDetailParams = (dataStore: any, data: any, editorRef: any) => {
|
||||
let is = hasIdRecursive(dataStore.options, data.category_id);
|
||||
@@ -40,14 +69,14 @@ export const initDetailParams = (dataStore: any, data: any, editorRef: any) => {
|
||||
stock_qty: data.stock_qty,
|
||||
id: data.id
|
||||
});
|
||||
console.log(data.detail, "=======detail========");
|
||||
|
||||
//详情
|
||||
if (!data.detail) {
|
||||
dataStore.detail = "";
|
||||
editorRef?.value?.clearEditor(); // 调用子组件的清空方法
|
||||
} else {
|
||||
dataStore.detail = data.detail;
|
||||
dataStore.detail = htmlDecode(data.detail); //htmlDecode(data.detail);
|
||||
console.log(data.detail, "=======detail========");
|
||||
}
|
||||
|
||||
//图片
|
||||
|
||||
Reference in New Issue
Block a user