You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
653 lines
27 KiB
JavaScript
653 lines
27 KiB
JavaScript
import { useEffect, useState, useRef, useCallback } from 'react';
|
|
import { App, ConfigProvider, Button, Form, Input, Flex, Checkbox, Popconfirm, Select, Space, Upload, Divider, Modal, Tabs } from 'antd';
|
|
import { UploadOutlined, LoadingOutlined } from '@ant-design/icons';
|
|
import '@dckj/react-better-modal/dist/index.css';
|
|
import DnDModal from '@/components/DnDModal';
|
|
import useStyleStore from '@/stores/StyleStore';
|
|
import useConversationStore from '@/stores/ConversationStore';
|
|
import useAuthStore from '@/stores/AuthStore';
|
|
|
|
import LexicalEditor from '@/components/LexicalEditor';
|
|
|
|
import { v4 as uuid } from 'uuid';
|
|
import { cloneDeep, debounce, isEmpty, } from '@/utils/commons';
|
|
import { writeIndexDB } from '@/utils/indexedDB';
|
|
import './EmailEditor.css';
|
|
import { postSendEmail } from '@/actions/EmailActions';
|
|
import { sentMsgTypeMapped, } from '@/channel/bubbleMsgUtils';
|
|
import { EmailBuilder, useEmailDetail, useEmailSignature } from '@/hooks/useEmail';
|
|
import useSnippetStore from '@/stores/SnippetStore'
|
|
import { useOrderStore } from '@/stores/OrderStore'
|
|
import PaymentlinkBtn from './PaymentlinkBtn';
|
|
|
|
// 禁止上传的附件类型
|
|
// .application, .exe, .app
|
|
const disallowedAttachmentTypes = ['.ps1','.msi','application/x-msdownload', 'application/x-ms-dos-executable', 'application/x-ms-wmd', 'application/x-ms-wmz', 'application/x-ms-xbap', 'application/x-msaccess', ];
|
|
|
|
const getAbstract = (longtext) => {
|
|
const lines = longtext.split('\n');
|
|
const firstLine = lines[0];
|
|
const abstract = firstLine.substring(0, 20);
|
|
return abstract;
|
|
};
|
|
const parseHTMLText = (html) => {
|
|
const parser = new DOMParser()
|
|
const dom = parser.parseFromString(html, 'text/html')
|
|
// Replace <br> and <p> with line breaks
|
|
Array.from(dom.body.querySelectorAll('br, p')).forEach(el => {
|
|
el.textContent = '\n' + el.textContent;
|
|
});
|
|
// Replace <hr> with a line of dashes
|
|
Array.from(dom.body.querySelectorAll('hr')).forEach(el => {
|
|
el.textContent = '\n------------------------------------------------------------------\n';
|
|
});
|
|
return dom.body.textContent || '';
|
|
}
|
|
|
|
const generateQuoteContent = (mailData, isRichText = true) => {
|
|
const html = `<br><br><hr><p class="font-sans"><b><strong >From: </strong></b><span >${((mailData.info?.MAI_From || '').replace(/</g,'<').replace(/>/g,'>'))} </span></p><p class="font-sans"><b><strong >Sent: </strong></b><span >${mailData.info?.MAI_SendDate || ''}</span></p><p class="font-sans"><b><strong >To: </strong></b><span >${(mailData.info?.MAI_To || '').replace(/</g,'<').replace(/>/g,'>')}</span></p><p class="font-sans"><b><strong >Subject: </strong></b><span >${mailData.info?.MAI_Subject || ''}</span></p><p>${mailData.info?.MAI_ContentType === 'text/html' ? mailData.content : mailData.content.replace(/\r\n/g, '<br>')}</p>`
|
|
return isRichText ? html : parseHTMLText(html)
|
|
};
|
|
|
|
|
|
const generateMailContent = (mailData) => `<br><br><p>${mailData.content}</p>`
|
|
|
|
/**
|
|
* @property {string} fromEmail - 发件人邮箱
|
|
* @property {string} fromUser - 发件人用户
|
|
* @property {string} fromOrder - 发件订单
|
|
* @property {string} toEmail - 收件人邮箱
|
|
* @property {string} conversationid - 会话ID
|
|
* @property {string} quoteid - 引用邮件ID
|
|
* @property {object} draft - 草稿
|
|
*/
|
|
const EmailEditorPopup = () => {
|
|
const { notification, message } = App.useApp();
|
|
const [form] = Form.useForm();
|
|
const [mobile] = useStyleStore((state) => [state.mobile]);
|
|
const [userId, username, emailList] = useAuthStore((state) => [state.loginUser.userId, state.loginUser.username, state.loginUser.emailList]);
|
|
const emailListOption = emailList?.map(ele => ({ ...ele, label: ele.email, key: ele.email, value: ele.email })) || [];
|
|
// const emailListMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.opi_sn]: v }), {});
|
|
const emailListAddrMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.email]: v }), {});
|
|
const emailListMatMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.mat_sn]: v }), {});
|
|
// console.log('emailListMapped', emailListOption, emailListAddrMapped);
|
|
|
|
const emailEdiorProps = useConversationStore((state) => state.emailEdiorProps);
|
|
const [open, setOpen, closeEditor1, currentEditKey, setCurrentEditKey] = useConversationStore((state) => [state.editorOpen, state.setEditorOpen, state.closeEditor1, state.currentEditKey, state.setCurrentEditKey]);
|
|
|
|
const propsKeysArr = Array.from(emailEdiorProps.keys());
|
|
const propsArr = Array.from(emailEdiorProps.values());
|
|
|
|
const [activeEdit, setActiveEdit] = useState(emailEdiorProps.get(currentEditKey) || {});
|
|
// const { fromEmail, fromUser, fromOrder, oid, toEmail, conversationid, quoteid, initial = {}, mailData: _mailData, action = 'reply', draft = {}, receiverName, ...props } = emailEdiorProps.get(currentEditKey) || {};
|
|
|
|
const onChangeActiveEditor = (key) => {
|
|
setCurrentEditKey(key);
|
|
const _find = emailEdiorProps.get(key) || {};
|
|
setActiveEdit(_find);
|
|
};
|
|
|
|
const onEditTab = (targetKey, action) => {
|
|
if (action === 'add') {
|
|
//
|
|
} else {
|
|
if (propsKeysArr.length === 1) {
|
|
setOpen(false);
|
|
}
|
|
closeEditor1(targetKey);
|
|
}
|
|
};
|
|
|
|
|
|
const mai_sn = activeEdit.quoteid;
|
|
const { loading: getLoading, mailData } = useEmailDetail(mai_sn, activeEdit.mailData)
|
|
|
|
|
|
const [stickToProps, setStickToProps] = useState({});
|
|
const [propsSerialize, setPropsSerialize] = useState('');
|
|
|
|
const [newFromEmail, setNewFromEmail] = useState('');
|
|
const [newToEmail, setNewToEmail] = useState('');
|
|
const [emailOPI, setEmailOPI] = useState('');
|
|
const [emailOrder, setEmailOrder] = useState('');
|
|
const [emailMat, setEmailMat] = useState('');
|
|
|
|
const stateReset = () => {
|
|
setStickToProps({})
|
|
setStickToCid('')
|
|
setEmailOrder('')
|
|
setEmailOPI('')
|
|
setNewFromEmail('')
|
|
setNewToEmail('')
|
|
}
|
|
|
|
const [contentPrefix, setContentPrefix] = useState('');
|
|
|
|
// 存储: 会话ID,
|
|
// 这个窗口没有模态, 即使不是focus, 还是需要保持会话ID
|
|
// 否则, 会话列表切换之后, 会话ID更新, 导致消息关联错误
|
|
const [stickToCid, setStickToCid] = useState(activeEdit.conversationid);
|
|
useEffect(() => {
|
|
const propsObj = { ...activeEdit, mai: activeEdit.mailData?.info?.MAI_MAT_SN, }
|
|
|
|
setContentPrefix(activeEdit.oid ? `<p>Dear Mr./Ms. ${activeEdit.receiverName || ''}</p><p>Reference Number: ${activeEdit.oid}</p>` : '');
|
|
|
|
// 没有引用邮件
|
|
if (isEmpty(activeEdit.quoteid)) {
|
|
setStickToProps(propsObj)
|
|
setPropsSerialize(JSON.stringify(propsObj))
|
|
|
|
setStickToCid(activeEdit.conversationid)
|
|
setEmailOrder(activeEdit.fromOrder)
|
|
setEmailOPI(activeEdit.fromUser)
|
|
setNewFromEmail(activeEdit.fromEmail)
|
|
setNewToEmail(activeEdit.toEmail)
|
|
|
|
const _findMat = emailListAddrMapped?.[activeEdit.fromEmail]?.mat_sn
|
|
setEmailMat(_findMat)
|
|
|
|
// if (open !== true) {
|
|
// form.resetFields()
|
|
// }
|
|
}
|
|
// 转发/回复时, 使用详情的账户信息
|
|
if (mailData.info?.MAI_MAT_SN) {
|
|
const reEmailO = mailData.info?.MAI_COLI_SN
|
|
const reEmailUser = mailData.info?.MAI_OPI_SN
|
|
const reEmailUserMat = mailData.info?.MAI_MAT_SN
|
|
|
|
setEmailOrder((prev) => reEmailO || prev || activeEdit.fromOrder)
|
|
setEmailOPI((prev) => reEmailUser || prev)
|
|
setEmailMat((prev) => reEmailUserMat || prev)
|
|
|
|
const _findMatOld = emailListMatMapped?.[reEmailUserMat]
|
|
if (_findMatOld) {
|
|
setNewFromEmail(_findMatOld.email)
|
|
setEmailOPI(_findMatOld.opi_sn)
|
|
setEmailMat(_findMatOld.mat_sn)
|
|
}
|
|
}
|
|
setShowQuoteContent(false);
|
|
setMergeQuote(true);
|
|
setQuoteContent('')
|
|
|
|
return () => {}
|
|
}, [open, mailData])
|
|
|
|
const handleSwitchEmail = (labelValue) => {
|
|
const { value } = labelValue
|
|
setNewFromEmail(value)
|
|
const _findMat = emailListAddrMapped?.[value]
|
|
setEmailMat(_findMat?.mat_sn)
|
|
setEmailOPI(_findMat?.opi_sn)
|
|
};
|
|
|
|
const { signature } = useEmailSignature(emailOPI)
|
|
|
|
const [isRichText, setIsRichText] = useState(mobile === false);
|
|
// const [isRichText, setIsRichText] = useState(false); // 默认纯文本
|
|
const [htmlContent, setHtmlContent] = useState('');
|
|
const [textContent, setTextContent] = useState('');
|
|
|
|
const [showCc, setShowCc] = useState(true);
|
|
const [showBcc, setShowBcc] = useState(false);
|
|
const handleShowCc = () => {
|
|
setShowCc(true);
|
|
};
|
|
const handleShowBcc = () => {
|
|
setShowBcc(true);
|
|
};
|
|
|
|
const handleEditorChange = ({ editorStateJSON, htmlContent, textContent }) => {
|
|
// console.log('textContent', textContent);
|
|
// console.log('html', html);
|
|
setHtmlContent(htmlContent);
|
|
setTextContent(textContent);
|
|
form.setFieldValue('content', htmlContent);
|
|
form.setFieldValue('abstract', getAbstract(textContent));
|
|
debouncedSave({htmlContent});
|
|
};
|
|
|
|
const [initialForm, setInitialForm] = useState({});
|
|
const [initialContent, setInitialContent] = useState('');
|
|
const [showQuoteContent, setShowQuoteContent] = useState(false);
|
|
const [mergeQuote, setMergeQuote] = useState(true);
|
|
const [quoteContent, setQuoteContent] = useState('');
|
|
|
|
useEffect(() => {
|
|
// console.log('quoteid', quoteid, isEmpty(quoteid), isEmpty(mailData.info));
|
|
|
|
if (isEmpty(activeEdit.quoteid) && activeEdit.action !== 'new') {
|
|
return () => {}
|
|
}
|
|
const { info } = mailData
|
|
// setShowCc(!isEmpty(mailData.info?.MAI_CS));
|
|
|
|
const signatureBody = generateMailContent({ content: signature })
|
|
|
|
// const preQuoteBody = generateQuoteContent(mailData)
|
|
|
|
// const _initialContent = isEmpty(mailData.info) ? signatureBody : signatureBody+preQuoteBody
|
|
|
|
if (!isEmpty(mailData.info) && activeEdit.action !== 'edit') {
|
|
setInitialContent(contentPrefix + signatureBody)
|
|
}
|
|
|
|
const _formValues = {
|
|
to: info?.replyToAll || newFromEmail,
|
|
cc: info?.MAI_CS || '',
|
|
// bcc: quote.bcc || '',
|
|
subject: `Re: ${info.MAI_Subject || ''}`,
|
|
}
|
|
const forwardValues = { from: newFromEmail, subject: `Fw: ${info.MAI_Subject || ''}` }
|
|
if (activeEdit.action === 'reply') {
|
|
form.setFieldsValue(_formValues)
|
|
setInitialForm(_formValues)
|
|
} else if (activeEdit.action === 'forward') {
|
|
setStickToCid('0')
|
|
form.setFieldsValue(forwardValues)
|
|
setInitialForm(forwardValues)
|
|
} else if (activeEdit.action === 'edit') {
|
|
const thisFormValues = {
|
|
to: info?.MAI_To || '',
|
|
cc: info?.MAI_CS || '',
|
|
subject: info?.MAI_Subject || '',
|
|
}
|
|
form.setFieldsValue(thisFormValues)
|
|
setInitialForm(thisFormValues)
|
|
const thisBody = generateMailContent(mailData)
|
|
// console.log('thisBody', thisBody);
|
|
|
|
setInitialContent(thisBody)
|
|
} else if (activeEdit.action === 'new') {
|
|
const newEmail = { to: newToEmail, subject: activeEdit.draft?.subject || '' }
|
|
form.setFieldsValue(newEmail)
|
|
setInitialForm(newEmail)
|
|
setInitialContent((activeEdit.draft?.content || contentPrefix || '') + signatureBody)
|
|
}
|
|
|
|
return () => {}
|
|
}, [propsSerialize, mailData.info, signature, newToEmail, newFromEmail]);
|
|
|
|
const [openPlainTextConfirm, setOpenPlainTextConfirm] = useState(false);
|
|
const handlePlainTextOpenChange = ({ target }) => {
|
|
const { checked: newChecked } = target;
|
|
if (!newChecked) {
|
|
setIsRichText(true);
|
|
setOpenPlainTextConfirm(false);
|
|
return;
|
|
}
|
|
setOpenPlainTextConfirm(true);
|
|
};
|
|
const confirmPlainText = () => {
|
|
setIsRichText(false);
|
|
setOpenPlainTextConfirm(false);
|
|
};
|
|
|
|
// 附件:
|
|
// 1. ~直接上传返回地址~
|
|
// 2. 发送文件信息
|
|
const [fileList, setFileList] = useState([]);
|
|
const handleChange = (info) => {
|
|
let newFileList = [...info.fileList];
|
|
// 2. Read from response and show file link
|
|
newFileList = newFileList.map((file) => {
|
|
if (file.response) {
|
|
// Component will show file.url as link
|
|
file.url = file.response.url;
|
|
}
|
|
return file;
|
|
});
|
|
setFileList(newFileList);
|
|
};
|
|
const normFile = (e) => {
|
|
// console.log('Upload event:', e);
|
|
if (Array.isArray(e)) {
|
|
return e;
|
|
}
|
|
return e?.fileList;
|
|
};
|
|
const uploadProps = {
|
|
// action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload',
|
|
// onChange: handleChange,
|
|
multiple: true,
|
|
fileList,
|
|
beforeUpload: (file) => {
|
|
// console.log('beforeUpload', file);
|
|
const lastDotIndex = file.name.lastIndexOf('.');
|
|
const extension = file.name.slice(lastDotIndex).toLocaleLowerCase();
|
|
if (disallowedAttachmentTypes.includes(file.type) || disallowedAttachmentTypes.includes(extension)) {
|
|
message.warning('不支持的文件格式: '+ extension)
|
|
return false;
|
|
}
|
|
|
|
setFileList(prev => [...prev, file]);
|
|
return false; // 阻止默认上传, 附件不上传阿里云
|
|
},
|
|
onRemove: (file) => {
|
|
const index = fileList.indexOf(file);
|
|
const newFileList = fileList.slice();
|
|
newFileList.splice(index, 1);
|
|
setFileList(newFileList);
|
|
},
|
|
onPreview: (file) => {
|
|
// console.log('pn preview', file);
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onloadend = (e) => {
|
|
if (file.size > 1.5 * 1024 * 1024) {
|
|
message.info('附件太大,无法预览')
|
|
// message.info('附件太大,无法预览, 请下载后查看')
|
|
// var downloadLink = document.createElement('a');
|
|
// downloadLink.href = e.target.result;
|
|
// downloadLink.download = file.name;
|
|
// downloadLink.click();
|
|
resolve(e.target.result);
|
|
return;
|
|
}
|
|
var win = window.open("", "_blank");
|
|
win.document.body.style.margin = '0';
|
|
if (file.type.startsWith('image/')) {
|
|
win.document.write("<img src='" + e.target.result + "' style=\"max-width: 100%;\" />");
|
|
} else if (file.type.startsWith('text/') || file.type === 'application/html' || file.type === 'application/xhtml+xml') {
|
|
var iframe = win.document.createElement('iframe');
|
|
iframe.srcdoc = e.target.result;
|
|
iframe.style.width = '100%';
|
|
iframe.style.height = '100%';
|
|
iframe.style.border = 'none';
|
|
win.document.body.appendChild(iframe);
|
|
win.document.body.style.margin = '0';
|
|
} else if (file.type === 'application/pdf') {
|
|
// win.document.write("<iframe src='" + e.target.result + "' width='100%' height='100%' frameborder=\"0\"></iframe>");
|
|
win.document.write("<embed src='" + e.target.result + "' width='100%' height='100%' style=\"border:none\"></embed>");
|
|
win.document.body.style.margin = '0';
|
|
} else if (file.type.startsWith('audio/')) {
|
|
win.document.write("<audio controls src='" + e.target.result + "' style=\"max-width: 100%;\"></audio>");
|
|
} else if (file.type.startsWith('video/')) {
|
|
win.document.write("<video controls src='" + e.target.result + "' style=\"max-width: 100%;\"></video>");
|
|
} else {
|
|
win.document.write("<h2>Preview not available for this file type</h2>");
|
|
}
|
|
// win.document.write("<iframe src='" + dataURL + "' width='100%' height='100%' style=\"border:none\"></iframe>");
|
|
resolve(reader.result)
|
|
};
|
|
if (file.type.startsWith('text/') || file.type === 'application/html' || file.type === 'application/xhtml+xml') {
|
|
reader.readAsText(file);
|
|
} else {
|
|
reader.readAsDataURL(file);
|
|
}
|
|
// reader.readAsDataURL(file);
|
|
reader.onerror = (error) => reject(error);
|
|
})
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 保存成功, 推一个气泡
|
|
* 再从异步通知更新消息发送状态
|
|
*/
|
|
const sentOrReceivedNewMessage = useConversationStore((state) => state.sentOrReceivedNewMessage);
|
|
const invokeEmailMessage = (msgObj) => {
|
|
const msgObjMerge = {
|
|
sender: 'me',
|
|
senderName: 'me',
|
|
// to: currentConversation.whatsapp_phone_number,
|
|
date: new Date(),
|
|
status: 'waiting', // accepted
|
|
...msgObj,
|
|
// id: `${currentConversation.sn}.${msgObj.id}`,
|
|
// id: `${stickToCid}.${msgObj.id}`,
|
|
conversationid: stickToCid,
|
|
msg_source: 'email',
|
|
};
|
|
// olog('invoke upload', msgObjMerge)
|
|
const contentToRender = sentMsgTypeMapped[msgObjMerge.type].contentToRender(msgObjMerge);
|
|
// console.log(contentToRender, 'contentToRender sendMessage------------------');
|
|
sentOrReceivedNewMessage(contentToRender.conversationid, contentToRender);
|
|
};
|
|
|
|
const [sendLoading, setSendLoading] = useState(false);
|
|
|
|
const onHandleSend = async () => {
|
|
// console.log('onSend callback', '\nisRichText', isRichText);
|
|
// console.log(form.getFieldsValue());
|
|
const body = structuredClone(form.getFieldsValue());
|
|
body.from = newFromEmail;
|
|
body.attaList = fileList;
|
|
body.opi_sn = emailOPI;
|
|
body.mat_sn = emailMat;
|
|
body.coli_sn = emailOrder || '';
|
|
// console.log('body', body, '\n', emailOrder);
|
|
const values = await form.validateFields();
|
|
const preQuoteBody = activeEdit.quoteid ? (quoteContent ? quoteContent : generateQuoteContent(mailData, isRichText)) : '';
|
|
body.mailcontent = isRichText ? EmailBuilder({ subject: values.subject, content: htmlContent+preQuoteBody }) : textContent+preQuoteBody
|
|
body.cc = values.cc || '';
|
|
body.bcc = values.bcc || '';
|
|
const msgObj = {
|
|
type: 'email',
|
|
id: uuid(),
|
|
from: body.from,
|
|
to: values.to,
|
|
cc: values.cc || '',
|
|
bcc: values.bcc || '',
|
|
subject: values.subject,
|
|
content: body.mailcontent,
|
|
email: {
|
|
subject: values.subject,
|
|
content: body.mailcontent,
|
|
},
|
|
coli_id: stickToProps.oid || (emailOrder ? `{${emailOrder}}` : ''),
|
|
}
|
|
setSendLoading(true);
|
|
body.externalID = stickToCid;
|
|
body.actionID = `${stickToCid}.${msgObj.id}`;
|
|
body.contenttype = isRichText ? 'text/html' : 'text/plain';
|
|
try {
|
|
const bubbleMsg = cloneDeep(msgObj);
|
|
bubbleMsg.id = `${stickToCid}.${msgObj.id}`;
|
|
// bubbleMsg.email.mai_sn = '';
|
|
bubbleMsg.content = undefined;
|
|
// invokeEmailMessage(bubbleMsg);
|
|
|
|
// console.log('postSendEmail', body, '\n', msgObj);
|
|
// return;
|
|
const result = await postSendEmail(body);
|
|
const mailSavedId = result.id || '';
|
|
bubbleMsg.email.mai_sn = mailSavedId;
|
|
// console.log('invokeEmailMessage', bubbleMsg);
|
|
|
|
invokeEmailMessage(bubbleMsg);
|
|
|
|
// setSendLoading(false);
|
|
|
|
setOpen(false);
|
|
} catch (error) {
|
|
notification.error({
|
|
message: "邮件保存失败",
|
|
description: error.message,
|
|
placement: "top",
|
|
duration: 3,
|
|
});
|
|
|
|
} finally {
|
|
setSendLoading(false);
|
|
}
|
|
};
|
|
|
|
|
|
const [openDrawerSnippet] = useSnippetStore((state) => [state.openDrawer])
|
|
const [openPaymentDrawer] = useOrderStore((state) => [state.openDrawer])
|
|
|
|
const [bakData, setBakData] = useState({});
|
|
const idleCallbackId = useRef(null);
|
|
const debouncedSave = useCallback(
|
|
debounce((data) => {
|
|
idleCallbackId.current = window.requestIdleCallback(() => {
|
|
console.log('Saving data (idle, debounced):', data);
|
|
if (currentEditKey) writeIndexDB([{ ...data, key: currentEditKey }], 'draft', 'EmailEditor')
|
|
});
|
|
}, 1500), // 1.5s
|
|
[]
|
|
);
|
|
const onEditChange = (changedValues, allValues) => {
|
|
console.log('onEditChange', changedValues,'\n', allValues)
|
|
// const { name, value } = e.target;
|
|
// setBakData(prevData => ({ ...prevData, [name]: value }));
|
|
// debouncedSave(bakData);
|
|
};
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (idleCallbackId.current && window.cancelIdleCallback) {
|
|
window.cancelIdleCallback(idleCallbackId.current);
|
|
}
|
|
};
|
|
}, [debouncedSave]);
|
|
|
|
return (
|
|
<>
|
|
<ConfigProvider theme={{ token: { colorPrimary: '#6366f1' } }}>
|
|
<DnDModal
|
|
rootClassName='email-editor-wrapper !border-indigo-300 '
|
|
open={open}
|
|
setOpen={setOpen}
|
|
// 300 + 24
|
|
// window.innerWidth - 600
|
|
initial={{ width: 880, height: window.innerHeight - 40, left: 20, top: 20 }}
|
|
onCancel={() => {
|
|
form.resetFields()
|
|
stateReset()
|
|
}}
|
|
titleClassName={`!pl-0 !pt-0 !pb-0`}
|
|
title={
|
|
<>
|
|
{/* {getLoading ? <LoadingOutlined className='mr-1' /> : null} */}
|
|
{/* {initialForm.subject || `${!isEmpty(quoteid) ? '回复: ' : '写邮件: '} ${newFromEmail || ''}`} */}
|
|
<Tabs editable type={'editable-card'} activeKey={currentEditKey} onChange={onChangeActiveEditor} className='[&_.ant-tabs-nav]:mb-0' items={propsArr.map(ele=>({...ele, label: (!isEmpty(activeEdit.quoteid) ? '回复: ' : '新邮件: ')+ele.subject}))} onEdit={onEditTab} hideAdd />
|
|
</>
|
|
}
|
|
footer={
|
|
<div className='w-full flex gap-6 justify-start items-center text-indigo-600'>
|
|
<Button type='primary' onClick={onHandleSend} loading={sendLoading}>
|
|
发送
|
|
</Button>
|
|
<Popconfirm
|
|
description='切换内容为纯文本格式将丢失信件和签名的格式, 确定使用纯文本?'
|
|
onConfirm={confirmPlainText}
|
|
open={openPlainTextConfirm}
|
|
onCancel={() => setOpenPlainTextConfirm(false)}>
|
|
<Checkbox checked={!isRichText} onChange={handlePlainTextOpenChange}>
|
|
纯文本
|
|
</Checkbox>
|
|
</Popconfirm>
|
|
<Select labelInValue options={emailListOption} value={{ key: newFromEmail, value: newFromEmail, label: newFromEmail }} onChange={handleSwitchEmail} variant={'borderless'} />
|
|
</div>
|
|
}>
|
|
<Form
|
|
form={form} onValuesChange={onEditChange}
|
|
preserve={false}
|
|
name={`email_max_form-${Date.now().toString(32)}`}
|
|
size='small'
|
|
layout={'inline'}
|
|
variant={'borderless'}
|
|
// initialValues={{}}
|
|
// onFinish={() => {}}
|
|
className='email-editor-wrapper *:mb-2 *:border-b *:border-t-0 *:border-x-0 *:border-indigo-100 *:border-solid '
|
|
requiredMark={false}
|
|
// labelCol={{ span: 3 }}
|
|
>
|
|
<Form.Item className='w-full'>
|
|
<Space.Compact className='w-full'>
|
|
<Form.Item name={'to'} label='收件人' rules={[{ required: true }]} className='!flex-1'>
|
|
<Input className='w-full' />
|
|
</Form.Item>
|
|
<Flex gap={4}>
|
|
{!showCc && (
|
|
<Button type='text' onClick={handleShowCc}>
|
|
Cc
|
|
</Button>
|
|
)}
|
|
{!showBcc && (
|
|
<Button type='text' hidden={showBcc} onClick={handleShowBcc}>
|
|
Bcc
|
|
</Button>
|
|
)}
|
|
</Flex>
|
|
</Space.Compact>
|
|
{/* <Input
|
|
addonAfter={
|
|
<Flex gap={4}>
|
|
{!showCc && (
|
|
<Button type='text' onClick={handleShowCc}>
|
|
Cc
|
|
</Button>
|
|
)}
|
|
{!showBcc && (
|
|
<Button type='text' hidden={showBcc} onClick={handleShowBcc}>
|
|
Bcc
|
|
</Button>
|
|
)}
|
|
</Flex>
|
|
}
|
|
/> */}
|
|
</Form.Item>
|
|
<Form.Item label='抄 送' name={'cc'} hidden={!showCc} className='w-full pt-1' >
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item label='密 送' name={'bcc'} hidden={!showBcc} className='w-full pt-1'>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item label='主 题' name={'subject'} rules={[{ required: true }]} className='w-full pt-1'>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item name='atta' label='' className='w-full py-1 border-b-0' valuePropName='fileList' getValueFromEvent={normFile}>
|
|
<Flex justify='space-between'>
|
|
<Upload {...uploadProps} name='file' className='w-full'>
|
|
<Button icon={<UploadOutlined />}>附件</Button>
|
|
</Upload>
|
|
<Flex align={'center'} className='absolute right-0'>
|
|
<Divider type='vertical' />
|
|
<Button type={'link'} onClick={() => openDrawerSnippet()}>
|
|
图文集
|
|
</Button>
|
|
<PaymentlinkBtn type={'link'} />
|
|
{/* 更多工具 */}
|
|
{/* <Popover
|
|
content={
|
|
<div className='flex flex-col gap-2'>
|
|
<Button type={'link'}>??</Button>
|
|
</div>
|
|
}
|
|
trigger='click'
|
|
><MoreOutlined /></Popover> */}
|
|
</Flex>
|
|
</Flex>
|
|
</Form.Item>
|
|
<Form.Item name='content' hidden>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item name='abstract' hidden>
|
|
<Input />
|
|
</Form.Item>
|
|
</Form>
|
|
<LexicalEditor {...{ isRichText }} onChange={handleEditorChange} defaultValue={initialContent} />
|
|
{activeEdit.quoteid && !showQuoteContent && (
|
|
<div className='flex justify-start items-center ml-2'>
|
|
<Button className='flex gap-2 ' type='link' onClick={() => setShowQuoteContent(!showQuoteContent)}>
|
|
显示引用内容 ↓
|
|
</Button>
|
|
{/* <Button className='flex gap-2 ' type='link' danger onClick={() => {setMergeQuote(false);setShowQuoteContent(false)}}>
|
|
删除引用内容
|
|
</Button> */}
|
|
</div>
|
|
)}
|
|
{showQuoteContent && (
|
|
<blockquote contentEditable className='border-0 outline-none' onBlur={(e) => setQuoteContent(`<blockquote>${e.target.innerHTML}</blockquote>`)} dangerouslySetInnerHTML={{ __html: generateQuoteContent(mailData) }}></blockquote>
|
|
)}
|
|
</DnDModal>
|
|
</ConfigProvider>
|
|
</>
|
|
)
|
|
};
|
|
export default EmailEditorPopup;
|