feat: 会话窗口

dev/chat
Lei OT 1 year ago
parent 5d83ce7662
commit 807edcb74f

@ -4,22 +4,26 @@ import { useGetJson } from '@/hooks/userFetch';
export const ConversationContext = createContext(); export const ConversationContext = createContext();
const API_HOST = 'http://127.0.0.1:4523/m2/3888351-0-default'; // local mock
const URL = { const URL = {
conversationList: 'http://127.0.0.1:4523/m2/3888351-0-default/142426823', conversationList: `${API_HOST}/142426823`,
templates: `${API_HOST}/142952738`,
}; };
const WS_URL = 'ws://202.103.68.144:8888/whatever/'; // const WS_URL = 'ws://202.103.68.144:8888/whatever/';
const WS_URL = 'ws://202.103.68.157:8888/whatever/';
// let realtimeAPI = new RealTimeAPI({ url: URL, protocol: 'aaa' }); // let realtimeAPI = new RealTimeAPI({ url: URL, protocol: 'aaa' });
let realtimeAPI = new RealTimeAPI({ url: WS_URL, protocol: 'WhatsApp' }); let realtimeAPI = new RealTimeAPI({ url: WS_URL, protocol: 'WhatsApp' });
export const useConversations = () => { export const useConversations = () => {
const [errors, setErrors] = useState([]); const [errors, setErrors] = useState([]);
const [messages, setMessages] = useState([]); const [messages, setMessages] = useState([]); // 页面上激活的对话
const [conversations, setConversations] = useState({}); const [conversations, setConversations] = useState({}); // 所有对话
const [currentID, setCurrentID] = useState(); const [currentID, setCurrentID] = useState();
const [conversationsList, setConversationsList] = useState([]); const [conversationsList, setConversationsList] = useState([]); // 对话列表
const [currentConversation, setCurrentConversation] = useState({ const [currentConversation, setCurrentConversation] = useState({
id: '', name: '' id: '', name: ''
}); });
const [templates, setTemplates] = useState([]);
const [url, setUrl] = useState(URL.conversationList); const [url, setUrl] = useState(URL.conversationList);
const data = useGetJson(url); const data = useGetJson(url);
@ -36,10 +40,17 @@ export const useConversations = () => {
return () => {}; return () => {};
}, [data]); }, [data]);
const getTemplates = () => {
setUrl(null); // reset url
setUrl(URL.templates);
}
const switchConversation = (cc) => { const switchConversation = (cc) => {
console.log('switch to ', cc.id, cc); console.log('switch to ', cc.id, cc);
setCurrentID(cc.id); setCurrentID(cc.id);
setCurrentConversation(cc); setCurrentConversation(cc);
setMessages(conversations[cc.id] || []);
}; };
const addError = (reason) => { const addError = (reason) => {
@ -55,9 +66,12 @@ export const useConversations = () => {
const addMessage = (message) => { const addMessage = (message) => {
setMessages((prevMessages) => [...prevMessages, message]); setMessages((prevMessages) => [...prevMessages, message]);
addMessageToConversations(currentConversation.id, message);
}; };
const handleMessage = (data) => { const handleMessage = (data) => {
const { errmsg, result: msgObj } = data;
const msg = data.result; const msg = data.result;
if (!msg) { if (!msg) {
return false; return false;
@ -88,7 +102,11 @@ export const useConversations = () => {
realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server')); realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server'));
realtimeAPI.keepAlive(); // Ping Server realtimeAPI.keepAlive(); // Ping Server
return { errors, messages, conversationsList, currentConversation, sendMessage, fetchConversations, switchConversation }; return {
errors, messages, conversationsList, currentConversation, sendMessage,
fetchConversations, switchConversation,
templates, setTemplates, getTemplates,
};
} }
export const useConversationContext = () => useContext(ConversationContext); export const useConversationContext = () => useContext(ConversationContext);

@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Layout, List, Avatar } from 'antd'; import { Layout, List, Avatar, Flex, Typography } from 'antd';
import Messages from './Components/Messages'; import Messages from './Components/Messages';
import InputBox from './Components/InputBox'; import InputBox from './Components/InputBox';
import ConversationsList from './Components/ConversationsList'; import ConversationsList from './Components/ConversationsList';
@ -9,7 +9,7 @@ import CustomerProfile from './Components/CustomerProfile';
import { useConversationContext } from '@/stores/ConversationContext'; import { useConversationContext } from '@/stores/ConversationContext';
import './Conversations.css'; import './Conversations.css';
import { useAuthContext } from '@/stores/AuthContext.js' import { useAuthContext } from '@/stores/AuthContext.js';
const { Sider, Content, Header, Footer } = Layout; const { Sider, Content, Header, Footer } = Layout;
@ -23,19 +23,25 @@ const CList = [
* *
*/ */
const ChatWindow = observer(() => { const ChatWindow = observer(() => {
const { loginUser: currentUser } = useAuthContext() const { loginUser: currentUser } = useAuthContext();
const { errors, messages, sendMessage, currentConversation } = useConversationContext(); const { errors, messages, sendMessage, currentConversation } = useConversationContext();
return ( return (
<Layout className='full-height' style={{ maxHeight: 'calc(100% - 150px)', height: 'calc(100% - 150px)' }}> <Layout className='full-height' style={{ maxHeight: 'calc(100% - 150px)', height: 'calc(100% - 150px)' }}>
<Sider width={220} theme={'light'} className='scrollable-column' style={{ height: '70vh' }}> <Sider width={240} theme={'light'} className='scrollable-column' style={{ height: '70vh' }}>
<ConversationsList /> <ConversationsList />
</Sider> </Sider>
<Content className='h70' style={{ maxHeight: '70vh', height: '70vh' }}> <Content className='h70' style={{ maxHeight: '70vh', height: '70vh' }}>
<Layout style={{ height: '100%' }}> <Layout style={{ height: '100%' }}>
<Header className='ant-layout-sider-light ant-card' style={{ paddingLeft: '10px' }}> <Header className='ant-layout-sider-light ant-card' style={{ padding: '10px', height: 'auto' }}>
<div>{currentConversation.name}</div> <Flex gap={16}>
<Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${currentConversation.name}`}>{currentConversation.name}</Avatar>
<Flex vertical={true} justify='space-between'>
<Typography.Text strong>{currentConversation.name}</Typography.Text>
{/* <div> HXY231119017</div> */}
</Flex>
</Flex>
{/* <List {/* <List
dataSource={[]} dataSource={[]}
renderItem={(item, ii) => ( renderItem={(item, ii) => (

@ -1,30 +1,48 @@
import { useRef, useEffect, useState } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { List, Avatar } from 'antd'; import { List, Avatar, Flex } from 'antd';
import { useConversationContext } from '@/stores/ConversationContext'; import { useConversationContext } from '@/stores/ConversationContext';
import { ChatItem, ChatList } from 'react-chat-elements';
/** /**
* [] * []
*/ */
const Conversations = observer(({ conversations }) => { const Conversations = observer(({ conversations }) => {
const { switchConversation, conversationsList } = useConversationContext() const { switchConversation, conversationsList } = useConversationContext();
console.log(conversationsList); console.log(conversationsList);
const [chatlist, setChatlist] = useState([]);
useEffect(() => {
setChatlist(
(conversationsList || []).map((item) => ({
...item,
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${item.name}`,
alt: item.name,
title: item.name,
subtitle: item.lastMessage,
date: item.last_time,
unread: item.new_msgs,
}))
);
return () => {};
}, [conversationsList]);
return ( return (
<List <>
<ChatList className='chat-list' dataSource={chatlist} onClick={(item) => switchConversation(item)} />
{/* <List
dataSource={conversationsList || []} dataSource={conversationsList || []}
renderItem={(item, ii) => ( renderItem={(item, ii) => (
<List.Item onClick={() => switchConversation(item)} actions={[<a key='list-loadmore-edit'>mark</a>]}> // actions={[<a key='list-loadmore-edit'>mark</a>]}
<List.Item onClick={() => switchConversation(item)}>
<List.Item.Meta <List.Item.Meta
avatar={ avatar={<Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${item.name}`}>{item.name}</Avatar>}
<Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${item.name}`} >
{item.name}
</Avatar>
}
title={item.name} title={item.name}
// description='{}' // description='{}'
/> />
</List.Item> </List.Item>
)} )}
/> /> */}
</>
); );
}); });
export default Conversations; export default Conversations;

