提交
This commit is contained in:
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>
|
||||
Reference in new issue
Block a user