dev/chat
Lei OT 1 year ago
parent 9269b362ac
commit 54c2364ac2

@ -17,7 +17,8 @@ export const fetchTemplates = async () => {
* @param {object} params { opisn } * @param {object} params { opisn }
*/ */
export const fetchConversationsList = async (params) => { export const fetchConversationsList = async (params) => {
const { result: data } = await fetchJSON(`${API_HOST}/getconversations`, params); const { errcode, result: data } = await fetchJSON(`${API_HOST}/getconversations`, params);
if (errcode !== 0) return [];
const list = (data || []).map((ele) => ({ ...ele, customer_name: `${ele.whatsapp_name || ''}`.trim(), whatsapp_name: `${ele.whatsapp_name || ''}`.trim() })); const list = (data || []).map((ele) => ({ ...ele, customer_name: `${ele.whatsapp_name || ''}`.trim(), whatsapp_name: `${ele.whatsapp_name || ''}`.trim() }));
return list; return list;
}; };
@ -72,3 +73,36 @@ export const fetchCleanUnreadMsgCount = async (params) => {
* ------------------------------------------------------------------------------------------------ * ------------------------------------------------------------------------------------------------
* 历史记录 * 历史记录
*/ */
/**
* @param {object} params { search, from_date, end_date, whatsapp_id, opisn, coli_id, msg_type }
* @todo msg_type
*/
export const fetchConversationsSearch = async (params) => {
const { errcode, result: data } = await fetchJSON(`${API_HOST}/conversation_search`, params);
const list =
errcode !== 0
? []
: (data || []).map((ele) => ({
...ele,
customer_name: `${ele.whatsapp_name || ''}`.trim(),
whatsapp_name: `${ele.whatsapp_name || ''}`.trim(),
matchMsgList: parseRenderMessageList((ele.msglist_AsJOSN || []).reverse()),
}));
return list;
};
/**
*
* @param {object} params { opisn, whatsappid, lasttime, pagesize, pagedir }
*/
export const fetchMessagesHistory = async (params) => {
const defaultParams = {
opisn: '',
whatsappid: '',
lasttime: '',
pagesize: MESSAGE_PAGE_SIZE,
pagedir: 'next',
};
const { errcode, result } = await fetchJSON(`${API_HOST}/get_item_messages`, {...defaultParams, ...params});
return errcode !== 0 ? [] : parseRenderMessageList((result || []).reverse());
}

@ -8,6 +8,8 @@ export const useFormStore = create(
setChatHistoryForm: (chatHistoryForm) => set({ chatHistoryForm, chatHistorySelectChat: {} }), setChatHistoryForm: (chatHistoryForm) => set({ chatHistoryForm, chatHistorySelectChat: {} }),
chatHistorySelectChat: {}, chatHistorySelectChat: {},
setChatHistorySelectChat: (chatHistorySelectChat) => set({ chatHistorySelectChat }), setChatHistorySelectChat: (chatHistorySelectChat) => set({ chatHistorySelectChat }),
msgHistorySelectMatch: {},
setMsgHistorySelectMatch: (msgHistorySelectMatch) => set({ msgHistorySelectMatch }),
// 订单跟踪页面 // 订单跟踪页面
orderFollowForm: { orderFollowForm: {

@ -1,12 +1,13 @@
import { memo, useCallback, useEffect, useRef, useState, forwardRef } from 'react'; import { memo, useCallback, useEffect, useRef, useState, forwardRef } from 'react';
import { Divider, Button, Input, Layout, DatePicker, Form, List, Spin } from 'antd'; import { Divider, Button, Input, Layout, DatePicker, Form, List, Spin, Flex } from 'antd';
import { ChatItem, MessageBox } from 'react-chat-elements'; import { ChatItem, MessageBox } from 'react-chat-elements';
import { fetchConversationsList, fetchMessages, MESSAGE_PAGE_SIZE } from '@/actions/ConversationActions'; import { fetchConversationsList, fetchMessages, MESSAGE_PAGE_SIZE, fetchConversationsSearch, fetchMessagesHistory } from '@/actions/ConversationActions';
import { isEmpty, stringToColour } from '@/utils/utils'; import { cloneDeep, flush, isEmpty, pick, stringToColour } from '@/utils/utils';
import useFormStore from '@/stores/FormStore'; import useFormStore from '@/stores/FormStore';
import { fetchSalesAgent, fetchCustomerList } from '@/actions/CommonActions'; import { fetchSalesAgent, fetchCustomerList } from '@/actions/CommonActions';
import SearchInput from '@/components/SearchInput'; import SearchInput from '@/components/SearchInput';
import { isNotEmpty } from '@/utils/commons';
const { Sider, Content, Header, Footer } = Layout; const { Sider, Content, Header, Footer } = Layout;
const { Search } = Input; const { Search } = Input;
@ -21,7 +22,11 @@ const SearchForm = memo(function ({ initialValues, onSubmit }) {
onSubmit?.({ onSubmit?.({
...values, ...values,
opisn: values?.agent?.value || '', opisn: values?.agent?.value || '',
customer_name: values?.customer?.label || '', whatsapp_id: values?.customer?.value || '',
...(isNotEmpty(values.msgDateRange) ? {
from_date: values.msgDateRange[0].format('YYYY-MM-DD'),
end_date: values.msgDateRange[1].format('YYYY-MM-DD'),
} : {}),
}); });
} }
return ( return (
@ -30,29 +35,21 @@ const SearchForm = memo(function ({ initialValues, onSubmit }) {
form={form} form={form}
initialValues={initialValues} initialValues={initialValues}
onFinish={handleSubmit} onFinish={handleSubmit}
style={{ style={{}}>
maxWidth: 'none', <Form.Item label='顾问' name='agent' style={{ width: '200px' }} rules={[{required: true, message: '请选择顾问'}]}>
}}>
<Form.Item label='顾问' name='agent' style={{ width: '200px' }}>
<SearchInput placeholder='搜索顾问' fetchOptions={fetchSalesAgent} /> <SearchInput placeholder='搜索顾问' fetchOptions={fetchSalesAgent} />
</Form.Item> </Form.Item>
<Form.Item label='客人' name='customer' style={{ width: '200px' }}> <Form.Item label='客人' name='customer' style={{ width: '200px' }}>
<SearchInput placeholder='搜索客人' fetchOptions={fetchCustomerList} /> <SearchInput placeholder='搜索客人' fetchOptions={fetchCustomerList} />
</Form.Item> </Form.Item>
{/* <Form.Item label='H' name='orderLabel' style={{ width: '200px' }}> <Form.Item label='订单号' name='coli_id'>
<Select <Input placeholder='订单号' allowClear />
showSearch </Form.Item>
placeholder='请选择'
optionFilterProp='children'
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
options={[]}
/>
</Form.Item> */}
<Form.Item label='关键词' name='search'> <Form.Item label='关键词' name='search'>
<Input placeholder='关键词' allowClear /> <Input placeholder='关键词' allowClear />
</Form.Item> </Form.Item>
<Form.Item label='日期' name='startDate'> <Form.Item label='日期' name='msgDateRange'>
<RangePicker /> <RangePicker format={'YYYY-MM-DD'} />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type='primary' htmlType='submit'> <Button type='primary' htmlType='submit'>
@ -67,6 +64,7 @@ function ChatHistory() {
// const [formValues, setFormValues] = useState({}); // const [formValues, setFormValues] = useState({});
const [formValues, setFormValues] = useFormStore(((state) => [state.chatHistoryForm, state.setChatHistoryForm])); const [formValues, setFormValues] = useFormStore(((state) => [state.chatHistoryForm, state.setChatHistoryForm]));
const [selectedConversation, setSelectedConversation] = useFormStore(((state) => [state.chatHistorySelectChat, state.setChatHistorySelectChat])); const [selectedConversation, setSelectedConversation] = useFormStore(((state) => [state.chatHistorySelectChat, state.setChatHistorySelectChat]));
const [selectMatch, setSelectedMatch] = useFormStore(((state) => [state.msgHistorySelectMatch, state.setMsgHistorySelectMatch]));
const handleSubmit = useCallback((values) => { const handleSubmit = useCallback((values) => {
setFormValues({ ...values }); setFormValues({ ...values });
@ -76,51 +74,73 @@ function ChatHistory() {
const [messageListLoading, setMessageListLoading] = useState(false); const [messageListLoading, setMessageListLoading] = useState(false);
const [conversationsList, setConversationsList] = useState([]); const [conversationsList, setConversationsList] = useState([]);
const [chatItemMessages, setChatItemMessages] = useState([]); const [chatItemMessages, setChatItemMessages] = useState([]);
const [paramsForMsgList, setParamsForMsgList] = useState({ loadNextPage: true, loadPrePage: true, }); // { opisn, whatsappid, lasttime, pagesize, pagedir }
const getConversationsList = async () => { const getConversationsList = async () => {
const allEmpty = Object.values(cloneDeep(formValues)).every((val) => {
return val === null || val === '' || val === undefined;
});
if (allEmpty) return;
setConversationsListLoading(true); setConversationsListLoading(true);
setChatItemMessages([]); setChatItemMessages([]);
const data = await fetchConversationsList({ opisn: formValues.opisn, customer_name: formValues.customer_name, }); const params = flush(pick(formValues, ['opisn', 'whatsapp_id', 'search', 'from_date', 'end_date', 'coli_id']));
const data = await fetchConversationsSearch(params);
setConversationsListLoading(false); setConversationsListLoading(false);
setConversationsList(data); setConversationsList(data);
if (data.length === 1) { if (data.length === 1) {
setSelectedConversation(data[0]); setSelectedConversation(data[0]);
await getMessages(data[0]); // await getMessagesNext(data[0]);
} }
}; };
const getMessagesPre = async (chatItem) => { const getMessagesPre = async (chatItem) => {
setMessageListLoading(true); setMessageListLoading(true);
const data = await fetchMessages({ opisn: chatItem.opi_sn, whatsappid: chatItem.whatsapp_phone_number, lasttime: chatItem?.pretime || '' }); const data = await fetchMessagesHistory({ opisn: chatItem.opi_sn, whatsappid: chatItem.whatsapp_phone_number, lasttime: chatItem?.pretime || '', pagedir: 'pre', });
setMessageListLoading(false); setMessageListLoading(false);
setChatItemMessages(prevValue => data.concat(prevValue)); setChatItemMessages(prevValue => data.concat(prevValue));
const thisPreTime = data.length > 0 ? data[0].orgmsgtime : ''; const thisPreTime = data.length > 0 ? data[0].orgmsgtime : '';
const loadPrePage = !(data.length === 0 || data.length < MESSAGE_PAGE_SIZE); const loadPrePage = !(data.length === 0 || data.length < MESSAGE_PAGE_SIZE);
setSelectedConversation({ ...chatItem, pretime: thisPreTime, loadPrePage }); setSelectedConversation({ ...chatItem, pretime: thisPreTime, loadPrePage });
}; };
const getMessages = async (chatItem) => { const getMessagesNext = async (chatItem) => {
console.log(chatItem);
setMessageListLoading(true); setMessageListLoading(true);
const data = await fetchMessages({ opisn: chatItem.opi_sn, whatsappid: chatItem.whatsapp_phone_number, lasttime: chatItem?.lasttime || '' }); const data = await fetchMessagesHistory({ opisn: chatItem.opi_sn, whatsappid: chatItem.whatsappid, lasttime: chatItem?.lasttime || '', pagedir: 'next', });
setMessageListLoading(false); setMessageListLoading(false);
setChatItemMessages(prevValue => prevValue.concat(data)); setChatItemMessages(prevValue => prevValue.concat(data.reverse()));
const thisPreTime = data.length > 0 ? data[0].orgmsgtime : ''; const thisLastTime = data.length > 0 ? data[0].orgmsgtime : '';
const loadPrePage = !(data.length === 0 || data.length < MESSAGE_PAGE_SIZE); const loadNextPage = !(data.length === 0 || data.length < MESSAGE_PAGE_SIZE);
const thisLastTime = data.length > 0 ? data[data.length - 1].orgmsgtime : ''; // const thisPreTime = data.length > 0 ? data[data.length - 1].orgmsgtime : '';
const loadNextPage = false; // debug: !(data.length === 0 || data.length < MESSAGE_PAGE_SIZE); // const loadPrePage = !(data.length === 0 || data.length < MESSAGE_PAGE_SIZE);
setSelectedConversation({ ...chatItem, lasttime: thisLastTime, loadNextPage, pretime: thisPreTime, loadPrePage}); setParamsForMsgList(preVal => ({ ...preVal, lasttime: thisLastTime, loadNextPage }));
// setSelectedConversation({ ...chatItem, lasttime: thisLastTime, loadNextPage, pretime: thisPreTime, loadPrePage});
}; };
useEffect(() => { useEffect(() => {
if (selectedConversation.whatsapp_phone_number) { setChatItemMessages([]);
setChatItemMessages([]); setSelectedMatch(selectedConversation.matchMsgList?.[0] || {});
getMessages({...selectedConversation, loadNextPage: true, lasttime: null }); setParamsForMsgList(preVal => ({ ...preVal, opi_sn: selectedConversation.opi_sn, whatsappid: selectedConversation.whatsapp_phone_number }));
} // if (selectedConversation.whatsapp_phone_number) {
// // getMessagesNext({...selectedConversation, loadNextPage: true, lasttime: null });
// }
return () => {};
}, [selectedConversation.conversationid]);
useEffect(() => {
setChatItemMessages([]);
setParamsForMsgList(preVal => ({ ...preVal, preTime: selectMatch.orgmsgtime, lasttime: selectMatch.orgmsgtime, loadPrePage:true, loadNextPage: true, }));
getMessagesNext({...paramsForMsgList, lasttime: selectMatch.orgmsgtime});
return () => {}; return () => {};
}, [selectedConversation.sn]); }, [selectMatch.sn]);
// useEffect(() => {
// getMessagesNext(paramsForMsgList);
// return () => {};
// }, [paramsForMsgList.lasttime]);
useEffect(() => { useEffect(() => {
if (formValues.opisn) { getConversationsList();
getConversationsList();
}
return () => {}; return () => {};
}, [formValues]); }, [formValues]);
@ -170,19 +190,19 @@ function ChatHistory() {
}; };
const onLoadMore = () => { const onLoadMore = () => {
getMessages(selectedConversation); getMessagesNext(paramsForMsgList);
// window.dispatchEvent(new Event('resize')); // window.dispatchEvent(new Event('resize'));
}; };
const loadMore = !messageListLoading && selectedConversation.loadNextPage ? ( const loadMore = !messageListLoading && paramsForMsgList.loadNextPage ? (
<div className='text-center pt-3 mb-3 h-8 leading-8 border-dotted border-0 border-t border-slate-300'> <div className='text-center pt-3 mb-3 h-8 leading-8 border-dotted border-0 border-t border-slate-300'>
<Button onClick={onLoadMore}>loading more</Button> <Button onClick={onLoadMore}>loading more</Button>
</div> </div>
) : null; ) : null;
const onLoadMorePre = () => { const onLoadMorePre = () => {
getMessagesPre(selectedConversation); getMessagesPre(paramsForMsgList);
// window.dispatchEvent(new Event('resize')); // window.dispatchEvent(new Event('resize'));
}; };
const loadMorePre = !messageListLoading && selectedConversation.loadPrePage ? ( const loadMorePre = !messageListLoading && paramsForMsgList.loadPrePage ? (
<div className='text-center pt-3 mb-3 h-8 leading-8 border-dotted border-0 border-t border-slate-300'> <div className='text-center pt-3 mb-3 h-8 leading-8 border-dotted border-0 border-t border-slate-300'>
<Button onClick={onLoadMorePre}>loading more</Button> <Button onClick={onLoadMorePre}>loading more</Button>
</div> </div>
@ -212,80 +232,99 @@ function ChatHistory() {
{conversationsList.map((item) => ( {conversationsList.map((item) => (
<ChatItem <ChatItem
{...item} {...item}
key={item.sn} key={item.conversationid}
id={item.sn} id={item.conversationid}
letterItem={{ id: item.whatsapp_name || item.whatsapp_phone_number, letter: (item.whatsapp_name || item.whatsapp_phone_number).split(" ")[0] }} letterItem={{ id: item.whatsapp_name || item.whatsapp_phone_number, letter: (item.whatsapp_name || item.whatsapp_phone_number).split(" ")[0] }}
alt={`${item.whatsapp_name}`} alt={`${item.whatsapp_name}`}
title={item.whatsapp_name || item.whatsapp_phone_number} title={item.whatsapp_name || item.whatsapp_phone_number}
subtitle={item.coli_id} subtitle={item.coli_id}
date={item.last_received_time} date={item.last_received_time}
className={String(item.sn) === String(selectedConversation.sn) ? '__active text-primary bg-neutral-100' : ''} className={String(item.conversationid) === String(selectedConversation.conversationid) ? '__active text-primary bg-neutral-100' : ''}
onClick={() => setSelectedConversation(item)} onClick={() => setSelectedConversation(item)}
/> />
))} ))}
</Spin> </Spin>
</Sider> </Sider>
<Content style={{ maxHeight: 'calc(100vh - 279px)', height: 'calc(100vh - 279px)', minWidth: '360px' }}> <Content style={{ maxHeight: 'calc(100vh - 279px)', height: 'calc(100vh - 279px)', minWidth: '360px' }}>
<div className='h-full relative' ref={messagesEndRef}> <Flex className='h-full relative'>
<List {(selectedConversation.matchMsgList || []).length > 0 && <div className='w-80 overflow-y-auto overflow-x-hidden'>
loading={messageListLoading} {(selectedConversation.matchMsgList).map((item) => (
header={loadMorePre} <ChatItem
loadMore={loadMore} {...item}
className='h-full overflow-y-auto px-2 relative' key={item.sn}
itemLayout='vertical' id={item.sn}
dataSource={chatItemMessages} letterItem={{ id: item.senderName, letter: (item.senderName).split(" ")[0] }}
renderItem={(message, index) => ( alt={`${item.senderName}`}
// <List.Item> title={item.senderName}
// <List.Item.Meta avatar={<Avatar src={item.avatarUrl} />} title={item.title} description={item.msgTime} /> subtitle={item.originText}
// <div>{item.content}</div> date={item.msgtime}
// </List.Item> // dateString={item.msgtime}
<MessageBoxWithRef className={String(item.sn) === String(selectMatch?.sn) ? '__active text-primary bg-neutral-100' : ' bg-white'}
ref={(el) => (messageRefs.current[index] = el)} onClick={() => setSelectedMatch(item)}
key={message.id} />
{...message} ))}
// position={message.sender === 'me' ? 'right' : 'left'} </div>}
position={'left'} <div className='h-full relative flex-1' ref={messagesEndRef}>
onReplyMessageClick={() => scrollToMessage(message.reply.id)} <List
onOpen={() => handlePreview(message)} loading={messageListLoading}
onTitleClick={() => handlePreview(message)} header={loadMorePre}
notch={false} loadMore={loadMore}
title={message.type === 'text' ? '' : message.title} className='h-full overflow-y-auto px-2 relative'
text={<RenderText str={message?.text || ''} />} itemLayout='vertical'
copiableDate={true} dataSource={chatItemMessages}
dateString={message.dateString || message.localDate} renderItem={(message, index) => (
className={[ // <List.Item>
'whitespace-pre-wrap mb-2', // <List.Item.Meta avatar={<Avatar src={item.avatarUrl} />} title={item.title} description={item.msgTime} />
message.whatsapp_msg_type === 'sticker' ? 'bg-transparent' : '', // <div>{item.content}</div>
message.sender === 'me' ? 'whatsappme-container' : '', // </List.Item>
].join(' ')} <MessageBoxWithRef
style={{ ref={(el) => (messageRefs.current[index] = el)}
backgroundColor: message.sender === 'me' ? '#ccd4ae' : '#fff', key={message.id}
}} {...message}
{...(message.type === 'meetingLink' // position={message.sender === 'me' ? 'right' : 'left'}
? { position={'left'}
actionButtons: [ onReplyMessageClick={() => scrollToMessage(message.reply.id)}
{ onOpen={() => handlePreview(message)}
onClickButton: () => { onTitleClick={() => handlePreview(message)}
navigator.clipboard.writeText(message.text); notch={false}
}, title={message.type === 'text' ? '' : message.title}
Component: () => <div>复制</div>, text={<RenderText str={message?.text || ''} />}
}, copiableDate={true}
], dateString={message.dateString || message.localDate}
className={[
'whitespace-pre-wrap mb-2',
message.whatsapp_msg_type === 'sticker' ? 'bg-transparent' : '',
message.sender === 'me' ? 'whatsappme-container' : '',
].join(' ')}
style={{
backgroundColor: message.sender === 'me' ? '#ccd4ae' : '#fff',
}}
{...(message.type === 'meetingLink'
? {
actionButtons: [
{
onClickButton: () => {
navigator.clipboard.writeText(message.text);
},
Component: () => <div>复制</div>,
},
],
}
: {})}
renderAddCmp={
<div className='border-dashed border-0 border-t border-slate-300 text-slate-600 space-x-2 emoji'>
<span className={`p-1 rounded-b ${message.msg_direction === 'outbound' ? 'text-white' : ''} `} style={{backgroundColor: message.msg_direction === 'outbound' ? stringToColour(message.senderName) : 'unset'}}>{message.senderName}</span>
<span>{message.dateString || message.localDate}</span>
<span>{message.statusCN}</span>
</div>
} }
: {})} // date={null}
renderAddCmp={ // status={null}
<div className='border-dashed border-0 border-t border-slate-300 text-slate-600 space-x-2 emoji'> />
<span className={`p-1 rounded-b ${message.msg_direction === 'outbound' ? 'text-white' : ''} `} style={{backgroundColor: message.msg_direction === 'outbound' ? stringToColour(message.senderName) : 'unset'}}>{message.senderName}</span> )}
<span>{message.dateString || message.localDate}</span>
<span>{message.statusCN}</span>
</div>
}
// date={null}
// status={null}
/> />
)} </div>
/> </Flex>
</div>
</Content> </Content>
</Layout> </Layout>
</> </>

Loading…
Cancel
Save