@ -1,7 +1,69 @@
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Card } from 'antd'; import { Card, Flex, Avatar, Typography, Radio, Button } from 'antd';
import { useAuthContext } from '@/stores/AuthContext.js';
import { useConversationContext } from '@/stores/ConversationContext';
import { useGetJson } from '@/hooks/userFetch';
const orderTags = [
{ value: 'potential', label: '潜力' },
{ value: 'important', label: '重点' },
{ label: '休眠', value: 'snooze' },
];
const orderStatus = [
{ value: 'pending', label: '报价中' },
// { value: 'in-progress', label: '' },
{ value: 'lost', label: '丢失' },
{ value: 'later', label: '以后联系' },
];
const { Meta } = Card;
const CustomerProfile = observer(({ customer }) => { const CustomerProfile = observer(({ customer }) => {
return <Card bordered={false} title={customer.name}>{/* other profile details */}</Card>; const { errors } = useConversationContext();
const { loginUser: currentUser } = useAuthContext();
const orderInfo = useGetJson('http://127.0.0.1:4523/m2/3888351-0-default/144062941');
const { quotes, contact, last_contact, ...order } = orderInfo || {};
return (
<div className=' divide-x-0 divide-y divide-dotted divide-slate-400/[.24]'>
<Card
bordered={false}
title={order?.order_no}
extra={<Radio.Group size={'small'} options={orderTags} value={'important'} onChange={({ target: { value } }) => {}} optionType='button' buttonStyle={'solid'} />}>
<Meta
title={<Radio.Group size={'small'} options={orderStatus} value={'pending'} onChange={({ target: { value } }) => {}} optionType='button' buttonStyle={'solid'} />}
description={' '}
/>
<Flex gap={16}>
<Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${contact?.name}`} />
<Flex vertical={true} justify='space-between'>
<Typography.Text strong>{contact?.name}</Typography.Text>
<div>
{contact?.phone} <span>{contact?.email}</span>{' '}
</div>
{/* <div>{order?.order_no}</div> */}
<div>
{order?.location} <span>{order?.local_datetime}</span>
</div>
<div></div>
</Flex>
</Flex>
</Card>
<Flex vertical={true} className='p-2 '>
<div>最新报价</div>
<p className='m-0 py-2 '>{quotes?.[0]?.name}</p>
<Flex justify={'space-between'} >
<Button size={'small'}>Book</Button>
<Button size={'small'}>报价历史</Button>
</Flex>
</Flex>
<p className='p-2 overflow-auto h-40'>{order?.order_detail}</p>
<Flex vertical={true} className='p-2 '>
<div>沟通记录</div>
<p className='m-0 py-2 '>{quotes?.[0]?.name}</p>
</Flex>
</div>
);
}); });
export default CustomerProfile; export default CustomerProfile;

@ -6,7 +6,7 @@ import { Input, Button } from 'antd';
const InputBox = observer(({ onSend }) => { const InputBox = observer(({ onSend }) => {
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const sendMessage = () => { const onOK = () => {
// console.log(message); // console.log(message);
if (typeof onSend === 'function' && message.trim() !== '') { if (typeof onSend === 'function' && message.trim() !== '') {
const msgObj = { const msgObj = {
@ -26,7 +26,7 @@ const InputBox = observer(({ onSend }) => {
return ( return (
<div> <div>
<Input.Search placeholder='Type message here' enterButton='Send' size='large' onSearch={sendMessage} value={message} onChange={(e) => setMessage(e.target.value)} /> <Input.Search placeholder='Type message here' enterButton='Send' size='large' onSearch={onOK} value={message} onChange={(e) => setMessage(e.target.value)} />
{/* <Input {/* <Input
placeholder='Type a message' placeholder='Type a message'
@ -35,10 +35,10 @@ const InputBox = observer(({ onSend }) => {
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
onKeyPress={(e) => { onKeyPress={(e) => {
if (e.shiftKey && e.charCode === 13) { if (e.shiftKey && e.charCode === 13) {
sendMessage(); onOK();
} }
}} }}
rightButtons={<button onClick={sendMessage}>Send</button>} rightButtons={<button onClick={onOK}>Send</button>}
/> */} /> */}
</div> </div>
); );

Loading…
Cancel
Save