Compare commits
7 Commits
main
...
dev/mobile
Author | SHA1 | Date |
---|---|---|
|
6f78fb678d | 1 year ago |
|
4089b85c72 | 1 year ago |
|
89602b9c10 | 1 year ago |
|
6dfd4b9e1f | 1 year ago |
|
dcb5e57282 | 1 year ago |
|
8ab8b1575b | 1 year ago |
|
fc68d43e0c | 1 year ago |
@ -1,428 +1,38 @@
|
|||||||
import { memo, useCallback, useEffect, useRef, useState, forwardRef } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { App, Divider, Button, Input, Layout, DatePicker, Form, List, Spin, Flex, Image } from 'antd';
|
import { Divider, Layout, Flex, Image } from 'antd';
|
||||||
import { LoadingOutlined } from '@ant-design/icons';
|
|
||||||
import { ChatItem, MessageBox } from 'react-chat-elements';
|
|
||||||
import { MESSAGE_PAGE_SIZE, fetchConversationsSearch, fetchMessagesHistory } from '@/actions/ConversationActions';
|
|
||||||
import { cloneDeep, flush, isEmpty, pick, stringToColour } from '@/utils/utils';
|
|
||||||
import useFormStore from '@/stores/FormStore';
|
import useFormStore from '@/stores/FormStore';
|
||||||
|
import SearchForm from './Conversations/History/SearchForm';
|
||||||
|
import ConversationsList from './Conversations/History/ConversationsList';
|
||||||
|
import MessagesMatchList from './Conversations/History/MessagesMatchList';
|
||||||
|
import MessagesList from './Conversations/History/MessagesList';
|
||||||
|
import ImageAlbumPreview from './Conversations/History/ImageAlumPreview';
|
||||||
|
|
||||||
import { fetchSalesAgent, fetchCustomerList } from '@/actions/CommonActions';
|
const { Sider, Content } = Layout;
|
||||||
import SearchInput from '@/components/SearchInput';
|
|
||||||
import { isNotEmpty } from '@/utils/commons';
|
|
||||||
|
|
||||||
const { Sider, Content, Header, Footer } = Layout;
|
const Index = (props) => {
|
||||||
const { Search } = Input;
|
const [formValues, setFormValues] = useFormStore((state) => [state.chatHistoryForm, state.setChatHistoryForm]);
|
||||||
const { RangePicker } = DatePicker;
|
|
||||||
|
|
||||||
const BIG_PAGE_SIZE = MESSAGE_PAGE_SIZE * 100;
|
|
||||||
|
|
||||||
// https://media-xsp2-1.cdn.whatsapp.net/v/t61.24694-24/424735646_380563021285029_2962758854250800176_n.jpg?ccb=11-4&oh=01_AdTogiVdUE-ToI9uH-VQKTTLyDbP7bocXUQe1OETOeCgcg&oe=65F7C6AB&_nc_sid=e6ed6c&_nc_cat=104
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
const SearchForm = memo(function ({ initialValues, onSubmit }) {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
function handleSubmit(values) {
|
|
||||||
const multiAgents = (values?.agent || []).map(ele => ele.value).join(',');
|
|
||||||
const multiCustomers = (values?.customer || []).map(ele => ele.value).join(',');
|
|
||||||
onSubmit?.({
|
|
||||||
...values,
|
|
||||||
opisn: multiAgents,
|
|
||||||
whatsapp_id: multiCustomers,
|
|
||||||
...(isNotEmpty(values.msgDateRange) ? {
|
|
||||||
from_date: values.msgDateRange[0].format('YYYY-MM-DD'),
|
|
||||||
end_date: values.msgDateRange[1].format('YYYY-MM-DD'),
|
|
||||||
} : {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Form layout={'inline'} form={form} initialValues={initialValues} onFinish={handleSubmit} style={{}}>
|
|
||||||
<Flex className='w-full'>
|
|
||||||
<Flex flex={'auto'} wrap='wrap' gap={4}>
|
|
||||||
<Form.Item label='发送人' name='agent' style={{ width: '200px' }} rules={[{ required: false, message: '请选择发送人' }]}>
|
|
||||||
<SearchInput placeholder='搜索发送人' fetchOptions={fetchSalesAgent} mode={'tags'} maxTagCount={0} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label='客人' name='customer' style={{ width: '200px' }}>
|
|
||||||
<SearchInput placeholder='搜索客人' fetchOptions={fetchCustomerList} mode={'tags'} maxTagCount={0} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label='订单号' name='coli_id'>
|
|
||||||
<Input placeholder='订单号' allowClear />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label='关键词' name='search'>
|
|
||||||
<Input placeholder='关键词' allowClear />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label='日期' name='msgDateRange'>
|
|
||||||
<RangePicker format={'YYYY-MM-DD'} />
|
|
||||||
</Form.Item>
|
|
||||||
</Flex>
|
|
||||||
<div style={{flex: '0 1 64px'}}>
|
|
||||||
<Form.Item>
|
|
||||||
<Button type='primary' htmlType='submit'>
|
|
||||||
搜索
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function ChatHistory() {
|
|
||||||
|
|
||||||
const { message: appMessage } = App.useApp()
|
|
||||||
|
|
||||||
// const [formValues, setFormValues] = useState({});
|
|
||||||
const [formValues, setFormValues] = useFormStore(((state) => [state.chatHistoryForm, state.setChatHistoryForm]));
|
|
||||||
const [selectedConversation, setSelectedConversation] = useFormStore(((state) => [state.chatHistorySelectChat, state.setChatHistorySelectChat]));
|
|
||||||
const [selectMatch, setSelectedMatch] = useFormStore(((state) => [state.msgHistorySelectMatch, state.setMsgHistorySelectMatch]));
|
|
||||||
const [paramsForMsgList, setParamsForMsgList] = useFormStore(((state) => [state.msgListParams, state.setMsgListParams]));
|
|
||||||
// { opisn, whatsappid, lasttime, pagesize, pagedir }
|
|
||||||
|
|
||||||
const handleSubmit = useCallback((values) => {
|
const handleSubmit = useCallback((values) => {
|
||||||
setFormValues({ ...values });
|
setFormValues({ ...values });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [conversationsListLoading, setConversationsListLoading] = useState(false);
|
|
||||||
const [messageListPreLoading, setMessageListPreLoading] = useState(false);
|
|
||||||
const [messageListLoading, setMessageListLoading] = useState(false);
|
|
||||||
const [conversationsList, setConversationsList] = useState([]);
|
|
||||||
const [chatItemMessages, setChatItemMessages] = useState([]);
|
|
||||||
const [imageAlbum, setImageAlbum] = useState([]);
|
|
||||||
// const [paramsForMsgList, setParamsForMsgList] = useState({ loadNextPage: true, loadPrePage: true, }); // { opisn, whatsappid, lasttime, pagesize, pagedir }
|
|
||||||
// const [selectMatch, setSelectedMatch] = useState({});
|
|
||||||
|
|
||||||
const getConversationsList = async () => {
|
|
||||||
// const allEmpty = Object.values(cloneDeep(formValues)).every((val) => {
|
|
||||||
// return val === null || val === '' || val === undefined;
|
|
||||||
// });
|
|
||||||
// if (allEmpty) return;
|
|
||||||
setConversationsListLoading(true);
|
|
||||||
setChatItemMessages([]);
|
|
||||||
setParamsForMsgList({});
|
|
||||||
setSelectedMatch({});
|
|
||||||
const params = flush(pick(formValues, ['opisn', 'whatsapp_id', 'search', 'from_date', 'end_date', 'coli_id']));
|
|
||||||
const data = await fetchConversationsSearch(params);
|
|
||||||
setConversationsListLoading(false);
|
|
||||||
setConversationsList(data);
|
|
||||||
if (data.length === 1) {
|
|
||||||
setSelectedConversation(data[0]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMessagesPre = async (chatItem) => {
|
|
||||||
setMessageListPreLoading(true);
|
|
||||||
const data = await fetchMessagesHistory({ ...chatItem, lasttime: chatItem.pretime, pagedir: 'pre', pagesize: BIG_PAGE_SIZE });
|
|
||||||
setMessageListPreLoading(false);
|
|
||||||
setChatItemMessages(prevValue => data.concat(prevValue));
|
|
||||||
const loadPrePage = !(data.length === 0 || data.length < BIG_PAGE_SIZE);
|
|
||||||
setParamsForMsgList({ loadPrePage });
|
|
||||||
};
|
|
||||||
const getMessagesNext = async (chatItem) => {
|
|
||||||
setMessageListLoading(true);
|
|
||||||
const data = await fetchMessagesHistory({...chatItem, pagedir: 'next', pagesize: BIG_PAGE_SIZE });
|
|
||||||
setMessageListLoading(false);
|
|
||||||
setChatItemMessages(prevValue => prevValue.concat(data));
|
|
||||||
const loadNextPage = !(data.length === 0 || data.length < BIG_PAGE_SIZE);
|
|
||||||
setParamsForMsgList({loadNextPage});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 选择指定消息之后, 定位
|
|
||||||
const scrollToSelectedMessage = (selected) => {
|
|
||||||
const findIndex = chatItemMessages.findIndex(item => item.id === selected.id);
|
|
||||||
if (findIndex !== -1) {
|
|
||||||
scrollToMessage(selected.id, findIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const handleMatchMsgClick = async (selected) => {
|
|
||||||
const findIndex = chatItemMessages.findIndex(item => item.id === selected.id);
|
|
||||||
if (findIndex === -1) {
|
|
||||||
setMessageListLoading(true);
|
|
||||||
await getMessagesPre({...paramsForMsgList });
|
|
||||||
setMessageListLoading(false);
|
|
||||||
}
|
|
||||||
setSelectedMatch(selected);
|
|
||||||
}
|
|
||||||
// 选择指定消息之后, 定位
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectMatch.sn) {
|
|
||||||
scrollToSelectedMessage(selectMatch);
|
|
||||||
}
|
|
||||||
return () => {};
|
|
||||||
}, [selectMatch.sn]);
|
|
||||||
|
|
||||||
// 选中会话之后, 获取消息记录
|
|
||||||
useEffect(() => {
|
|
||||||
setChatItemMessages([]);
|
|
||||||
setParamsForMsgList({});
|
|
||||||
setSelectedMatch({});
|
|
||||||
if (isEmpty(selectedConversation.conversationid) || isEmpty(selectedConversation.opi_sn)) {
|
|
||||||
return () => {};
|
|
||||||
}
|
|
||||||
const firstActionPageParams = { opisn: selectedConversation.opi_sn, whatsappid: selectedConversation.whatsapp_phone_number };
|
|
||||||
if (isEmpty(selectedConversation.matchMsgList)) {
|
|
||||||
firstActionPageParams.loadPrePage = false;
|
|
||||||
firstActionPageParams.loadNextPage = true;
|
|
||||||
} else {
|
|
||||||
firstActionPageParams.pretime = selectedConversation.matchMsgList[0].orgmsgtime;
|
|
||||||
firstActionPageParams.lasttime = selectedConversation.matchMsgList[0].orgmsgtime;
|
|
||||||
firstActionPageParams.loadPrePage = true;
|
|
||||||
firstActionPageParams.loadNextPage = true;
|
|
||||||
}
|
|
||||||
setParamsForMsgList(firstActionPageParams);
|
|
||||||
async function getFirstNext() {
|
|
||||||
await getMessagesNext(firstActionPageParams);
|
|
||||||
}
|
|
||||||
async function getFirstPre() {
|
|
||||||
await getMessagesPre(firstActionPageParams);
|
|
||||||
}
|
|
||||||
// getFirstPre();
|
|
||||||
getFirstNext();
|
|
||||||
|
|
||||||
return () => {};
|
|
||||||
}, [selectedConversation.conversationid]);
|
|
||||||
|
|
||||||
// 更新是否需要显示上一页,下一页按钮
|
|
||||||
useEffect(() => {
|
|
||||||
if (chatItemMessages.length > 0) {
|
|
||||||
setParamsForMsgList({pretime: chatItemMessages[0].orgmsgtime, lasttime: chatItemMessages[chatItemMessages.length - 1].orgmsgtime });
|
|
||||||
setImageAlbum(chatItemMessages.filter(ele => ele.whatsapp_msg_type === 'image').map(ele => ele.data.uri));
|
|
||||||
}
|
|
||||||
return () => {};
|
|
||||||
}, [chatItemMessages])
|
|
||||||
|
|
||||||
const onLoadMore = () => {
|
|
||||||
getMessagesNext(paramsForMsgList);
|
|
||||||
// window.dispatchEvent(new Event('resize'));
|
|
||||||
};
|
|
||||||
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'>
|
|
||||||
<Button onClick={onLoadMore}>load more</Button>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
const onLoadMorePre = () => {
|
|
||||||
getMessagesPre(paramsForMsgList);
|
|
||||||
// window.dispatchEvent(new Event('resize'));
|
|
||||||
};
|
|
||||||
const loadMorePre =
|
|
||||||
paramsForMsgList.loadPrePage && chatItemMessages.length > 0 ? (
|
|
||||||
<div className='text-center h-8 leading-8 '>
|
|
||||||
{messageListPreLoading ? <LoadingOutlined className='text-primary' /> : <Button onClick={onLoadMorePre}>load more previous </Button>}
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getConversationsList();
|
|
||||||
return () => {};
|
|
||||||
}, [formValues]);
|
|
||||||
|
|
||||||
const RenderText = memo(function renderText({ str, className }) {
|
|
||||||
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) || [];
|
|
||||||
const extraClass = isEmpty(emojis) ? '' : '';
|
|
||||||
const objArr = parts.reduce((prev, curr, index) => {
|
|
||||||
if (links.includes(curr)) {
|
|
||||||
prev.push({ type: 'link', key: curr });
|
|
||||||
} else if (emojis.includes(curr)) {
|
|
||||||
prev.push({ type: 'emoji', key: curr });
|
|
||||||
} else {
|
|
||||||
prev.push({ type: 'text', key: curr });
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<span className={`text-sm leading-5 emoji-text ${className} ${extraClass} `} key={'msg-text'}>
|
|
||||||
{(objArr || []).map((part, index) => {
|
|
||||||
if (part.type === 'link') {
|
|
||||||
return (
|
|
||||||
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-sm'>
|
|
||||||
{part.key}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return part.key;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const [previewVisible, setPreviewVisible] = useState(false);
|
|
||||||
const [previewSrc, setPreviewSrc] = useState();
|
|
||||||
const [previewIndex, setPreviewIndex] = useState();
|
|
||||||
const onPreviewClose = () => {
|
|
||||||
setPreviewSrc('');
|
|
||||||
setPreviewVisible(false);
|
|
||||||
};
|
|
||||||
const handlePreview = (msg) => {
|
|
||||||
switch (msg.whatsapp_msg_type) {
|
|
||||||
case 'image':
|
|
||||||
setPreviewVisible(true);
|
|
||||||
setPreviewSrc(msg.data.uri);
|
|
||||||
setPreviewIndex(imageAlbum.findIndex((url) => url === msg.data.uri));
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case 'document':
|
|
||||||
window.open(msg.data.link || msg.data.uri, '_blank', 'noopener,noreferrer');
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handlePreviewItem = (msg) => {
|
|
||||||
switch (msg.whatsapp_msg_type) {
|
|
||||||
case 'image':
|
|
||||||
// eslint-disable-next-line no-fallthrough
|
|
||||||
case 'document':
|
|
||||||
window.open(msg.data.link || msg.data.uri, '_blank', 'noopener,noreferrer');
|
|
||||||
return false;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const messagesEndRef = useRef(null);
|
|
||||||
const messageRefs = useRef([]);
|
|
||||||
const [focusMsg, setFocusMsg] = useState('');
|
|
||||||
const scrollToMessage = (id, index) => {
|
|
||||||
const _i = index || chatItemMessages.findIndex((msg) => msg.id === id);
|
|
||||||
if (_i >= 0) {
|
|
||||||
messageRefs.current[_i].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
||||||
setFocusMsg(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
const MessageBoxWithRef = forwardRef((props, ref) => (
|
|
||||||
<li ref={ref} >
|
|
||||||
<MessageBox {...props} />
|
|
||||||
</li>
|
|
||||||
));
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SearchForm onSubmit={handleSubmit} initialValues={formValues} />
|
<SearchForm onSubmit={handleSubmit} initialValues={formValues} />
|
||||||
<Divider plain orientation='left' className='mb-0'></Divider>
|
<Divider plain orientation='left' className='mb-0'></Divider>
|
||||||
<Layout hasSider className='h-screen chathistory-wrapper chatwindow-wrapper' style={{ maxHeight: 'calc(100% - 279px)', height: 'calc(100% - 279px)' }}>
|
<Layout hasSider className='h-screen chathistory-wrapper chatwindow-wrapper' style={{ maxHeight: 'calc(100% - 279px)', height: 'calc(100% - 279px)' }}>
|
||||||
<Sider width={240} theme={'light'} className='h-full overflow-y-auto overflow-x-hidden' style={{ maxHeight: 'calc(100vh - 279px)', height: 'calc(100vh - 279px)' }}>
|
<Sider width={240} theme={'light'} className='h-full overflow-y-auto overflow-x-hidden' style={{ maxHeight: 'calc(100vh - 279px)', height: 'calc(100vh - 279px)' }}>
|
||||||
<Spin spinning={conversationsListLoading}>
|
<ConversationsList />
|
||||||
{conversationsList.map((item) => (
|
|
||||||
<ChatItem
|
|
||||||
{...item}
|
|
||||||
key={item.conversationid}
|
|
||||||
id={item.conversationid}
|
|
||||||
letterItem={{ id: item.whatsapp_name || item.whatsapp_phone_number, letter: (item.whatsapp_name || item.whatsapp_phone_number).split(' ')[0] }}
|
|
||||||
alt={`${item.whatsapp_name}`}
|
|
||||||
title={item.whatsapp_name || item.whatsapp_phone_number}
|
|
||||||
subtitle={`${item.OPI_Name || ''} ${item.coli_id || ''}`}
|
|
||||||
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)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</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' }}>
|
||||||
<Flex className='h-full relative'>
|
<Flex className='h-full relative'>
|
||||||
{(selectedConversation.matchMsgList || []).length > 0 && isNotEmpty(formValues.search) && (
|
<MessagesMatchList />
|
||||||
<div className='w-80 overflow-y-auto overflow-x-hidden'>
|
<MessagesList />
|
||||||
<p className='text-center'><mark>{formValues.search}</mark> 的相关记录, 点击定位上下文</p>
|
|
||||||
{selectedConversation.matchMsgList.map((item) => (
|
|
||||||
<ChatItem
|
|
||||||
{...item}
|
|
||||||
key={item.sn}
|
|
||||||
id={item.sn}
|
|
||||||
letterItem={{
|
|
||||||
id: item.sender === 'me' ? selectedConversation.OPI_Name || item.senderName : item.senderName,
|
|
||||||
letter: (item.sender === 'me' ? selectedConversation.OPI_Name || item.senderName : item.senderName).split(' ')[0],
|
|
||||||
}}
|
|
||||||
alt={`${item.senderName}`}
|
|
||||||
title={item.sender === 'me' ? selectedConversation.OPI_Name || item.senderName : item.senderName}
|
|
||||||
subtitle={item.originText}
|
|
||||||
date={item.msgtime}
|
|
||||||
dateString={item.dateText}
|
|
||||||
className={String(item.sn) === String(selectMatch?.sn) ? '__active text-primary bg-neutral-100' : ' bg-white'}
|
|
||||||
onClick={() => handleMatchMsgClick(item)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className='h-full relative flex-1 border-dashed border-y-0 border-r-0 border-l border-slate-200' ref={messagesEndRef}>
|
|
||||||
<List
|
|
||||||
loading={messageListLoading}
|
|
||||||
header={loadMorePre}
|
|
||||||
loadMore={loadMore}
|
|
||||||
className='h-full overflow-y-auto px-2 relative'
|
|
||||||
itemLayout='vertical'
|
|
||||||
dataSource={chatItemMessages}
|
|
||||||
renderItem={(message, index) => (
|
|
||||||
// <List.Item>
|
|
||||||
// <List.Item.Meta avatar={<Avatar src={item.avatarUrl} />} title={item.title} description={item.msgTime} />
|
|
||||||
// <div>{item.content}</div>
|
|
||||||
// </List.Item>
|
|
||||||
<MessageBoxWithRef
|
|
||||||
ref={(el) => (messageRefs.current[index] = el)}
|
|
||||||
key={message.id}
|
|
||||||
{...message}
|
|
||||||
// position={message.sender === 'me' ? 'right' : 'left'}
|
|
||||||
position={'left'}
|
|
||||||
replyButton={false}
|
|
||||||
onReplyMessageClick={() => scrollToMessage(message.reply.id)}
|
|
||||||
onOpen={() => handlePreview(message)}
|
|
||||||
onTitleClick={() => handlePreview(message)}
|
|
||||||
notch={false}
|
|
||||||
title={message.whatsapp_msg_type === 'text' ? '' : message.title}
|
|
||||||
text={<RenderText str={message?.text || ''} className={message.status === 'failed' ? 'line-through text-neutral-400' : ''} />}
|
|
||||||
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' : '',
|
|
||||||
focusMsg === message.id ? 'message-box-focus' : '',
|
|
||||||
message.status === 'failed' ? 'failed-msg' : '',
|
|
||||||
].join(' ')}
|
|
||||||
style={{
|
|
||||||
backgroundColor: message.sender === 'me' ? '#ccd4ae' : '#fff',
|
|
||||||
}}
|
|
||||||
{...(message.type === 'meetingLink'
|
|
||||||
? {
|
|
||||||
actionButtons: [
|
|
||||||
{
|
|
||||||
onClickButton: () => {
|
|
||||||
navigator.clipboard.writeText(message.text);
|
|
||||||
appMessage.success('复制成功😀');
|
|
||||||
},
|
|
||||||
Component: () => <div key='copy'>复制</div>,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
renderAddCmp={
|
|
||||||
<div key={'msg-prefix'} 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.msg_direction === 'outbound' ? selectedConversation.OPI_Name : message.senderName}
|
|
||||||
</span>
|
|
||||||
<span>{message.dateString || message.localDate}</span>
|
|
||||||
<span>{message.statusCN}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
// date={null}
|
|
||||||
// status={null}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
{/* <Image width={0} height={0} src={null} preview={{ visible: previewVisible, src: previewSrc, onClose: onPreviewClose }} /> */}
|
<ImageAlbumPreview />
|
||||||
<Image.PreviewGroup items={imageAlbum} preview={{ current: previewIndex, visible: previewVisible, onClose: onPreviewClose, onChange: setPreviewIndex }} />
|
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
export default Index;
|
||||||
export default ChatHistory;
|
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
import { ChatItem } from 'react-chat-elements';
|
||||||
|
import useFormStore from '@/stores/FormStore';
|
||||||
|
import { flush, pick } from '@/utils/utils';
|
||||||
|
import { fetchConversationsSearch } from '@/actions/ConversationActions';
|
||||||
|
|
||||||
|
const ConversationsList = ({ ...props }) => {
|
||||||
|
const [formValues,] = useFormStore(((state) => [state.chatHistoryForm,]));
|
||||||
|
const [selectedConversation, setSelectedConversation] = useFormStore((state) => [state.chatHistorySelectChat, state.setChatHistorySelectChat]);
|
||||||
|
|
||||||
|
const [conversationsListLoading, setConversationsListLoading] = useState(false);
|
||||||
|
const [conversationsList, setConversationsList] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getConversationsList();
|
||||||
|
return () => {};
|
||||||
|
}, [formValues]);
|
||||||
|
|
||||||
|
const getConversationsList = async () => {
|
||||||
|
setConversationsListLoading(true);
|
||||||
|
const params = flush(pick(formValues, ['opisn', 'whatsapp_id', 'search', 'from_date', 'end_date', 'coli_id']));
|
||||||
|
const data = await fetchConversationsSearch(params);
|
||||||
|
setConversationsListLoading(false);
|
||||||
|
setConversationsList(data);
|
||||||
|
if (data.length === 1) {
|
||||||
|
setSelectedConversation(data[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Spin spinning={conversationsListLoading}>
|
||||||
|
{conversationsList.map((item) => (
|
||||||
|
<ChatItem
|
||||||
|
{...item}
|
||||||
|
key={item.conversationid}
|
||||||
|
id={item.conversationid}
|
||||||
|
letterItem={{ id: item.whatsapp_name || item.whatsapp_phone_number, letter: (item.whatsapp_name || item.whatsapp_phone_number).split(' ')[0] }}
|
||||||
|
alt={`${item.whatsapp_name}`}
|
||||||
|
title={item.whatsapp_name || item.whatsapp_phone_number}
|
||||||
|
subtitle={`${item.OPI_Name || ''} ${item.coli_id || ''}`}
|
||||||
|
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)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Spin>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ConversationsList;
|
@ -0,0 +1,30 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Image } from 'antd';
|
||||||
|
import useFormStore from '@/stores/FormStore';
|
||||||
|
|
||||||
|
const ImageAlbumPreview = (props) => {
|
||||||
|
const [ImageAlbum,] = useFormStore((state) => [state.ImageAlbum, ]);
|
||||||
|
const [ImagePreviewSrc, setImagePreviewSrc] = useFormStore((state) => [state.ImagePreviewSrc, state.setImagePreviewSrc]);
|
||||||
|
|
||||||
|
const [previewVisible, setPreviewVisible] = useState(false);
|
||||||
|
const [previewIndex, setPreviewIndex] = useState();
|
||||||
|
const onPreviewClose = () => {
|
||||||
|
setImagePreviewSrc('');
|
||||||
|
setPreviewVisible(false);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (ImagePreviewSrc) {
|
||||||
|
setPreviewVisible(true);
|
||||||
|
setPreviewIndex(ImageAlbum.findIndex((url) => url === ImagePreviewSrc));
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {};
|
||||||
|
}, [ImagePreviewSrc]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Image.PreviewGroup items={ImageAlbum} preview={{ current: previewIndex, visible: previewVisible, onClose: onPreviewClose, onChange: setPreviewIndex }} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ImageAlbumPreview;
|
@ -0,0 +1,268 @@
|
|||||||
|
import { useRef, useEffect, useState, forwardRef, memo } from 'react';
|
||||||
|
import { App, Flex, List, Button, } from 'antd';
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import { MessageBox } from 'react-chat-elements';
|
||||||
|
import { MESSAGE_PAGE_SIZE, fetchMessagesHistory } from '@/actions/ConversationActions';
|
||||||
|
import useFormStore from '@/stores/FormStore';
|
||||||
|
import { isEmpty, stringToColour } from '@/utils/utils';
|
||||||
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
|
|
||||||
|
const BIG_PAGE_SIZE = MESSAGE_PAGE_SIZE * 100;
|
||||||
|
const MessagesList = ({ ...props }) => {
|
||||||
|
const { message: appMessage } = App.useApp();
|
||||||
|
|
||||||
|
const [formValues] = useFormStore((state) => [state.chatHistoryForm]);
|
||||||
|
const [selectedConversation] = useFormStore((state) => [state.chatHistorySelectChat]);
|
||||||
|
const [paramsForMsgList, setParamsForMsgList] = useFormStore((state) => [state.msgListParams, state.setMsgListParams]);
|
||||||
|
const [selectMatch, setSelectedMatch] = useFormStore((state) => [state.msgHistorySelectMatch, state.setMsgHistorySelectMatch]);
|
||||||
|
const [setImageAlbumList, setImagePreviewSrc] = useFormStore(useShallow((state) => [state.setImageAlbum, state.setImagePreviewSrc]));
|
||||||
|
|
||||||
|
const [chatItemMessages, setChatItemMessages] = useState([]);
|
||||||
|
const [messageListPreLoading, setMessageListPreLoading] = useState(false);
|
||||||
|
const [messageListLoading, setMessageListLoading] = useState(false);
|
||||||
|
|
||||||
|
const getMessagesPre = async (chatItem) => {
|
||||||
|
setMessageListPreLoading(true);
|
||||||
|
const data = await fetchMessagesHistory({ ...chatItem, lasttime: chatItem.pretime, pagedir: 'pre', pagesize: BIG_PAGE_SIZE });
|
||||||
|
setMessageListPreLoading(false);
|
||||||
|
setChatItemMessages((prevValue) => data.concat(prevValue));
|
||||||
|
const loadPrePage = !(data.length === 0 || data.length < BIG_PAGE_SIZE);
|
||||||
|
setParamsForMsgList({ loadPrePage });
|
||||||
|
};
|
||||||
|
const getMessagesNext = async (chatItem) => {
|
||||||
|
setMessageListLoading(true);
|
||||||
|
const data = await fetchMessagesHistory({ ...chatItem, pagedir: 'next', pagesize: BIG_PAGE_SIZE });
|
||||||
|
setMessageListLoading(false);
|
||||||
|
setChatItemMessages((prevValue) => prevValue.concat(data));
|
||||||
|
const loadNextPage = !(data.length === 0 || data.length < BIG_PAGE_SIZE);
|
||||||
|
setParamsForMsgList({ loadNextPage });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择指定消息之后, 定位
|
||||||
|
const scrollToSelectedMessage = (selected) => {
|
||||||
|
const findIndex = chatItemMessages.findIndex((item) => item.id === selected.id);
|
||||||
|
if (findIndex !== -1) {
|
||||||
|
scrollToMessage(selected.id, findIndex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选中会话之后, 获取消息记录
|
||||||
|
useEffect(() => {
|
||||||
|
setChatItemMessages([]);
|
||||||
|
setParamsForMsgList({});
|
||||||
|
setSelectedMatch({});
|
||||||
|
if (isEmpty(selectedConversation.conversationid) || isEmpty(selectedConversation.opi_sn)) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
const firstActionPageParams = { opisn: selectedConversation.opi_sn, whatsappid: selectedConversation.whatsapp_phone_number, loadNextPage: true };
|
||||||
|
// if (isEmpty(selectedConversation.matchMsgList)) {
|
||||||
|
if (!isEmpty(formValues?.from_date)) {
|
||||||
|
firstActionPageParams.lasttime = formValues.from_date;
|
||||||
|
firstActionPageParams.loadPrePage = true;
|
||||||
|
}
|
||||||
|
if (!isEmpty(formValues?.search)) {
|
||||||
|
firstActionPageParams.pretime = selectedConversation.matchMsgList[0].orgmsgtime;
|
||||||
|
firstActionPageParams.lasttime = selectedConversation.matchMsgList[0].orgmsgtime;
|
||||||
|
firstActionPageParams.loadPrePage = true;
|
||||||
|
}
|
||||||
|
setParamsForMsgList(firstActionPageParams);
|
||||||
|
async function getFirstNext() {
|
||||||
|
await getMessagesNext(firstActionPageParams);
|
||||||
|
}
|
||||||
|
getFirstNext();
|
||||||
|
|
||||||
|
return () => {};
|
||||||
|
}, [selectedConversation.conversationid]);
|
||||||
|
|
||||||
|
// 点击匹配的记录, 定位滚动
|
||||||
|
useEffect(() => {
|
||||||
|
async function getFirstPre(_params) {
|
||||||
|
await getMessagesPre(_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
const findIndex = chatItemMessages.findIndex((item) => item.id === selectMatch.id);
|
||||||
|
if (findIndex === -1) {
|
||||||
|
setMessageListLoading(true);
|
||||||
|
getFirstPre({ ...paramsForMsgList });
|
||||||
|
setMessageListLoading(false);
|
||||||
|
}
|
||||||
|
scrollToSelectedMessage(selectMatch);
|
||||||
|
return () => {};
|
||||||
|
}, [selectMatch.id]);
|
||||||
|
|
||||||
|
// 更新是否需要显示上一页,下一页按钮
|
||||||
|
useEffect(() => {
|
||||||
|
if (chatItemMessages.length > 0) {
|
||||||
|
setParamsForMsgList({ pretime: chatItemMessages[0].orgmsgtime, lasttime: chatItemMessages[chatItemMessages.length - 1].orgmsgtime });
|
||||||
|
const album = chatItemMessages.filter((ele) => ele.whatsapp_msg_type === 'image').map((ele) => ele.data.uri);
|
||||||
|
setImageAlbumList(album);
|
||||||
|
}
|
||||||
|
return () => {};
|
||||||
|
}, [chatItemMessages]);
|
||||||
|
|
||||||
|
const onLoadMore = () => {
|
||||||
|
getMessagesNext(paramsForMsgList);
|
||||||
|
// window.dispatchEvent(new Event('resize'));
|
||||||
|
};
|
||||||
|
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'>
|
||||||
|
<Button onClick={onLoadMore}>load more</Button>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
const onLoadMorePre = () => {
|
||||||
|
getMessagesPre(paramsForMsgList);
|
||||||
|
// window.dispatchEvent(new Event('resize'));
|
||||||
|
};
|
||||||
|
const loadMorePre =
|
||||||
|
paramsForMsgList.loadPrePage && chatItemMessages.length > 0 ? (
|
||||||
|
<div className='text-center h-8 leading-8 '>
|
||||||
|
{messageListPreLoading ? <LoadingOutlined className='text-primary' /> : <Button onClick={onLoadMorePre}>load more previous </Button>}
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const messagesEndRef = useRef(null);
|
||||||
|
const messageRefs = useRef([]);
|
||||||
|
const [focusMsg, setFocusMsg] = useState('');
|
||||||
|
const scrollToMessage = (id, index) => {
|
||||||
|
const _i = index || chatItemMessages.findIndex((msg) => msg.id === id);
|
||||||
|
if (_i >= 0) {
|
||||||
|
messageRefs.current[_i].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
setFocusMsg(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const RenderText = memo(function renderText({ str, className }) {
|
||||||
|
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) || [];
|
||||||
|
const extraClass = isEmpty(emojis) ? '' : '';
|
||||||
|
const objArr = parts.reduce((prev, curr, index) => {
|
||||||
|
if (links.includes(curr)) {
|
||||||
|
prev.push({ type: 'link', key: curr });
|
||||||
|
} else if (emojis.includes(curr)) {
|
||||||
|
prev.push({ type: 'emoji', key: curr });
|
||||||
|
} else {
|
||||||
|
prev.push({ type: 'text', key: curr });
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<span className={`text-sm leading-5 emoji-text ${className} ${extraClass} `} key={'msg-text'}>
|
||||||
|
{(objArr || []).map((part, index) => {
|
||||||
|
if (part.type === 'link') {
|
||||||
|
return (
|
||||||
|
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-sm'>
|
||||||
|
{part.key}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return part.key;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
|
const MessageBoxWithRef = forwardRef((props, ref) => (
|
||||||
|
<li ref={ref}>
|
||||||
|
<MessageBox {...props} />
|
||||||
|
</li>
|
||||||
|
));
|
||||||
|
|
||||||
|
const handlePreview = (msg) => {
|
||||||
|
switch (msg.whatsapp_msg_type) {
|
||||||
|
case 'image':
|
||||||
|
setImagePreviewSrc(msg.data.uri);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case 'document':
|
||||||
|
window.open(msg.data.link || msg.data.uri, '_blank', 'noopener,noreferrer');
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex vertical className='flex-1'>
|
||||||
|
<div className='px-2 py-1 text-primary bg-white border-0 border-b border-slate-200'>
|
||||||
|
{selectedConversation.whatsapp_name} {selectedConversation.whatsapp_phone_number}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ height: 'calc(100% - 30px)' }} className='h-full flex-auto relative border-dashed border-y-0 border-r-0 border-l border-slate-200' ref={messagesEndRef}>
|
||||||
|
<List
|
||||||
|
loading={messageListLoading}
|
||||||
|
header={loadMorePre}
|
||||||
|
loadMore={loadMore}
|
||||||
|
className='h-full overflow-y-auto px-2 relative'
|
||||||
|
itemLayout='vertical'
|
||||||
|
dataSource={chatItemMessages}
|
||||||
|
renderItem={(message, index) => (
|
||||||
|
// <List.Item>
|
||||||
|
// <List.Item.Meta avatar={<Avatar src={item.avatarUrl} />} title={item.title} description={item.msgTime} />
|
||||||
|
// <div>{item.content}</div>
|
||||||
|
// </List.Item>
|
||||||
|
<MessageBoxWithRef
|
||||||
|
ref={(el) => (messageRefs.current[index] = el)}
|
||||||
|
key={message.id}
|
||||||
|
{...message}
|
||||||
|
// position={message.sender === 'me' ? 'right' : 'left'}
|
||||||
|
position={'left'}
|
||||||
|
replyButton={false}
|
||||||
|
onReplyMessageClick={() => scrollToMessage(message.reply.id)}
|
||||||
|
onOpen={() => handlePreview(message)}
|
||||||
|
onTitleClick={() => handlePreview(message)}
|
||||||
|
notch={false}
|
||||||
|
title={message.whatsapp_msg_type === 'text' ? '' : message.title}
|
||||||
|
text={<RenderText str={message?.text || ''} className={message.status === 'failed' ? 'line-through text-neutral-400' : ''} />}
|
||||||
|
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' : '',
|
||||||
|
focusMsg === message.id ? 'message-box-focus' : '',
|
||||||
|
message.status === 'failed' ? 'failed-msg' : '',
|
||||||
|
].join(' ')}
|
||||||
|
style={{
|
||||||
|
backgroundColor: message.sender === 'me' ? '#ccd4ae' : '#fff',
|
||||||
|
}}
|
||||||
|
{...(message.type === 'meetingLink'
|
||||||
|
? {
|
||||||
|
actionButtons: [
|
||||||
|
{
|
||||||
|
onClickButton: () => {
|
||||||
|
navigator.clipboard.writeText(message.text);
|
||||||
|
appMessage.success('复制成功😀');
|
||||||
|
},
|
||||||
|
Component: () => <div key='copy'>复制</div>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
renderAddCmp={
|
||||||
|
<div key={'msg-prefix'} 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.msg_direction === 'outbound' ? selectedConversation.OPI_Name : message.senderName}
|
||||||
|
</span>
|
||||||
|
<span>{message.dateString || message.localDate}</span>
|
||||||
|
<span>{message.statusCN}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
// date={null}
|
||||||
|
// status={null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default MessagesList;
|
@ -0,0 +1,40 @@
|
|||||||
|
import { ChatItem } from 'react-chat-elements';
|
||||||
|
import useFormStore from '@/stores/FormStore';
|
||||||
|
import { isNotEmpty } from '@/utils/commons';
|
||||||
|
|
||||||
|
const MessagesMatchList = ({ ...props }) => {
|
||||||
|
const [formValues] = useFormStore((state) => [state.chatHistoryForm]);
|
||||||
|
const [selectedConversation] = useFormStore((state) => [state.chatHistorySelectChat]);
|
||||||
|
const [selectMatch, setSelectedMatch] = useFormStore((state) => [state.msgHistorySelectMatch, state.setMsgHistorySelectMatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(selectedConversation.matchMsgList || []).length > 0 && isNotEmpty(formValues.search) && (
|
||||||
|
<div className='w-80 overflow-y-auto overflow-x-hidden'>
|
||||||
|
<p className='text-center'>
|
||||||
|
<mark>{formValues.search}</mark> 的相关记录, 点击定位上下文
|
||||||
|
</p>
|
||||||
|
{selectedConversation.matchMsgList.map((item) => (
|
||||||
|
<ChatItem
|
||||||
|
{...item}
|
||||||
|
key={item.sn}
|
||||||
|
id={item.sn}
|
||||||
|
letterItem={{
|
||||||
|
id: item.sender === 'me' ? selectedConversation.OPI_Name || item.senderName : item.senderName,
|
||||||
|
letter: (item.sender === 'me' ? selectedConversation.OPI_Name || item.senderName : item.senderName).split(' ')[0],
|
||||||
|
}}
|
||||||
|
alt={`${item.senderName}`}
|
||||||
|
title={item.sender === 'me' ? selectedConversation.OPI_Name || item.senderName : item.senderName}
|
||||||
|
subtitle={item.originText}
|
||||||
|
date={item.msgtime}
|
||||||
|
dateString={item.dateText}
|
||||||
|
className={String(item.sn) === String(selectMatch?.sn) ? '__active text-primary bg-neutral-100' : ' bg-white'}
|
||||||
|
onClick={() => setSelectedMatch(item)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default MessagesMatchList;
|
@ -0,0 +1,64 @@
|
|||||||
|
/* eslint-disable react/display-name */
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { Form, Flex, Input, Button, DatePicker } from 'antd';
|
||||||
|
import SearchInput from '@/components/SearchInput';
|
||||||
|
import { isNotEmpty } from '@/utils/commons';
|
||||||
|
import { fetchSalesAgent, fetchCustomerList } from '@/actions/CommonActions';
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
const SearchForm = memo(function ({ initialValues, onSubmit, onReset }) {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
function handleSubmit(values) {
|
||||||
|
const multiAgents = (values?.agent || []).map((ele) => ele.value).join(',');
|
||||||
|
const multiCustomers = (values?.customer || []).map((ele) => ele.value).join(',');
|
||||||
|
onSubmit?.({
|
||||||
|
...values,
|
||||||
|
opisn: multiAgents,
|
||||||
|
whatsapp_id: multiCustomers,
|
||||||
|
...(isNotEmpty(values.msgDateRange)
|
||||||
|
? {
|
||||||
|
from_date: values.msgDateRange[0].format('YYYY-MM-DD'),
|
||||||
|
end_date: values.msgDateRange[1].format('YYYY-MM-DD'),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Form layout={'inline'} form={form} initialValues={initialValues} onFinish={handleSubmit} style={{}}>
|
||||||
|
<Flex className='w-full'>
|
||||||
|
<Flex flex={'auto'} wrap='wrap' gap={4}>
|
||||||
|
<Form.Item label='发送人' name='agent' style={{ width: '200px' }} rules={[{ required: false, message: '请选择发送人' }]}>
|
||||||
|
<SearchInput placeholder='搜索发送人' fetchOptions={fetchSalesAgent} mode={'tags'} maxTagCount={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label='客人' name='customer' style={{ width: '200px' }}>
|
||||||
|
<SearchInput placeholder='搜索客人' fetchOptions={fetchCustomerList} mode={'tags'} maxTagCount={0} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label='订单号' name='coli_id'>
|
||||||
|
<Input placeholder='订单号' allowClear />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label='关键词' name='search'>
|
||||||
|
<Input placeholder='关键词' allowClear />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label='日期' name='msgDateRange'>
|
||||||
|
<RangePicker format={'YYYY-MM-DD'} />
|
||||||
|
</Form.Item>
|
||||||
|
</Flex>
|
||||||
|
<div style={{ flex: '0 1 64px' }} className='flex justify-between'>
|
||||||
|
{/* <Button
|
||||||
|
onClick={() => {
|
||||||
|
form.resetFields();
|
||||||
|
if (typeof onReset === 'function') {
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
重置
|
||||||
|
</Button> */}
|
||||||
|
<Button type='primary' htmlType='submit'>
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
export default SearchForm;
|
@ -0,0 +1,31 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState, useCallback } from 'react';
|
||||||
|
import { Divider, Layout } from 'antd';
|
||||||
|
import useFormStore from '@/stores/FormStore';
|
||||||
|
import SearchForm from '@/views/Conversations/History/SearchForm';
|
||||||
|
import ConversationsList from '@/views/Conversations/History/ConversationsList';
|
||||||
|
|
||||||
|
const { Sider, Content, Header } = Layout;
|
||||||
|
const History = (props) => {
|
||||||
|
const [formValues, setFormValues] = useFormStore((state) => [state.chatHistoryForm, state.setChatHistoryForm]);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback((values) => {
|
||||||
|
setFormValues({ ...values });
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div className='chathistory-wrapper chatwindow-wrapper'>
|
||||||
|
<SearchForm onSubmit={handleSubmit} initialValues={formValues} />
|
||||||
|
<ConversationsList />
|
||||||
|
{/* <Layout hasSider className='h-screen chathistory-wrapper chatwindow-wrapper' style={{ maxHeight: 'calc(100% - 279px)', height: 'calc(100% - 279px)' }}>
|
||||||
|
<Header
|
||||||
|
className='header px-2 h-8 border-0 border-b border-neutral-200 border-solid '
|
||||||
|
style={{ position: 'sticky', top: 0, zIndex: 1, width: '100%', background: 'white' }}>
|
||||||
|
<SearchForm onSubmit={handleSubmit} initialValues={formValues} />
|
||||||
|
</Header>
|
||||||
|
<Content style={{ maxHeight: 'calc(100vh - 279px)', height: 'calc(100vh - 279px)', minWidth: '360px' }}>
|
||||||
|
<ConversationsList />
|
||||||
|
</Content>
|
||||||
|
</Layout> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default History;
|
Loading…
Reference in New Issue