test: 编辑邮件窗口

# Conflicts:
#	src/utils/commons.js
dev/ckeditor
Lei OT 4 months ago
parent 0d9dd3ad8c
commit 7b0bb05e89

@ -46,7 +46,7 @@ const DnDModal = ({ children, open, setOpen, onCancel, onMove, onResize, initial
maskClosable={false}
// theme='dark'
// className={'!border !border-solid !border-indigo-500 rounded !p-2' }
titleBarClassName='!bg-neutral-100 !rounded !rounded-b-none !border-none !p-3 !font-bold !text-slate-600'
titleBarClassName={`!bg-neutral-100 !rounded !rounded-b-none !border-none !p-3 !font-bold !text-slate-600 ${props.titleClassName}`}
contentClassName='!p-2'
footerClassName='!p-2'
className={`!rounded-t !rounded-b-none !border !border-solid !shadow-heavy ${props.rootClassName}`}

@ -31,6 +31,7 @@ import useAuthStore from '@/stores/AuthStore'
import '@/assets/index.css'
import CustomerRelation from '@/views/customer_relation/index'
import NewEmail from './views/NewEmail'
useAuthStore.getState().loadUserSession()
@ -86,6 +87,8 @@ const router = createBrowserRouter([
{ path: 'customer_relation/index', element: <CustomerRelation /> },
],
},
{ path: 'email/:action/:quoteid', element: <NewEmail />},
{ path: 'email/new', element: <NewEmail />},
],
},
{

@ -557,11 +557,63 @@ const messageSlice = (set, get) => ({
*/
const emailSlice = (set, get) => ({
emailMsg: { id: -1, conversationid: '', actionId: '', order_opi: '', coli_sn: '', msgOrigin: {} },
setEmailMsg: (emailMsg) => set({ emailMsg }),
setEmailMsg: (emailMsg) => {
const { editorOpen } = get();
return editorOpen ? false : set({ emailMsg }); // 已经打开的不更新
},
detailPopupOpen: false,
setDetailOpen: (v) => set({ detailPopupOpen: v }),
openDetail: () => set(() => ({ detailPopupOpen: true })),
closeDetail: () => set(() => ({ detailPopupOpen: false })),
editorOpen: false,
setEditorOpen: (v) => set({ editorOpen: v }),
openEditor: () => set(() => ({ editorOpen: true })),
closeEditor: () => set(() => ({ editorOpen: false })),
// EmailEditorPopup 组件的 props
// @property {string} fromEmail - 发件人邮箱
// @property {string} fromUser - 发件人用户
// @property {string} fromOrder - 发件订单
// @property {string} toEmail - 收件人邮箱
// @property {string} conversationid - 会话ID
// @property {string} quoteid - 引用邮件ID
// @property {object} draft - 草稿
// @property {string} action - reply / forward / new / edit
// @property {string} oid - coli_sn
// @property {object} mailData - 邮件内容
// @property {string} receiverName - 收件人称呼
emailEdiorProps: new Map(),
setEditorProps: (v) => {
const { emailEdiorProps } = get();
const uniqueKey = v.quoteid || Date.now().toString(32);
const currentEditValue = {...v, key: `${v.action}-${uniqueKey}`};
const news = new Map(emailEdiorProps).set(currentEditValue.key, currentEditValue);
for (const [key, value] of news.entries()) {
console.log(value);
}
return set((state) => ({ emailEdiorProps: news, currentEditKey: currentEditValue.key, currentEditValue }))
// return set((state) => ({ emailEdiorProps: { ...state.emailEdiorProps, ...v } }))
},
closeEditor1: (key) => {
const { emailEdiorProps } = get();
const newProps = new Map(emailEdiorProps);
newProps.delete(key);
return set(() => ({ emailEdiorProps: newProps }))
},
clearEditor: () => {
return set(() => ({ emailEdiorProps: new Map() }))
},
currentEditKey: '',
setCurrentEditKey: (key) => {
const { emailEdiorProps, setCurrentEditValue } = get();
const value = emailEdiorProps.get(key);
setCurrentEditValue(value);
return set(() => ({ currentEditKey: key }))
},
currentEditValue: {},
setCurrentEditValue: (v) => {
return set(() => ({ currentEditValue: v }))
}
})
export const useConversationStore = create(

@ -12,6 +12,7 @@ import CustomerProfile from './Conversations/Online/order/CustomerProfile';
import ReplyWrapper from './Conversations/Online/ReplyWrapper';
import './Conversations/Conversations.css';
import EmailEditorPopup from './Conversations/Online/Input/EmailEditorPopup';
const { Sider, Content, Header, Footer } = Layout;
@ -75,6 +76,7 @@ const ChatWindow = () => {
<CustomerProfile disabled />
</Sider>
</Layout>
<EmailEditorPopup key='email-editor-online' />
</>
);
};

@ -72,7 +72,7 @@ const EmailContent = ({ id, content: MailContent, className='', ...props }) => {
} catch (error) {
console.error('Error calculating height:', error)
}
setIframeHeight(200)
// setIframeHeight(200)
}
useEffect(() => {

@ -149,7 +149,7 @@ const EmailDetail = ({ open, setOpen, emailMsg={}, disabled=false, ...props }) =
footer={<ActionBtns className='w-full !justify-start' />}>
<EmailDetailInline { ...{ mailData, emailMsg, loading, mailID } } />
</DnDModal>
<EmailEditorPopup
{/* <EmailEditorPopup
open={openEmailEditor}
setOpen={setOpenEmailEditor}
fromEmail={fromEmail}
@ -163,8 +163,8 @@ const EmailDetail = ({ open, setOpen, emailMsg={}, disabled=false, ...props }) =
initial={{ ...props.initialPosition, ...props.initialSize }}
mailData={mailData}
action={action}
key={`email-detail-inner-${action}-popup_${mailID}`}
/>
key={`email-detail-inner-${action}_${mailID}`}
/> */}
</>
)
}

@ -49,12 +49,19 @@ const EmailDetailInline = ({ mailID, emailMsg = {}, disabled = false, ...props }
return () => {}
}, [mailID])
const onOpenEditor = (msgOrigin, action) => {
const { from, to } = msgOrigin
setOpenEmailEditor(true)
setFromEmail(action === 'edit' ? from : to)
setAction(action)
// setOpen(false)
const onOpenEditor = (msgOrigin, action='reply') => {
window.open(`/email/${action}/${msgOrigin.email.mai_sn || 0}`, `${action}-${msgOrigin.email.mai_sn || 0}`,'left=20,width=1000')
console.log('first000', emailMsg)
console.log('first', msgOrigin)
if (typeof props.onOpenEditor === 'function') {
props.onOpenEditor(msgOrigin, action);
} else {
const { from, to } = msgOrigin
setOpenEmailEditor(true)
setFromEmail(action === 'edit' ? from : to)
setAction(action)
// // setOpen(false)
}
}
const { loading, mailData, orderDetail, postEmailResend } = useEmailDetail(mailID)
@ -219,7 +226,7 @@ const EmailDetailInline = ({ mailID, emailMsg = {}, disabled = false, ...props }
</Flex>
</div>
</div>
<EmailEditorPopup
{/* <EmailEditorPopup
open={openEmailEditor}
setOpen={setOpenEmailEditor}
fromEmail={fromEmail}
@ -233,8 +240,8 @@ const EmailDetailInline = ({ mailID, emailMsg = {}, disabled = false, ...props }
// initial={{ ...initialPosition, ...initialSize }}
mailData={void 0}
action={action}
key={`email-detail-inner-${action}-inline_${mailID}`}
/>
key={`email-detail-inline-${action}_${mailID}`}
/> */}
</>
)
}

@ -5,8 +5,12 @@ import dayjs from 'dayjs'
import { InboxIcon, SendPlaneFillIcon, ExpandIcon } from '@/components/Icons'
import EmailDetailInline from './EmailDetailInline'
import { debounce, isEmpty } from '@/utils/commons'
import useConversationStore from '@/stores/ConversationStore';
const EmailListDrawer = ({ showExpandBtn=true, title, list: otherEmailList, currentConversationID, opi_sn, oid, emailItem: clickItem, onOpenEditor, ...props }) => {
const [, setEmailMsg] = useConversationStore((state) => [state.emailMsg, state.setEmailMsg]);
const EmailListDrawer = ({ showExpandBtn=true, title, list: otherEmailList, currentConversationID, opi_sn, oid, emailItem: clickItem, ...props }) => {
const [open, setOpen] = useState(false)
const [selectedEmail, setSelectedEmail] = useState({})
const searchInputRef = useRef(null)
@ -38,11 +42,15 @@ const EmailListDrawer = ({ showExpandBtn=true, title, list: otherEmailList, curr
msgOrigin: {
from: '',
to: '',
...(emailItem?.msgOrigin || {}),
id: emailItem.MAI_SN,
email: { mai_sn: emailItem.MAI_SN, subject: emailItem.MAI_Subject, id: emailItem.MAI_SN },
subject: emailItem.MAI_Subject,
},
}
console.log('emailItem', emailItem);
setSelectedEmail(emailMsg)
setEmailMsg(emailMsg);
};
const [pageCurrent, setPageCurrent] = useState(1);
@ -52,11 +60,11 @@ const EmailListDrawer = ({ showExpandBtn=true, title, list: otherEmailList, curr
useEffect(() => {
if (!isEmpty(clickItem)) {
onClickEmailItem(clickItem)
setOpen(true);
const itemIndex = dataSource.findIndex((ele) => ele.MAI_SN === clickItem.MAI_SN);
const page = Math.ceil((itemIndex+1) / 8) || 1;
setPageCurrent(page);
onClickEmailItem({...clickItem, ...dataSource[itemIndex]});
setOpen(true);
}
return () => {}
@ -129,7 +137,7 @@ const EmailListDrawer = ({ showExpandBtn=true, title, list: otherEmailList, curr
setOpen(false)
}}
open={open}>
<EmailDetailInline {...{ mailID: selectedEmail.MAI_SN, emailMsg: selectedEmail }} />
<EmailDetailInline {...{ mailID: selectedEmail.MAI_SN, emailMsg: selectedEmail, onOpenEditor }} />
</Drawer>
</>
)

@ -69,7 +69,7 @@ const EmailQuotation = ({ sfi_sn, ...props }) => {
邮件
</Button>
<EmailEditorPopup
{/* <EmailEditorPopup
open={editorOpen}
setOpen={setEditorOpen}
fromEmail={pickEmail.key}
@ -86,7 +86,7 @@ const EmailQuotation = ({ sfi_sn, ...props }) => {
// customerDetail={customerDetail}
action={'new'}
key={`email-quotation-new-popup_${currentConversation.sn}`}
/>
/> */}
</>
)
}

@ -258,7 +258,7 @@ const EmailComposer = ({ ...props }) => {
<Alert message="账户没有配置邮箱地址" description='请 重新登录 获取最新配置' type="warning" showIcon className=' absolute top-0 left-0 right-0 bottom-0' />
}
<EmailEditorPopup
{/* <EmailEditorPopup
{...{ open, setOpen }}
fromEmail={fromEmail}
fromUser={fromUser}
@ -270,7 +270,7 @@ const EmailComposer = ({ ...props }) => {
customerDetail={customerDetail}
action='new'
key={'email-new-editor-popup'}
/>
/> */}
</ConfigProvider>
)
}

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { App, ConfigProvider, Button, Form, Input, Flex, Checkbox, Popconfirm, Select, Space, Upload, Divider, Modal } from 'antd';
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';
@ -10,7 +10,7 @@ import useAuthStore from '@/stores/AuthStore';
import LexicalEditor from '@/components/LexicalEditor';
import { v4 as uuid } from 'uuid';
import { cloneDeep, isEmpty, } from '@/utils/commons';
import { cloneDeep, debounce, isEmpty, writeIndexDB, } from '@/utils/commons';
import './EmailEditor.css';
import { postSendEmail } from '@/actions/EmailActions';
import { sentMsgTypeMapped, } from '@/channel/bubbleMsgUtils';
@ -60,10 +60,9 @@ const generateMailContent = (mailData) => `<br><br><p>${mailData.content}</p>`
* @property {string} quoteid - 引用邮件ID
* @property {object} draft - 草稿
*/
const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid, toEmail, conversationid, quoteid, initial = {}, mailData: _mailData, action = 'reply', draft = {}, customerDetail={}, ...props }) => {
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 })) || [];
@ -72,8 +71,35 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
const emailListMatMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.mat_sn]: v }), {});
// console.log('emailListMapped', emailListOption, emailListAddrMapped);
const mai_sn = quoteid;
const { loading: getLoading, mailData } = useEmailDetail(mai_sn, _mailData)
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({});
@ -99,24 +125,24 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
// : ID,
// , 使focus, ID
// , , ID,
const [stickToCid, setStickToCid] = useState(conversationid);
const [stickToCid, setStickToCid] = useState(activeEdit.conversationid);
useEffect(() => {
const propsObj = { open, setOpen, fromEmail, fromUser, fromOrder, oid, toEmail, conversationid, quoteid, mai: _mailData?.info?.MAI_MAT_SN, action, draft }
const propsObj = { ...activeEdit, mai: activeEdit.mailData?.info?.MAI_MAT_SN, }
setContentPrefix(oid ? `<p>Dear Mr./Ms. ${customerDetail.name || ''}</p><p>Reference Number: ${oid}</p>` : '');
setContentPrefix(activeEdit.oid ? `<p>Dear Mr./Ms. ${activeEdit.receiverName || ''}</p><p>Reference Number: ${activeEdit.oid}</p>` : '');
//
if (isEmpty(quoteid)) {
if (isEmpty(activeEdit.quoteid)) {
setStickToProps(propsObj)
setPropsSerialize(JSON.stringify(propsObj))
setStickToCid(conversationid)
setEmailOrder(fromOrder)
setEmailOPI(fromUser)
setNewFromEmail(fromEmail)
setNewToEmail(toEmail)
setStickToCid(activeEdit.conversationid)
setEmailOrder(activeEdit.fromOrder)
setEmailOPI(activeEdit.fromUser)
setNewFromEmail(activeEdit.fromEmail)
setNewToEmail(activeEdit.toEmail)
const _findMat = emailListAddrMapped?.[fromEmail]?.mat_sn
const _findMat = emailListAddrMapped?.[activeEdit.fromEmail]?.mat_sn
setEmailMat(_findMat)
// if (open !== true) {
@ -129,7 +155,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
const reEmailUser = mailData.info?.MAI_OPI_SN
const reEmailUserMat = mailData.info?.MAI_MAT_SN
setEmailOrder((prev) => reEmailO || prev || fromOrder)
setEmailOrder((prev) => reEmailO || prev || activeEdit.fromOrder)
setEmailOPI((prev) => reEmailUser || prev)
setEmailMat((prev) => reEmailUserMat || prev)
@ -178,6 +204,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
setTextContent(textContent);
form.setFieldValue('content', htmlContent);
form.setFieldValue('abstract', getAbstract(textContent));
debouncedSave({htmlContent});
};
const [initialForm, setInitialForm] = useState({});
@ -189,7 +216,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
useEffect(() => {
// console.log('quoteid', quoteid, isEmpty(quoteid), isEmpty(mailData.info));
if (isEmpty(quoteid) && action !== 'new') {
if (isEmpty(activeEdit.quoteid) && activeEdit.action !== 'new') {
return () => {}
}
const { info } = mailData
@ -201,7 +228,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
// const _initialContent = isEmpty(mailData.info) ? signatureBody : signatureBody+preQuoteBody
if (!isEmpty(mailData.info) && action !== 'edit') {
if (!isEmpty(mailData.info) && activeEdit.action !== 'edit') {
setInitialContent(contentPrefix + signatureBody)
}
@ -212,14 +239,14 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
subject: `Re: ${info.MAI_Subject || ''}`,
}
const forwardValues = { from: newFromEmail, subject: `Fw: ${info.MAI_Subject || ''}` }
if (action === 'reply') {
if (activeEdit.action === 'reply') {
form.setFieldsValue(_formValues)
setInitialForm(_formValues)
} else if (action === 'forward') {
} else if (activeEdit.action === 'forward') {
setStickToCid('0')
form.setFieldsValue(forwardValues)
setInitialForm(forwardValues)
} else if (action === 'edit') {
} else if (activeEdit.action === 'edit') {
const thisFormValues = {
to: info?.MAI_To || '',
cc: info?.MAI_CS || '',
@ -231,11 +258,11 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
// console.log('thisBody', thisBody);
setInitialContent(thisBody)
} else if (action === 'new') {
const newEmail = { to: newToEmail, subject: draft?.subject || '' }
} else if (activeEdit.action === 'new') {
const newEmail = { to: newToEmail, subject: activeEdit.draft?.subject || '' }
form.setFieldsValue(newEmail)
setInitialForm(newEmail)
setInitialContent((draft?.content || contentPrefix || '') + signatureBody)
setInitialContent((activeEdit.draft?.content || contentPrefix || '') + signatureBody)
}
return () => {}
@ -391,7 +418,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
body.coli_sn = emailOrder || '';
// console.log('body', body, '\n', emailOrder);
const values = await form.validateFields();
const preQuoteBody = quoteid ? (quoteContent ? quoteContent : generateQuoteContent(mailData, isRichText)) : '';
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 || '';
@ -450,6 +477,32 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
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' } }}>
@ -457,15 +510,19 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
rootClassName='email-editor-wrapper !border-indigo-300 '
open={open}
setOpen={setOpen}
initial={{ width: window.innerWidth - 600, height: window.innerHeight - 40, left: 300 + 24, top: 20 }}
// 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 || ''}`}
{/* {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={
@ -486,7 +543,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
</div>
}>
<Form
form={form}
form={form} onValuesChange={onEditChange}
preserve={false}
name={`email_max_form-${Date.now().toString(32)}`}
size='small'
@ -573,7 +630,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, oid,
</Form.Item>
</Form>
<LexicalEditor {...{ isRichText }} onChange={handleEditorChange} defaultValue={initialContent} />
{quoteid && !showQuoteContent && (
{activeEdit.quoteid && !showQuoteContent && (
<div className='flex justify-start items-center ml-2'>
<Button className='flex gap-2 ' type='link' onClick={() => setShowQuoteContent(!showQuoteContent)}>
显示引用内容

@ -192,18 +192,35 @@ const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => {
const handleContactClick = (data) => {
return data.length > 1 ? handleContactListClick(data) : handleContactItemClick(data[0]);
}
// EmailEditor
const [setEditorOpen, setEditorProps] = useConversationStore((state) => [state.setEditorOpen, state.setEditorProps]);
const [openEmailEditor, setOpenEmailEditor] = useState(false);
const [fromEmail, setFromEmail] = useState('');
const [ReferEmailMsg, setReferEmailMsg] = useState('');
const onOpenEditor = (emailMsgContent) => {
const [action, setAction] = useState('')
const onOpenEditor = (emailMsgContent, action='reply') => {
const { from, to } = emailMsgContent; // msgtext
// console.log('emailMsgContent', emailMsgContent);
console.log('emailMsgContent', emailMsgContent);
setOpenEmailEditor(true);
setFromEmail(to);
// setFromEmail(to);
setFromEmail(action === 'edit' ? from : to)
setReferEmailMsg({...emailMsgContent, order_opi: Number(orderDetail?.opi_sn || userId)});
setAction(action)
setEditorProps({
action,
fromEmail: action === 'edit' ? from : to,
fromUser: Number(orderDetail?.opi_sn || userId),
fromOrder: currentConversation.coli_sn,
oid: orderDetail?.order_no || emailMsgContent?.coli_id,
receiverName: customerDetail.name,
quoteid: emailMsgContent.mai_sn || emailMsgContent.id,
conversationid: currentConversation.sn,
subject: emailMsgContent.subject,
});
// setEditorOpen(true);
};
const [openEmailDetail, setOpenEmailDetail] = useState(false);
@ -220,7 +237,17 @@ const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => {
const [emailList, setEmailList] = useState([]);
useEffect(() => {
const _emailList = longList.filter(item => item.msg_source === 'email').map(ele => ({...ele, MAI_SN: ele.msgtext?.email?.mai_sn, MAI_Subject: ele.msgtext?.email?.subject, SenderReceiver: ele.from, MAI_SendDate: ele.msgtime, Direction: ele.msg_direction === 'inbound' ? '收' : '发' })).reverse();
const _emailList = longList
.filter((item) => item.msg_source === 'email')
.map((ele) => ({
...ele,
MAI_SN: ele.msgtext?.email?.mai_sn,
MAI_Subject: ele.msgtext?.email?.subject,
SenderReceiver: ele.from,
MAI_SendDate: ele.msgtime,
Direction: ele.msg_direction === 'inbound' ? '收' : '发',
}))
.reverse()
setEmailList(_emailList);
setEmailItem({});
}, [longList]);
@ -274,7 +301,7 @@ const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => {
onCancel={() => setNewChatModalVisible(false)}
/>
{/* <EmailEditor open ={openEmailEditor} setOpen={setOpenEmailEditor} reference={ReferEmailMsg} setRefernce={setReferEmailMsg} {...{ fromEmail, }} key={'email-editor-reply'} /> */}
<EmailEditorPopup
{/* <EmailEditorPopup
open={openEmailEditor}
setOpen={setOpenEmailEditor}
fromEmail={fromEmail}
@ -284,10 +311,11 @@ const MessagesWrapper = ({ updateRead = true, forceGetMessages }) => {
customerDetail={customerDetail}
quoteid={ReferEmailMsg.mai_sn || ReferEmailMsg.id}
conversationid={currentConversation.sn}
key={`email-msg-reply-top-popup_${ReferEmailMsg.id}`}
/>
key={`email-msg-reply-online_${ReferEmailMsg.id}`}
action={action}
/> */}
<EmailDetail open={openEmailDetail} setOpen={setOpenEmailDetail} emailMsg={emailDetail} key={`email-detail-${emailDetail.id}`} {...{initialPosition, initialSize, setInitialPosition, setInitialSize}} />
<EmailListDrawer showExpandBtn={false} list={emailList} emailItem={emailItem} currentConversationID={currentConversation.sn} oid={currentConversation.coli_sn} opi_sn={currentConversation.opi_sn} />
<EmailListDrawer showExpandBtn={false} list={emailList} emailItem={emailItem} currentConversationID={currentConversation.sn} oid={currentConversation.coli_sn} opi_sn={currentConversation.opi_sn} {...{onOpenEditor}} />
</>
)
};

@ -0,0 +1,639 @@
import { useEffect, useState, useRef, useCallback } from 'react'
import { useParams, useNavigate, useLocation } from 'react-router-dom';
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, writeIndexDB } from '@/utils/commons'
import '@/views/Conversations/Online/Input/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 '@/views/Conversations/Online/Input/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, '&lt;')
.replace(/>/g, '&gt;')} </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, '&lt;')
.replace(/>/g, '&gt;')}</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>`
/**
* ! 无状态管理
*/
const NewEmail = ({ ...props }) => {
const pageParam = useParams();
const editorKey = `${pageParam.action}-${pageParam.quoteid}`
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(editorKey) || {})
// 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 = pageParam.quoteid // 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(() => {
console.log('useeffect', mailData.info);
setTimeout(() => {
document.title = mailData.info?.MAI_Subject || 'New Email-'
}, 1500);
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(pageParam.quoteid) && pageParam.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) && pageParam.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 (pageParam.action === 'reply') {
form.setFieldsValue(_formValues)
setInitialForm(_formValues)
} else if (pageParam.action === 'forward') {
setStickToCid('0')
form.setFieldsValue(forwardValues)
setInitialForm(forwardValues)
} else if (pageParam.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 (pageParam.action === 'new') {
const newEmail = { to: newToEmail, subject: pageParam.draft?.subject || '' }
form.setFieldsValue(newEmail)
setInitialForm(newEmail)
setInitialContent((pageParam.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)
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' } }}>
<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='抄&nbsp;&nbsp;&nbsp;&nbsp;送' name={'cc'} hidden={!showCc} className='w-full pt-1'>
<Input />
</Form.Item>
<Form.Item label='密&nbsp;&nbsp;&nbsp;&nbsp;送' name={'bcc'} hidden={!showBcc} className='w-full pt-1'>
<Input />
</Form.Item>
<Form.Item label='主&nbsp;&nbsp;&nbsp;&nbsp;题' 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>
)}
</ConfigProvider>
</>
)
}
export default NewEmail
Loading…
Cancel
Save