This commit is contained in:
zlwu committed 2026-06-06 19:44:18 +08:00
1 parent dacfdc9ff6
commit f9676f8759
906 files changed
+533306 -12946

No files matched your search

@@ -7,9 +7,42 @@
<script setup lang="ts">
import { ref, watch, computed, onMounted } from 'vue';
import Editor from '@tinymce/tinymce-vue';
import { useUserStore } from '@/store/modules/user';
import { fetchUploadAttachment } from '@/api/admin';
import { resolveCrmAttachmentUrl } from '@/utils/crm-attachment';
import { buildAttachmentUploadFormData, CRM_UPLOAD_BIZ_TYPE } from '@/utils/crm-upload';
const userStore = useUserStore();
/** 须高于 ElConfigProvider 初始 z-index(3000) 及多层 el-dialog 叠加 */
const TINYMCE_Z_INDEX_FLOOR = 10000;
/** 读取当前页面 Element Plus 弹层最高 z-index,保证 TinyMCE 浮层在其上 */
const resolveTinyMceBaseZIndex = (): number => {
let maxZ = 3000;
document.querySelectorAll('.el-overlay, .el-dialog, .el-popper').forEach((el) => {
const z = Number.parseInt(window.getComputedStyle(el).zIndex || '0', 10);
if (!Number.isNaN(z) && z > maxZ) maxZ = z;
});
return Math.max(maxZ + 200, TINYMCE_Z_INDEX_FLOOR);
};
const syncTinyMceFloatingZIndex = (baseZ: number) => {
const z = String(baseZ);
const backdropZ = String(baseZ - 1);
document.querySelectorAll('.tox-tinymce-aux').forEach((el) => {
(el as HTMLElement).style.zIndex = z;
});
document.querySelectorAll('.tox-dialog-wrap').forEach((el) => {
(el as HTMLElement).style.zIndex = z;
});
document.querySelectorAll('.tox-dialog').forEach((el) => {
(el as HTMLElement).style.zIndex = String(baseZ + 1);
});
document.querySelectorAll('.tox-dialog__backdrop').forEach((el) => {
(el as HTMLElement).style.zIndex = backdropZ;
});
document.querySelectorAll('.tox-menu, .tox-pop, .tox-toolbar__overflow, .tox-collection').forEach((el) => {
(el as HTMLElement).style.zIndex = String(baseZ + 2);
});
};
interface Props {
modelValue?: string;
@@ -31,12 +64,6 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits(['update:modelValue']);
// 获取 Token
const getToken = () => userStore.accessToken || '';
// 获取上传 URL
const getUploadUrl = () => '/web/admin/attachment/upload';
// 生成唯一 ID
const editorId = `tinymce-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
@@ -82,7 +109,7 @@ const isSXSupplier = computed(() => {
// TinyMCE 配置
const editorInit = computed(() => {
// 根据供方编码决定是否显示背景图片
const baseZIndex = resolveTinyMceBaseZIndex();
const backgroundStyle = isSXSupplier.value
? `
/* 水印背景层 - 使用伪元素覆盖整个编辑器区域 */
@@ -248,66 +275,36 @@ const editorInit = computed(() => {
branding: false,
promotion: false,
// 设置弹窗的 z-index,确保在 Element Plus Dialog 之上
// Element Plus Dialog 默认 z-index 是 2000+,所以设置为 3000
base_z_index: 3000,
// 设置弹窗的 z-index,确保在 Element Plus Dialog 之上(须高于 App.vue ElConfigProvider :z-index="3000")
base_z_index: baseZIndex,
// 图片上传配置
// 图片上传配置(复用门户统一附件接口,与 CRM 其它上传一致)
images_upload_handler: (blobInfo: any, progress: any) => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
const xhr = new XMLHttpRequest();
xhr.open('POST', getUploadUrl());
// 设置请求头
const token = getToken();
const clientId = getClientId();
if (token) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
}
if (clientId) {
xhr.setRequestHeader('clientid', clientId);
}
// 上传进度
xhr.upload.onprogress = (e) => {
progress((e.loaded / e.total) * 100);
};
// 上传成功
xhr.onload = () => {
if (xhr.status === 200) {
try {
const json = JSON.parse(xhr.responseText);
if (json.code === 200) {
// 返回图片URL
resolve(json.data.url);
console.log('图片上传成功:', json.data.url);
} else {
const errorMsg = json.msg || '上传失败';
reject(errorMsg);
console.error('图片上传失败:', errorMsg);
}
} catch (e) {
reject('响应解析失败');
console.error('图片上传失败:响应解析错误');
}
} else {
reject('HTTP错误:' + xhr.status);
console.error('图片上传失败:HTTP ' + xhr.status);
return new Promise(async (resolve, reject) => {
try {
progress(10);
const file = new File([blobInfo.blob()], blobInfo.filename(), {
type: blobInfo.blob().type || 'image/jpeg'
});
const formData = buildAttachmentUploadFormData(file, {
bizType: CRM_UPLOAD_BIZ_TYPE.QUOTE_ATTACHMENT
});
progress(40);
const res = await fetchUploadAttachment(formData);
const fileUrl = res?.fileUrl;
if (!fileUrl) {
reject('上传失败');
return;
}
};
// 上传失败
xhr.onerror = () => {
reject('网络错误');
console.error('图片上传失败:网络错误');
};
xhr.send(formData);
progress(100);
const imageUrl = resolveCrmAttachmentUrl(fileUrl);
resolve(imageUrl);
console.log('图片上传成功:', imageUrl);
} catch (error: any) {
const errorMsg = error?.message || '上传失败';
reject(errorMsg);
console.error('图片上传失败:', errorMsg);
}
});
},
@@ -334,6 +331,20 @@ const editorInit = computed(() => {
// 初始化回调
setup: (editor: any) => {
const refreshFloatingLayers = () => {
const z = resolveTinyMceBaseZIndex();
syncTinyMceFloatingZIndex(z);
};
editor.on('init', () => {
refreshFloatingLayers();
});
editor.on('OpenWindow', refreshFloatingLayers);
editor.on('CloseWindow', refreshFloatingLayers);
editor.on('ShowMenu', refreshFloatingLayers);
editor.on('focus', refreshFloatingLayers);
// 编辑器初始化完成
// 注意:水印已在 content_style 的 body::before 伪元素中设置,无需重复添加
editor.on('init', () => {
@@ -544,20 +555,29 @@ onMounted(() => {
</style>
<style lang="scss">
/* TinyMCE 全局弹窗样式 - 不使用 scoped,确保对所有 TinyMCE 弹窗生效 */
/* TinyMCE 全局浮层:高于 ElConfigProvider z-index(3000) 及嵌套 el-dialog */
.tox-tinymce-aux {
z-index: 3000 !important;
z-index: 10000 !important;
}
.tox-dialog-wrap {
z-index: 3000 !important;
z-index: 10000 !important;
}
.tox-dialog {
z-index: 3001 !important;
z-index: 10001 !important;
}
.tox-dialog__backdrop {
z-index: 2999 !important;
z-index: 9999 !important;
}
.tox-menu,
.tox-pop,
.tox-toolbar__overflow,
.tox-collection,
.tox-swatches,
.tox-selector {
z-index: 10002 !important;
}
</style>