Merge branch 'main' of github.com:hainatravel/global-sales

dev/emitter
LiaoYijun 10 months ago
commit 584bacb6c3

@ -75,3 +75,12 @@ export const getEmailFetchAction = async (param) => {
})
return result
};
/**
* 报价信邮件草稿
* @param {object} { sfi_sn, coli_sn, lgc }
*/
export const getEmailQuotationDraftAction = async (param) => {
const { result } = await fetchJSON(`${EMAIL_HOST}/QuotationLetter`, param)
return { subject: (result.Subject || ''), content: (result.MailContent || '').replace(/[\r\n]/g, '') }
}

@ -284,11 +284,11 @@ export const sentMsgTypeMapped = {
const whatsappMsgMapped = {
'whatsapp.inbound_message.received': {
getMsg: (result) => {
console.log('whatsapp.inbound_message.received', result);
// console.log('whatsapp.inbound_message.received', result);
return isEmpty(result?.whatsappInboundMessage) ? null : { ...result.whatsappInboundMessage, conversationid: result.conversationid, messageorigin: result.messageorigin, msg_source: 'WABA', msg_direction: 'inbound' };
},
contentToRender: (contentObj) => {
console.log('whatsapp.inbound_message.received to render', contentObj);
// console.log('whatsapp.inbound_message.received to render', contentObj);
return parseRenderMessageItem(contentObj);
},
contentToUpdate: () => null,
@ -325,7 +325,7 @@ const whatsappMsgMapped = {
const emailMsgMapped = {
'email.inbound.received': {
getMsg: (result) => {
console.log('email.inbound.received', result);
// console.log('email.inbound.received', result);
const data1 = pick(result, ['conversationid', 'opi_sn', 'coli_sn', 'coli_id']);
return isEmpty(result?.emailMessage) ? null : { ...result.emailMessage, ...data1, msg_source: 'email', msg_direction: 'inbound' };
},
@ -337,7 +337,7 @@ const emailMsgMapped = {
},
'email.updated': {
getMsg: (result) => {
console.log('email.updated', result);
// console.log('email.updated', result);
const { emailMessage } = result;
const data1 = pick(result, ['conversationid', 'opi_sn', 'coli_sn', 'coli_id']);
return isEmpty(result?.emailMessage) ? null : { ...emailMessage, ...data1, msg_source: 'email', msg_direction: 'outbound' };
@ -796,7 +796,7 @@ export const whatsappError = {
'131047': '[131047] 会话未激活. \n请使用模板消息💬发送',
'131053': '[131053] 文件上传失败.',
'131048': '[131048] 账户被风控.', // 消息发送太多, 达到垃圾数量限制
'131049': '[131049] 号码触发风控. \n请暂停发送营销消息, 引导客户主动发起会话.', // 消息发送太多, 营销限制
'131049': '[131049] 号码触发风控. \n请暂停发送营销消息, 使用跟进模板\n或引导客户主动发起会话.', // 消息发送太多, 营销限制
'131031': '[131031] 账户已锁定.',
'130472': '[130472] 此号码不接收商业号消息\n请使用邮件联系 或 引导客户主动发起会话.',
};

@ -104,6 +104,11 @@ export class RealTimeAPI {
}
sendMessage(messageObject) {
console.log(
`%c websocket Message OUT ⬆`,
'background:#41b883 ; padding: 1px; border-radius: 3px; color: #fff',
JSON.stringify(messageObject),
);
this.webSocket.next(messageObject);
}

@ -427,27 +427,27 @@ function BlockOptionsDropdownList({ editor, blockType, toolbarRef, setShowBlockO
return (
<div className='dropdown' ref={dropDownRef}>
<button className='item' onClick={formatParagraph}>
<button type='button' className='item' onClick={formatParagraph}>
<span className='icon paragraph' />
<span className='text'>Normal</span>
{blockType === 'paragraph' && <span className='active' />}
</button>
<button className='item' onClick={formatLargeHeading}>
<button type='button' className='item' onClick={formatLargeHeading}>
<span className='icon large-heading' />
<span className='text'>Heading 1</span>
{blockType === 'h1' && <span className='active' />}
</button>
<button className='item' onClick={formatSmallHeading}>
<button type='button' className='item' onClick={formatSmallHeading}>
<span className='icon small-heading' />
<span className='text'>Heading 2</span>
{blockType === 'h2' && <span className='active' />}
</button>
<button className='item' onClick={formatSmallHeading3}>
<button type='button' className='item' onClick={formatSmallHeading3}>
<span className='icon h3' />
<span className='text'>Heading 3</span>
{blockType === 'h3' && <span className='active' />}
</button>
<button className='item' onClick={formatBulletList}>
<button type='button' className='item' onClick={formatBulletList}>
<span className='icon bullet-list' />
<span className='text'>Bullet List</span>
{blockType === 'ul' && <span className='active' />}
@ -767,7 +767,7 @@ export default function ToolbarPlugin() {
return (
<div className='toolbar' ref={toolbarRef}>
<button
<button type='button'
disabled={!canUndo}
onClick={() => {
editor.dispatchCommand(UNDO_COMMAND);
@ -776,7 +776,7 @@ export default function ToolbarPlugin() {
aria-label='Undo'>
<i className='format undo' />
</button>
<button
<button type='button'
disabled={!canRedo}
onClick={() => {
editor.dispatchCommand(REDO_COMMAND);
@ -788,7 +788,7 @@ export default function ToolbarPlugin() {
<Divider />
{supportedBlockTypes.has(blockType) && (
<>
<button className='toolbar-item block-controls' onClick={() => setShowBlockOptionsDropDown(!showBlockOptionsDropDown)} aria-label='Formatting Options'>
<button type='button' className='toolbar-item block-controls' onClick={() => setShowBlockOptionsDropDown(!showBlockOptionsDropDown)} aria-label='Formatting Options'>
<span className={'icon block-type ' + blockType} />
<span className='text'>{blockTypeToBlockName[blockType]}</span>
<i className='chevron-down' />
@ -815,7 +815,7 @@ export default function ToolbarPlugin() {
editor={editor}
/>
<Divider />
<button
<button type='button'
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
}}
@ -823,7 +823,7 @@ export default function ToolbarPlugin() {
aria-label='Format Bold'>
<i className='format bold' />
</button>
<button
<button type='button'
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
}}
@ -831,7 +831,7 @@ export default function ToolbarPlugin() {
aria-label='Format Italics'>
<i className='format italic' />
</button>
<button
<button type='button'
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
}}
@ -839,7 +839,7 @@ export default function ToolbarPlugin() {
aria-label='Format Underline'>
<i className='format underline' />
</button>
<button
<button type='button'
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
}}
@ -847,7 +847,7 @@ export default function ToolbarPlugin() {
aria-label='Format Strikethrough'>
<i className='format strikethrough' />
</button>
{/* <button
{/* <button type='button'
onClick={() => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, "code");
}}
@ -856,11 +856,11 @@ export default function ToolbarPlugin() {
>
<i className="format code" />
</button> */}
<button onClick={insertLink} className={'toolbar-item spaced ' + (isLink ? 'active' : '')} aria-label='Insert Link'>
<button type='button' onClick={insertLink} className={'toolbar-item spaced ' + (isLink ? 'active' : '')} aria-label='Insert Link'>
<i className='format link' />
</button>
{isLink && createPortal(<FloatingLinkEditor editor={editor} />, document.body)}
<button onClick={insertHorizontalRule}
<button type='button' onClick={insertHorizontalRule}
// onClick={() => {
// editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined);
// }}

@ -20,7 +20,7 @@
background-color: rgba(40, 40, 40, 0.6);
flex-grow: 0px;
flex-shrink: 1px;
z-index: 100;
z-index: 1200;
}
.Modal__modal {
padding: 20px;

@ -154,7 +154,8 @@ const websocketSlice = (set, get) => ({
}, 500);
},
handleMessage: (data) => {
olog('handleMessage------------------', data);
olog('websocket Message IN ⬇', JSON.stringify(data));
// olog('websocket Messages ----', data);
// console.log(data);
const { updateMessageItem, sentOrReceivedNewMessage, addGlobalNotify } = get();
const { errcode, errmsg, result } = data;
@ -167,11 +168,11 @@ const websocketSlice = (set, get) => ({
// addError('Error Connecting to Server');
resultType = 'error';
}
console.log(resultType, 'result.type');
// console.log(resultType, 'result.type');
const msgObj = receivedMsgTypeMapped[resultType].getMsg(result);
const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj);
const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj);
console.log('msgRender msgUpdate', msgRender, msgUpdate);
// console.log('msgRender msgUpdate', msgRender, msgUpdate);
if ([
'whatsapp.message.updated', 'message', 'error',
'email.updated',
@ -192,7 +193,7 @@ const websocketSlice = (set, get) => ({
const msgNotify = receivedMsgTypeMapped[resultType].contentToNotify(msgObj);
addGlobalNotify(msgNotify);
}
console.log('handleMessage*******************');
// console.log('handleMessage*******************');
},
});

@ -0,0 +1,91 @@
import { useEffect, useState } from 'react'
import { App, Button } from 'antd'
import EmailEditorPopup from '../Input/EmailEditorPopup'
import { useOrderStore } from '@/stores/OrderStore'
import useAuthStore from '@/stores/AuthStore'
import useConversationStore from '@/stores/ConversationStore'
import { getEmailQuotationDraftAction } from '@/actions/EmailActions'
import { isEmpty } from '@/utils/commons'
const EmailQuotation = ({ sfi_sn, ...props }) => {
const {notification} = App.useApp()
const currentConversation = useConversationStore((state) => state.currentConversation)
const { userId, username, emailList, email } = useAuthStore((state) => state.loginUser)
const [orderDetail, customerDetail] = useOrderStore((s) => [s.orderDetail, s.customerDetail])
const emailListOption = emailList?.map((ele) => ({ ...ele, label: ele.email, key: ele.email, value: ele.email })) || []
const emailListAddrMapped = emailListOption?.reduce((r, v) => ({ ...r, [v.email]: v }), {})
const [pickEmail, setPickEmail] = useState({ key: email, email })
useEffect(() => {
const order_opi = Number(orderDetail?.opi_sn || userId)
const find =
emailListOption?.find((ele) => ele.opi_sn === order_opi && ele.default === true) ||
emailListOption?.find((ele) => ele.opi_sn === order_opi && ele.backup === true) ||
emailListOption?.find((ele) => ele.opi_sn === order_opi) ||
emailListOption?.find((ele) => ele.default === true) ||
emailListOption?.find((ele) => ele.backup === true) ||
emailListOption[0]
setPickEmail(find)
return () => {}
}, [orderDetail])
const [draftLoading, setDraftLoading] = useState(false)
const [draft, setDraft] = useState({})
const getEmailDraft = async ({ sfi_sn, coli_sn, lgc = 1 }) => {
if (isEmpty(sfi_sn)) {
return false
}
try {
setDraftLoading(true)
const data = await getEmailQuotationDraftAction({ sfi_sn, coli_sn, lgc })
setDraft(data)
setDraftLoading(false)
} catch (err) {
setDraftLoading(false)
notification.error({
message: '请求失败',
description: err.message || '网络异常',
placement: 'top',
duration: 3,
})
}
}
const [editorOpen, setEditorOpen] = useState(false)
return (
<>
<Button
type='link'
key={'email-now'}
onClick={async () => {
getEmailDraft({ sfi_sn, coli_sn: currentConversation.coli_sn })
setEditorOpen(true)
}}>
邮件
</Button>
<EmailEditorPopup
open={editorOpen}
setOpen={setEditorOpen}
fromEmail={pickEmail.key}
fromUser={orderDetail.opi_sn}
toEmail={currentConversation?.channels?.email || customerDetail?.email}
fromOrder={currentConversation.coli_sn}
conversationid={currentConversation.sn}
// emailMsg={ReferEmailMsg}
// quoteid={mailID}
// initial={{ ...initialPosition, ...initialSize }}
// mailData={mailData}
draft={draft}
action={'new'}
key={`email-quotation-new-popup_${currentConversation.sn}`}
/>
</>
)
}
export default EmailQuotation

@ -258,7 +258,7 @@ const EmailComposer = ({ ...props }) => {
fromOrder={currentConversation.coli_sn}
conversationid={currentConversation.sn}
toEmail={toEmail}
quickData={quickData}
draft={quickData}
action='new'
key={'email-new-editor-popup'}
/>

@ -72,9 +72,9 @@ const generateMailContent = (mailData) => `
* @property {string} toEmail - 收件人邮箱
* @property {string} conversationid - 会话ID
* @property {string} quoteid - 引用邮件ID
* @property {object} quickData - 纯文本输入的草稿. 仅在从快速窗口打开时传递
* @property {object} draft - 草稿
*/
const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, toEmail, conversationid, quoteid, initial = {}, mailData: _mailData, action = 'reply', quickData = {}, ...props }) => {
const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, toEmail, conversationid, quoteid, initial = {}, mailData: _mailData, action = 'reply', draft = {}, ...props }) => {
const { notification, message } = App.useApp();
const [form] = Form.useForm();
@ -115,7 +115,7 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, toEma
useEffect(() => {
if (isEmpty(quoteid)) {
// console.log('emailEditorPopup effect', open, '\nto', toEmail)
setStickToProps({ fromEmail, fromUser, fromOrder, toEmail, conversationid, quoteid, action });
setStickToProps({ fromEmail, fromUser, fromOrder, toEmail, conversationid, quoteid, action, draft });
setStickToCid(conversationid)
setEmailOrder(fromOrder)
@ -198,7 +198,6 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, toEma
}
const { info, } = mailData
// setShowCc(!isEmpty(mailData.info?.MAI_CS));
setShowCc(true);
const preQuoteBody = generateQuoteContent(mailData);
@ -233,14 +232,14 @@ const EmailEditorPopup = ({ open, setOpen, fromEmail, fromUser, fromOrder, toEma
setInitialContent(thisBody)
} else if (action === 'new') {
const newEmail = { to: newToEmail, subject: quickData?.subject || '' }
const newEmail = { to: newToEmail, subject: draft?.subject || '' }
form.setFieldsValue(newEmail);
setInitialForm(newEmail);
setInitialContent(quickData?.content || '');
setInitialContent(draft?.content || '');
}
return () => {};
}, [stickToProps, mailData.info, newToEmail, newFromEmail]);
}, [stickToProps, mailData.info, draft, newToEmail, newFromEmail]);
const [openPlainTextConfirm, setOpenPlainTextConfirm] = useState(false);
const handlePlainTextOpenChange = ({ target }) => {

@ -1,6 +1,7 @@
import { LinkOutlined } from '@ant-design/icons'
import { Button, Flex, List, Popover } from 'antd'
import { useState } from 'react'
import EmailQuotation from '../Components/EmailQuotation'
const QuotesHistory = ((props) => {
@ -10,14 +11,14 @@ const QuotesHistory = ((props) => {
setOpen(newOpen)
}
const handleCopyClick = (url) => {
const handleCopyClick = (url) => {
navigator.clipboard.writeText(url)
setOpen(false)
}
return (
<>
<Popover
<Popover zIndex={2}
content={
<List
className='w-96 h-4/6 overflow-y-auto text-slate-900'
@ -32,15 +33,18 @@ const QuotesHistory = ((props) => {
<List.Item className='' key={item.letterid} >
<List.Item.Meta
className='text-neutral-800'
title={<a target='_blank' href={item.letterurl}><LinkOutlined />&nbsp;{item.lettertitle}</a>}
title={<a target='_blank' href={item.letterurl} rel='noreferrer'><LinkOutlined />&nbsp;{item.lettertitle}</a>}
description={
<Flex justify='space-between'>
<span>{item.letterdate}</span>
<div>
<EmailQuotation sfi_sn={item.letterid} />
<Button onClick={() => {
handleCopyClick(item.letterurl)
}} size={'small'} type='link' key={'send'}>
复制
</Button>
</div>
</Flex>
}
/>

@ -180,7 +180,7 @@ function SnippetList() {
</Form.Item>
</Modal>
<Space direction='vertical' size='large' className='w-full'>
<Space direction='vertical' size='small' className='w-full h-full overflow-hidden'>
<Form
layout={'inline'}
form={searchform}
@ -210,8 +210,8 @@ function SnippetList() {
</Button>
</Form.Item>
</Form>
<Row gutter={16}>
<Col span={8}>
<Row gutter={6} className=' ' style={{height: 'calc(100vh - 196px)'}}>
<Col span={8} className='h-[inherit] overflow-x-hidden overflow-y-auto'>
<List
bordered
dataSource={snippetList}
@ -231,7 +231,7 @@ function SnippetList() {
)}
/>
</Col>
<Col span={16}>
<Col span={16} className='h-[inherit] overflow-y-auto '>
<HtmlPreview
value={currentSnippet.content}
loading={isHtmlLoading}
@ -240,6 +240,7 @@ function SnippetList() {
onDelete={() => handelSnippetDelete()}
/>
</Col>
<div></div>
</Row>
</Space>
{contextHolder}

Loading…
Cancel
Save