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

+79 -49
View File
@@ -34,46 +34,47 @@
</el-form>
<!-- 联系人列表 -->
<el-table
v-if="customerId"
v-loading="loading"
:data="contactList"
@current-change="handleCurrentChange"
:max-height="400"
border
highlight-current-row
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="联系人姓名" prop="contactName" min-width="150" :show-overflow-tooltip="true">
<template #default="scope">
<div style="display: flex; align-items: center">
<span style="margin-right: 5px">{{ scope.row.contactName }}</span>
<el-tag v-if="scope.row.isPrimary === 1" type="success" size="small" style="margin-right: 5px">主要</el-tag>
<el-tag v-if="scope.row.isKey === 1" type="warning" size="small">决策人</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="性别" prop="gender" width="80" align="center">
<template #default="scope">
<DictTag dict-type="crm_contact_gender" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="手机号码" prop="mobile" width="130" />
<el-table-column label="邮箱" prop="email" width="180" :show-overflow-tooltip="true" />
<el-table-column label="部门" prop="department" width="120" :show-overflow-tooltip="true" />
<el-table-column label="职位" prop="position" width="120" :show-overflow-tooltip="true" />
</el-table>
<div v-if="customerId" class="panel dialog-list-panel">
<div class="list-body list-wrap dialog-list-body-only">
<el-table
v-loading="loading"
:data="contactList"
class="list-table"
stripe
highlight-current-row
@current-change="handleCurrentChange"
:max-height="400"
>
<el-table-column type="index" label="序号" min-width="60" align="center" header-align="center" />
<el-table-column label="联系人姓名" prop="contactName" min-width="150" align="center" header-align="center" :show-overflow-tooltip="true">
<template #default="scope">
<div class="contact-name-cell">
<span>{{ scope.row.contactName }}</span>
<el-tag v-if="scope.row.isPrimary === 1" type="success" size="small">主要</el-tag>
<el-tag v-if="scope.row.isKey === 1" type="warning" size="small">决策人</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="性别" prop="gender" min-width="80" align="center" header-align="center">
<template #default="scope">
<DictTag dict-type="crm_contact_gender" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="手机号码" prop="mobile" min-width="130" align="center" header-align="center" />
<el-table-column label="邮箱" prop="email" min-width="180" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="部门" prop="department" min-width="120" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="职位" prop="position" min-width="120" align="center" header-align="center" :show-overflow-tooltip="true" />
</el-table>
<!-- 分页 -->
<ListPagination
v-if="customerId"
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
class="mt-3"
/>
<ListPagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
<!-- 当前选中联系人 -->
<template v-if="currentContact">
@@ -132,6 +133,7 @@ watch(
dialogVisible.value = val
if (val && props.customerId) {
await dictStore.loadAllDict()
resetQueryParams()
getList()
}
}
@@ -166,28 +168,34 @@ const total = ref(0)
const currentContact = ref<ContactVO | null>(null)
const queryFormRef = ref<ElFormInstance>()
const resetQueryParams = () => {
queryParams.value.pageNum = 1
queryParams.value.contactName = undefined
queryParams.value.mobile = undefined
queryParams.value.position = undefined
queryFormRef.value?.resetFields()
}
const getList = async () => {
if (!props.customerId) {
contactList.value = []
total.value = 0
return
}
loading.value = true
try {
queryParams.value.customerId = props.customerId
const keyword = [queryParams.value.contactName, queryParams.value.mobile, queryParams.value.position]
.filter(Boolean)
.join(' ')
.trim()
const res = await fetchContactList({
page: queryParams.value.pageNum,
pageSize: queryParams.value.pageSize,
customerId: String(props.customerId),
keyword: keyword || undefined
contactName: queryParams.value.contactName?.trim() || undefined,
mobile: queryParams.value.mobile?.trim() || undefined,
position: queryParams.value.position?.trim() || undefined
})
const data = (res as any)?.list ?? (res as any)?.rows ?? (res as any)?.data?.list ?? []
contactList.value = data
total.value = (res as any)?.total ?? (res as any)?.data?.total ?? data.length
contactList.value = res.list || res.rows || []
total.value = res.total ?? 0
} catch (error) {
console.error('获取联系人列表失败:', error)
} finally {
@@ -201,10 +209,24 @@ const handleQuery = () => {
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
resetQueryParams()
getList()
}
watchDebounced(
() => ({
contactName: queryParams.value.contactName,
mobile: queryParams.value.mobile,
position: queryParams.value.position
}),
() => {
if (!props.customerId) return
queryParams.value.pageNum = 1
getList()
},
{ debounce: 300, deep: true }
)
const handleCurrentChange = (row: ContactVO) => {
currentContact.value = row
}
@@ -243,4 +265,12 @@ const handleClose = () => {
.form-buttons-right {
margin-left: auto !important;
}
.contact-name-cell {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 5px;
}
</style>
+60 -36
View File
@@ -36,43 +36,53 @@
</el-form>
<!-- 客户列表 -->
<el-table v-loading="loading" :data="customerList" @current-change="handleCurrentChange" :max-height="400" border highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="客户名称" prop="customerName" min-width="230" :show-overflow-tooltip="true" />
<el-table-column label="客户类型" prop="customerType" width="150" align="center">
<template #default="scope">
<DictTag dict-type="crm_customer_type" :value="scope.row.customerType" />
</template>
</el-table-column>
<el-table-column label="所属行业" prop="industry" width="120" :show-overflow-tooltip="true">
<template #default="scope">
<DictTag dict-type="crm_customer_industry" :value="scope.row.industry" />
</template>
</el-table-column>
<el-table-column label="客户级别" prop="level" width="100" align="center">
<template #default="scope">
<DictTag dict-type="crm_customer_level" :value="scope.row.level" />
</template>
</el-table-column>
<el-table-column label="联系人" prop="contactName" width="100" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" prop="mobile" width="120" />
<el-table-column label="地区" prop="province" width="120">
<template #default="scope">
<span>{{ scope.row.province }} {{ scope.row.city }}</span>
</template>
</el-table-column>
<el-table-column label="负责人" prop="ownerUserName" width="100" :show-overflow-tooltip="true" />
</el-table>
<div class="panel dialog-list-panel">
<div class="list-body list-wrap dialog-list-body-only">
<el-table
v-loading="loading"
:data="customerList"
class="list-table"
stripe
highlight-current-row
@current-change="handleCurrentChange"
:max-height="400"
>
<el-table-column type="index" label="序号" min-width="60" align="center" header-align="center" />
<el-table-column label="客户名称" prop="customerName" min-width="230" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="客户类型" prop="customerType" min-width="150" align="center" header-align="center">
<template #default="scope">
<DictTag dict-type="crm_customer_type" :value="scope.row.customerType" />
</template>
</el-table-column>
<el-table-column label="所属行业" prop="industry" min-width="120" align="center" header-align="center" :show-overflow-tooltip="true">
<template #default="scope">
<DictTag dict-type="crm_customer_industry" :value="scope.row.industry" />
</template>
</el-table-column>
<el-table-column label="客户级别" prop="level" min-width="100" align="center" header-align="center">
<template #default="scope">
<DictTag dict-type="crm_customer_level" :value="scope.row.level" />
</template>
</el-table-column>
<el-table-column label="联系人" prop="contactName" min-width="100" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" prop="mobile" min-width="120" align="center" header-align="center" />
<el-table-column label="地区" prop="province" min-width="120" align="center" header-align="center">
<template #default="scope">
<span>{{ scope.row.province }} {{ scope.row.city }}</span>
</template>
</el-table-column>
<el-table-column label="负责人" prop="ownerUserName" min-width="100" align="center" header-align="center" :show-overflow-tooltip="true" />
</el-table>
<!-- 分页 -->
<ListPagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
class="mt-3"
/>
<ListPagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
<!-- 当前选中客户 -->
<template v-if="currentCustomer">
@@ -188,6 +198,20 @@ const resetQuery = () => {
handleQuery()
}
watchDebounced(
() => ({
customerName: queryParams.value.customerName,
customerNo: queryParams.value.customerNo,
customerType: queryParams.value.customerType,
mobile: queryParams.value.mobile
}),
() => {
queryParams.value.pageNum = 1
getList()
},
{ debounce: 300, deep: true }
)
const handleCurrentChange = (row: any) => {
currentCustomer.value = row
}
@@ -22,6 +22,7 @@
import { computed, ref, watch, onBeforeUnmount } from 'vue'
import { PictureFilled } from '@element-plus/icons-vue'
import request from '@/utils/http'
import { resolveCrmAttachmentUrl } from '@/utils/crm-attachment'
const props = defineProps({
src: {
@@ -166,8 +167,15 @@ watch(
}
if (props.isOssId) {
const ossIds = newSrc.split(',').filter((id) => id.trim())
const urls = await getUrlsByOssIds(ossIds)
const ossKeys = newSrc.split(',').filter((id) => id.trim())
// 门户 CRM:MinIO 相对路径(含 /),直接拼公开前缀展示
if (ossKeys.some((key) => key.includes('/'))) {
const urls = ossKeys.map((key) => resolveCrmAttachmentUrl(key)).filter(Boolean)
realSrc.value = urls[0] || ''
realSrcList.value = urls
return
}
const urls = await getUrlsByOssIds(ossKeys)
realSrc.value = urls[0] || ''
realSrcList.value = urls
return
@@ -41,43 +41,53 @@
</el-form>
<!-- 商机列表 -->
<el-table v-loading="loading" :data="opportunityList" @current-change="handleCurrentChange" :max-height="400" border highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="商机编号" prop="opportunityNo" width="140" align="center" :show-overflow-tooltip="true" />
<el-table-column label="商机名称" prop="opportunityName" min-width="180" :show-overflow-tooltip="true" />
<el-table-column label="客户名称" prop="customerName" width="150" :show-overflow-tooltip="true" />
<el-table-column label="商机阶段" prop="stage" width="120" align="center">
<template #default="scope">
<DictTag dict-type="crm_opportunity_stage" :value="scope.row.stage" />
</template>
</el-table-column>
<el-table-column label="预计成交率" prop="winRate" width="100" align="center">
<template #default="scope">
<el-progress :percentage="scope.row.winRate || 0" :color="getWinRateColor(scope.row.winRate)" />
</template>
</el-table-column>
<el-table-column label="预计成交日期" prop="expectedCloseDate" width="120" align="center">
<template #default="scope">
<span>{{ scope.row.expectedCloseDate || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="负责人" prop="ownerUserName" width="100" :show-overflow-tooltip="true" />
<el-table-column label="状态" prop="status" width="80" align="center">
<template #default="scope">
<DictTag dict-type="crm_opportunity_status" :value="scope.row.status" />
</template>
</el-table-column>
</el-table>
<div class="panel dialog-list-panel">
<div class="list-body list-wrap dialog-list-body-only">
<el-table
v-loading="loading"
:data="opportunityList"
class="list-table"
stripe
highlight-current-row
@current-change="handleCurrentChange"
:max-height="400"
>
<el-table-column type="index" label="序号" min-width="60" align="center" header-align="center" />
<el-table-column label="商机编号" prop="opportunityNo" min-width="140" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="商机名称" prop="opportunityName" min-width="180" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="客户名称" prop="customerName" min-width="150" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="商机阶段" prop="stage" min-width="120" align="center" header-align="center">
<template #default="scope">
<DictTag dict-type="crm_opportunity_stage" :value="scope.row.stage" />
</template>
</el-table-column>
<el-table-column label="预计成交率" prop="winRate" min-width="100" align="center" header-align="center">
<template #default="scope">
<el-progress :percentage="scope.row.winRate || 0" :color="getWinRateColor(scope.row.winRate)" />
</template>
</el-table-column>
<el-table-column label="预计成交日期" prop="expectedCloseDate" min-width="120" align="center" header-align="center">
<template #default="scope">
<span>{{ scope.row.expectedCloseDate || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="负责人" prop="ownerUserName" min-width="100" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="状态" prop="status" min-width="80" align="center" header-align="center">
<template #default="scope">
<DictTag dict-type="crm_opportunity_status" :value="scope.row.status" />
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<ListPagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
class="mt-3"
/>
<ListPagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
<!-- 当前选中商机 -->
<template v-if="currentOpportunity">
@@ -202,6 +212,20 @@ const resetQuery = () => {
handleQuery()
}
watchDebounced(
() => ({
opportunityName: queryParams.value.opportunityName,
opportunityNo: queryParams.value.opportunityNo,
stage: queryParams.value.stage,
status: queryParams.value.status
}),
() => {
queryParams.value.pageNum = 1
getList()
},
{ debounce: 300, deep: true }
)
const handleCurrentChange = (row: OpportunityVO) => {
currentOpportunity.value = row
}
+68 -34
View File
@@ -39,40 +39,54 @@
</el-form>
<!-- 报价单列表 -->
<el-table v-loading="loading" :data="quoteList" @current-change="handleCurrentChange" :max-height="400" border highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="报价单编号" prop="quoteNo" width="140" align="center" :show-overflow-tooltip="true" />
<el-table-column label="状态" prop="status" width="100" align="center">
<template #default="scope">
<DictTag dict-type="crm_quote_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="报价主题" prop="quoteTitle" min-width="180" :show-overflow-tooltip="true" />
<el-table-column label="客户名称" prop="customerName" width="150" :show-overflow-tooltip="true" />
<el-table-column label="合同关联情况" prop="contractLinkedCount" width="120" align="center">
<template #default="scope">
<span>{{ scope.row.contractLinkedCount != null ? `已关联${scope.row.contractLinkedCount}个合同` : '未关联' }}</span>
</template>
</el-table-column>
<el-table-column label="报价金额" prop="finalAmount" width="120" align="right">
<template #default="scope">
<span>{{ scope.row.finalAmount != null ? Number(scope.row.finalAmount).toLocaleString() : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="报价日期" prop="quoteDate" width="120" align="center" />
<el-table-column label="有效期" prop="expireDate" width="120" align="center" />
<el-table-column label="负责人" prop="ownerUserName" width="100" :show-overflow-tooltip="true" />
</el-table>
<div class="panel dialog-list-panel">
<div class="list-body list-wrap dialog-list-body-only">
<el-table
v-loading="loading"
:data="quoteList"
class="list-table"
stripe
highlight-current-row
@current-change="handleCurrentChange"
:max-height="400"
>
<el-table-column type="index" label="序号" min-width="60" align="center" header-align="center" />
<el-table-column label="报价单编号" prop="quoteNo" min-width="140" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="状态" prop="status" min-width="100" align="center" header-align="center">
<template #default="scope">
<DictTag dict-type="crm_quote_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="报价主题" prop="quoteTitle" min-width="180" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="客户名称" prop="customerName" min-width="150" align="center" header-align="center" :show-overflow-tooltip="true" />
<el-table-column label="合同关联情况" prop="contractLinkedCount" min-width="120" align="center" header-align="center">
<template #default="scope">
<span>{{ scope.row.contractLinkedCount != null ? `已关联${scope.row.contractLinkedCount}个合同` : '未关联' }}</span>
</template>
</el-table-column>
<el-table-column label="报价金额(元)" prop="finalAmount" min-width="130" align="right" header-align="center">
<template #default="scope">
<span class="money-display">{{
scope.row.finalAmount != null
? `¥${Number(scope.row.finalAmount).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
: '-'
}}</span>
</template>
</el-table-column>
<el-table-column label="报价日期" prop="quoteDate" min-width="120" align="center" header-align="center" />
<el-table-column label="有效期" prop="expireDate" min-width="120" align="center" header-align="center" />
<el-table-column label="负责人" prop="ownerUserName" min-width="100" align="center" header-align="center" :show-overflow-tooltip="true" />
</el-table>
<!-- 分页 -->
<ListPagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
class="mt-3"
/>
<ListPagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
<!-- 当前选中报价单 -->
<template v-if="currentQuote">
@@ -82,7 +96,13 @@
<el-descriptions-item label="报价标题">{{ currentQuote.quoteTitle }}</el-descriptions-item>
<el-descriptions-item label="报价编号">{{ currentQuote.quoteNo }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ currentQuote.customerName }}</el-descriptions-item>
<el-descriptions-item label="报价金额">{{ currentQuote.finalAmount != null ? Number(currentQuote.finalAmount).toLocaleString() : '-' }}</el-descriptions-item>
<el-descriptions-item label="报价金额(元)">
<span class="money-display">{{
currentQuote.finalAmount != null
? `¥${Number(currentQuote.finalAmount).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
: '-'
}}</span>
</el-descriptions-item>
<el-descriptions-item label="报价日期">{{ currentQuote.quoteDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="有效期至">{{ currentQuote.expireDate || '-' }}</el-descriptions-item>
</el-descriptions>
@@ -207,6 +227,20 @@ const resetQuery = () => {
handleQuery()
}
watchDebounced(
() => ({
quoteNo: queryParams.value.quoteNo,
quoteTitle: queryParams.value.quoteTitle,
customerName: queryParams.value.customerName,
status: queryParams.value.status
}),
() => {
queryParams.value.pageNum = 1
getList()
},
{ debounce: 300, deep: true }
)
const handleCurrentChange = async (row: QuoteVO) => {
if (!row) {
currentQuote.value = null
@@ -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>