新建邮件弹窗页面改用jeditor渲染信息字段

This commit is contained in:
jingrow 2026-05-12 01:05:15 +08:00
parent c7d6f976c0
commit 2bff091e92

View File

@ -93,9 +93,12 @@
<!-- Rich Text Body -->
<div class="compose-field compose-body">
<label class="field-label">{{ t('Message') }}</label>
<div class="text-editor-wrapper">
<div ref="editorHost"></div>
<div class="message-editor-wrapper">
<Jeditor
v-model="editorContent"
:df="{ fieldname: 'message', fieldtype: 'Jeditor', label: t('Message') }"
:disabled="false"
/>
</div>
</div>
@ -174,16 +177,13 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, nextTick, onBeforeUnmount, watch } from 'vue'
import { ref, reactive, computed, nextTick, watch } from 'vue'
import { NDrawer, NDrawerContent, NSelect, NInput, NButton, NCheckbox, NDatePicker, useMessage } from 'naive-ui'
import { Icon } from '@iconify/vue'
import { api } from '@/shared/api/common'
import { t } from '@/shared/i18n'
import LinkField from '@/shared/components/LinkField.vue'
import Quill from 'quill'
import ImageResize from 'quill-image-resize'
import MagicUrl from 'quill-magic-url'
import 'quill/dist/quill.snow.css'
import Jeditor from '@/core/pagetype/form/controls/Jeditor.vue'
// -------------------------------------------------------
// Public API
@ -238,17 +238,16 @@ function open(opts?: Partial<typeof props>) {
}
visible.value = true
nextTick(async () => {
initEditor()
// Pre-fill content if provided
if (propsRef.defaultContent) {
editorContent.value = propsRef.defaultContent || ''
}
// Append email signature
await appendSignature()
// Append reply quoting
if (propsRef.isReply && propsRef.lastEmail) {
appendReplyQuote()
}
// Pre-fill content if provided
if (propsRef.defaultContent) {
setEditorHtml(propsRef.defaultContent || '')
}
// Restore draft if available
await restoreDraft()
// Load existing attachments from reference document
@ -259,7 +258,6 @@ function open(opts?: Partial<typeof props>) {
function close() {
saveDraft() // Save draft before closing
visible.value = false
destroyEditor()
}
// Internal props ref (mutable copy for open())
@ -271,6 +269,7 @@ defineExpose({ open, close, visible })
// Form state
// -------------------------------------------------------
const showCcBcc = ref(false)
const editorContent = ref('')
const form = reactive({
sender: '',
recipients: [] as string[],
@ -301,7 +300,7 @@ function resetForm() {
showCcBcc.value = false
signatureLoaded.value = false
replyQuoteAdded.value = false
nextTick(() => setEditorHtml(''))
editorContent.value = ''
}
// -------------------------------------------------------
@ -367,11 +366,11 @@ async function appendSignature() {
currentSignature.value = sig
signatureLoaded.value = true
// Append signature to editor
const currentHtml = getEditorHtml()
const currentHtml = editorContent.value
if (currentHtml && !stripHtml(currentHtml).includes(stripHtml(sig))) {
setEditorHtml(currentHtml + sig)
editorContent.value = currentHtml + sig
} else if (!currentHtml) {
setEditorHtml(sig)
editorContent.value = sig
}
}
}
@ -380,12 +379,12 @@ function onSenderChange(newSender: string) {
// Re-append signature for new sender
if (signatureLoaded.value && currentSignature.value) {
// Remove old signature first
const html = getEditorHtml()
const html = editorContent.value
const sigHtml = stripHtml(currentSignature.value)
if (html) {
const plain = stripHtml(html)
if (plain.includes(sigHtml)) {
setEditorHtml(html.replace(currentSignature.value, ''))
editorContent.value = html.replace(currentSignature.value, '')
}
}
}
@ -467,7 +466,7 @@ async function onTemplateChange(templateName: string) {
const data = await fetchTemplateContent(templateName)
if (!data) return
if (data.subject) form.subject = data.subject
if (data.message) setEditorHtml(data.message)
if (data.message) editorContent.value = data.message
}
// -------------------------------------------------------
@ -530,9 +529,9 @@ function appendReplyQuote() {
const quoteHeader = `<p>${t('On {0}, {1} wrote:', [date, senderName])}</p>`
const quotedBlock = `<blockquote>${content}</blockquote>`
const current = getEditorHtml()
const current = editorContent.value
const quoteHtml = separator + quoteHeader + quotedBlock
setEditorHtml((current || '') + '<div><br></div>' + quoteHtml)
editorContent.value = (current || '') + '<div><br></div>' + quoteHtml
replyQuoteAdded.value = true
}
@ -606,13 +605,12 @@ function getDraftKey(): string {
}
function saveDraft() {
if (!quill) return
const draft = {
recipients: form.recipients,
cc: form.cc,
bcc: form.bcc,
subject: form.subject,
content: getEditorHtml(),
content: editorContent.value,
sender: form.sender,
emailTemplate: form.emailTemplate,
}
@ -630,7 +628,7 @@ async function restoreDraft() {
if (draft.cc?.length) form.cc = draft.cc
if (draft.bcc?.length) form.bcc = draft.bcc
if (draft.subject) form.subject = draft.subject
if (draft.content) setEditorHtml(draft.content)
if (draft.content) editorContent.value = draft.content
if (draft.sender) form.sender = draft.sender
if (draft.emailTemplate) form.emailTemplate = draft.emailTemplate
localStorage.removeItem(getDraftKey()) // Remove after restore
@ -654,57 +652,6 @@ watch(visible, (v) => {
}
})
// -------------------------------------------------------
// Rich text editor (Quill)
// -------------------------------------------------------
const editorHost = ref<HTMLDivElement | null>(null)
let quill: Quill | null = null
let syncTimer: any = null
function registerQuillExtensions() {
try { Quill.register('modules/imageResize', ImageResize, true) } catch {}
try { Quill.register('modules/magicUrl', MagicUrl, true) } catch {}
}
function initEditor() {
if (!editorHost.value || quill) return
registerQuillExtensions()
const toolbar = [
[{ header: [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike', 'clean'],
[{ color: [] }, { background: [] }],
['link', 'image'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ align: [] }],
]
quill = new Quill(editorHost.value, {
theme: 'snow',
placeholder: t('Write your message...'),
modules: { toolbar, imageResize: {}, magicUrl: true },
})
}
function destroyEditor() {
if (syncTimer) clearTimeout(syncTimer)
quill = null
}
function getEditorHtml(): string {
if (!quill) return ''
return (quill.root as HTMLElement).innerHTML || ''
}
function setEditorHtml(html: string) {
if (!quill) return
if (!html) { quill.setText(''); return }
const delta = (quill as any).clipboard.convert({ html, text: '' })
quill.setContents(delta)
}
onBeforeUnmount(() => destroyEditor())
// -------------------------------------------------------
// Attachments
// -------------------------------------------------------
@ -763,7 +710,7 @@ async function sendEmail() {
cc: form.cc.join(', ') || undefined,
bcc: form.bcc.join(', ') || undefined,
subject: form.subject,
content: getEditorHtml(),
content: editorContent.value,
sender: form.sender || undefined,
send_email: 1,
send_me_a_copy: form.sendMeCopy ? 1 : 0,
@ -869,22 +816,12 @@ function splitEmailList(str: string | undefined): string[] {
flex: 1;
}
.text-editor-wrapper {
min-height: 260px;
border: 1px solid var(--n-border-color, #e0e0e6);
border-radius: 6px;
overflow: hidden;
.message-editor-wrapper :deep(.jeditor-control .field-label) {
display: none;
}
.text-editor-wrapper :deep(.ql-toolbar) {
border: none;
border-bottom: 1px solid var(--n-border-color, #e0e0e6);
}
.text-editor-wrapper :deep(.ql-container) {
border: none;
.message-editor-wrapper :deep(.jeditor-control .jeditor) {
min-height: 220px;
font-size: 14px;
}
.attachment-area {