加载模板

dev/ckeditor
Lei OT 1 month ago
parent 1393bf9899
commit e51581202d

@ -327,14 +327,23 @@ export const updateEmailAction = async (params = { mai_sn_list: [], set: {} }) =
/** /**
* 获取邮件模板 * 获取邮件模板
* @param {object} params - Parameters for the email template request.
* @param {number} [params.coli_sn] - Customer order line item serial number.
* @param {number} [params.lgc] - Language code.
* @param {number} [params.opi_sn] - Order product item serial number.
* @param {string} [params.remind_type] - Type of reminder.
* @param {number} [params.remind_index] - Index of the reminder.
*/ */
export const getEmailTemplateAction = async (template_name, params = { coli_sn: 0, lgc: 1 }) => { export const getEmailTemplateAction = async (params = { coli_sn: 0, lgc: 1, opi_sn: 0, remind_type: '', remind_index: 0 }) => {
const { errcode, result } = await fetchJSON(`${EMAIL_HOST}/email/template/${template_name}`, params) const { errcode, result } = await fetchJSON(`${API_HOST}/v3/reminder_letter`, params)
return errcode === 0 ? {} : result const { html, bodyContent, bodyText } = parseHTMLString(result?.MailContent, true) ;
return errcode === 0 ? {...result, bodyContent }: {}
} }
/** /**
* 保存邮件草稿 * 保存邮件草稿
* @param {object} body - The body of the email.
* @param {boolean} [isDraft=false] - Whether the email is a draft.
*/ */
export const saveEmailDraftOrSendAction = async (body, isDraft = false) => { export const saveEmailDraftOrSendAction = async (body, isDraft = false) => {
const url = isDraft !== false ? `${API_HOST}/v3/email_draft_save` : `${EMAIL_HOST}/sendmail`; const url = isDraft !== false ? `${API_HOST}/v3/email_draft_save` : `${EMAIL_HOST}/sendmail`;

@ -6,7 +6,7 @@
// export const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_144'; // prod: Slave // export const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_144'; // prod: Slave
// debug: // debug:
// export const API_HOST = 'http://202.103.68.144:8889/v2'; export const API_HOST = 'http://202.103.68.144:8889/v2';
// export const WS_URL = 'ws://202.103.68.144:8888'; // export const WS_URL = 'ws://202.103.68.144:8888';
// export const EMAIL_HOST = 'http://202.103.68.231:888/service-mail'; // export const EMAIL_HOST = 'http://202.103.68.231:888/service-mail';
// export const WAI_HOST = 'http://47.83.248.120/api/v1'; // 香港服务器 // export const WAI_HOST = 'http://47.83.248.120/api/v1'; // 香港服务器
@ -18,7 +18,7 @@ export const EMAIL_ATTA_HOST = 'https://p9axztuwd7x8a7.mycht.cn/attachment'; //
// prod: // prod:
// export const WAI_HOST = 'https://wai-server-qq4qmtq7wc9he4.mycht.cn/api/v1'; // export const WAI_HOST = 'https://wai-server-qq4qmtq7wc9he4.mycht.cn/api/v1';
export const EMAIL_HOST = 'https://p9axztuwd7x8a7.mycht.cn/mail-server/service-mail'; export const EMAIL_HOST = 'https://p9axztuwd7x8a7.mycht.cn/mail-server/service-mail';
export const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_server/v2'; // export const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_server/v2';
export const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_server'; // prod: export const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_server'; // prod:
export const VONAGE_URL = 'https://p9axztuwd7x8a7.mycht.cn/vonage-server'; // 语音和视频接口: export const VONAGE_URL = 'https://p9axztuwd7x8a7.mycht.cn/vonage-server'; // 语音和视频接口:
export const HT3 = process.env.NODE_ENV === 'production' ? 'https://p9axztuwd7x8a7.mycht.cn/ht3' : 'https://p9axztuwd7x8a7.mycht.cn/ht3'; export const HT3 = process.env.NODE_ENV === 'production' ? 'https://p9axztuwd7x8a7.mycht.cn/ht3' : 'https://p9axztuwd7x8a7.mycht.cn/ht3';

@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react' import { useState, useEffect, useCallback } from 'react'
import { isEmpty, readIndexDB, } from '@/utils/commons' import { isEmpty, objectMapper, olog, readIndexDB, } from '@/utils/commons'
import { getEmailDetailAction, postResendEmailAction, getSalesSignatureAction, getEmailOrderAction, queryEmailListAction } from '@/actions/EmailActions' import { getEmailDetailAction, postResendEmailAction, getSalesSignatureAction, getEmailOrderAction, queryEmailListAction, getEmailTemplateAction } from '@/actions/EmailActions'
import { App } from 'antd' import { App } from 'antd'
import useConversationStore from '@/stores/ConversationStore'; import useConversationStore from '@/stores/ConversationStore';
import { msgStatusRenderMapped } from '@/channel/bubbleMsgUtils'; import { msgStatusRenderMapped } from '@/channel/bubbleMsgUtils';
@ -11,7 +11,7 @@ export const useEmailSignature = (opi_sn) => {
const [signature, setSignature] = useState('') const [signature, setSignature] = useState('')
const getSignature = useCallback(async () => { const getSignature = useCallback(async () => {
if (isEmpty(opi_sn)) { if (isEmpty(Number(opi_sn))) {
return false return false
} }
try { try {
@ -24,9 +24,9 @@ export const useEmailSignature = (opi_sn) => {
useEffect(() => { useEffect(() => {
getSignature() getSignature()
}, [opi_sn]) }, [getSignature])
return { signature, getSignature } return { signature }
} }
@ -35,18 +35,18 @@ export const useEmailSignature = (opi_sn) => {
* @param data 直接传递, 不重复获取 * @param data 直接传递, 不重复获取
* * 在详情点击`回复`呼出编辑时 * * 在详情点击`回复`呼出编辑时
*/ */
export const useEmailDetail = (mai_sn, data) => { export const useEmailDetail = (mai_sn, data={}, oid=0) => {
const {notification} = App.useApp() const {notification} = App.useApp()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [mailData, setMailData] = useState({ loading, info: {}, content: '', attachments: [], AttachList: [] }) const [mailData, setMailData] = useState({ loading, info: { MAI_COLI_SN: 0 }, content: '', attachments: [], AttachList: [] })
const [coliSN, setColiSN] = useState(''); const [coliSN, setColiSN] = useState(oid);
const [orderDetail, setOrderDetail] = useState({}); const [orderDetail, setOrderDetail] = useState({});
const [updateMessageItem] = useConversationStore(state => [state.updateMessageItem]); const [updateMessageItem] = useConversationStore(state => [state.updateMessageItem]);
useEffect(() => { useEffect(() => {
const getEmailDetail = async () => { const getEmailDetail = async () => {
if (isEmpty(mai_sn)) { if (isEmpty(Number(mai_sn))) {
return false return false
} }
try { try {
@ -182,6 +182,117 @@ export const useEmailList = (mailboxDirNode) => {
return { loading, isFreshData, error, mailList, refresh } return { loading, isFreshData, error, mailList, refresh }
} }
const orderMailTypes = new Map([
['48001', '发送一催'],
['48002', '发送二催'],
['48003', '发送三催'],
['48004', '发送已收客人付款通知'],
['48005', '发送FAQ及PreSurvey'],
['48006', '发送降价邮件'],
['48007', '发送亚马逊津贴'],
['48008', '发送报价信'],
['48009', '提交调度'],
['48010', '发送确认信'],
['48011', '发送余款提醒信'],
['48012', '发送入境提醒'],
['48013', '首站关怀'],
['48014', '抵桂面谈'],
['48015', '末站关怀'],
['48016', '发送计划'],
['48017', '报价中'],
['48018', '以后联系'],
['48019', '成行'],
['48020', '团款收讫'],
['48021', '取消'],
['48022', '丢失'],
['48023', '已做计划'],
['48024', '一催回复'],
['48025', '二催回复'],
['48026', '第一次报价回复'],
['48027', '发送商务感谢信'],
['48028', '商务酒店房满邮件'],
['48029', '商务预订传真'],
['48030', '商务取消邮件'],
['48031', '商务单团财务表'],
['48032', '商务收款单'],
['48033', '商务退款单'],
['48034', '商务财务转帐申请'],
['48035', '商务未订先确认'],
['48036', '商务等待确认'],
['48037', '商务已确认'],
['48038', '商务已付款'],
['48039', '商务成功订单'],
['48040', '商务取消订单'],
['48041', '商务不成行订单'],
['48042', '商务已付款未出票'],
['48043', '商务已付款并出票'],
['48044', '商务无效订单'],
['48045', '问候提醒'],
['48046', '发送邮件'],
['48047', '来华一周年问候'],
['48048', '来华二周年提醒'],
['48049', 'CH Phone询问邮件'],
['48050', '订单电话销售'],
['48051', '等待付订金'],
['48052', '商务订单预订中'],
['48053', '客户需求调查'],
['48054', '发送TourCredits通知邮件'],
['48055', 'GH海外PostSurvey'],
])
export const emailTemplates = [ export const emailTemplates = [
{ key: '', value: '', label: ''}, { type: 'RemindOneWL', index: 1, key: '1@RemindOneWL', value: '1@RemindOneWL', label: '一催模板一,询问客人是否收到报价信' },
{ type: 'RemindOneWL', index: 2, key: '2@RemindOneWL', value: '2@RemindOneWL', label: '一催模板二,询问客人是否修改行程' },
{ type: 'divider' },
{ type: 'RemindTwoWL', index: 1, key: '1@RemindTwoWL', value: '1@RemindTwoWL', label: '二催模板一,询问客人对行程的看法' },
{ type: 'RemindTwoWL', index: 2, key: '2@RemindTwoWL', value: '2@RemindTwoWL', label: '二催模板二,表达服务的意识' },
{ type: 'divider' },
{ type: 'RemindThreeWL', index: 1, key: '1@RemindThreeWL', value: '1@RemindThreeWL', label: '三催模板三,强调价格有效期' },
]; ];
export const emailTemplateMap = emailTemplates.reduce((acc, cur) => {
if (cur.type === 'divider') {
return acc
}
acc[cur.key] = cur
return acc
}, {});
/**
*
* @param {object} params - Parameters for the email template request.
* @param {number} [params.coli_sn] - Customer order line item serial number.
* @param {number} [params.lgc] - Language code.
* @param {number} [params.opi_sn] - Order product item serial number.
*/
export const useEmailTemplate = (templateKey, params) => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [templateContent, setTemplateContent] = useState({ mailtype: '', mailtypeName: '', subject: '', mailcontent: '' })
const getTemplateContent = useCallback(async () => {
if (!templateKey || !Number(params.coli_sn) || !Number(params.opi_sn)) {
setTemplateContent({ mailtype: '', mailtypeName: '', subject: '', mailcontent: '' })
setLoading(false)
setError(null)
return
}
setLoading(true)
setError(null)
try {
const { index: remind_index, type: remind_type } = emailTemplateMap[templateKey] || {};
const _params = { ...params, remind_index, remind_type};
const x = await getEmailTemplateAction(_params)
const lowerCaseShallow = Object.keys(x).reduce((acc, key) => ({...acc, [key.toLowerCase()]: x[key]}), {})
setTemplateContent({...lowerCaseShallow, mailtypeName: orderMailTypes.get(lowerCaseShallow.mailtype)})
} catch (networkError) {
setError(new Error(`Failed to get template content: ${networkError.message}.`))
} finally {
setLoading(false)
}
}, [templateKey, params.opi_sn, params.coli_sn, params.lgc])
useEffect(() => {
getTemplateContent()
}, [getTemplateContent])
return { loading, error, templateContent };
}

@ -87,8 +87,10 @@ const router = createBrowserRouter([
{ path: 'customer_relation/index', element: <CustomerRelation /> }, { path: 'customer_relation/index', element: <CustomerRelation /> },
], ],
}, },
{ path: 'email/:action/:quoteid/:oid/:templateKey', element: <NewEmail />},
{ path: 'email/:action/:quoteid/:oid', element: <NewEmail />},
{ path: 'email/:action/:quoteid', element: <NewEmail />}, { path: 'email/:action/:quoteid', element: <NewEmail />},
{ path: 'email/new', element: <NewEmail />}, // { path: 'email/new/0/:oid', element: <NewEmail />},
], ],
}, },
{ {

@ -1,6 +1,6 @@
import { useEffect, useState, useRef, useCallback } from 'react' import { useEffect, useState, useRef, useCallback, useMemo } from 'react'
import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { App, ConfigProvider, Button, Form, Input, Flex, Checkbox, Popconfirm, Select, Space, Upload, Divider, Modal, Tabs, Radio } from 'antd' import { App, ConfigProvider, Button, Form, Input, Flex, Checkbox, Popconfirm, Select, Space, Upload, Divider, Modal, Tabs, Radio, Typography } from 'antd'
import { UploadOutlined, LoadingOutlined, SaveOutlined, SendOutlined, CheckCircleOutlined } from '@ant-design/icons' import { UploadOutlined, LoadingOutlined, SaveOutlined, SendOutlined, CheckCircleOutlined } from '@ant-design/icons'
import '@dckj/react-better-modal/dist/index.css' import '@dckj/react-better-modal/dist/index.css'
import useStyleStore from '@/stores/StyleStore' import useStyleStore from '@/stores/StyleStore'
@ -10,11 +10,11 @@ import useAuthStore from '@/stores/AuthStore'
import LexicalEditor from '@/components/LexicalEditor' import LexicalEditor from '@/components/LexicalEditor'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { cloneDeep, debounce, isEmpty, writeIndexDB, readIndexDB, deleteIndexDBbyKey } from '@/utils/commons' import { cloneDeep, debounce, isEmpty, writeIndexDB, readIndexDB, deleteIndexDBbyKey, olog } from '@/utils/commons'
import '@/views/Conversations/Online/Input/EmailEditor.css' import '@/views/Conversations/Online/Input/EmailEditor.css'
import { parseHTMLString, postSendEmail, saveEmailDraftOrSendAction } from '@/actions/EmailActions' import { parseHTMLString, postSendEmail, saveEmailDraftOrSendAction } from '@/actions/EmailActions'
import { sentMsgTypeMapped } from '@/channel/bubbleMsgUtils' import { sentMsgTypeMapped } from '@/channel/bubbleMsgUtils'
import { EmailBuilder, useEmailDetail, useEmailSignature } from '@/hooks/useEmail' import { EmailBuilder, useEmailDetail, useEmailSignature, useEmailTemplate } from '@/hooks/useEmail'
import useSnippetStore from '@/stores/SnippetStore' import useSnippetStore from '@/stores/SnippetStore'
// import { useOrderStore } from '@/stores/OrderStore' // import { useOrderStore } from '@/stores/OrderStore'
import PaymentlinkBtn from '@/views/Conversations/Online/Input/PaymentlinkBtn' import PaymentlinkBtn from '@/views/Conversations/Online/Input/PaymentlinkBtn'
@ -70,39 +70,31 @@ const generateQuoteContent = (mailData, isRichText = true) => {
const generateMailContent = (mailData) => `<br><p>${mailData.content}</p><br>` const generateMailContent = (mailData) => `<br><p>${mailData.content}</p><br>`
/** /**
* 独立窗口编辑器
*
* - 从销售平台进入: 自动复制 storage, 可读取loginUser
*
* ! 无状态管理 * ! 无状态管理
*/ */
const NewEmail = () => { const NewEmail = () => {
const pageParam = useParams(); const pageParam = useParams();
const editorKey = pageParam.action==='new' ? `new-${Date.now().toString(32)}` : `${pageParam.action}-${pageParam.quoteid}` const { templateKey } = pageParam
const editorKey = pageParam.action==='new' ? `new-0-${pageParam.oid}` : `${pageParam.action}-${pageParam.quoteid}`
const { notification, message } = App.useApp() const { notification, message } = App.useApp()
const [form] = Form.useForm() const [form] = Form.useForm()
const [mobile] = useStyleStore((state) => [state.mobile]) const [mobile] = useStyleStore((state) => [state.mobile])
const [userId, username, emailList] = useAuthStore((state) => [state.loginUser.userId, state.loginUser.username, state.loginUser.emailList]) 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 emailListOption = useMemo(() => emailList?.map((ele) => ({ ...ele, label: ele.email, key: ele.email, value: ele.email })) || [], [emailList])
// const emailListMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.opi_sn]: v }), {}); const emailListOPIMapped = useMemo(() => emailListOption?.reduce((r, v) => ({ ...r, [v.opi_sn]: v }), {}), [emailListOption]);
const emailListAddrMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.email]: v }), {}) const emailListAddrMapped = useMemo(() => emailListOption?.reduce((r, v) => ({ ...r, [v.email]: v }), {}), [emailListOption])
const emailListMatMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.mat_sn]: v }), {}) const emailListMatMapped = useMemo(() => emailListOption?.reduce((r, v) => ({ ...r, [v.mat_sn]: v }), {}), [emailListOption])
// console.log('emailListMapped', emailListOption, emailListAddrMapped); // 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 mai_sn = pageParam.quoteid // activeEdit.quoteid const mai_sn = pageParam.quoteid // activeEdit.quoteid
const { loading: getLoading, mailData, orderDetail } = useEmailDetail(mai_sn) const { loading: quoteLoading, mailData, orderDetail } = useEmailDetail(mai_sn, null, pageParam.oid)
const { loading: loadingTamplate, templateContent } = useEmailTemplate(templateKey, {coli_sn: pageParam.oid, opi_sn: orderDetail.opi_sn || mailData.info?.MAI_OPI_SN || 0, lgc: 1});
const { signature } = useEmailSignature(orderDetail.opi_sn || mailData.info?.MAI_OPI_SN || 0)
const [newFromEmail, setNewFromEmail] = useState('') const [newFromEmail, setNewFromEmail] = useState('')
const [newToEmail, setNewToEmail] = useState('') const [newToEmail, setNewToEmail] = useState('')
@ -121,35 +113,198 @@ const NewEmail = () => {
const [contentPrefix, setContentPrefix] = useState('') const [contentPrefix, setContentPrefix] = useState('')
const [localDraft, setLocalDraft] = useState(); const [localDraft, setLocalDraft] = useState();
const readMailboxLocalCache = async () => { // const readMailboxLocalCache = async () => {
const readCache = await readIndexDB(editorKey, 'draft', 'mailbox') // console.log('===============', 'readMailboxLocalCache')
if (readCache) { // const readCache = await readIndexDB(editorKey, 'draft', 'mailbox')
const btn = ( // if (readCache) {
<Space> // const btn = (
<Button type='link' size='small' onClick={() => notification.destroy()}> // <Space>
关闭 // <Button type='link' size='small' onClick={() => notification.destroy()}>
</Button> //
{/* <Button type="primary" size="small" onClick={() => notification.destroy()}> // </Button>
Confirm // {/* <Button type="primary" size="small" onClick={() => notification.destroy()}>
</Button> */} // Confirm
</Space> // </Button> */}
) // </Space>
// notification.open({ // )
// key: editorKey, // // notification.open({
// placement: 'top', // // key: editorKey,
// // message: '', // // placement: 'top',
// description: '', // // // message: '',
// duration: 0, // // description: '',
// actions: btn, // // duration: 0,
// }) // // actions: btn,
setLocalDraft(readCache); // // })
// setLocalDraft(readCache)
// if (!isEmpty(localDraft)) {
// const { htmlContent, ...draftFormsValues } = localDraft
// const _findMatOld = emailListMatMapped?.[draftFormsValues.mat_sn]
// const _from = draftFormsValues?.from || _findMatOld?.email || ''
// form.setFieldsValue(draftFormsValues)
// setNewFromEmail(_from)
// setEmailOPI(draftFormsValues.opi_sn)
// setEmailMat(draftFormsValues.mat_sn)
// setEmailOrder(draftFormsValues.coli_sn)
// requestAnimationFrame(() => {
// setInitialContent(htmlContent)
// })
// return false
// }
// }
// }
// useEffect(() => {
// readMailboxLocalCache()
// return () => {}
// }, [])
//
// -
// -
useEffect(() => {
console.log('useEffect 1---- \nform.setFieldsValue ');
if (isEmpty(mailData.content) && isEmpty(orderDetail.order_no)) {
return () => {}
} }
} const docTitle = mailData.info?.MAI_Subject || 'New Email-';
document.title = docTitle
const { info } = mailData
const { ...templateFormValues } = templateContent;
const orderReceiver = orderDetail.contact?.[0]?.email || ''
const _findMatOld = emailListOPIMapped?.[orderDetail.opi_sn]
const orderSender = _findMatOld?.email || ''
const _findMatOldE = emailListMatMapped?.[info.MAI_MAT_SN]
const quotedMailSender = _findMatOldE?.email || ''
const sender = quotedMailSender || orderSender
const quotedMailSenderObj = sender; // { key: sender, label: sender, value: sender }
const defaultMAT = emailListAddrMapped?.[sender]?.mat_sn || ''
const _form2 = {
coli_sn: pageParam.oid || info?.coli_sn || '',
mat_sn: info?.MAI_MAT_SN || defaultMAT,
opi_sn: info?.MAI_OPI_SN || orderDetail.opi_sn || '',
}
let readyToInitialContent = '';
let _formValues = {};
// 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) && !['edit', 'new'].includes(pageParam.action)) {
readyToInitialContent = contentPrefix + signatureBody
}
switch (pageParam.action) {
case 'reply':
_formValues = {
from: quotedMailSenderObj,
to: info?.replyTo || orderReceiver,
cc: info?.MAI_CS || '',
// bcc: quote.bcc || '',
subject: `Re: ${info.MAI_Subject || ''}`,
..._form2
}
break
case 'replyall':
_formValues = {
from: quotedMailSenderObj,
to: info?.replyToAll || orderReceiver,
cc: info?.MAI_CS || '',
// bcc: quote.bcc || '',
subject: `Re: ${info.MAI_Subject || ''}`,
..._form2
}
break
case 'forward':
_formValues = {
from: quotedMailSenderObj,
subject: `Fw: ${info.MAI_Subject || ''}`,
// coli_sn: pageParam.oid,
..._form2
}
break
case 'edit':
_formValues = {
from: quotedMailSenderObj,
to: info?.MAI_To || '',
cc: info?.MAI_CS || '',
subject: `Fw: ${info.MAI_Subject || ''}`,
id: pageParam.quoteid,
mai_sn: pageParam.quoteid,
..._form2
}
readyToInitialContent = generateMailContent(mailData)
break
case 'new':
_formValues = {
...templateFormValues,
from: quotedMailSenderObj,
to: orderReceiver,
..._form2
}
readyToInitialContent = generateMailContent({ content: templateContent.bodycontent })
break
default:
break
}
olog('222', _formValues, pageParam.action)
form.setFieldsValue(_formValues) // todo: from
setInitialContent(readyToInitialContent);
return () => {}
}, [orderDetail.order_no, quoteLoading, loadingTamplate])
// const readFromTemplate = () => {
// const { mailcontent, ...templateFormValues } = templateContent;
// if (mailcontent) {
// const _findMatOld = emailListOPIMapped?.[orderDetail.opi_sn]
// const _from = _findMatOld?.email || ''
// form.setFieldsValue({...templateFormValues, to: orderDetail?.contact?.[0]?.email || '', from1: { key: _from, value: _from, label: _from}});
// // setNewFromEmail(_from);
// // setEmailOPI(draftFormsValues.opi_sn)
// requestAnimationFrame(() => {
// setInitialContent(mailcontent);
// });
// }
// }
useEffect(() => { useEffect(() => {
readMailboxLocalCache() // readMailboxLocalCache()
if (loadingTamplate) {
notification.open({
key: editorKey,
placement: 'top',
// message: '',
description: '正在加载邮件模板...',
duration: 0,
icon: <LoadingOutlined />,
// actions: btn,
// closeIcon: null,
closable: false,
})
} else {
notification.destroy(editorKey)
}
// readFromTemplate();
return () => {} return () => {}
}, []) }, [loadingTamplate])
@ -157,6 +312,8 @@ const NewEmail = () => {
console.log('useeffect\n', orderDetail.order_no); console.log('useeffect\n', orderDetail.order_no);
setContentPrefix(orderDetail.order_no ? `<p>Dear Mr./Ms. ${orderDetail.contact?.[0]?.name || ''}</p><p>Reference Number: ${orderDetail.order_no}</p>` : '') setContentPrefix(orderDetail.order_no ? `<p>Dear Mr./Ms. ${orderDetail.contact?.[0]?.name || ''}</p><p>Reference Number: ${orderDetail.order_no}</p>` : '')
// form.setFieldsValue({ to: orderDetail.contact?.[0]?.email || '' })
setNewToEmail(orderDetail.contact?.[0]?.email || '')
return () => {} return () => {}
}, [orderDetail.order_no, editorKey]) }, [orderDetail.order_no, editorKey])
@ -169,7 +326,6 @@ const NewEmail = () => {
setEmailOPI(_findMat?.opi_sn) setEmailOPI(_findMat?.opi_sn)
} }
const { signature } = useEmailSignature(emailOPI)
const [isRichText, setIsRichText] = useState(mobile === false) const [isRichText, setIsRichText] = useState(mobile === false)
// const [isRichText, setIsRichText] = useState(false); // // const [isRichText, setIsRichText] = useState(false); //
@ -193,11 +349,11 @@ const NewEmail = () => {
form.setFieldValue('content', htmlContent) form.setFieldValue('content', htmlContent)
const { bodyText: abstract } = parseHTMLString(htmlContent, true); const { bodyText: abstract } = parseHTMLString(htmlContent, true);
const body = { const body = {
from: newFromEmail, // from: newFromEmail,
attaList: fileList, // attaList: fileList,
opi_sn: emailOPI, // opi_sn: emailOPI,
mat_sn: emailMat, // mat_sn: emailMat,
coli_sn: emailOrder || '', // coli_sn: emailOrder || '',
} }
// form.setFieldValue('abstract', getAbstract(textContent)) // form.setFieldValue('abstract', getAbstract(textContent))
debouncedSave({ ...form.getFieldsValue(), ...body, htmlContent, abstract, }) debouncedSave({ ...form.getFieldsValue(), ...body, htmlContent, abstract, })
@ -205,129 +361,111 @@ const NewEmail = () => {
const [initialContent, setInitialContent] = useState('') const [initialContent, setInitialContent] = useState('')
const [showQuoteContent, setShowQuoteContent] = useState(false) const [showQuoteContent, setShowQuoteContent] = useState(false)
const [mergeQuote, setMergeQuote] = useState(true) // const [mergeQuote, setMergeQuote] = useState(true)
const [quoteContent, setQuoteContent] = useState('') const [quoteContent, setQuoteContent] = useState('')
const setPreFillInProperty = () => { }; // const setPreFillInProperty = () => { };
useEffect(() => { // useEffect(() => {
console.log('useEffect 2---- \nform.setFieldsValue '); // console.log('useEffect 2---- \nform.setFieldsValue ');
const docTitle = localDraft?.subject || mailData.info?.MAI_Subject || 'New Email-'; // const docTitle = localDraft?.subject || mailData.info?.MAI_Subject || 'New Email-';
setTimeout(() => { // setTimeout(() => {
document.title = docTitle // document.title = docTitle
}, 1500); // }, 1500);
if (!isEmpty(localDraft)) { // //
const {htmlContent, ...draftFormsValues} = localDraft; // if (isEmpty(pageParam.quoteid)) {
const _findMatOld = emailListMatMapped?.[draftFormsValues.mat_sn] // // setEmailOrder(orderDetail.order_no)
const _from = draftFormsValues?.from || _findMatOld?.email || '' // // setEmailOPI(orderDetail.opi_sn)
// // setNewFromEmail(activeEdit.fromEmail)
form.setFieldsValue(draftFormsValues); // // setNewToEmail(activeEdit.toEmail)
setNewFromEmail(_from);
setEmailOPI(draftFormsValues.opi_sn) // // const _findMat = emailListAddrMapped?.[activeEdit.fromEmail]?.mat_sn
setEmailMat(draftFormsValues.mat_sn) // // setEmailMat(_findMat)
setEmailOrder(draftFormsValues.coli_sn)
requestAnimationFrame(() => { // // if (open !== true) {
setInitialContent(htmlContent); // // form.resetFields()
}); // // }
// }
return () => {}; // // /, 使
} // if (mailData.info?.MAI_MAT_SN) {
// const reEmailO = mailData.info?.MAI_COLI_SN
// // const reEmailUser = mailData.info?.MAI_OPI_SN
if (isEmpty(pageParam.quoteid)) { // const reEmailUserMat = mailData.info?.MAI_MAT_SN
// setEmailOrder(orderDetail.order_no) // setEmailOrder((prev) => reEmailO || prev ) // activeEdit.fromOrder
// setEmailOPI(orderDetail.opi_sn) // setEmailOPI((prev) => reEmailUser || prev)
// setNewFromEmail(activeEdit.fromEmail) // setEmailMat((prev) => reEmailUserMat || prev)
// setNewToEmail(activeEdit.toEmail)
// const _findMatOld = emailListMatMapped?.[reEmailUserMat]
// const _findMat = emailListAddrMapped?.[activeEdit.fromEmail]?.mat_sn // if (_findMatOld) {
// setEmailMat(_findMat) // setNewFromEmail(_findMatOld.email)
// setEmailOPI(_findMatOld.opi_sn)
// if (open !== true) { // setEmailMat(_findMatOld.mat_sn)
// form.resetFields() // }
// } // }
} // setShowQuoteContent(false)
// /, 使 // // setMergeQuote(true)
if (mailData.info?.MAI_MAT_SN) { // setQuoteContent('')
const reEmailO = mailData.info?.MAI_COLI_SN
const reEmailUser = mailData.info?.MAI_OPI_SN
const reEmailUserMat = mailData.info?.MAI_MAT_SN // if (isEmpty(pageParam.quoteid) && pageParam.action !== 'new') {
// return () => {}
setEmailOrder((prev) => reEmailO || prev ) // activeEdit.fromOrder // }
setEmailOPI((prev) => reEmailUser || prev) // const { info } = mailData
setEmailMat((prev) => reEmailUserMat || prev) // // setShowCc(!isEmpty(mailData.info?.MAI_CS));
const _findMatOld = emailListMatMapped?.[reEmailUserMat] // const signatureBody = generateMailContent({ content: signature })
if (_findMatOld) {
setNewFromEmail(_findMatOld.email) // // const preQuoteBody = generateQuoteContent(mailData)
setEmailOPI(_findMatOld.opi_sn)
setEmailMat(_findMatOld.mat_sn) // // const _initialContent = isEmpty(mailData.info) ? signatureBody : signatureBody+preQuoteBody
}
} // if (!isEmpty(mailData.info) && pageParam.action !== 'edit') {
setShowQuoteContent(false) // setInitialContent(contentPrefix + signatureBody)
setMergeQuote(true) // }
setQuoteContent('')
// const forwardValues = { from: newFromEmail, subject: `Fw: ${info.MAI_Subject || ''}` }
// if (pageParam.action === 'reply') {
if (isEmpty(pageParam.quoteid) && pageParam.action !== 'new') { // const _formValues = {
return () => {} // to: info?.replyTo || newFromEmail,
} // cc: info?.MAI_CS || '',
const { info } = mailData // // bcc: quote.bcc || '',
// setShowCc(!isEmpty(mailData.info?.MAI_CS)); // subject: `Re: ${info.MAI_Subject || ''}`,
// }
const signatureBody = generateMailContent({ content: signature }) // form.setFieldsValue(_formValues)
// } else if (pageParam.action === 'replyall') {
// const preQuoteBody = generateQuoteContent(mailData) // const _formValues = {
// to: info?.replyToAll || newFromEmail,
// const _initialContent = isEmpty(mailData.info) ? signatureBody : signatureBody+preQuoteBody // cc: info?.MAI_CS || '',
// // bcc: quote.bcc || '',
if (!isEmpty(mailData.info) && pageParam.action !== 'edit') { // subject: `Re: ${info.MAI_Subject || ''}`,
setInitialContent(contentPrefix + signatureBody) // }
} // form.setFieldsValue(_formValues)
// } else if (pageParam.action === 'forward') {
const forwardValues = { from: newFromEmail, subject: `Fw: ${info.MAI_Subject || ''}` } // form.setFieldsValue(forwardValues)
if (pageParam.action === 'reply') { // } else if (pageParam.action === 'edit') {
const _formValues = { // const thisFormValues = {
to: info?.replyTo || newFromEmail, // to: info?.MAI_To || '',
cc: info?.MAI_CS || '', // cc: info?.MAI_CS || '',
// bcc: quote.bcc || '', // subject: info?.MAI_Subject || '',
subject: `Re: ${info.MAI_Subject || ''}`, // id: pageParam.quoteid,
} // }
form.setFieldsValue(_formValues) // form.setFieldsValue(thisFormValues)
} else if (pageParam.action === 'replyall') { // const thisBody = generateMailContent(mailData)
const _formValues = { // // console.log('thisBody', thisBody);
to: info?.replyToAll || newFromEmail,
cc: info?.MAI_CS || '', // setInitialContent(thisBody)
// bcc: quote.bcc || '', // } else if (pageParam.action === 'new') {
subject: `Re: ${info.MAI_Subject || ''}`, // // todo:
} // const newEmail = { to: newToEmail, subject: '' }
form.setFieldsValue(_formValues) // // form.setFieldsValue(newEmail)
} else if (pageParam.action === 'forward') { // // setInitialContent((contentPrefix || '') + signatureBody)
form.setFieldsValue(forwardValues) // }
} else if (pageParam.action === 'edit') {
const thisFormValues = { // return () => {}
to: info?.MAI_To || '', // }, [ mailData.info, signature, newToEmail, contentPrefix])
cc: info?.MAI_CS || '',
subject: info?.MAI_Subject || '',
id: pageParam.quoteid,
}
form.setFieldsValue(thisFormValues)
const thisBody = generateMailContent(mailData)
// console.log('thisBody', thisBody);
setInitialContent(thisBody)
} else if (pageParam.action === 'new') {
// todo:
const newEmail = { to: newToEmail, subject: '' }
form.setFieldsValue(newEmail)
setInitialContent((contentPrefix || '') + signatureBody)
}
return () => {}
}, [ mailData.info, signature, newToEmail, contentPrefix, localDraft])
const [openPlainTextConfirm, setOpenPlainTextConfirm] = useState(false) const [openPlainTextConfirm, setOpenPlainTextConfirm] = useState(false)
const handlePlainTextOpenChange = ({ target }) => { const handlePlainTextOpenChange = ({ target }) => {
@ -470,11 +608,11 @@ const NewEmail = () => {
const onHandleSaveOrSend = async (isDraft = false) => { const onHandleSaveOrSend = async (isDraft = false) => {
// console.log('onSend callback', '\nisRichText', isRichText); // console.log('onSend callback', '\nisRichText', isRichText);
// console.log(form.getFieldsValue()); console.log(form.getFieldsValue());
const body = structuredClone(form.getFieldsValue()) const body = structuredClone(form.getFieldsValue())
body.mailtype = ''; // todo: // body.mailtype = ''; // todo:
// body.id = ''; // todo: 稿 // body.id = ''; // todo: 稿
body.from = newFromEmail // body.from = newFromEmail // todo:
body.attaList = fileList body.attaList = fileList
body.opi_sn = emailOPI body.opi_sn = emailOPI
body.mat_sn = emailMat body.mat_sn = emailMat
@ -513,7 +651,7 @@ const NewEmail = () => {
// console.log('postSendEmail', body, '\n', msgObj); // console.log('postSendEmail', body, '\n', msgObj);
// return; // return;
// todo: 稿, // todo: 稿, ; ID
const result = await saveEmailDraftOrSendAction(body, isDraft) const result = await saveEmailDraftOrSendAction(body, isDraft)
const mailSavedId = result.id || '' const mailSavedId = result.id || ''
bubbleMsg.email.mai_sn = mailSavedId bubbleMsg.email.mai_sn = mailSavedId
@ -582,12 +720,32 @@ const NewEmail = () => {
return ( return (
<> <>
<ConfigProvider theme={{ token: { colorPrimary: '#6366f1' } }}> <ConfigProvider theme={{ token: { colorPrimary: '#6366f1' } }}>
<Form
form={form}
onValuesChange={onEditChange}
// onFinishFailed={onFinishFailed}
preserve={false}
name={`email_max_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 }}
>
<div className='w-full flex gap-4 justify-start items-center text-indigo-600 pb-1 mb-2 border-x-0 border-t-0 border-b border-solid border-neutral-200'> <div className='w-full flex gap-4 justify-start items-center text-indigo-600 pb-1 mb-2 border-x-0 border-t-0 border-b border-solid border-neutral-200'>
<Button type='primary' onClick={onHandleSaveOrSend} loading={sendLoading} icon={<SendOutlined />}> <Button type='primary' size='middle' onClick={onHandleSaveOrSend} loading={sendLoading} icon={<SendOutlined />}>
发送 发送
</Button> </Button>
<Select labelInValue options={emailListOption} value={{ key: newFromEmail, value: newFromEmail, label: newFromEmail }} onChange={handleSwitchEmail} labelRender={item => `发件人: ${item.label || '选择'}`} variant={'borderless'} className='[&_.ant-select-selection-item]:font-bold' /> <Form.Item noStyle name={'from'} rules={[{ required: true, message: '请选择发件地址' }]}>
<Select labelInValue={false} options={emailListOption} labelRender={item => `发件人: ${item?.label || '选择'}`} variant={'borderless'} placeholder='发件人: 选择' className='[&_.ant-select-selection-item]:font-bold [&_.ant-select-selection-placeholder]:font-bold [&_.ant-select-selection-placeholder]:text-black' classNames={{popup: {root:'min-w-60'}}} />
<div className="ant-form-item-explain-error text-red-500" >请选择发件地址</div>
</Form.Item>
<div className='ml-auto'></div> <div className='ml-auto'></div>
<span>{orderDetail.order_no}</span>
<span>{templateContent.mailtypeName}</span>
<Popconfirm trigger1={['hover', 'click']} <Popconfirm trigger1={['hover', 'click']}
description='切换内容为纯文本格式将丢失信件和签名的格式, 确定使用纯文本?' description='切换内容为纯文本格式将丢失信件和签名的格式, 确定使用纯文本?'
onConfirm={confirmPlainText} onConfirm={confirmPlainText}
@ -601,20 +759,6 @@ const NewEmail = () => {
</Popconfirm> </Popconfirm>
<Button onClick={() => onHandleSaveOrSend(true)} type='dashed' icon={<SaveOutlined />} size='small' className='' >存草稿</Button> <Button onClick={() => onHandleSaveOrSend(true)} type='dashed' icon={<SaveOutlined />} size='small' className='' >存草稿</Button>
</div> </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'> <Form.Item className='w-full'>
<Space.Compact className='w-full'> <Space.Compact className='w-full'>
<Form.Item name={'to'} label='收件人' rules={[{ required: true }]} className='!flex-1'> <Form.Item name={'to'} label='收件人' rules={[{ required: true }]} className='!flex-1'>
@ -675,9 +819,24 @@ const NewEmail = () => {
<Form.Item name='id' hidden> <Form.Item name='id' hidden>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item name='mai_sn' hidden>
<Input />
</Form.Item>
<Form.Item name='mat_sn' hidden>
<Input />
</Form.Item>
<Form.Item name='coli_sn' hidden>
<Input />
</Form.Item>
<Form.Item name='opi_sn' hidden>
<Input />
</Form.Item>
<Form.Item name='mailtype' hidden>
<Input />
</Form.Item>
</Form> </Form>
<LexicalEditor {...{ isRichText }} onChange={handleEditorChange} defaultValue={initialContent} /> <LexicalEditor {...{ isRichText }} onChange={handleEditorChange} defaultValue={initialContent} />
{pageParam.quoteid && pageParam.action!=='edit' && !showQuoteContent && ( {!isEmpty(Number(pageParam.quoteid)) && pageParam.action!=='edit' && !showQuoteContent && (
<div className='flex justify-start items-center ml-2'> <div className='flex justify-start items-center ml-2'>
<Button className='flex gap-2 ' type='link' onClick={() => setShowQuoteContent(!showQuoteContent)}> <Button className='flex gap-2 ' type='link' onClick={() => setShowQuoteContent(!showQuoteContent)}>
显示引用内容 {/*(不可更改)*/} 显示引用内容 {/*(不可更改)*/}

@ -5,7 +5,8 @@ import dayjs from 'dayjs'
import { useEmailList } from '@/hooks/useEmail' import { useEmailList } from '@/hooks/useEmail'
import { isEmpty } from '@/utils/commons' import { isEmpty } from '@/utils/commons'
import { MailboxDirIcon } from './MailboxDirIcon' import { MailboxDirIcon } from './MailboxDirIcon'
import { AttachmentIcon } from '@/components/Icons' import { AttachmentIcon, MailCheckIcon } from '@/components/Icons'
import NewEmailButton from './NewEmailButton'
const { RangePicker } = DatePicker const { RangePicker } = DatePicker
@ -124,49 +125,7 @@ const MailBox = ({ mailboxDir, onMailItemClick, ...props }) => {
return ( return (
<> <>
<div className='bg-white h-auto px-1 flex gap-1 items-center'> <div className='bg-white h-auto px-1 flex gap-1 items-center'>
<Dropdown.Button <NewEmailButton />
className='w-auto'
placement='bottom'
arrow
type={'primary'}
menu={{
items: [
{
key: '1',
label: '一催模板一,询问客人是否收到报价信',
},
{
key: '2',
label: '一催模板二,询问客人是否修改行程',
},
{
type: 'divider',
},
{
key: '3',
label: '二催模板一,询问客人对行程的看法',
},
{
key: '4',
label: '二催模板二,表达服务的意识',
},
{
type: 'divider',
},
{
key: '5',
label: '三催模板三,强调价格有效期',
},
],
onClick: (item) => {
console.info('menu', item)
},
}}
onClick={() => {
console.info('新邮件')
}}>
新邮件
</Dropdown.Button>
<Flex wrap gap='middle' justify={'center'} className='min-w-40'> <Flex wrap gap='middle' justify={'center'} className='min-w-40'>
<Tooltip title='全选'> <Tooltip title='全选'>
<Checkbox></Checkbox> <Checkbox></Checkbox>
@ -175,7 +134,7 @@ const MailBox = ({ mailboxDir, onMailItemClick, ...props }) => {
<Button shape='circle' type='text' size='small' icon={<ReadOutlined />} /> <Button shape='circle' type='text' size='small' icon={<ReadOutlined />} />
</Tooltip> </Tooltip>
<Tooltip title='已处理'> <Tooltip title='已处理'>
<Button shape='circle' type='text' size='small' icon={<CheckSquareOutlined />} /> <Button shape='circle' type='text' size='small' icon={<MailCheckIcon />} />
</Tooltip> </Tooltip>
<Tooltip title='刷新'> <Tooltip title='刷新'>
<Button shape='circle' type='text' size='small' icon={<ReloadOutlined />} onClick={refresh} /> <Button shape='circle' type='text' size='small' icon={<ReloadOutlined />} onClick={refresh} />

@ -0,0 +1,38 @@
import { createContext, useEffect, useMemo, useState } from 'react'
import { Flex, Button, Tooltip, List, Form, Row, Col, Dropdown, Input, Checkbox, DatePicker, Switch, Breadcrumb, Skeleton } from 'antd'
import useConversationStore from '@/stores/ConversationStore'
import { emailTemplates, openPopup, useEmailTemplate } from '@/hooks/useEmail';
import { POPUP_FEATURES } from '@/config';
const NewEmailButton = ({ ...props }) => {
const [mailboxActiveNode, setMailboxActiveNode] = useConversationStore((state) => [state.mailboxActiveNode, state.setMailboxActiveNode])
const COLI_SN = useMemo(() => mailboxActiveNode?.COLI_SN || 0, [mailboxActiveNode.COLI_SN])
const handleTemplateDropdown = ({ key, domEvent }) => {
console.log(key)
openPopup(`/email/new/0/${COLI_SN}/${key}`, `new-0-${COLI_SN}-${key}`, POPUP_FEATURES)
};
const handleNewEmail = () => {
console.info('新邮件')
openPopup(`/email/new/0/${COLI_SN}`, `new-0-${COLI_SN}`, POPUP_FEATURES)
}
return (
<>
<Dropdown.Button
className={`w-auto ${props.className}`}
placement='bottom'
arrow
type={'primary'}
menu={{
items: emailTemplates,
onClick: handleTemplateDropdown,
}}
onClick={handleNewEmail}>
新邮件
</Dropdown.Button>
</>
)
}
export default NewEmailButton
Loading…
Cancel
Save