From c466ac953b0e5502d9177781f3d7f3548e3f57ca Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 25 Mar 2024 09:24:24 +0800 Subject: [PATCH 1/4] =?UTF-8?q?perf:=20=E6=A8=A1=E6=9D=BF:=20=E6=9C=89head?= =?UTF-8?q?er,=20footer,=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit perf: 历史记录: 显示绝对时间 fix: 模板: 只发送body perf: 提示文字 style: 历史记录: 引用的来源title color perf: 历史记录: 会话的的时间 --- src/actions/ConversationActions.js | 10 +++ src/lib/msgUtils.js | 12 ++-- src/views/ChatHistory.jsx | 7 ++- .../Components/ExpireTimeClock.jsx | 2 - .../Components/Input/MediaUpload.jsx | 2 +- .../Components/Input/Template.jsx | 61 ++++++++++++++++--- .../Components/InputComposer.jsx | 2 +- .../Conversations/Components/MessagesList.jsx | 46 +++++++++++++- 8 files changed, 120 insertions(+), 22 deletions(-) diff --git a/src/actions/ConversationActions.js b/src/actions/ConversationActions.js index e48059e..cede50e 100644 --- a/src/actions/ConversationActions.js +++ b/src/actions/ConversationActions.js @@ -4,6 +4,7 @@ import { fetchJSON, postJSON } from '@/utils/request' import { parseRenderMessageList } from '@/lib/msgUtils'; import { API_HOST } from '@/config'; import { isEmpty } from '@/utils/commons'; +import dayjs from 'dayjs'; export const fetchTemplates = async () => { const data = await fetchJSON(`${API_HOST}/listtemplates`); @@ -62,6 +63,14 @@ export const fetchConversationItemClose = async (body) => { return result; }; +/** + * @param {object} body { phone_number, name } + */ +export const postNewConversationItem = async (body) => { + const { errcode, result } = await postJSON(`${API_HOST}/newconversation`, body); + return errcode !== 0 ? {} : result; +}; + /** * @param {object} params { opisn, whatsappid } */ @@ -89,6 +98,7 @@ export const fetchConversationsSearch = async (params) => { whatsapp_name: `${ele.whatsapp_name || ''}`.trim(), opi_sn: ele.OPI_SN || ele.opi_sn || 0, OPI_Name: `${ele.OPI_Name || ele.opi_name || ''}`.trim(), + dateText: dayjs((ele.last_received_time || ele.last_send_time)).format('MM-DD HH:mm'), matchMsgList: parseRenderMessageList((ele.msglist_AsJOSN || [])), // .reverse()), })); return list; diff --git a/src/lib/msgUtils.js b/src/lib/msgUtils.js index e82cf39..0610874 100644 --- a/src/lib/msgUtils.js +++ b/src/lib/msgUtils.js @@ -1,4 +1,5 @@ import { cloneDeep, isEmpty, olog } from "@/utils/utils"; +import dayjs from "dayjs"; import { v4 as uuid } from "uuid"; export const replaceTemplateString = (str, replacements) => { @@ -150,7 +151,7 @@ export const sentMsgTypeMapped = { }), }, whatsappTemplate: { - contentToSend: (msg) => ({ action: 'message', actionId: msg.id, renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: msg.template }), + contentToSend: (msg) => ({ action: 'message', actionId: msg.id, renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: { ...msg.template, components: msg.template.components.filter(com => com.type.toLowerCase() === 'body') }}), // todo: 其他组件不发送是否可以 contentToRender: (msg) => { const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({ ...r, [v.type]: v }), {}) : null; // const templateParam = (templateDataMapped?.body?.parameters || []).map(e => e.text); @@ -161,7 +162,7 @@ export const sentMsgTypeMapped = { actionId: msg.id, conversationid: msg.id.split('.')[0], type: 'text', - title: msg.template_origin.components.header?.[0]?.text || '', + title: msg.template.name, // || msg.template_origin.components.header?.[0]?.text || '', text: autoLinkText(templateDataMapped?.body?.text || ''), // msg.template_origin.components.body?.[0]?.text || '', }; }, @@ -462,7 +463,7 @@ export const parseRenderMessageItem = (msg) => { /** * 从数据库读取的记录 */ -export const parseRenderMessageList = (messages, conversationid = null) => { +export const parseRenderMessageList = (messages) => { return messages.map((msg, i) => { let msgContentString = ''; if (typeof msg.msgtext_AsJOSN === 'string') { @@ -480,6 +481,7 @@ export const parseRenderMessageList = (messages, conversationid = null) => { type: msgContent.type, ...(typeof whatsappMsgTypeMapped[msgType].type === 'function' ? whatsappMsgTypeMapped[msgType].type(msg) : { type: whatsappMsgTypeMapped[msgType].type || 'text' }), date: msgContent?.sendTime || msg.msgtime || '', + dateText: dayjs(msgContent?.sendTime || msg.msgtime).format('MM-DD HH:mm'), localDate: (msg.msgtime || '').replace('T', ' '), from: msgContent.from, sender: msgContent.from, @@ -505,8 +507,8 @@ export const parseRenderMessageList = (messages, conversationid = null) => { ...(typeof whatsappMsgTypeMapped[(msg.messageorigin_AsJOSN?.type || 'unsupported')]?.renderForReply === 'function' ? whatsappMsgTypeMapped[(msg.messageorigin_AsJOSN?.type || 'unsupported')].renderForReply(msg.messageorigin_AsJOSN) : {}), - // titleColor: msg.messageorigin_AsJOSN?.customerProfile?.name ? '#a791ff' : "#128c7e", - titleColor: msg.messageorigin_direction === 'inbound' ? '#a791ff' : "#128c7e", + titleColor: msg.messageorigin_AsJOSN?.customerProfile?.name ? '#a791ff' : "#128c7e", + // titleColor: msg.messageorigin_direction === 'inbound' ? '#a791ff' : "#128c7e", }, origin: msg.messageorigin_AsJOSN, }), diff --git a/src/views/ChatHistory.jsx b/src/views/ChatHistory.jsx index f355076..1eef08b 100644 --- a/src/views/ChatHistory.jsx +++ b/src/views/ChatHistory.jsx @@ -314,8 +314,9 @@ function ChatHistory() { alt={`${item.whatsapp_name}`} title={item.whatsapp_name || item.whatsapp_phone_number} subtitle={`${item.OPI_Name || ''} ${item.coli_id || ''}`} - date={item.last_received_time} + date={item.last_received_time || item.last_send_time} // dateString={item.last_received_time} + dateString={item.dateText} className={String(item.conversationid) === String(selectedConversation.conversationid) ? '__active text-primary bg-neutral-100' : ''} onClick={() => setSelectedConversation(item)} /> @@ -340,7 +341,7 @@ function ChatHistory() { title={item.sender === 'me' ? selectedConversation.OPI_Name || item.senderName : item.senderName} subtitle={item.originText} date={item.msgtime} - // dateString={item.msgtime} + dateString={item.dateText} className={String(item.sn) === String(selectMatch?.sn) ? '__active text-primary bg-neutral-100' : ' bg-white'} onClick={() => handleMatchMsgClick(item)} /> @@ -392,7 +393,7 @@ function ChatHistory() { navigator.clipboard.writeText(message.text); appMessage.success('复制成功😀'); }, - Component: () =>
复制
, + Component: () =>
复制
, }, ], } diff --git a/src/views/Conversations/Components/ExpireTimeClock.jsx b/src/views/Conversations/Components/ExpireTimeClock.jsx index a23907e..da3f2a3 100644 --- a/src/views/Conversations/Components/ExpireTimeClock.jsx +++ b/src/views/Conversations/Components/ExpireTimeClock.jsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { Typography } from 'antd'; import { ClockCircleOutlined } from '@ant-design/icons'; -import useConversationStore from '@/stores/ConversationStore'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; @@ -13,7 +12,6 @@ dayjs.extend(timezone); dayjs.extend(relativeTime); const ExpireTimeClock = ({ expireTime }) => { - // const expireTime = useConversationStore((state) => state.currentConversation.conversation_expiretime); const [customerDateTime, setCustomerDateTime] = useState(''); const [isExpired, setIsExpired] = useState(false); diff --git a/src/views/Conversations/Components/Input/MediaUpload.jsx b/src/views/Conversations/Components/Input/MediaUpload.jsx index 64a4b2a..6084f67 100644 --- a/src/views/Conversations/Components/Input/MediaUpload.jsx +++ b/src/views/Conversations/Components/Input/MediaUpload.jsx @@ -80,7 +80,7 @@ const ImageUpload = ({ disabled, invokeUploadFileMessage, invokeSendUploadMessag } }} > - +
webp(100K)
图片(5M)
视频(16M)
语音(16M)
附件(100M)
} > + ) : btn.type.toLowerCase() === 'phone_number' ? ( + + ) : ( + + ) + )} + + ); + } + const renderForm = ({ tempItem }) => { const templateText = tempItem.components.body?.[0]?.text || ''; const tempArr = splitTemplate(templateText); @@ -167,8 +212,10 @@ const InputTemplate = ({ mobile, disabled = false, invokeSendMessage }) => { description={ <>
-
{renderForm({ tempItem: item })}
+ {renderHeader({ tempItem: item })} +
{renderForm({ tempItem: item })}
{item.components?.footer?.[0] ?
{item.components.footer[0].text || ''}
: null} + {renderButtons({ tempItem: item })}
} diff --git a/src/views/Conversations/Components/InputComposer.jsx b/src/views/Conversations/Components/InputComposer.jsx index 686eedf..dabbf29 100644 --- a/src/views/Conversations/Components/InputComposer.jsx +++ b/src/views/Conversations/Components/InputComposer.jsx @@ -255,7 +255,7 @@ const InputComposer = ({ mobile }) => { showCount={textabled} placeholder={ gt24h - ? 'This session has expired. Please send a template message to activate the session' + ? '会话已过期. 请发送打招呼消息激活对话💬.' : mobile === undefined ? 'Enter 发送, Shift+Enter 换行\n支持复制粘贴 [截图/文件] 以备发送' : 'Enter 换行, 点击 Send 发送' diff --git a/src/views/Conversations/Components/MessagesList.jsx b/src/views/Conversations/Components/MessagesList.jsx index 6a8b7f6..a0df233 100644 --- a/src/views/Conversations/Components/MessagesList.jsx +++ b/src/views/Conversations/Components/MessagesList.jsx @@ -4,7 +4,7 @@ import { App, Button } from 'antd'; import { DownOutlined, LoadingOutlined } from '@ant-design/icons'; import { useShallow } from 'zustand/react/shallow'; import useConversationStore from '@/stores/ConversationStore'; -import { isEmpty, } from '@/utils/utils'; +import { groupBy, isEmpty, } from '@/utils/utils'; const MessagesList = ({ messages, handlePreview, reference, longListLoading, getMoreMessages, shouldScrollBottom, loadNextPage, handleContactClick, ...props }) => { @@ -32,7 +32,16 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get useEffect(scrollToBottom, [messages]); - const RenderText = memo(function renderText({ str, className }) { + const RenderText = memo(function renderText({ str, className, template }) { + + let headerObj, footerObj, buttonsArr; + if (!isEmpty(template)) { + const componentsObj = groupBy(template.components, (item) => item.type); + headerObj = componentsObj.header[0]; + footerObj = componentsObj.footer[0]; + buttonsArr = componentsObj.buttons.reduce((r, c) => r.concat(c.buttons), []); + } + const parts = str.split(/(https?:\/\/[^\s]+|\p{Emoji_Presentation})/gmu).filter((s) => s !== ''); const links = str.match(/https?:\/\/[\S]+/gi) || []; const emojis = str.match(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g) || []; @@ -49,6 +58,17 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get }, []); return ( + {headerObj ? ( +
+ {'text' === headerObj.format.toLowerCase() &&
{headerObj.text}
} + {'image' === headerObj.format.toLowerCase() && } + {['document', 'video'].includes(headerObj.format.toLowerCase()) && ( + + [ {headerObj.format} ] + + )} +
+ ) : null} {(objArr || []).map((part, index) => { if (part.type === 'link') { return ( @@ -61,6 +81,26 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get return part.key; } })} + {footerObj ?
{footerObj.text}
: null} + {buttonsArr && buttonsArr.length > 0 ? ( +
+ {buttonsArr.map((btn, index) => + btn.type.toLowerCase() === 'url' ? ( + + ) : btn.type.toLowerCase() === 'phone_number' ? ( + + ) : ( + + ) + )} +
+ ) : null}
); }); @@ -99,7 +139,7 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get onReplyMessageClick={() => scrollToMessage(message.reply.id)} onOpen={() => handlePreview(message)} onTitleClick={() => handlePreview(message)} - text={} + text={} {...(message.sender === 'me' ? { styles: { backgroundColor: '#ccd4ae' }, From df5ccbd51c38b046f80d67fbdd300b08af1f13d5 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Mon, 25 Mar 2024 10:07:03 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E5=AE=BD=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/DesktopApp.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/DesktopApp.jsx b/src/views/DesktopApp.jsx index b899758..9ff4e5c 100644 --- a/src/views/DesktopApp.jsx +++ b/src/views/DesktopApp.jsx @@ -57,7 +57,7 @@ function DesktopApp() {
- + App logo From e823c1d7c4584abf56b24eded16105b01c665d1a Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 25 Mar 2024 10:48:43 +0800 Subject: [PATCH 3/4] =?UTF-8?q?perf:=20=E6=A8=A1=E6=9D=BF=E6=8E=92?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/ConversationActions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/actions/ConversationActions.js b/src/actions/ConversationActions.js index cede50e..fdc327b 100644 --- a/src/actions/ConversationActions.js +++ b/src/actions/ConversationActions.js @@ -1,5 +1,5 @@ -import { groupBy, pick } from '@/utils/utils'; +import { groupBy, pick, sortArrayByOrder } from '@/utils/utils'; import { fetchJSON, postJSON } from '@/utils/request' import { parseRenderMessageList } from '@/lib/msgUtils'; import { API_HOST } from '@/config'; @@ -11,7 +11,10 @@ export const fetchTemplates = async () => { const canUseTemplates = (data?.result?.items || []) .filter((_t) => _t.status !== 'REJECTED') .map((ele) => ({ ...ele, components_origin: ele.components, components: groupBy(ele.components, (_c) => _c.type.toLowerCase()) })); - return canUseTemplates; + const topName = ['say_hello_from_trip_advisor', 'free_style_7', 'free_style_2', 'free_style_1', 'free_style_3', 'free_style_4']; + const top = sortArrayByOrder( canUseTemplates.filter((_t) => topName.includes(_t.name)), 'name', topName); + const raw = canUseTemplates.filter((_t) => !topName.includes(_t.name)); + return [...top, ...raw]; }; /** From 707cc505de0f54bbdfce597215be0809c8024a46 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Mon, 25 Mar 2024 12:12:19 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E5=A4=8D=E6=9D=82=E7=9A=84=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/msgUtils.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/lib/msgUtils.js b/src/lib/msgUtils.js index 0610874..edaa653 100644 --- a/src/lib/msgUtils.js +++ b/src/lib/msgUtils.js @@ -100,7 +100,7 @@ export const sentMsgTypeMapped = { photo: { type: 'image', contentToSend: (msg) => ({ - ...mediaMsg.contentToSend({...msg, type: 'image'}), + ...mediaMsg.contentToSend({ ...msg, type: 'image' }), msgtype: 'image', }), contentToRender: (msg) => ({ @@ -112,7 +112,7 @@ export const sentMsgTypeMapped = { sticker: { type: 'sticker', contentToSend: (msg) => ({ - ...mediaMsg.contentToSend({...msg, type: 'sticker'}), + ...mediaMsg.contentToSend({ ...msg, type: 'sticker' }), msgtype: 'sticker', }), contentToRender: (msg) => ({ @@ -151,7 +151,33 @@ export const sentMsgTypeMapped = { }), }, whatsappTemplate: { - contentToSend: (msg) => ({ action: 'message', actionId: msg.id, renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: { ...msg.template, components: msg.template.components.filter(com => com.type.toLowerCase() === 'body') }}), // todo: 其他组件不发送是否可以 + contentToSend: (msg) => ({ + action: 'message', + actionId: msg.id, + renderId: msg.id, + to: msg.to, + msgtype: 'template', + msgcontent: { + ...msg.template, + components: [ + ...msg.template.components.filter((com) => !['footer', 'buttons', 'header'].includes(com.type.toLowerCase())), + ...(msg.template.components.filter((com) => 'header' === com.type.toLowerCase()).length > 0 + ? msg.template.components + .filter((com) => 'header' === com.type.toLowerCase()) + .map((ele) => ({ type: 'header', parameters: [{ text: ele.text, type: ele.format.toLowerCase(), [ele.format.toLowerCase()]: { link: ele.example.header_url[0] } }] })) + : []), + ...(msg.template.components.filter((com) => 'buttons' === com.type.toLowerCase()).length > 0 + ? msg.template.components + .filter((com) => 'buttons' === com.type.toLowerCase())[0] + // .buttons.filter((btns) => ! ['phone_number', 'url'].includes( btns.type.toLowerCase())) + .buttons.filter((btns) => ! isEmpty(btns.example)) // 静态按钮不发 + .map((btn, btnI) => ({ type: 'button', sub_type: btn.type.toLowerCase(), index: btnI, + // parameters: [{ text: 'lq1FTtA8', type: 'text' }] + })) + : []), + ], + }, + }), contentToRender: (msg) => { const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({ ...r, [v.type]: v }), {}) : null; // const templateParam = (templateDataMapped?.body?.parameters || []).map(e => e.text);