feat: 移动端

fix: 更新未读消息数;
style: 历史记录查询表单
perf: Emoji picker 使用native 避免下载图片
fix: 退出登录
perf: 历史记录: 会话显示Me的名字
dev/mobile
Lei OT 2 years ago
parent 6d3c40188c
commit 5b158f80e1

@ -87,6 +87,7 @@ export const fetchConversationsSearch = async (params) => {
...ele,
customer_name: `${ele.whatsapp_name || ''}`.trim(),
whatsapp_name: `${ele.whatsapp_name || ''}`.trim(),
OPI_Name: `${ele.OPI_Name || ''}`.trim(),
matchMsgList: parseRenderMessageList((ele.msglist_AsJOSN || [])), // .reverse()),
}));
return list;

@ -36,7 +36,7 @@ function DebounceSelect({ fetchOptions, debounceTimeout = 800, ...props }) {
optionFilterProp='label'
>
{options.map((d) => (
<Select.Option key={d.value} title={d.label}>
<Select.Option key={`${d.value}${d.label}`} title={d.label}>
{d.label}
</Select.Option>
))}

@ -29,7 +29,7 @@ const router = createBrowserRouter([
element: <AuthApp />,
errorElement: <ErrorPage />,
children: [
{
{
element: <DesktopApp />,
children: [
{ index: true, element: <OrderFollow /> },
@ -41,11 +41,12 @@ const router = createBrowserRouter([
{ path: 'account/profile', element: <AccountProfile /> },
]
},
{
path: 'm',
{
path: 'm',
element: <MobileApp />,
children: [
{ path: 'conversation', element: <MobileConversation /> },
{ path: 'chat/:order_sn', element: <MobileChat /> },
{ path: 'chat', element: <MobileChat /> },
]
},

@ -32,12 +32,16 @@ const initialConversationState = {
// templates: [],
// conversationsList: [], // 对话列表
// currentConversation: {}, // 当前对话
conversationsList: [], // 对话列表
currentConversation: {}, // 当前对话
// activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: <messageItem>[] }
activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: <messageItem>[] }
// referenceMsg: {},
referenceMsg: {},
complexMsg: {},
totalNotify: 0,
msgListLoading: false,
};
@ -219,6 +223,7 @@ const messageSlice = (set, get) => ({
totalNotify: 0,
msgListLoading: false,
activeConversations: {},
refreshTotalNotify: () => set((state) => ({ totalNotify: state.conversationsList.reduce((r, c) => r+c.unread_msg_count, 0) })),
setMsgLoading: (msgListLoading) => set({ msgListLoading }),
receivedMessageList: (conversationid, msgList) =>
set((state) => ({
@ -343,15 +348,9 @@ export const useConversationStore = create(
setInitial(true);
// const autoGetMsgs = conversationsList.length > 5 ? 5 : conversationsList.length;
// for (let index = 0; index < autoGetMsgs; index++) {
// const chatItem = conversationsList[index];
// const msgData = await fetchMessages({ opisn: chatItem.opi_sn, whatsappid: chatItem.whatsapp_phone_number });
// receivedMessageList(chatItem.sn, msgData);
// }
// for (const chatItem of conversationsList) {
// }
},
reset: () => set(initialConversationState),
}))
);

@ -52,7 +52,8 @@ function AuthApp() {
theme={{
token: {
colorPrimary: colorPrimary,
borderRadius: borderRadius
borderRadius: borderRadius,
fontFamily: "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Noto Color Emoji','Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol'",
},
algorithm: theme.defaultAlgorithm,
}}

@ -35,32 +35,33 @@ const SearchForm = memo(function ({ initialValues, onSubmit }) {
});
}
return (
<Form
layout={'inline'}
form={form}
initialValues={initialValues}
onFinish={handleSubmit}
style={{}}>
<Form.Item label='顾问' name='agent' style={{ width: '200px' }} rules={[{required: true, 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>
<Form.Item>
<Button type='primary' htmlType='submit'>
搜索
</Button>
</Form.Item>
<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>
);
});
@ -89,7 +90,7 @@ function ChatHistory() {
const allEmpty = Object.values(cloneDeep(formValues)).every((val) => {
return val === null || val === '' || val === undefined;
});
if (allEmpty) return;
// if (allEmpty) return;
setConversationsListLoading(true);
setChatItemMessages([]);
setParamsForMsgList({});
@ -224,11 +225,11 @@ function ChatHistory() {
return prev;
}, []);
return (
<span className={`text-base leading-5 emoji-text ${className} ${extraClass} `}>
<span className={`text-sm leading-5 emoji-text ${className} ${extraClass} `}>
{(objArr || []).map((part, index) => {
if (part.type === 'link') {
return (
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-base'>
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-sm'>
{part.key}
</a>
);
@ -284,8 +285,9 @@ function ChatHistory() {
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.coli_id}
subtitle={`${item.OPI_Name || ''} ${item.coli_id || ''}`}
date={item.last_received_time}
// dateString={item.last_received_time}
className={String(item.conversationid) === String(selectedConversation.conversationid) ? '__active text-primary bg-neutral-100' : ''}
onClick={() => setSelectedConversation(item)}
/>
@ -300,7 +302,7 @@ function ChatHistory() {
{...item}
key={item.sn}
id={item.sn}
letterItem={{ id: item.senderName, letter: (item.senderName).split(" ")[0] }}
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.senderName}
subtitle={item.originText}
@ -362,7 +364,7 @@ function ChatHistory() {
: {})}
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 className={`p-1 rounded-b ${message.msg_direction === 'outbound' ? 'text-white' : ''} `} style={{backgroundColor: message.msg_direction === 'outbound' ? stringToColour(message.senderName) : 'unset'}}>{selectedConversation.OPI_Name || message.senderName}</span>
<span>{message.dateString || message.localDate}</span>
<span>{message.statusCN}</span>
</div>

@ -10,7 +10,8 @@ import useAuthStore from '@/stores/AuthStore';
/**
* []
*/
const Conversations = () => {
const Conversations = ({ mobile }) => {
const routerReplace = mobile === undefined ? false : true;
const { state: orderRow } = useLocation();
const { coli_guest_WhatsApp } = orderRow || {};
const { order_sn } = useParams();
@ -22,8 +23,6 @@ const Conversations = () => {
const conversationsList = useConversationStore((state) => state.conversationsList);
const addToConversationList = useConversationStore((state) => state.addToConversationList);
const delConversationitem = useConversationStore((state) => state.delConversationitem);
const receivedMessageList = useConversationStore((state) => state.receivedMessageList);
const setMsgLoading = useConversationStore((state) => state.setMsgLoading);
const [tabSelectedConversation, setTabSelectedConversation] = useState({});
const [tabCnt, setTabCnt] = useState(-1);
@ -70,34 +69,16 @@ const Conversations = () => {
return false;
}
};
const getMessages = async (item) => {
setMsgLoading(true);
const data = await fetchMessages({ opisn: userId, whatsappid: item.whatsapp_phone_number, lasttime: '' });
setMsgLoading(false);
receivedMessageList(item.sn, data);
const thisLastTime = data.length > 0 ? data[0].orgmsgtime : '';
const loadNextPage = !(data.length === 0 || data.length < MESSAGE_PAGE_SIZE);
updateCurrentConversation({ lasttime: thisLastTime, loadNextPage });
};
useEffect(() => {
const messagesList = activeConversations[`${currentConversation.sn}`] || [];
if (currentConversation.sn && messagesList.length < 20) {
getMessages(currentConversation);
}
return () => {};
}, [currentConversation.sn]);
const onSwitchConversation = async (item) => {
setCurrentConversation(item);
const routePrefix = mobile === undefined ? `/order/chat` : `/m/chat`;
if (isEmpty(item.coli_sn)) {
navigate(`/order/chat`, { replace: true });
navigate(routePrefix, { replace: true });
} else {
setSwitchToC(item);
setShouldFetchCList(false);
navigate(`/order/chat/${item.coli_sn}`, { replace: true });
navigate(`${routePrefix}/${item.coli_sn}`, { replace: routePrefix });
}
// if (!isEmpty(item.coli_sn)) {
// setSwitchToC(item);
@ -113,7 +94,7 @@ const Conversations = () => {
await fetchConversationItemClose({ conversationid: item.sn, opisn: item.opi_sn });
delConversationitem(item);
if (String(order_sn) === String(item.coli_sn)) {
navigate(`/order/chat`, { replace: true });
navigate(`/order/chat`, { replace: routerReplace });
}
};

@ -2,7 +2,7 @@ import { useState } from 'react';
import { Popover, Button } from 'antd';
import EmojiPicker from 'emoji-picker-react';
const InputTemplate = ({ disabled = false, inputEmoji }) => {
const InputTemplate = ({ mobile, disabled = false, inputEmoji }) => {
const [openPopup, setOpenPopup] = useState(false);
const handlePickEmoji = (emojiData) => {
@ -13,16 +13,16 @@ const InputTemplate = ({ disabled = false, inputEmoji }) => {
<>
<Popover
overlayClassName='p-0'
placement={'right'}
placement={mobile === undefined ? 'right' : 'top'}
overlayInnerStyle={{ padding: 0, borderRadius: '8px' }}
forceRender={true}
content={<EmojiPicker skinTonesDisabled={true} emojiStyle='google' onEmojiClick={handlePickEmoji} />}
// title='😀'
content={<EmojiPicker skinTonesDisabled={true} emojiStyle='native' onEmojiClick={handlePickEmoji} className='chatwindow-wrapper' />}
// title='😃'
trigger='click'
open={openPopup}
onOpenChange={setOpenPopup}>
<Button type='text' className=' px-1' size={'middle'} disabled={disabled}>
😀
😃
</Button>
</Popover>
</>

@ -20,7 +20,7 @@ const splitTemplate = (template) => {
}, []);
return obj;
};
const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
const InputTemplate = ({ mobile, disabled = false, invokeSendMessage }) => {
const searchInputRef = useRef(null);
const { notification } = App.useApp();
const loginUser = useAuthStore((state) => state.loginUser);
@ -126,7 +126,7 @@ const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
return (
<>
<Popover
overlayClassName='w-3/5'
overlayClassName={[mobile === undefined ? 'w-3/5' : 'w-full'].join(' ')}
fresh
forceRender
destroyTooltipOnHide={true}

@ -46,14 +46,14 @@ const fileTypesExt = {
audio: ['aac', 'mp4', 'm4a', 'mp3', 'amr', 'ogg'],
};
const InputComposer = () => {
const userId = useAuthStore(state => state.loginUser.userId);
const websocket = useConversationStore(state => state.websocket);
const websocketOpened = useConversationStore(state => state.websocketOpened);
const currentConversation = useConversationStore(state => state.currentConversation);
const [referenceMsg, setReferenceMsg] = useConversationStore(state => [state.referenceMsg, state.setReferenceMsg]);
const [complexMsg, setComplexMsg] = useConversationStore(state => [state.complexMsg, state.setComplexMsg]);
const sentOrReceivedNewMessage = useConversationStore(state => state.sentOrReceivedNewMessage);
const InputComposer = ({ mobile }) => {
const userId = useAuthStore((state) => state.loginUser.userId);
const websocket = useConversationStore((state) => state.websocket);
const websocketOpened = useConversationStore((state) => state.websocketOpened);
const currentConversation = useConversationStore((state) => state.currentConversation);
const [referenceMsg, setReferenceMsg] = useConversationStore((state) => [state.referenceMsg, state.setReferenceMsg]);
const [complexMsg, setComplexMsg] = useConversationStore((state) => [state.complexMsg, state.setComplexMsg]);
const sentOrReceivedNewMessage = useConversationStore((state) => state.sentOrReceivedNewMessage);
const talkabled = !isEmpty(currentConversation.sn) && websocketOpened;
const gt24h = currentConversation.last_received_time ? dayjs().diff(dayjs(currentConversation.last_received_time), 'hour') > 24 : true;
@ -120,15 +120,14 @@ const InputComposer = () => {
const contentToSend = sentMsgTypeMapped[msgObjMerge.type].contentToSend(msgObjMerge);
// olog('invoke upload send +++ ', contentToSend)
websocket.sendMessage({ ...contentToSend, opi_sn: userId, coli_sn: currentConversation.coli_sn });
}
};
const { message } = App.useApp();
const [pastedUploading, setPastedUploading] = useState(false);
const readPasted = async (file, rename = false) => {
// 使 FileReader
const reader = new FileReader();
const suffix = file.name.slice(file.name.lastIndexOf('.')+1);
const suffix = file.name.slice(file.name.lastIndexOf('.') + 1);
const newName = `${uuid()}.${suffix}`; // rename ? `${uuid()}.${suffix}` : file.name;
const type = Object.keys(fileTypesExt).find((type) => fileTypesExt[type].includes(suffix));
const dataUri = aliOSSHost + newName;
@ -161,7 +160,7 @@ const InputComposer = () => {
let isNotFile = true;
for (let i = 0; i < items.length; i++) {
// if (items[i].type.indexOf("image") !== -1) {
if (items[i].kind.indexOf("file") !== -1) {
if (items[i].kind.indexOf('file') !== -1) {
isNotFile = false;
tmpfile = items[i].getAsFile();
break;
@ -180,17 +179,17 @@ const InputComposer = () => {
setComplexMsg(_tmpFile.msgData);
setPastedUploading(true);
const { file_url } = await postUploadFileItem(tmpfile, _tmpFile.newName);
setPastedUploading(false);
setComplexMsg({..._tmpFile.msgData, uploadStatus: file_url ? 'done' : 'error'});
setPastedUploading(false); // todo: data uri
setComplexMsg({ ..._tmpFile.msgData, uploadStatus: file_url ? 'done' : 'error' });
return;
}
};
const focusInput = () => {
textInputRef.current.focus({ cursor: 'end', preventScroll: true, });
textInputRef.current.focus({ cursor: 'end', preventScroll: true });
};
const addEmoji = emoji => {
setTextContent(prevValue => {
const addEmoji = (emoji) => {
setTextContent((prevValue) => {
return prevValue + emoji;
});
};
@ -248,7 +247,9 @@ const InputComposer = () => {
<Input.TextArea
onPaste={handlePaste}
ref={textInputRef}
size='large' maxLength={2000} showCount={textabled}
size='large'
maxLength={2000}
showCount={textabled}
placeholder={gt24h ? 'This session has expired. Please send a template message to activate the session' : 'Enter 发送, Shift+Enter 换行\n支持复制粘贴 [截图/文件] 以备发送'}
rows={2}
disabled={!textabled}
@ -265,8 +266,8 @@ const InputComposer = () => {
/>
<Flex justify={'space-between'} className=' bg-gray-200 p-1 rounded-b'>
<Flex gap={4} className='*:text-primary *:rounded-none'>
<InputTemplate key='templates' disabled={!talkabled || textabled} invokeSendMessage={invokeSendMessage} />
<InputEmoji key='emoji' disabled={!textabled} inputEmoji={addEmoji} />
<InputTemplate key='templates' disabled={!talkabled || textabled} invokeSendMessage={invokeSendMessage} {...{ mobile }} />
<InputEmoji key='emoji' disabled={!textabled} inputEmoji={addEmoji} {...{ mobile }} />
<InputMediaUpload key={'addNewMedia'} disabled={!textabled} {...{ invokeUploadFileMessage, invokeSendUploadMessage }} />
{/* <Button type='text' className='' icon={<YoutubeOutlined />} size={'middle'} />
<Button type='text' className='' icon={<AudioOutlined />} size={'middle'} />

@ -45,11 +45,11 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
return prev;
}, []);
return (
<span className={`text-base leading-5 emoji-text whitespace-pre-wrap ${className} ${extraClass}`}>
<span className={`text-sm leading-5 emoji-text whitespace-pre-wrap ${className} ${extraClass}`}>
{(objArr || []).map((part, index) => {
if (part.type === 'link') {
return (
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-base'>
<a href={part.key} target='_blank' key={`${part.key}${index}`} rel='noreferrer' className='text-sm'>
{part.key}
</a>
);

@ -6,21 +6,33 @@ import MessagesList from './MessagesList';
import { fetchCleanUnreadMsgCount, fetchMessages, MESSAGE_PAGE_SIZE } from '@/actions/ConversationActions';
import { fetchOrderConversationsList, } from '@/actions/ConversationActions';
import { isEmpty } from '@/utils/utils';
import useAuthStore from '@/stores/AuthStore';
const MessagesWrapper = () => {
const userId = useAuthStore((state) => state.loginUser.userId);
const [currentConversation, updateCurrentConversation, setCurrentConversation] = useConversationStore(useShallow((state) => [state.currentConversation, state.updateCurrentConversation, state.setCurrentConversation]));
const conversationsList = useConversationStore(useShallow((state) => state.conversationsList));
const activeMessages = useConversationStore(useShallow((state) => (state.currentConversation.sn && state.activeConversations[state.currentConversation.sn] ? state.activeConversations[state.currentConversation.sn]: [])));
const addToConversationList = useConversationStore((state) => state.addToConversationList);
const receivedMessageList = useConversationStore((state) => state.receivedMessageList);
const setMsgLoading = useConversationStore((state) => state.setMsgLoading);
const refreshTotalNotify = useConversationStore(useShallow((state) => state.refreshTotalNotify));
const [longList, setLongList] = useState([]);
const [longListLoading, setLongListLoading] = useState(false);
const [shouldScrollBottom, setShouldScrollBottom] = useState(true);
useEffect(() => {
setLongList(activeMessages);
setShouldScrollBottom(true);
if (currentConversation.sn && activeMessages.length < 20) {
getFirstPageMessages(currentConversation);
}
if (currentConversation.opi_sn && currentConversation.whatsapp_phone_number && activeMessages.length > 0) {
fetchCleanUnreadMsgCount({ opisn: currentConversation.opi_sn, whatsappid: currentConversation.whatsapp_phone_number });
refreshTotalNotify();
}
const thisLastTime = activeMessages.length > 0 ? activeMessages[0].orgmsgtime : '';
const loadNextPage = !(activeMessages.length === 0 || activeMessages.length < MESSAGE_PAGE_SIZE);
@ -29,6 +41,17 @@ const MessagesWrapper = () => {
return () => {};
}, [activeMessages, currentConversation.sn]);
const getFirstPageMessages = async (item) => {
setMsgLoading(true);
const data = await fetchMessages({ opisn: userId, whatsappid: item.whatsapp_phone_number, lasttime: '' });
setMsgLoading(false);
receivedMessageList(item.sn, data);
const thisLastTime = data.length > 0 ? data[0].orgmsgtime : '';
const loadNextPage = !(data.length === 0 || data.length < MESSAGE_PAGE_SIZE);
updateCurrentConversation({ lasttime: thisLastTime, loadNextPage });
};
const getMoreMessages = async () => {
setShouldScrollBottom(false);
setLongListLoading(true);

@ -122,9 +122,10 @@
.chatwindow-wrapper .referrer-msg,
.chatwindow-wrapper .rce-mbox-reply-message,
.chatwindow-wrapper .emoji,
.chatwindow-wrapper .epr-emoji-native,
.chatwindow-wrapper .ant-input-textarea-affix-wrapper.ant-input-affix-wrapper >textarea.ant-input
{
font-family: 'Open Sans', 'Noto Sans',"Noto Color Emoji", 'Apple Color Emoji', 'Twemoji Mozilla', 'Segoe UI Emoji', 'Segoe UI Symbol', 'EmojiOne Color', 'Android Emoji', Arial, sans-serif;
font-family: 'Open Sans', 'Noto Sans',"Noto Color Emoji", 'Apple Color Emoji', 'Twemoji Mozilla', 'Segoe UI Emoji', 'Segoe UI Symbol', 'EmojiOne Color', 'Android Emoji', Arial, sans-serif!important;
font-weight: 400;
}
.chatwindow-wrapper .rce-mbox-text a{
@ -182,8 +183,11 @@
{
display: inline-block;
}
.chatwindow-wrapper .rce-mbox-file>button{
width: 100%;
}
/** Chat history */
/** Chat history ------------------------------------------------------------------------------------ */
/* .chathistory-wrapper .rce-mbox-time {
user-select: auto;
-webkit-user-select: auto;
@ -214,3 +218,15 @@
animation-name: message-box-default-focus-x;
animation-duration: 1s;
}
/**
* Mobile chat ------------------------------------------------------------------------------------
*/
.mobilechat-wrapper .rce-container-mbox .rce-mbox{
max-width: 320px;
}
.mobilechat-wrapper .rce-mtlink,
.mobilechat-wrapper .rce-mtlink-item{
min-width: unset;
}

@ -41,7 +41,7 @@ function MobileApp() {
<Outlet />
</Content>
</Layout>
<Footer>桂林海纳国际旅行社有限公司</Footer>
{/* <Footer>桂林海纳国际旅行社有限公司</Footer> */}
</Layout>
)
}

@ -1,4 +1,5 @@
import useAuthStore from '@/stores/AuthStore'
import useConversationStore from '@/stores/ConversationStore'
import { Flex, Result, Spin, Typography } from 'antd'
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
@ -7,11 +8,13 @@ import { useNavigate } from 'react-router-dom'
function Logout() {
const navigate = useNavigate()
const { logout } = useAuthStore()
const logout = useAuthStore(state => state.logout)
const reset = useConversationStore((state) => state.reset);
useEffect(() => {
logout()
logout()
reset();
navigate('/p/dingding/qrcode')
}, [])
@ -29,4 +32,4 @@ function Logout() {
)
}
export default Logout
export default Logout

@ -1,68 +1,26 @@
import { memo, useCallback, useEffect, useRef, useState, forwardRef } from 'react'
import { App, Avatar, List, Layout, Input, DatePicker, Button, Spin } from 'antd'
import { ChatItem, MessageBox } from 'react-chat-elements'
import { fetchConversationsList, fetchMessages, MESSAGE_PAGE_SIZE } from '@/actions/ConversationActions'
import { isEmpty } from '@/utils/utils'
import useFormStore from '@/stores/FormStore'
import { useShallow } from 'zustand/react/shallow'
import { Layout } from 'antd';
import MessagesHeader from '@/views/Conversations/Components/MessagesHeader';
import MessagesWrapper from '@/views/Conversations/Components/MessagesWrapper';
import InputComposer from '@/views/Conversations/Components/InputComposer';
import { RightOutlined } from '@ant-design/icons'
import { fetchSalesAgent, fetchCustomerList } from '@/actions/CommonActions'
import SearchInput from '@/components/SearchInput'
const { Sider, Content, Header, Footer } = Layout
const { TextArea } = Input
const { RangePicker } = DatePicker
const data = [
{
title: 'Tyler Dru Kelly',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'Hi Nazly Parlindungan Siregar, this is Sharon , travel advisor of Asia Highlights. We got your inquiry for your trip toJapan , are you available for a quick chat to discuss about your trip? I have some ideas to share with you . Looking forward to talking with you!',
},
{
title: 'Chhavi',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'Hi Sharon, thanks for reaching out. I am extremely busy person, please feel free to write down or send me any note and I will respond to you immediately. Cheers🙏',
},
{
title: 'Nathan Posey',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'It is too late to see cherry blossom from April 28, is it ok for you? For your 7 days tour, visit Tokyo and Kyoto is ok. I will suggest a tour with private car for you because your mother cannot walk too much.',
},
]
const { Content, Header, Footer } = Layout;
function Chat() {
const { notification } = App.useApp()
return (
<>
<List
itemLayout='horizontal'
dataSource={data}
renderItem={(item, index) => (
<List.Item>
<List.Item.Meta
avatar={<Avatar src={item.avatarUrl} />}
title={<a href='https://ant.design'>{item.title}</a>}
description={item.msgTime}
/>
</List.Item>
)}
/>
<TextArea rows={4} placeholder='聊天窗口' maxLength={2000} />
<Button type='primary' onClick={() => {
notification.info({
message: '温馨提示',
description: '功能还在开发中,敬请期待',
placement: 'top',
duration: 60,
})
}}>发送</Button>
<Layout className='h-full chatwindow-wrapper mobilechat-wrapper' style={{ maxHeight: 'calc(100vh - 84px)', height: 'calc(100vh - 84px)', minWidth: '360px' }}>
<Header className='ant-layout-sider-light ant-card h-auto flex justify-between gap-1 items-center'>
<MessagesHeader />
</Header>
<Content className='flex-grow bg-whatsapp-bg relative'>
<MessagesWrapper />
</Content>
<Footer className='ant-layout-sider-light p-0'>
<InputComposer mobile />
</Footer>
</Layout>
</>
)
);
}
export default Chat
export default Chat;

@ -1,90 +1,12 @@
import { memo, useCallback, useEffect, useRef, useState, forwardRef } from 'react'
import { Avatar, List, Button, Input, Layout, Select, DatePicker, Form, Spin } from 'antd'
import { ChatItem, MessageBox } from 'react-chat-elements'
import { fetchConversationsList, fetchMessages, MESSAGE_PAGE_SIZE } from '@/actions/ConversationActions'
import { isEmpty } from '@/utils/utils'
import useFormStore from '@/stores/FormStore'
import { useShallow } from 'zustand/react/shallow'
import { useNavigate, useHref } from 'react-router-dom'
import ConversationsList from '@/views/Conversations/Components/ConversationsList';
import { RightOutlined } from '@ant-design/icons'
import { fetchSalesAgent, fetchCustomerList } from '@/actions/CommonActions'
import SearchInput from '@/components/SearchInput'
const { Sider, Content, Header, Footer } = Layout
const { Search } = Input
const { RangePicker } = DatePicker
const data = [
{
title: 'Tyler Dru Kelly',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: '15127570944',
},
{
title: 'Chhavi',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'WLJ240311112',
},
{
title: 'Nathan Posey',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'WLJ240311114',
},
{
title: 'Philip',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'WLJ240312062',
},
{
title: 'Jeanne',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'H240313032',
},
{
title: 'Susan Puls',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'H240311213',
},
{
title: 'Ana Beatriz',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'H240312073',
},
{
title: 'Kathleen Anne Workman',
avatarUrl: 'https://api.dicebear.com/7.x/miniavs/svg?seed=' + Math.random() * 10000,
msgTime: 'HXY240104171',
},
]
function Login() {
const navigate = useNavigate()
function Conversation() {
return (
<>
<List
itemLayout='horizontal'
dataSource={data}
renderItem={(item, index) => (
<List.Item
onClick={() => {
navigate('/m/chat')}}
actions={[<RightOutlined key='goto' />]}
>
<List.Item.Meta
avatar={<Avatar src={item.avatarUrl} />}
title={<a href='https://ant.design'>{item.title}</a>}
description={item.msgTime}
/>
</List.Item>
)}
/>
</>
<div className='chatwindow-wrapper'>
<ConversationsList mobile={true} />
</div>
)
}
export default Login
export default Conversation

Loading…
Cancel
Save