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.
Global-sales/src/views/Conversations/Online/Input/EmailEditorPopup.jsx

491 lines
17 KiB
JavaScript

import { useEffect, useState } from 'react';
import { App, ConfigProvider, Button, Form, Input, Flex, Checkbox, Popconfirm, Select, Space, Upload, Divider } 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, isEmpty } from '@/utils/commons';
import './EmailEditor.css';
import { postSendEmail } from '@/actions/EmailActions';
import { sentMsgTypeMapped, } from '@/channel/bubbleMsgUtils';
import { useEmailDetail } from '@/hooks/useEmail';
import useSnippetStore from '@/stores/SnippetStore'
import { useOrderStore } from '@/stores/OrderStore'
const getAbstract = (longtext) => {
const lines = longtext.split('\n');
const firstLine = lines[0];
const abstract = firstLine.substring(0, 20);
return abstract;
};
const generateQuoteContent = (mailData) => `<br><br>
<hr>
<p>
<b>
<strong >From: </strong>
</b>
<span >${((mailData.info?.MAI_From || '').replace(/</g,'&lt;').replace(/>/g,'&gt;'))} </span>
</p>
<p>
<b>
<strong >Sent: </strong>
</b>
<span >${mailData.info?.MAI_SendDate || ''}</span>
</p>
<p>
<b>
<strong >To: </strong>
</b>
<span >${(mailData.info?.MAI_To || '').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</span>
</p>
<p>
<b>
<strong >Subject: </strong>
</b>
<span >${mailData.info.subject || ''}</span>
</p>
<p>
${mailData.content}
</p>
`;
const generateMailContent = (mailData) => `
<p>
${mailData.content}
</p>`
const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, toEmail, conversationid, quoteid, initial = {}, mailData: _mailData, action = 'reply', ...props }) => {
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 mai_sn = quoteid;
const { loading, mailData } = useEmailDetail(mai_sn, _mailData)
const [newFromEmail, setNewFromEmail] = useState(fromEmail);
const [newToEmail, setNewToEmail] = useState(toEmail);
const [emailOPI, setEmailOPI] = useState(fromUser);
const [emailMat, setEmailMat] = useState('');
useEffect(() => {
const emailUser = mailData.info?.MAI_OPI_SN || fromUser // quote.order_opi
const emailUserMat = mailData.info?.MAI_MAT_SN
// const _default =
// emailListOption?.find((ele) => ele.opi_sn === emailUser && ele.default === true) ||
// emailListOption?.find((ele) => ele.opi_sn === emailUser && ele.backup === true) ||
// emailListOption?.find((ele) => ele.opi_sn === emailUser) ||
// emailListOption?.find((ele) => ele.default === true) ||
// emailListOption?.find((ele) => ele.backup === true)
setNewFromEmail(fromEmail);
setEmailOPI(emailUser);
setNewToEmail(toEmail);
const _findMat = emailListAddrMapped?.[fromEmail]?.mat_sn
setEmailMat(emailUserMat || _findMat)
// 转发/回复时, 从发出的邮件操作, 获取原来的发件人邮箱
const _findMatOld = emailListMatMapped?.[emailUserMat]
if (isEmpty(_findMat) && _findMatOld) {
setNewFromEmail(_findMatOld.email)
setEmailOPI(_findMatOld.opi_sn)
setEmailMat(_findMatOld.mat_sn)
}
return () => {}
}, [open, fromEmail, fromUser, toEmail, mailData])
const handleSwitchEmail = (labelValue) => {
const { value } = labelValue
setNewFromEmail(value)
const _findMat = emailListAddrMapped?.[value]
setEmailMat(_findMat?.mat_sn)
setEmailOPI(_findMat?.opi_sn)
};
const { notification, message } = App.useApp();
const [form] = Form.useForm();
// 存储会话ID,
// 这个窗口没有模态, 即使不是focus, 还是需要保持会话ID
// 否则, 会话列表切换之后, 会话ID更新, 导致消息关联错误
const [stickToCid, setStickToCid] = useState(conversationid);
useEffect(() => {
setStickToCid(conversationid)
if (open !== true) {
form.resetFields();
}
return () => {}
}, [open])
const [isRichText, setIsRichText] = useState(mobile === false);
// const [isRichText, setIsRichText] = useState(false); // 默认纯文本
const [htmlContent, setHtmlContent] = useState('');
const [textContent, setTextContent] = useState('');
const [showCc, setShowCc] = useState(false);
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));
};
const [initialForm, setInitialForm] = useState({});
const [initialContent, setInitialContent] = useState('');
/**
* 加载邮件数据
*/
useEffect(() => {
// console.log('quoteid', quoteid, isEmpty(quoteid), isEmpty(mailData.info));
if (isEmpty(quoteid) && action !== 'new') {
return () => {};
}
const { info, } = mailData
setShowCc(!isEmpty(mailData.info?.MAI_CS));
const preQuoteBody = generateQuoteContent(mailData);
if ( !isEmpty(mailData.info) && action !== 'edit') {
setInitialContent(preQuoteBody);
}
const _formValues = {
to: info?.MAI_From || fromEmail,
cc: info?.MAI_CS || '',
// bcc: quote.bcc || '',
subject: `Re: ${info.subject || ''}`,
};
const forwardValues = { subject: `Fw: ${info.subject || ''}` };
if (action === 'reply') {
form.setFieldsValue(_formValues);
setInitialForm(_formValues);
} else if (action === 'forward') {
form.setFieldsValue(forwardValues);
setInitialForm(forwardValues);
} else if (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 (action === 'new') {
const newEmail = { to: toEmail, }
form.setFieldsValue(newEmail);
setInitialForm(newEmail);
}
return () => {};
}, [open, action, mailData.info]);
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) => {
setFileList(prev => [...prev, file]);
return false;
},
onRemove: (file) => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
},
};
/**
* 先推到消息记录上面, 再发送
*/
const sentOrReceivedNewMessage = useConversationStore((state) => state.sentOrReceivedNewMessage);
const invokeEmailMessage = (msgObj) => {
const msgObjMerge = {
sender: 'me',
senderName: 'me',
msg_source: 'email',
// to: currentConversation.whatsapp_phone_number,
date: new Date(),
status: 'waiting', // accepted
...msgObj,
// id: `${currentConversation.sn}.${msgObj.id}`,
// id: `${stickToCid}.${msgObj.id}`,
conversationid: stickToCid,
};
// 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.mailcontent = isRichText ? htmlContent : textContent;
body.from = newFromEmail || fromEmail;
body.attaList = fileList;
body.opi_sn = emailOPI;
body.mat_sn = emailMat;
console.log('body', body);
const values = await form.validateFields();
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,
},
}
setSendLoading(true);
body.externalID = stickToCid;
body.actionID = `${stickToCid}.${msgObj.id}`;
try {
const bubbleMsg = cloneDeep(msgObj);
bubbleMsg.id = `${stickToCid}.${msgObj.id}`;
bubbleMsg.email.mai_sn = '';
bubbleMsg.content = undefined;
invokeEmailMessage(bubbleMsg);
const result = await postSendEmail(body);
// const mailSavedId = result.id || '';
// bubbleMsg.email.mai_sn = mailSavedId;
// invokeEmailMessage(bubbleMsg);
// setSendLoading(false);
setOpen(false);
} catch (error) {
notification.error({
message: "邮件保存失败",
description: error.message,
placement: "top",
duration: 3,
});
}
setSendLoading(false);
};
const [openDrawerSnippet] = useSnippetStore((state) => [state.openDrawer])
const [openPaymentDrawer] = useOrderStore((state) => [state.openDrawer])
return (
<>
<ConfigProvider theme={{ token: { colorPrimary: '#6366f1' } }}>
<DnDModal
rootClassName='email-editor-wrapper !border-indigo-300 '
open={open}
setOpen={setOpen}
initial={{ top: isEmpty(quoteid) ? 20 : 74 }}
onCancel={() => {
form.resetFields();
}}
title={
<>
{loading ? <LoadingOutlined className='mr-1' /> : null}
{initialForm.subject || `${!isEmpty(quoteid) ? '回复: ' : '写邮件: '} ${fromEmail || ''}`}
</>
}
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={newFromEmail}
onChange={handleSwitchEmail}
variant={'borderless'}
/>
</div>
}>
<Form
form={form}
preserve={false}
name='conversation_filter_form'
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'>
{/* todo: 添加: 支付链接 */}
<Divider type='vertical' />
<Button type={'link'} onClick={() => openDrawerSnippet()}>图文集</Button>
<Button type={'link'} onClick={() => openPaymentDrawer()}>支付链接</Button>
{/* 更多工具 */}
{/* <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} />
</DnDModal>
</ConfigProvider>
</>
);
};
export default EmailEditorPopup;