From 339e79455353a64ffc8d2d8b2129c5f3374e8358 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 21 Feb 2024 15:45:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=98=BE=E7=A4=BA=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E6=8A=A5=E4=BB=B7=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/OrderStore.js | 3 + src/views/AuthApp.jsx | 2 +- .../Components/CustomerProfile.jsx | 31 +++++++-- src/views/OrderFollow.jsx | 65 ++++++++++--------- 4 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/stores/OrderStore.js b/src/stores/OrderStore.js index a8963a0..c85ef28 100644 --- a/src/stores/OrderStore.js +++ b/src/stores/OrderStore.js @@ -12,6 +12,8 @@ const useOrderStore = create((set, get) => ({ lastQuotation: {}, + quotationList: [], + fetchOrderDetail: async (colisn) => { const json = await fetchJSON(`${API_HOST}/getorderinfo`, { colisn }) if (json.errcode === 0 && json.result.length > 0) { @@ -20,6 +22,7 @@ const useOrderStore = create((set, get) => ({ orderDetail: orderResult, customerDetail: orderResult.contact.length > 0 ? orderResult.contact[0] : {}, lastQuotation: orderResult.quotes.length > 0 ? orderResult.quotes[0] : {}, + quotationList: orderResult.quotes, })) } }, diff --git a/src/views/AuthApp.jsx b/src/views/AuthApp.jsx index 1bffdea..5ec5a9e 100644 --- a/src/views/AuthApp.jsx +++ b/src/views/AuthApp.jsx @@ -77,7 +77,7 @@ function AuthApp() { selectedKeys={[defaultPath]} items={[ { key: '/order/follow', label: 订单跟踪 }, - { key: '/order/chat', label: 销售聊天 }, + { key: '/order/chat', label: 在线聊天 }, { key: '/chat/history', label: 聊天历史 }, { key: '/sales/management', label: 销售管理 } ]} diff --git a/src/views/Conversations/Components/CustomerProfile.jsx b/src/views/Conversations/Components/CustomerProfile.jsx index 957a0c3..bb95be1 100644 --- a/src/views/Conversations/Components/CustomerProfile.jsx +++ b/src/views/Conversations/Components/CustomerProfile.jsx @@ -1,6 +1,6 @@ import { useEffect } from 'react' import { useLocation, useParams } from 'react-router-dom' -import { Card, Flex, Select, Typography, Radio, Button, Table } from 'antd' +import { Card, Flex, Select, Typography, List, Button, Table } from 'antd' import { useAuthContext } from '@/stores/AuthContext.js' import { UserOutlined, LinkOutlined, EllipsisOutlined, SmileOutlined, SyncOutlined, PhoneOutlined, MailOutlined, WhatsAppOutlined, SmileTwoTone } from '@ant-design/icons' @@ -10,7 +10,7 @@ import useOrderStore from '@/stores/OrderStore' const CustomerProfile = (() => { const { order_sn: order_sn } = useParams() - const { orderDetail, customerDetail, lastQuotation, + const { orderDetail, customerDetail, lastQuotation, quotationList, fetchOrderDetail, setOrderPropValue } = useOrderStore() @@ -18,6 +18,14 @@ const CustomerProfile = (() => { fetchOrderDetail(order_sn) }, [order_sn]) + const paginationProps = { + showLessItems: true, + showTotal: (total) => { return `总数:${total}` } + } + + let regularText = '' + if (orderDetail.buytime > 0) regularText = '(R' + orderDetail.buytime + ')' + return (
{ > - {customerDetail.name} + {customerDetail.name + regularText} {customerDetail.phone} {customerDetail.email} {customerDetail.whatsapp_phone_number} @@ -76,9 +84,24 @@ const CustomerProfile = (() => { 最新报价

{lastQuotation.lettertitle}

- + + ( + + {item.lettertitle}} + description={item.letterdate} + /> + + )} + />

diff --git a/src/views/OrderFollow.jsx b/src/views/OrderFollow.jsx index a4ae66e..0dbb4b4 100644 --- a/src/views/OrderFollow.jsx +++ b/src/views/OrderFollow.jsx @@ -276,46 +276,46 @@ function OrderList({ formValues }) { const deptMap = new Map([ ['1', 'CH直销组'], - ['2','CH大客户组'], - ['7','市场推广'], - ['8','德语市场'], - ['9','日语市场'], - ['10', '商旅市场'], - ['11', '法语市场'], - ['12', '西语市场'], - ['13', '英文在线组'], - ['14', '商务Biztravel'], - ['15', 'CH产品'], - ['16', 'APP移动项目组'], - ['17', 'ChinaTravel组'], - ['18', 'CT市场'], - ['20', '俄语市场'], - ['21', '意语市场'], - ['22', '爱游网'], - ['23', '三峡站'], - ['24', '桂林站'], - ['25', '上海站'], - ['26', '北京站'], - ['27', '西藏站'], - ['28', 'AH亚洲项目组'], - ['29', 'DMC地接组'], - ['30', 'Trippest项目组'], - ['31', '花梨鹰'], - ['32', 'Daytours板块'], - ['33', 'GH项目组'], - ['34', 'trippest网站'], - ['35', 'newsletter营销'], + ['2', 'CH大客户组'], + ['7', '市场推广'], + ['8', '德语市场'], + ['9', '日语市场'], + ['10', '商旅市场'], + ['11', '法语市场'], + ['12', '西语市场'], + ['13', '英文在线组'], + ['14', '商务Biztravel'], + ['15', 'CH产品'], + ['16', 'APP移动项目组'], + ['17', 'ChinaTravel组'], + ['18', 'CT市场'], + ['20', '俄语市场'], + ['21', '意语市场'], + ['22', '爱游网'], + ['23', '三峡站'], + ['24', '桂林站'], + ['25', '上海站'], + ['26', '北京站'], + ['27', '西藏站'], + ['28', 'AH亚洲项目组'], + ['29', 'DMC地接组'], + ['30', 'Trippest项目组'], + ['31', '花梨鹰'], + ['32', 'Daytours板块'], + ['33', 'GH项目组'], + ['34', 'trippest网站'], + ['35', 'newsletter营销'], ]) const groupOrderData = groupByParam(orderData, 'OPI_DEI_SN') const deptKeys = Object.keys(groupOrderData) const collapseItems = [] - deptKeys.forEach(deptNo => { + deptKeys.forEach((deptNo, index) => { const deptOrderList = groupOrderData[deptNo] collapseItems.push( { - key: deptNo, + key: index, label: deptMap.get(deptNo) + '订单', children: ) + return () } + function OrderFollow() { const [advanceChecked, toggleAdvance] = useState(false) From b9552d8e79e8c58fc6c37f15a43dcf0af4d01270 Mon Sep 17 00:00:00 2001 From: Jimmy Liow Date: Wed, 21 Feb 2024 16:30:05 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E4=B8=80=E6=9C=9F?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E8=B0=83=E6=95=B4=E5=AF=BC=E8=88=AA=E8=8F=9C?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/AuthApp.jsx | 1 - src/views/ChatHistory.jsx | 32 ++++++++++++++++---------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/views/AuthApp.jsx b/src/views/AuthApp.jsx index 5ec5a9e..ad59010 100644 --- a/src/views/AuthApp.jsx +++ b/src/views/AuthApp.jsx @@ -79,7 +79,6 @@ function AuthApp() { { key: '/order/follow', label: 订单跟踪 }, { key: '/order/chat', label: 在线聊天 }, { key: '/chat/history', label: 聊天历史 }, - { key: '/sales/management', label: 销售管理 } ]} /> diff --git a/src/views/ChatHistory.jsx b/src/views/ChatHistory.jsx index 96dd59d..dc608c9 100644 --- a/src/views/ChatHistory.jsx +++ b/src/views/ChatHistory.jsx @@ -44,48 +44,48 @@ const SearchForm = memo(function ({ onSubmit }) { maxWidth: 'none', }} > - + (option?.label ?? '').toLowerCase().includes(input.toLowerCase())} options={[ { - value: 'Coco', - label: 'Coco', + value: 'jack', + label: 'Jack', }, { - value: 'Ann', - label: 'Ann', + value: 'lucy', + label: 'Lucy', }, { - value: 'Lora', - label: 'Lora', + value: 'tom', + label: 'Tom', }, ]} /> From 77bde97ee1fdaab676ded5e72ad36dbbbac467ca Mon Sep 17 00:00:00 2001 From: Lei OT Date: Thu, 22 Feb 2024 09:16:00 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=88=B7=E6=96=B0?= =?UTF-8?q?=E5=90=8E=E5=AE=9A=E4=BD=8D;=20=E6=B6=88=E6=81=AF=E5=8E=9F?= =?UTF-8?q?=E6=96=87=E5=8E=9F=E5=A7=8B=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/msgUtils.js | 42 +++++++++++++------ src/stores/ConversationStore.js | 13 ++++-- src/views/Conversations/ChatWindow.jsx | 5 ++- .../Components/ConversationsList.jsx | 29 +++++++------ .../Components/InputComposer.jsx | 3 +- .../Conversations/Components/Messages.jsx | 6 ++- 6 files changed, 63 insertions(+), 35 deletions(-) diff --git a/src/lib/msgUtils.js b/src/lib/msgUtils.js index 2ffb741..1860e34 100644 --- a/src/lib/msgUtils.js +++ b/src/lib/msgUtils.js @@ -31,7 +31,7 @@ export const sentMsgTypeMapped = { renderId: msg.id, to: msg.to, msgtype: 'text', - msgcontent: { body: msg.text, ...(msg.context ? { context: msg.context, message_origin: msg.message_origin } : {}) }, + msgcontent: { body: msg.text, ...(msg.context ? { context: msg.context, message_origin: msg.message_origin.msgOrigin } : {}) }, }), contentToRender: (msg) => ({ ...msg, @@ -39,7 +39,12 @@ export const sentMsgTypeMapped = { conversationid: msg.id.split('.')[0], ...(msg.context ? { - reply: { message: msg.message_origin.text, title: msg.message_origin.senderName || 'Reference' }, + reply: { + message: msg.message_origin.text, + title: msg.message_origin.senderName || 'Reference', + titleColor: msg.message_origin?.senderName !== 'me' ? '#a791ff' : "#128c7e", + // titleColor: "#a791ff", + }, } : {}), }), @@ -63,7 +68,7 @@ export const sentMsgTypeMapped = { conversationid: msg.id.split('.')[0], ...(msg.context ? { - reply: { message: msg.message_origin.text, title: msg.message_origin.senderName || 'Reference' }, + reply: { message: msg.message_origin.text, title: msg.message_origin.senderName || 'Reference', titleColor: "#a791ff", }, } : {}), }), @@ -91,7 +96,7 @@ const whatsappMsgMapped = { 'whatsapp.inbound_message.received': { getMsg: (result) => { console.log('whatsapp.inbound_message.received', result); - return isEmpty(result?.whatsappInboundMessage) ? null : { ...result.whatsappInboundMessage, conversationid: result.conversationid }; + return isEmpty(result?.whatsappInboundMessage) ? null : { ...result.whatsappInboundMessage, conversationid: result.conversationid, messageorigin: result.messageorigin }; }, contentToRender: (contentObj) => { console.log('whatsapp.inbound_message.received to render', contentObj); @@ -165,6 +170,7 @@ export const whatsappMsgTypeMapped = { text: { type: 'text', data: (msg) => ({ id: msg.wamid, text: msg.text.body }), + renderForReply: (msg) => ({ id: msg.wamid, message: msg.text.body }), }, image: { type: 'photo', @@ -216,7 +222,7 @@ export const whatsappMsgTypeMapped = { unsupported: { type: 'system', data: (msg) => ({ text: 'Message type is currently not supported.' }) }, reaction: { type: 'text', - data: (msg) => ({ id: msg.wamid, text: msg.reaction?.emoji || msg.reaction?.text?.body || 'Reaction', reply: { message: '{content}', title: 'React from', titleColor: '#1ba784' } }), // todo: + data: (msg) => ({ id: msg.wamid, text: msg.reaction?.emoji || '', }), }, document: { type: 'file', @@ -234,8 +240,8 @@ export const whatsappMsgTypeMapped = { return { id: msg.wamid, text: templateDataMapped?.body?.text || templateDataMapped?.body?.parameters?.[0]?.text || '', title: msg.template.name } }, renderForReply: (msg) => { - const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({...r, [v.type]: v}), {}) : null; - return { id: msg.wamid, message: templateDataMapped?.body?.text || templateDataMapped?.body?.parameters?.[0]?.text || '', title: `${msg.template.name}` } + const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({ ...r, [v.type]: v }), {}) : null; + return { id: msg.wamid, message: templateDataMapped?.body?.text || templateDataMapped?.body?.parameters?.[0]?.text || '', title: `${msg.template.name}` }; }, }, }; @@ -245,11 +251,13 @@ export const whatsappMsgTypeMapped = { export const parseRenderMessageItem = (msg) => { console.log('parseRenderMessageItem', msg); return { + msgOrigin: msg, date: msg?.sendTime || msg?.createTime || '', ...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}), conversationid: msg.conversationid, ...(typeof whatsappMsgTypeMapped[msg.type].type === 'function' ? whatsappMsgTypeMapped[msg.type].type(msg) : { type: whatsappMsgTypeMapped[msg.type].type || 'text' }), // type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text', + from: msg.from, sender: msg.from, senderName: msg?.customerProfile?.name || msg.from, // title: msg.customerProfile.name, @@ -257,13 +265,18 @@ export const parseRenderMessageItem = (msg) => { whatsapp_name: msg?.customerProfile?.name || '', whatsapp_phone_number: msg.from, whatsapp_msg_type: msg.type, - ...(isEmpty(msg.context) + ...(isEmpty(msg.context) && isEmpty(msg.reaction) ? {} : { reply: { - message: `${msg.context.id}`, // todo: msg.context.text?.body || msg.context.text, - title: msg?.customerProfile?.name || msg.from, - titleColor: "#53bdeb", // "#128c7e", + /** + * reply: { message: msg.messageorigin, title: 'React from', titleColor: '#1ba784' } + */ + title: msg.messageorigin?.customerProfile?.name || 'me', + ...(typeof whatsappMsgTypeMapped[msg?.messageorigin?.type]?.renderForReply === 'function' + ? whatsappMsgTypeMapped[msg.messageorigin.type].renderForReply(msg.messageorigin) + : {}), + titleColor: msg.messageorigin?.customerProfile?.name ? '#a791ff' : "#128c7e", }, origin: msg.context, }), @@ -279,10 +292,12 @@ export const parseRenderMessageList = (messages, conversationid = null) => { const msgType = msgContent.type; // const parseMethod = msgContent.bizType === 'whatsapp' ? cloneDeep(whatsappMsgTypeMapped) : {}; return { + msgOrigin: msgContent, ...(whatsappMsgTypeMapped?.[msgType]?.data(msgContent) || {}), type: msgContent.type, ...(typeof whatsappMsgTypeMapped[msgType].type === 'function' ? whatsappMsgTypeMapped[msgType].type(msg) : { type: whatsappMsgTypeMapped[msgType].type || 'text' }), date: msgContent?.sendTime || msg.msgtime || '', + from: msgContent.from, sender: msgContent.from, senderName: msgContent?.customerProfile?.name || msgContent.from, ...(msg.msg_direction === 'outbound' @@ -298,11 +313,12 @@ export const parseRenderMessageList = (messages, conversationid = null) => { : { reply: { message: msg.messageorigin_AsJOSN.text?.body || msg.messageorigin_AsJOSN.text, - title: msg.messageorigin_AsJOSN.senderName || '@', // msg.messageorigin_AsJOSN.from + title: msg.messageorigin_AsJOSN?.customerProfile?.name || 'me', // msg.messageorigin_AsJOSN.senderName || '@', ...(typeof whatsappMsgTypeMapped[msg.messageorigin_AsJOSN.type]?.renderForReply === 'function' ? whatsappMsgTypeMapped[msg.messageorigin_AsJOSN.type].renderForReply(msg.messageorigin_AsJOSN) : {}), - titleColor: '#53bdeb', // "#128c7e", // todo: 原始消息的方向 + titleColor: msg.messageorigin_AsJOSN?.customerProfile?.name ? '#a791ff' : "#128c7e", + // titleColor: msg.messageorigin_direction === 'inbound' ? '#a791ff' : "#128c7e", }, origin: msg.messageorigin_AsJOSN, }), diff --git a/src/stores/ConversationStore.js b/src/stores/ConversationStore.js index 22e55d0..c815290 100644 --- a/src/stores/ConversationStore.js +++ b/src/stores/ConversationStore.js @@ -16,6 +16,7 @@ const initialConversationState = { // websocketRetrytimes: null, errors: [], // 错误信息 + initialState: false, // templates: [], @@ -26,7 +27,6 @@ const initialConversationState = { // referenceMsg: {}, }; -olog('initialConversationState'); export const templatesSlice = (set) => ({ templates: [], @@ -167,8 +167,11 @@ export const conversationSlice = (set, get) => ({ }); export const messageSlice = (set, get) => ({ + msgListLoading: false, activeConversations: {}, + setMsgLoading: (msgListLoading) => set({ msgListLoading }), receivedMessageList: (conversationid, msgList) => set((state) => ({ + msgListLoading: false, activeConversations: { ...state.activeConversations, [String(conversationid)]: msgList } })), updateMessageItem: (message) => { // msgUpdate @@ -250,18 +253,20 @@ export const useConversationStore = create(devtools((set, get) => ({ // state actions addError: (error) => set((state) => ({ errors: [...state.errors, error] })), + setInitial: (v) => set({ initialState: v }), // side effects fetchInitialData: async (userId) => { - olog('fetch init'); - const { addToConversationList, setTemplates } = get(); + const { addToConversationList, setTemplates, setInitial } = get(); const conversationsList = await fetchConversationsList({ opisn: userId }); addToConversationList(conversationsList); const templates = await fetchTemplates(); setTemplates(templates); + + setInitial(true); }, }))); -// window.store = useConversationStore; // debug: + export default useConversationStore; diff --git a/src/views/Conversations/ChatWindow.jsx b/src/views/Conversations/ChatWindow.jsx index 76dcda7..c498262 100644 --- a/src/views/Conversations/ChatWindow.jsx +++ b/src/views/Conversations/ChatWindow.jsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { Layout, Spin, Button } from 'antd'; -import { RightCircleOutlined, RightOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; +import { RightCircleOutlined, RightOutlined, ReloadOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; // import { useParams, useNavigate } from 'react-router-dom'; import MessagesHeader from './Components/MessagesHeader'; import Messages from './Components/Messages'; @@ -52,9 +52,10 @@ const ChatWindow = () => { -
+
diff --git a/src/views/Conversations/Components/ConversationsList.jsx b/src/views/Conversations/Components/ConversationsList.jsx index de79ac8..a778263 100644 --- a/src/views/Conversations/Components/ConversationsList.jsx +++ b/src/views/Conversations/Components/ConversationsList.jsx @@ -3,11 +3,7 @@ import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { Button, Dropdown } from 'antd'; import { MoreOutlined } from '@ant-design/icons'; import { useAuthContext } from '@/stores/AuthContext'; -import { - fetchOrderConversationsList, - fetchConversationItemClose, - fetchMessages, -} from '@/actions/ConversationActions'; +import { fetchOrderConversationsList, fetchConversationItemClose, fetchMessages } from '@/actions/ConversationActions'; import { ChatList, } from 'react-chat-elements'; import { isEmpty } from '@/utils/utils'; import useConversationStore from '@/stores/ConversationStore'; @@ -50,7 +46,7 @@ const Conversations = () => { const navigate = useNavigate(); const { loginUser } = useAuthContext(); const { userId } = loginUser; - const { activeConversations, currentConversation, conversationsList, addToConversationList, setCurrentConversation, receivedMessageList, } = useConversationStore(); + const { initialState, activeConversations, currentConversation, conversationsList, addToConversationList, setCurrentConversation, receivedMessageList, setMsgLoading } = useConversationStore(); const [chatlist, setChatlist] = useState([]); useEffect(() => { setChatlist( @@ -80,12 +76,12 @@ const Conversations = () => { const [shouldFetchCList, setShouldFetchCList] = useState(true); useEffect(() => { - if (order_sn && shouldFetchCList) { + if (order_sn && shouldFetchCList && initialState) { getOrderConversationList(order_sn); } return () => {}; - }, [order_sn, shouldFetchCList]); + }, [order_sn, shouldFetchCList, initialState]); const getOrderConversationList = async (colisn) => { const { whatsapp_phone_number } = switchToC; @@ -93,19 +89,26 @@ const Conversations = () => { const data = await fetchOrderConversationsList({ opisn: userId, colisn: colisn, whatsappid: whatsappID }); if (!isEmpty(data)) { addToConversationList(data); - const ifCurrent = data.findIndex((item) => item.sn === currentConversation.sn); - switchConversation(data[ifCurrent === -1 ? 0 : ifCurrent]); + } + // const ifCurrent = data.findIndex((item) => item.sn === currentConversation.sn); + const ifCurrent = conversationsList.findIndex((item) => item.coli_sn === Number(colisn)); + if (ifCurrent !== -1) { + switchConversation(conversationsList[ifCurrent === -1 ? 0 : ifCurrent]); } else { // reset chat window setCurrentConversation({ sn: '', customer_name: '', coli_sn: order_sn }); return false; } }; + const getMessages = async (item) => { + setMsgLoading(true); + const data = await fetchMessages({ opisn: userId, whatsappid: item.whatsapp_phone_number }); + receivedMessageList(item.sn, data); + }; const switchConversation = async (item) => { const messagesList = activeConversations[`${item.sn}`] || []; - if (isEmpty(messagesList)) { - const data = await fetchMessages({ opisn: userId, whatsappid: item.whatsapp_phone_number }); - receivedMessageList(item.sn, data); + if (messagesList.length < 20) { + await getMessages(item); } if (String(item.sn) === String(currentConversation.sn)) { return false; diff --git a/src/views/Conversations/Components/InputComposer.jsx b/src/views/Conversations/Components/InputComposer.jsx index 5e70192..35f73a7 100644 --- a/src/views/Conversations/Components/InputComposer.jsx +++ b/src/views/Conversations/Components/InputComposer.jsx @@ -71,7 +71,8 @@ const InputBox = () => {
{referenceMsg.id && ( -
{referenceMsg.text}
+
+ {referenceMsg.senderName}{referenceMsg.text}