reducer context 管理conversation state; 拆分reducer, 减少re-render; 整理目录; 新消息累加未读数量test: 关闭会话;
parent
0cf3c5d541
commit
11d9b4269b
@ -0,0 +1,103 @@
|
||||
|
||||
import { groupBy } from '@/utils/utils';
|
||||
import { fetchJSON, postJSON } from '@/utils/request'
|
||||
|
||||
const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_callback';
|
||||
|
||||
const NAME_SPACE = 'CONVERSATION/';
|
||||
export const initWebsocket = (socket) => ({
|
||||
type: NAME_SPACE + 'INIT_WEBSOCKET',
|
||||
payload: socket,
|
||||
});
|
||||
export const closeWebsocket0 = () => ({
|
||||
type: NAME_SPACE + 'CLOSE_WEBSOCKET',
|
||||
payload: null,
|
||||
});
|
||||
export const closeWebsocket = () => {};
|
||||
|
||||
export const receivedNewMessage = (targetId, message) => ({
|
||||
type: NAME_SPACE + 'RECEIVED_NEW_MESSAGE',
|
||||
payload: {
|
||||
targetId,
|
||||
message,
|
||||
},
|
||||
});
|
||||
export const sentNewMessage = (message) => ({
|
||||
type: NAME_SPACE + 'SENT_NEW_MESSAGE',
|
||||
payload: {
|
||||
targetId: message.conversationid,
|
||||
message,
|
||||
},
|
||||
});
|
||||
|
||||
export const addError = (error) => {
|
||||
return {
|
||||
type: NAME_SPACE + 'ADD_ERROR',
|
||||
payload: error,
|
||||
};
|
||||
};
|
||||
|
||||
export const receivedTemplates = (data) => ({
|
||||
type: NAME_SPACE + 'SET_TEMPLATE_LIST',
|
||||
payload: data,
|
||||
});
|
||||
|
||||
export const receivedConversationList = (data) => {
|
||||
return {
|
||||
type: NAME_SPACE + 'SET_CONVERSATION_LIST',
|
||||
payload: data,
|
||||
};
|
||||
};
|
||||
export const addConversationList = (data) => {
|
||||
return {
|
||||
type: NAME_SPACE + 'ADD_CONVERSATIONS',
|
||||
payload: data,
|
||||
};
|
||||
};
|
||||
export const updateConversationListItemNew = (message) => ({
|
||||
type: NAME_SPACE + 'UPDATE_CONVERSATION_LIST_ITEM_NEW',
|
||||
payload: message,
|
||||
});
|
||||
|
||||
export const receivedCustomerProfile = (data) => ({
|
||||
type: NAME_SPACE + 'SET_CUSTOMER_ORDER_PROFILE',
|
||||
payload: data,
|
||||
});
|
||||
export const setActiveConversations = (obj) => ({
|
||||
type: NAME_SPACE + 'SET_ACTIVE_CONVERSATIONS',
|
||||
payload: obj,
|
||||
});
|
||||
export const setCurrentConversation = (obj) => ({
|
||||
type: NAME_SPACE + 'SET_CURRENT_CONVERSATION',
|
||||
payload: obj,
|
||||
});
|
||||
export const updateMessageItem = (message) => ({
|
||||
type: NAME_SPACE + 'UPDATE_SENT_MESSAGE_ITEM',
|
||||
payload: message,
|
||||
});
|
||||
|
||||
export const fetchTemplates = async () => {
|
||||
const data = await fetchJSON(`${API_HOST}/listtemplates`);
|
||||
const canUseTemplates = (data?.result?.items || [])
|
||||
.filter((_t) => _t.status !== 'REJECTED')
|
||||
.map((ele) => ({ ...ele, components: groupBy(ele.components, (_c) => _c.type.toLowerCase()) }));
|
||||
return canUseTemplates;
|
||||
};
|
||||
|
||||
export const fetchConversationsList = async (opisn) => {
|
||||
const { result: data } = await fetchJSON(`${API_HOST}/getconversations`, { opisn });
|
||||
const list = data.map((ele) => ({ ...ele, customer_name: ele.whatsapp_name.trim() }));
|
||||
return list;
|
||||
};
|
||||
|
||||
export const fetchCustomerProfile = async (colisn) => {
|
||||
const { result } = await fetchJSON(`${API_HOST}/getorderinfo`, { colisn });
|
||||
const data = result?.[0] || {};
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postConversationItemClose = async (body) => {
|
||||
const getParams = body ? new URLSearchParams(body).toString() : '';
|
||||
const { result } = await postJSON(`${API_HOST}/closeconversation?${getParams}`);
|
||||
return result;
|
||||
};
|
@ -0,0 +1,13 @@
|
||||
const initialState = {
|
||||
websocket: null,
|
||||
errors: [], // 错误信息
|
||||
|
||||
conversationsList: [], // 对话列表
|
||||
templates: [],
|
||||
customerOrderProfile: {},
|
||||
|
||||
activeConversations: {}, // 激活的对话的消息列表: { [conversationId]: [] }
|
||||
currentConversation: {}, // 当前对话
|
||||
|
||||
};
|
||||
export default initialState;
|
@ -0,0 +1,99 @@
|
||||
import initialState from '@/records/ConversationState';
|
||||
|
||||
const NAME_SPACE = 'CONVERSATION/';
|
||||
|
||||
const ConversationReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case NAME_SPACE + 'INIT_WEBSOCKET':
|
||||
return { ...state, websocket: action.payload };
|
||||
|
||||
case NAME_SPACE + 'SET_CONVERSATION_LIST':
|
||||
return { ...state, conversationsList: action.payload };
|
||||
case NAME_SPACE + 'ADD_CONVERSATIONS':
|
||||
return { ...state, conversationsList: [...action.payload, ...state.conversationsList] };
|
||||
|
||||
case NAME_SPACE + 'SET_TEMPLATE_LIST':
|
||||
return { ...state, templates: action.payload };
|
||||
|
||||
case NAME_SPACE + 'SET_CUSTOMER_ORDER_PROFILE':
|
||||
return { ...state, customerOrderProfile: action.payload };
|
||||
|
||||
case NAME_SPACE + 'SET_CURRENT_CONVERSATION': {
|
||||
// 清空未读
|
||||
const { conversationsList } = state;
|
||||
const targetId = action.payload.sn;
|
||||
const newConversationList = conversationsList.map((ele) => {
|
||||
if (String(ele.sn) === String(targetId)) {
|
||||
return { ...ele, unread_msg_count: 0 };
|
||||
}
|
||||
return ele;
|
||||
});
|
||||
|
||||
return { ...state, currentConversation: action.payload, conversationsList: newConversationList };
|
||||
}
|
||||
case NAME_SPACE + 'SET_ACTIVE_CONVERSATIONS': {
|
||||
const { activeConversations } = state;
|
||||
return { ...state, activeConversations: { ...activeConversations, ...action.payload } };
|
||||
}
|
||||
case NAME_SPACE + 'UPDATE_SENT_MESSAGE_ITEM': {
|
||||
console.log('UPDATE_SENT_MESSAGE_ITEM-----------------------------------------------------------------');
|
||||
// 更新会话中的消息
|
||||
const { activeConversations, conversationsList } = state;
|
||||
const message = action.payload;
|
||||
const targetId = message.conversationid;
|
||||
const targetMsgs = (activeConversations[String(targetId)] || []).map((ele) => {
|
||||
if (ele.id === ele.actionId && ele.actionId === message.actionId) {
|
||||
return { ...ele, id: message.id, status: message.status, dateString: message.dateString };
|
||||
} else if (ele.id === message.id) {
|
||||
return { ...ele, id: message.id, status: message.status, dateString: message.dateString };
|
||||
}
|
||||
return ele;
|
||||
});
|
||||
|
||||
// 更新列表的时间
|
||||
const newConversationList = conversationsList.map((ele) => {
|
||||
if (String(ele.sn) === String(targetId) && message.type !== 'error') {
|
||||
return { ...ele, last_received_time: message.date };
|
||||
}
|
||||
return ele;
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
activeConversations: { ...state.activeConversations, [String(targetId)]: targetMsgs },
|
||||
conversationsList: newConversationList,
|
||||
};
|
||||
}
|
||||
case NAME_SPACE + 'SENT_NEW_MESSAGE':
|
||||
case NAME_SPACE + 'RECEIVED_NEW_MESSAGE': {
|
||||
const { activeConversations, conversationsList, currentConversation } = state;
|
||||
const { targetId, message } = action.payload;
|
||||
const targetMsgs = activeConversations[String(targetId)] || [];
|
||||
console.log(activeConversations, String(targetId), 'sent sent sent');
|
||||
const newConversationList = conversationsList.map((ele) => {
|
||||
if (String(ele.sn) === String(targetId)) {
|
||||
return {
|
||||
...ele,
|
||||
last_received_time: message.date,
|
||||
unread_msg_count: String(ele.sn) !== String(currentConversation.sn) ? ele.unread_msg_count + 1 : ele.unread_msg_count,
|
||||
};
|
||||
}
|
||||
return ele;
|
||||
});
|
||||
return {
|
||||
...state,
|
||||
activeConversations: { ...activeConversations, [String(targetId)]: [...targetMsgs, message] },
|
||||
conversationsList: newConversationList,
|
||||
};
|
||||
}
|
||||
case NAME_SPACE + 'ADD_ERROR': {
|
||||
console.log('add error', state.errors, action.payload);
|
||||
const prelist = state.errors || [];
|
||||
return { ...state, errors: [...prelist, action.payload] };
|
||||
}
|
||||
default:
|
||||
// throw new Error(`Unknown action: ${action.type}`);
|
||||
return state;
|
||||
}
|
||||
};
|
||||
export default ConversationReducer;
|
@ -0,0 +1,12 @@
|
||||
import {createContext, useContext} from 'react';
|
||||
|
||||
export const ConversationContext = createContext();
|
||||
|
||||
export const useConversationContext = () => useContext(ConversationContext);
|
||||
|
||||
|
||||
export const ConversationStateContext = createContext();
|
||||
export const ConversationDispatchContext = createContext();
|
||||
|
||||
export const useConversationState = () => useContext(ConversationStateContext);
|
||||
export const useConversationDispatch = () => useContext(ConversationDispatchContext);
|
@ -0,0 +1,27 @@
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { useConversationState } from '@/stores/ConversationContext';
|
||||
|
||||
import { Flex, Typography, Avatar } from 'antd';
|
||||
import LocalTimeClock from './LocalTimeClock';
|
||||
|
||||
const MessagesHeader = () => {
|
||||
const { currentConversation } = useConversationState();
|
||||
return (
|
||||
<>
|
||||
<Flex gap={16}>
|
||||
{currentConversation.customer_name && <Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${currentConversation.customer_name}`} />}
|
||||
<Flex flex={'1'} justify='space-between'>
|
||||
<Flex vertical={true} justify='space-between'>
|
||||
<Typography.Text strong>{currentConversation.customer_name}</Typography.Text>
|
||||
<Typography.Text>{currentConversation.whatsapp_phone_number}</Typography.Text>
|
||||
</Flex>
|
||||
<Flex vertical={true} justify='space-between'>
|
||||
<Typography.Text>{/* <LocalTimeClock /> <Typography.Text strong>{order?.location} </Typography.Text> */}</Typography.Text>
|
||||
{/* <Typography.Text>{customerDateTime}</Typography.Text> */}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MessagesHeader;
|
@ -1,30 +1,97 @@
|
||||
import { useContext } from 'react';
|
||||
import { ConversationContext, useConversations, } from '@/stores/Conversations/ConversationContext';
|
||||
import { useContext, useReducer, useEffect } from 'react';
|
||||
import { ConversationStateContext, ConversationDispatchContext } from '@/stores/ConversationContext';
|
||||
import ConversationReducer from '@/reducers/ConversationReducer';
|
||||
import {
|
||||
initWebsocket,
|
||||
addError,
|
||||
fetchConversationsList,
|
||||
fetchTemplates,
|
||||
receivedConversationList,
|
||||
receivedTemplates,
|
||||
setActiveConversations,
|
||||
updateMessageItem,
|
||||
receivedNewMessage,
|
||||
} from '@/actions/ConversationActions';
|
||||
import initialState from '@/records/ConversationState';
|
||||
|
||||
import { AuthContext } from '@/stores/AuthContext';
|
||||
|
||||
import { RealTimeAPI } from '@/lib/realTimeAPI';
|
||||
import { receivedMsgTypeMapped } from '@/lib/msgUtils';
|
||||
import { isEmpty } from '@/utils/utils';
|
||||
|
||||
// const WS_URL = 'ws://202.103.68.144:8888/whatever/';
|
||||
// const WS_URL = 'ws://120.79.9.217:10022/whatever/';
|
||||
const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_callback'; // prod:
|
||||
|
||||
export const ConversationProvider = ({ children, loginUser, realtimeAPI }) => {
|
||||
const ConversationProvider = ({ children }) => {
|
||||
const { loginUser } = useContext(AuthContext);
|
||||
const { userId } = loginUser;
|
||||
console.log('====', loginUser);
|
||||
|
||||
const conversations = useConversations({loginUser, realtimeAPI});
|
||||
return <ConversationContext.Provider value={conversations}>{children}</ConversationContext.Provider>;
|
||||
};
|
||||
const realtimeAPI = new RealTimeAPI({ url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`, protocol: 'WhatsApp' });
|
||||
const [state, dispatch] = useReducer(ConversationReducer, { ...initialState, websocket: null });
|
||||
|
||||
// export default ConversationProvider;
|
||||
console.log('ConversationProvider', state, dispatch);
|
||||
|
||||
const AuthAndConversationProvider = ({ children }) => {
|
||||
const { loginUser } = useContext(AuthContext);
|
||||
const {userId} = loginUser;
|
||||
const realtimeAPI = new RealTimeAPI({ url: `${WS_URL}?opisn=${userId || ''}&_spam=${Date.now().toString()}`, protocol: 'WhatsApp' });
|
||||
useEffect(() => {
|
||||
console.log('invoke provider');
|
||||
|
||||
realtimeAPI.onError(() => dispatch(addError('Error')));
|
||||
realtimeAPI.onMessage(handleMessage);
|
||||
realtimeAPI.onCompletion(() => dispatch(addError('Connection broken')));
|
||||
|
||||
dispatch(initWebsocket(realtimeAPI));
|
||||
return () => {
|
||||
realtimeAPI.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchConversationsList(userId).then((data) => {
|
||||
console.log(data, 'llllllllllllllllllllllll');
|
||||
const dataMapped = data.reduce((r, v) => ({ ...r, [`${v.sn}`]: [] }), {});
|
||||
dispatch(receivedConversationList(data));
|
||||
dispatch(setActiveConversations(dataMapped));
|
||||
}); // todo: 和刷新订单会话页面有冲突
|
||||
|
||||
fetchTemplates().then((data) => dispatch(receivedTemplates(data)));
|
||||
|
||||
return () => {};
|
||||
}, []);
|
||||
const handleMessage = (data) => {
|
||||
console.log('handleMessage------------------');
|
||||
console.log(data);
|
||||
const { errcode, errmsg, result } = data;
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
let resultType = result?.action || result.type;
|
||||
if (errcode !== 0) {
|
||||
// addError('Error Connecting to Server');
|
||||
resultType = 'error';
|
||||
}
|
||||
console.log(resultType, 'result.type');
|
||||
const msgObj = receivedMsgTypeMapped[resultType].getMsg(result);
|
||||
const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj);
|
||||
const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj);
|
||||
console.log('msgRender msgUpdate', msgRender, msgUpdate);
|
||||
if (['whatsapp.message.updated', 'message', 'error'].includes(resultType)) {
|
||||
dispatch(updateMessageItem(msgUpdate));
|
||||
// return false;
|
||||
}
|
||||
if (!isEmpty(msgRender)) {
|
||||
dispatch(receivedNewMessage(msgRender.conversationid, msgRender));
|
||||
}
|
||||
console.log('handleMessage*******************');
|
||||
};
|
||||
// return <ConversationContext.Provider value={{ ...state, dispatch }}>{children}</ConversationContext.Provider>;
|
||||
return (
|
||||
<ConversationProvider loginUser={loginUser} realtimeAPI={realtimeAPI} >
|
||||
{children}
|
||||
</ConversationProvider>
|
||||
<ConversationStateContext.Provider value={{ ...state }}>
|
||||
<ConversationDispatchContext.Provider value={dispatch}>{children}</ConversationDispatchContext.Provider>
|
||||
</ConversationStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthAndConversationProvider;
|
||||
export default ConversationProvider;
|
||||
|
Loading…
Reference in New Issue