test: 标记未未读 todo: 新建会话

dev/timezone
Lei OT 1 year ago
parent e2f2fa44ed
commit 69cc8ccf6c

@ -82,6 +82,15 @@ export const fetchCleanUnreadMsgCount = async (params) => {
return errcode !== 0 ? {} : result; return errcode !== 0 ? {} : result;
}; };
/**
* 标记未未读
* @param {object} body conversationItem: { sn, ... }
*/
export const fetchConversationItemUnread = async (body) => {
const { errcode, result } = await fetchJSON(`${API_HOST}/set_state_unread`, body);
return errcode !== 0 ? {} : result;
};
/** /**
* ------------------------------------------------------------------------------------------------ * ------------------------------------------------------------------------------------------------
* 历史记录 * 历史记录
@ -105,6 +114,7 @@ export const fetchConversationsSearch = async (params) => {
OPI_Name: `${ele.OPI_Name || ele.opi_name || ''}`.trim(), OPI_Name: `${ele.OPI_Name || ele.opi_name || ''}`.trim(),
dateText: dayjs((ele.lasttime || ele.lasttime)).format('MM-DD HH:mm'), dateText: dayjs((ele.lasttime || ele.lasttime)).format('MM-DD HH:mm'),
matchMsgList: parseRenderMessageList((ele.msglist_AsJOSN || [])), // .reverse()), matchMsgList: parseRenderMessageList((ele.msglist_AsJOSN || [])), // .reverse()),
coli_id: '',
})); }));
return list; return list;
}; };

@ -158,6 +158,9 @@
height: 10px; height: 10px;
right: -9px; right: -9px;
} }
.chatwindow-wrapper .rce-citem-avatar{
border-bottom: 1px solid #0000000d;
}
.chatwindow-wrapper .rce-avatar-letter{ .chatwindow-wrapper .rce-avatar-letter{
margin-top: 0; margin-top: 0;
/* margin-left: 5px; */ /* margin-left: 5px; */

@ -1,15 +1,17 @@
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState, useRef } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { Dropdown, Input, Button, Empty, Tooltip } from 'antd'; import { Dropdown, Input, Button, Empty, Tooltip, Tag, Select } from 'antd';
import { PlusOutlined, WhatsAppOutlined, LoadingOutlined, HistoryOutlined, FireOutlined } from '@ant-design/icons'; import { PlusOutlined, WhatsAppOutlined, LoadingOutlined, HistoryOutlined, FireOutlined, MessageFilled, FilterOutlined, PlusSquareOutlined } from '@ant-design/icons';
import { fetchConversationsList, fetchOrderConversationsList, fetchConversationItemClose, fetchConversationsSearch, postNewConversationItem } from '@/actions/ConversationActions'; import { fetchConversationsList, fetchOrderConversationsList, fetchConversationItemClose, fetchConversationsSearch, postNewConversationItem, fetchConversationItemUnread } from '@/actions/ConversationActions';
import { ChatItem } from 'react-chat-elements'; import { ChatItem } from 'react-chat-elements';
import ConversationsNewItem from './ConversationsNewItem'; import ConversationsNewItem from './ConversationsNewItem';
import { isEmpty, olog } from '@/utils/commons'; import { isEmpty, olog } from '@/utils/commons';
import useConversationStore from '@/stores/ConversationStore'; import useConversationStore from '@/stores/ConversationStore';
import useAuthStore from '@/stores/AuthStore'; import useAuthStore from '@/stores/AuthStore';
import { useVisibilityState } from '@/hooks/useVisibilityState'; import { useVisibilityState } from '@/hooks/useVisibilityState';
import { OrderLabelDefaultOptions, OrderStatusDefaultOptions, RemindStateDefaultOptions } from '@/stores/OrderStore'
const { Option, OptGroup } = Select;
/** /**
* [] * []
*/ */
@ -27,6 +29,7 @@ const Conversations = ({ mobile }) => {
const [conversationsListLoading, setConversationsListLoading] = useConversationStore((state) => [state.conversationsListLoading, state.setConversationsListLoading]); const [conversationsListLoading, setConversationsListLoading] = useConversationStore((state) => [state.conversationsListLoading, state.setConversationsListLoading]);
const addToConversationList = useConversationStore((state) => state.addToConversationList); const addToConversationList = useConversationStore((state) => state.addToConversationList);
const delConversationitem = useConversationStore((state) => state.delConversationitem); const delConversationitem = useConversationStore((state) => state.delConversationitem);
const updateConversationItem = useConversationStore((state) => state.updateConversationItem);
const closedConversationsList = useConversationStore((state) => state.closedConversationsList); const closedConversationsList = useConversationStore((state) => state.closedConversationsList);
const setClosedConversationList = useConversationStore((state) => state.setClosedConversationList); const setClosedConversationList = useConversationStore((state) => state.setClosedConversationList);
@ -38,7 +41,7 @@ const Conversations = ({ mobile }) => {
async function refreshConversationList() { async function refreshConversationList() {
setConversationsListLoading(mobile !== undefined ? true : false); setConversationsListLoading(mobile !== undefined ? true : false);
const _list = await fetchConversationsList({opisn:userId}); const _list = await fetchConversationsList({ opisn: userId });
addToConversationList(_list); addToConversationList(_list);
setConversationsListLoading(false); setConversationsListLoading(false);
} }
@ -58,7 +61,6 @@ const Conversations = ({ mobile }) => {
return () => {}; return () => {};
}, [isVisible]); }, [isVisible]);
const [dataSource, setDataSource] = useState(conversationsList); const [dataSource, setDataSource] = useState(conversationsList);
useEffect(() => { useEffect(() => {
setDataSource(conversationsList); setDataSource(conversationsList);
@ -138,21 +140,46 @@ const Conversations = ({ mobile }) => {
setClosedConversationList(_clist); setClosedConversationList(_clist);
}; };
const handleConversationItemUnread = async (item) => {
// updateConversationItem({ sn: item.sn, unread_msg_count: 1000 });
// const bak_dataSource = dataSource;
// const targetIndex = bak_dataSource.findIndex((ele) => String(ele.sn) === String(item.sn));
// bak_dataSource.splice(targetIndex, 1, {
// ...conversationsList[targetIndex],
// ...{ sn: item.sn, unread_msg_count: 1000 },
// })
setDataSource((prev) =>
prev.map((ele) => {
return String(ele.sn) === String(item.sn) ? { ...ele, unread_msg_count: 1000 } : ele;
})
);
// setDataSource(bak_dataSource);
// setDataSource(conversationsList);
await fetchConversationItemUnread({ conversationid: item.sn });
refreshConversationList();
}
const searchInputRef = useRef(null); const searchInputRef = useRef(null);
const [searchContent, setSearchContent] = useState(''); const [searchContent, setSearchContent] = useState('');
const handleSearchConversations = (val) => { const handleSearchConversations = (val) => {
const fromSource = activeList ? conversationsList : closedConversationsList;
if (val.toLowerCase().trim() !== '') { if (val.toLowerCase().trim() !== '') {
const res = conversationsList.filter( const res = fromSource.filter(
(item) => (item.whatsapp_name.toLowerCase()).includes(val.toLowerCase().trim()) (item) =>
|| (item.whatsapp_phone_number.toLowerCase()).includes(val.toLowerCase().trim()) item.whatsapp_name.toLowerCase().includes(val.toLowerCase().trim()) ||
|| (item.coli_id.toLowerCase()).includes(val.toLowerCase().trim()) item.whatsapp_phone_number.toLowerCase().includes(val.toLowerCase().trim()) ||
item.coli_id.toLowerCase().includes(val.toLowerCase().trim())
); );
setDataSource(res); setDataSource(res);
return false; return false;
} }
setDataSource(conversationsList); setDataSource(fromSource);
}; };
const handleRichSearchConvs = (params) => {
// alert('1')
}
const [newChatModalVisible, setNewChatModalVisible] = useState(false); const [newChatModalVisible, setNewChatModalVisible] = useState(false);
const [newChatFormValues, setNewChatFormValues] = useState(); const [newChatFormValues, setNewChatFormValues] = useState();
const handleNewChat = async (values) => { const handleNewChat = async (values) => {
@ -168,7 +195,7 @@ const Conversations = ({ mobile }) => {
// setCurrentConversation(newItem); // setCurrentConversation(newItem);
// } // }
// setNewChatFormValues(values); // setNewChatFormValues(values);
} };
const [activeList, setActiveList] = useState(true); const [activeList, setActiveList] = useState(true);
// const closedVisible = closedConversationsList.length > 0; // const closedVisible = closedConversationsList.length > 0;
@ -177,16 +204,41 @@ const Conversations = ({ mobile }) => {
setDataSource(_active ? closedConversationsList : conversationsList); setDataSource(_active ? closedConversationsList : conversationsList);
setActiveList(!activeList); setActiveList(!activeList);
setCurrentConversation({}); setCurrentConversation({});
} };
const filterTags = [
{ color: 'volcano', label: '重点', value: '重点' },
{ color: 'success', label: '成行', value: '成行' },
{ color: 'gold', label: '老客户', value: '老客户' },
{ color: 'blue', label: '走团中', value: '走团中' },
{ color: 'red-inverse', label: '投诉', value: '投诉' },
];
/**
* todo: 弹出, 多选
*/
const filterTag = (
<Select defaultValue="" dropdownStyle={{ width: '10rem', maxHeight: 400, overflow: 'auto' }}>
<Option value="">所有</Option>
<OptGroup key={'label'} label={'标签'}>
{filterTags.map((item, index) => (<Option value={item.value} key={item.value}>{item.label}</Option>))}
</OptGroup>
<OptGroup key={'remind'} label={'提醒'}>
{RemindStateDefaultOptions.map((item, index) => (<Option value={item.value} key={item.value}>{item.label}</Option>))}
</OptGroup>
<OptGroup key={'stat'} label={'状态'}>
{OrderStatusDefaultOptions.map((item, index) => (<Option value={`state.${item.value}`} key={`state.${item.value}`}>{item.label}</Option>))}
</OptGroup>
</Select>
);
return ( return (
<div className='flex flex-col h-inherit'> <div className='flex flex-col h-inherit'>
<div className='flex gap-1'> <div className='flex gap-1'>
{[404, 383].includes(userId) && <Button onClick={() => setNewChatModalVisible(true)} icon={<PlusOutlined />} type={'primary'} />} {['404', 383].includes(userId) && <Button onClick={() => setNewChatModalVisible(true)} icon={<PlusSquareOutlined />} type={'text'} className='text-primary' />}
<Input.Search <Input.Search
className='' className=''
ref={searchInputRef} ref={searchInputRef}
onSearch={handleSearchConversations}
allowClear allowClear
value={searchContent} value={searchContent}
onChange={(e) => { onChange={(e) => {
@ -211,7 +263,11 @@ const Conversations = ({ mobile }) => {
setTabSelectedConversation({}); setTabSelectedConversation({});
return false; return false;
}} }}
placeholder={`搜索名称/号码/订单号${conversationsListLoading ? '...' : ''}`} placeholder={`名称/号码/订单号${conversationsListLoading ? '...' : ''}`}
onSearch={handleRichSearchConvs}
// addonBefore={filterTag}
// addonBefore={<FilterOutlined />}
// enterButton={'Filter'}
/> />
<Tooltip key={'conversation-list'} title={activeList ? '历史会话' : '活跃会话'}> <Tooltip key={'conversation-list'} title={activeList ? '历史会话' : '活跃会话'}>
<Button <Button
@ -232,12 +288,14 @@ const Conversations = ({ mobile }) => {
<Dropdown <Dropdown
key={item.sn} key={item.sn}
menu={{ menu={{
items: [{ label: '关闭会话', key: 'close', danger: true }], items: [{ label: '标记为未读', key: 'unread' }, { label: '关闭会话', key: 'close', danger: true }, ],
onClick: ({ key, domEvent }) => { onClick: ({ key, domEvent }) => {
domEvent.stopPropagation(); domEvent.stopPropagation();
switch (key) { switch (key) {
case 'close': case 'close':
return handleConversationItemClose(item); return handleConversationItemClose(item);
case 'unread':
return handleConversationItemUnread(item);
default: default:
return; return;
@ -245,24 +303,35 @@ const Conversations = ({ mobile }) => {
}, },
}} }}
trigger={['contextMenu']}> trigger={['contextMenu']}>
<ChatItem <div
{...item}
key={item.sn}
id={item.sn}
letterItem={{ id: item.whatsapp_name || item.whatsapp_phone_number, letter: (item.whatsapp_name || item.whatsapp_phone_number).slice(0, 5) }}
alt={item.whatsapp_name}
title={item.whatsapp_name || item.whatsapp_phone_number}
subtitle={item.coli_id}
date={item.last_received_time || item.last_send_time}
unread={item.unread_msg_count}
className={[ className={[
'border-0 border-t1 border-solid border-neutral-200',
String(item.sn) === String(currentConversation.sn) ? '__active text-primary bg-whatsapp-bg' : '', String(item.sn) === String(currentConversation.sn) ? '__active text-primary bg-whatsapp-bg' : '',
String(item.sn) === String(tabSelectedConversation?.sn) ? ' bg-neutral-200' : '', String(item.sn) === String(tabSelectedConversation?.sn) ? ' bg-neutral-200' : '',
].join(' ')} ].join(' ')}>
statusText={<WhatsAppOutlined key={'channel'} className='text-whatsapp' />} <div className='pl-4 pt-1 text-xs text-right'>
statusColor={'#fff'} {/* {filterTags.map((tag) => <Tag color={tag.color} key={tag.value}>{tag.label}</Tag>)} */}
onClick={() => onSwitchConversation(item)} </div>
/> <ChatItem
{...item}
key={item.sn}
id={item.sn}
letterItem={{ id: item.whatsapp_name || item.whatsapp_phone_number, letter: (item.whatsapp_name || item.whatsapp_phone_number).slice(0, 5) }}
alt={item.whatsapp_name}
title={item.whatsapp_name || item.whatsapp_phone_number}
subtitle={item.coli_id}
date={item.last_received_time || item.last_send_time}
unread={item.unread_msg_count > 99 ? 0 : item.unread_msg_count}
// className={[
// String(item.sn) === String(currentConversation.sn) ? '__active text-primary bg-whatsapp-bg' : '',
// String(item.sn) === String(tabSelectedConversation?.sn) ? ' bg-neutral-200' : '',
// ].join(' ')}
statusText={<WhatsAppOutlined key={'channel'} className='text-whatsapp' />}
statusColor={'#fff'}
onClick={() => onSwitchConversation(item)}
customStatusComponents={[...(item.unread_msg_count > 99 ? [() => <div className='w-4 h-4 bg-red-500 rounded-full' key={'unread'}></div>] : [])]}
/>
</div>
</Dropdown> </Dropdown>
))} ))}
{dataSource.length === 0 && <Empty description={'无数据'} />} {dataSource.length === 0 && <Empty description={'无数据'} />}

@ -13,7 +13,7 @@ export const ConversationItemForm = ({ initialValues, onFormInstanceReady }) =>
useEffect(() => { useEffect(() => {
onFormInstanceReady(form); onFormInstanceReady(form);
setFormatPhone(phoneNumberToWAID(initialValues.phone_number)); setFormatPhone(phoneNumberToWAID(initialValues.phone_number || ''));
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -42,20 +42,26 @@ export const ConversationItemForm = ({ initialValues, onFormInstanceReady }) =>
<Form.Item hidden name={'wa_id'} dependencies={['phone_number']}> <Form.Item hidden name={'wa_id'} dependencies={['phone_number']}>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item {initialValues.is_current_order && (
name={'coli_id'} <>
label={initialValues?.is_current_order ? '关联当前订单' : '关联订单'} <Form.Item
rules={[{ required: true, message: '关联的订单' }]} name={'coli_id'}
validateStatus='warning' label={'关联当前订单'}
help='请务必确认关联的订单是否正确'> rules={[{ required: true, message: '关联的订单' }]}
<Input placeholder='关联的订单' disabled={initialValues?.is_current_order || false} /> validateStatus='warning'
</Form.Item> help='请务必确认关联的订单是否正确'>
<Form.Item name={'coli_sn'} label='订单SN' hidden={initialValues?.is_current_order || false} rules={[{ required: true, message: '订单SN' }]}> <Input placeholder='关联的订单' disabled={initialValues.is_current_order} />
<Input placeholder='订单SN' /> </Form.Item>
</Form.Item> <Form.Item name={'coli_sn'} label='订单SN' hidden={initialValues.is_current_order} rules={[{ required: true, message: '订单SN' }]}>
{/* <Form.Item name={'name'} label='' rules={[{ required: true, message: '' }]}> <Input placeholder='订单SN' />
<Input placeholder='请输入联系人名称' /> </Form.Item>
</Form.Item> */} </>
)}
{!initialValues.is_current_order && (
<Form.Item name={'name'} label='联系人名称' rules={[{ required: true, message: '请输入联系人名称' }]}>
<Input placeholder='请输入联系人名称' />
</Form.Item>
)}
</Form> </Form>
); );
}; };

@ -85,7 +85,7 @@ function DesktopApp() {
<Link to='/order/chat'> <Link to='/order/chat'>
在线聊天 在线聊天
<Badge <Badge
count={totalNotify} count={totalNotify > 0 ? totalNotify : undefined}
style={{ style={{
backgroundColor: '#52c41a', backgroundColor: '#52c41a',
}} }}

Loading…
Cancel
Save