发送失败; 一些样式

dev/mobile
Lei OT 2 years ago
parent 8dd9d92ba7
commit acdc1315b3

@ -29,6 +29,32 @@ export const autoLinkText = (text) => {
"msgcontent": "{\"body\":\"txtmsgtest\"}"
}
*/
const mediaMsg = {
contentToSend: (msg) => ({
action: 'message',
actionId: msg.id,
renderId: msg.id,
to: msg.to,
msgcontent: {
[msg.type]: { link: msg.data.dataUri, caption: msg.text },
...(msg.context ? { context: msg.context, message_origin: msg.message_origin.msgOrigin } : {}),
},
}),
contentToRender: (msg) => ({
...msg,
actionId: msg.id,
conversationid: msg.id.split('.')[0],
...(msg.context
? {
reply: {
message: msg.message_origin.text,
title: msg.message_origin.senderName || 'Reference',
titleColor: msg.message_origin?.senderName !== 'me' ? '#a791ff' : '#128c7e',
},
}
: {}),
}),
};
export const sentMsgTypeMapped = {
text: {
type: 'text',
@ -44,14 +70,14 @@ export const sentMsgTypeMapped = {
...msg,
actionId: msg.id,
conversationid: msg.id.split('.')[0],
originText: (msg.text),
originText: msg.text,
text: autoLinkText(msg.text),
...(msg.context
? {
reply: {
message: msg.message_origin.text,
title: msg.message_origin.senderName || 'Reference',
titleColor: msg.message_origin?.senderName !== 'me' ? '#a791ff' : "#128c7e",
titleColor: msg.message_origin?.senderName !== 'me' ? '#a791ff' : '#128c7e',
// titleColor: "#a791ff",
},
}
@ -61,25 +87,23 @@ export const sentMsgTypeMapped = {
photo: {
type: 'image',
contentToSend: (msg) => ({
action: 'message',
actionId: msg.id,
renderId: msg.id,
to: msg.to,
...mediaMsg.contentToSend({...msg, type: 'image'}),
msgtype: 'image',
msgcontent: {
image: { link: msg.data.uri, caption: msg.text },
...(msg.context ? { context: msg.context, message_origin: msg.message_origin } : {}),
},
}),
contentToRender: (msg) => ({
...msg,
actionId: msg.id,
conversationid: msg.id.split('.')[0],
...(msg.context
? {
reply: { message: msg.message_origin.text, title: msg.message_origin.senderName || 'Reference', titleColor: "#a791ff", },
}
: {}),
...mediaMsg.contentToRender(msg),
}),
},
video: {
type: 'video',
contentToSend: (msg) => ({
...mediaMsg.contentToSend(msg),
msgtype: 'video',
}),
contentToRender: (msg) => ({
...msg,
...mediaMsg.contentToRender(msg),
}),
},
whatsappTemplate: {
@ -95,7 +119,7 @@ export const sentMsgTypeMapped = {
conversationid: msg.id.split('.')[0],
type: 'text',
title: msg.template_origin.components.header?.[0]?.text || '',
text: autoLinkText( templateDataMapped?.body?.text || ''), // msg.template_origin.components.body?.[0]?.text || '',
text: autoLinkText(templateDataMapped?.body?.text || ''), // msg.template_origin.components.body?.[0]?.text || '',
};
},
},
@ -123,7 +147,7 @@ const whatsappMsgMapped = {
contentObj = {
...contentObj,
type: 'error',
text: {body: `Message failed to send.` }, // contentObj.errorMessage
text: {body: `${whatsappError?.[contentObj.errorCode] || contentObj.errorMessage}` }, // contentObj.errorMessage // Message failed to send.
id: contentObj.id,
wamid: contentObj.id,
};
@ -138,7 +162,7 @@ const whatsappMsgMapped = {
id: msgcontent.wamid,
status: msgStatusRenderMapped[(msgcontent?.status || 'failed')],
sender: 'me',
dateString: msgcontent.status==='failed' ? '发送失败 ❌' : '',
dateString: msgcontent.status==='failed' ? `发送失败 ${whatsappError?.[msgcontent.errorCode] || msgcontent.errorMessage}` : '',
}),
},
};
@ -167,7 +191,7 @@ export const receivedMsgTypeMapped = {
// 发送消息的同步返回: 发送失败时
getMsg: (result) => result,
contentToRender: () => null,
contentToUpdate: (msgcontent) => ({ ...msgcontent, id: msgcontent.actionId, status: msgcontent?.status || 'failed', dateString: '发送失败 ❌', conversationid: msgcontent.actionId.split('.')[0], }),
contentToUpdate: (msgcontent) => ({ ...msgcontent, id: msgcontent.actionId, status: msgcontent?.status || 'failed', dateString: `发送失败 ${msgcontent.error.message}`, conversationid: msgcontent.actionId.split('.')[0], }),
},
};
export const whatsappMsgTypeMapped = {
@ -274,7 +298,7 @@ export const parseRenderMessageItem = (msg) => {
whatsapp_name: msg?.customerProfile?.name || '',
whatsapp_phone_number: msg.from,
whatsapp_msg_type: msg.type,
...(isEmpty(msg.context) && isEmpty(msg.reaction)
...((isEmpty(msg.context) && isEmpty(msg.reaction)) || msg.context.forwarded === true
? {}
: {
reply: {
@ -314,7 +338,7 @@ export const parseRenderMessageList = (messages, conversationid = null) => {
sender: 'me',
senderName: 'me',
status: msgStatusRenderMapped[msgContent?.status || 'failed'],
dateString: msgStatusRenderMapped[msgContent?.status || 'failed'] === 'failed' ? '发送失败 ❌' : '',
dateString: msgStatusRenderMapped[msgContent?.status || 'failed'] === 'failed' ? `发送失败 ${whatsappError?.[msgContent.errorCode] || msgContent.errorMessage}` : '',
}
: {}),
...(isEmpty(msg.messageorigin_AsJOSN) && isEmpty(msgContent.context)
@ -337,3 +361,10 @@ export const parseRenderMessageList = (messages, conversationid = null) => {
};
});
};
export const whatsappError = {
'131026': '消息无法投递. [未注册/使用旧版/未同意政策]',
'131047': '会话超过24小时.',
'131053': '文件上传失败.',
'131048': '账户被风控.', // 消息发送太多, 达到垃圾数量限制
'131031': '账户已锁定.',
};

@ -225,11 +225,12 @@ const messageSlice = (set, get) => ({
const { activeConversations, conversationsList, currentConversation } = get();
const targetMsgs = activeConversations[String(targetId)] || [];
const targetIndex = conversationsList.findIndex((ele) => String(ele.sn) === String(targetId));
const updateTime = message.type !== 'system' && message.sender !== 'me' ? message.date : null;
const newConversation =
targetId !== -1
? {
...conversationsList[targetIndex],
last_received_time: message.type !== 'system' && message.sender !== 'me' ? message.date : conversationsList[targetIndex].last_received_time,
last_received_time: updateTime || conversationsList[targetIndex].last_received_time,
unread_msg_count:
String(targetId) !== String(currentConversation.sn) && message.sender !== 'me'
? conversationsList[targetIndex].unread_msg_count + 1
@ -248,7 +249,7 @@ const messageSlice = (set, get) => ({
conversationsList: [...conversationsList],
currentConversation: {
...currentConversation,
last_received_time: String(targetId) === String(currentConversation.sn) ? message.date : currentConversation.last_received_time,
last_received_time: String(targetId) === String(currentConversation.sn) ? updateTime : currentConversation.last_received_time,
},
}));

@ -16,46 +16,119 @@ import {
import useConversationStore from '@/stores/ConversationStore';
import { v4 as uuid } from 'uuid';
const props = {
name: 'file',
action: 'https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188',
headers: {
authorization: 'authorization-text',
},
showUploadList: false,
/**
* image
* ext: ani;bmp;gif;ico;jpe;jpeg;jpg;pcx;png;psd;tga;tif;tiff;wmf
*
* audio
* ext: aac;ac3;aif;aifc;aiff;au;cda;dts;fla;flac;it;m1a;m2a;m3u;m4a;mid;midi;mka;mod;mp2;mp3;mpa;ogg;ra;rmi;spc;rmi;snd;umx;voc;wav;wma;xm
*
* video
* ext: 3g2;3gp;3gp2;3gpp;amr;amv;asf;avi;bdmv;bik;d2v;divx;drc;dsa;dsm;dss;dsv;evo;f4v;flc;fli;flic;flv;hdmov;ifo;ivf;m1v;m2p;m2t;m2ts;m2v;m4b;m4p;m4v;mkv;mp2v;mp4;mp4v;mpe;mpeg;mpg;mpls;mpv2;mpv4;mov;mts;ogm;ogv;pss;pva;qt;ram;ratdvd;rm;rmm;rmvb;roq;rpm;smil;smk;swf;tp;tpr;ts;vob;vp6;webm;wm;wmp;wmv
*/
const fileTypes = {
'photo': ['ani','bmp','gif','ico','jpe','jpeg','jpg','pcx','png','psd','tga','tif','tiff','wmf'],
'audio': ['aac','ac3','aif','aifc','aiff','au','cda','dts','fla','flac','it','m1a','m2a','m3u','m4a','mid','midi','mka','mod','mp2','mp3','mpa','ogg','ra','rmi','spc','rmi','snd','umx','voc','wav','wma','xm'],
'video': ['3g2','3gp','3gp2','3gpp','amr','amv','asf','avi','bdmv','bik','d2v','divx','drc','dsa','dsm','dsv','evo','f4v','flc','fli','flic','flv','hdmov','ifo','ivf','m1v','m2p','m2t','m2ts','m2v','m4b','m4p','m4v','mkv','mp2v','mp4','mp4v','mpe','mpeg','mpg','mpls','mpv2','mpv4','mov','mts','ogm','ogv','pss','pva','qt','ram','ratdvd','rm','rmm','rmvb','roq','rpm','smil','smk','swf','tp','tpr','ts','vob','vp6','webm','wm','wmp','wmv'],
};
const mockGetOSSData = () => ({
dir: 'user-dir/',
expire: '1577811661',
host: '//haina-sale-system.oss-cn-shenzhen.aliyuncs.com',
accessId: 'LTAI5t8FBxiMYTd4tBSZzVy5',
// todo:
policy: 'eGl4aWhhaGFrdWt1ZGFkYQ==',
signature: 'ZGFob25nc2hhbw==',
});
const ImageUpload = ({ disabled, invokeSendMessage }) => {
const { currentConversation, setComplexMsg } = useConversationStore();
const { currentConversation, setComplexMsg, complexMsg } = useConversationStore();
const [uploading, setUploading] = useState(false);
const [OSSData, setOSSData] = useState();
const handleSendImage = (src) => {
const handleSendImage = (file) => {
console.log(file);
const msgObj = {
type: 'photo',
data: { uri: src, width: '100%', height: 150, },
type: file.type, // 'photo',
name: file.name,
status: 'loading',
data: { uri: file.previewSrc, dataUri: file.dataUri, width: '100%', height: 150 },
id: uuid(),
};
setComplexMsg(msgObj);
// invokeSendMessage(msgObj);
};
const beforeUpload = async (file) => {
console.log('beforeUpload', file);
// 使 FileReader
const reader = new FileReader();
// let ret = true;
//
reader.onload = (event) => {
const previewSrc = event.target.result;
// test: src
const dataUri = `https://images.chinahighlights.com/allpicture/2020/04/9330cd3c78a34c81afd3b1fb.jpg`;
// const dataUri = `https://data.chinahighlights.com/video/CH-homepage-video.mp4`;
const suffix = file.name.slice(file.name.lastIndexOf('.')+1);
const type = Object.keys(fileTypes).find((type) => fileTypes[type].includes(suffix));
const name = file.name;
// ret = type === 'photo';
// const filename = Date.now() + suffix;
// file.url = OSSData.dir + filename;
handleSendImage({ previewSrc, dataUri, type, suffix, name});
};
// dataURL
reader.readAsDataURL(file);
return file;
// return ret ? file : false;
// if (!OSSData) return false;
// const expire = Number(OSSData.expire) * 1000;
// if (expire < Date.now()) {
// // await init();
// const result = await mockGetOSSData();
// setOSSData(result);
// }
// const suffix = file.name.slice(file.name.lastIndexOf('.'));
// const filename = Date.now() + suffix;
// file.url = OSSData.dir + filename;
// return file;
};
const uploadProps = {
name: 'file',
action: 'https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188',
// action: OSSData?.host,
headers: {
authorization: 'authorization-text',
},
showUploadList: false,
// onChange: handleChange,
// onRemove,
// data: getExtraData,
beforeUpload,
};
return (
<Upload
{...props}
{...uploadProps}
onChange={(info) => {
console.log('fileList', info.fileList);
setUploading(info.file.status === 'uploading');
setComplexMsg({...complexMsg, status: 'loading'})
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
setComplexMsg({...complexMsg, status: info.file.status})
}
if (info.file.status === 'done') {
// todo:
setComplexMsg({...complexMsg, status: info.file.status, data: { ...complexMsg.data, uri: complexMsg.data.dataUri}})
// message.success(`${info.file.name} file uploaded successfully`);
// test: src
// handleSendImage('blob:https://web.whatsapp.com/bbe878fc-7bde-447f-aa28-a4b929621a50');
handleSendImage('https://images.chinahighlights.com//allpicture/2020/04/9330cd3c78a34c81afd3b1fb.jpg');
} else if (info.file.status === 'error') {
message.error(`图片添加失败`);
}
}}>
<Button key={'addPic'} type='text' loading={uploading} disabled={disabled} icon={<FileImageOutlined />} size={'middle'} className='text-primary rounded-none' />
}}
>
<Button key={'addPic'} type='text' disabled={disabled} icon={<FileImageOutlined />} size={'middle'} className='text-primary rounded-none' />
</Upload>
);
};

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { Input, Flex, Button, Image } from 'antd';
// import { Input } from 'react-chat-elements';
import useAuthStore from '@/stores/AuthStore';
@ -14,8 +14,10 @@ import {
FolderAddOutlined,
FilePdfOutlined,
CloseCircleOutlined,
YoutubeOutlined,
AudioOutlined, PlayCircleOutlined, LoadingOutlined, CheckCircleOutlined, FileOutlined
} from '@ant-design/icons';
import { isEmpty } from '@/utils/utils';
import { isEmpty, olog } from '@/utils/utils';
import { v4 as uuid } from 'uuid';
import { sentMsgTypeMapped } from '@/lib/msgUtils';
import InputTemplate from './Input/Template';
@ -23,17 +25,18 @@ import InputEmoji from './Input/Emoji';
import InputImageUpload from './Input/ImageUpload';
import dayjs from 'dayjs';
const InputBox = () => {
const { loginUser: { userId } } = useAuthStore()
const InputComposer = () => {
const {
loginUser: { userId },
} = useAuthStore();
const { websocket, websocketOpened, currentConversation, referenceMsg, setReferenceMsg, complexMsg, setComplexMsg, sentOrReceivedNewMessage } = useConversationStore();
const [textContent, setTextContent] = useState('');
const talkabled = !isEmpty(currentConversation.sn) && websocketOpened;
const gt24h = currentConversation.last_received_time ? dayjs().diff(dayjs(currentConversation.last_received_time), 'hour') > 24 : true;
const textabled = talkabled && !gt24h;
const textInputRef = useRef(null);
const [textContent, setTextContent] = useState('');
const invokeSendMessage = (msgObj) => {
const msgObjMerge = {
sender: 'me',
@ -44,7 +47,7 @@ const InputBox = () => {
...msgObj,
id: `${currentConversation.sn}.${uuid()}`,
};
console.log('sendMessage------------------', msgObjMerge);
olog('sendMessage------------------', msgObjMerge)
const contentToSend = sentMsgTypeMapped[msgObjMerge.type].contentToSend(msgObjMerge);
console.log('content to send-------------------------------------', contentToSend);
websocket.sendMessage({ ...contentToSend, opi_sn: userId, coli_sn: currentConversation.coli_sn });
@ -57,8 +60,12 @@ const InputBox = () => {
setComplexMsg({});
};
const focusInput = () => {
textInputRef.current.focus({ cursor: 'end' });
};
const handleSendText = () => {
if (textContent.trim() !== '' || !isEmpty(complexMsg) ) {
if (textContent.trim() !== '' || !isEmpty(complexMsg)) {
const msgObj = {
type: 'text',
text: textContent,
@ -68,6 +75,11 @@ const InputBox = () => {
}
};
useEffect(() => {
focusInput();
return () => {};
}, [referenceMsg, complexMsg]);
return (
<div>
{referenceMsg.id && (
@ -82,13 +94,17 @@ const InputBox = () => {
{complexMsg.id && (
<Flex justify='space-between' className='reply-to bg-gray-100 p-1 rounded-none text-slate-500'>
<div className='pl-2 pr-1 py-1'>
<Image width={100} src={complexMsg.data.uri} />
<span className=' pl-1 '></span>
{complexMsg.type === 'photo' && <Image width={100} src={complexMsg.data.uri} />}
{complexMsg.type === 'video' && <FileOutlined className=' text-red-400' />}
{complexMsg.type !== 'photo' && <span className='px-1'>{complexMsg.name}</span>}
{complexMsg.status === 'loading' && <LoadingOutlined className='px-1' />}
{complexMsg.status === 'done' && <CheckCircleOutlined className='px-1 text-primary' />}
</div>
<Button type='text' title='删除' className=' rounded-none text-slate-500' icon={<CloseCircleOutlined />} size={'middle'} onClick={() => setComplexMsg({})} />
</Flex>
)}
<Input.TextArea
ref={textInputRef}
size='large'
placeholder={gt24h ? 'This session has expired. Please send a template message to activate the session' : 'Enter for Send, Shift+Enter for new line'}
rows={2}
@ -107,9 +123,11 @@ const InputBox = () => {
<Flex justify={'space-between'} className=' bg-gray-200 p-1 rounded-b'>
<Flex gap={4} className='*:text-primary *:rounded-none'>
<InputTemplate key='templates' disabled={textabled} invokeSendMessage={invokeSendMessage} />
<InputEmoji key='emoji' disabled={!textabled} inputEmoji={s => setTextContent(`${textContent}${s}`)} />
<InputEmoji key='emoji' disabled={!textabled} inputEmoji={(s) => setTextContent(`${textContent}${s}`)} />
{/* <InputImageUpload key={'addNewPic'} disabled={!textabled} invokeSendMessage={invokeSendMessage} /> */}
{/* <Button type='text' className='' icon={<FolderAddOutlined />} size={'middle'} />
{/* <Button type='text' className='' icon={<YoutubeOutlined />} size={'middle'} />
<Button type='text' className='' icon={<AudioOutlined />} size={'middle'} />
<Button type='text' className='' icon={<FolderAddOutlined />} size={'middle'} />
<Button type='text' className='' icon={<CloudUploadOutlined />} size={'middle'} />
<Button type='text' className='' icon={<FilePdfOutlined />} size={'middle'} /> */}
</Flex>
@ -121,4 +139,4 @@ const InputBox = () => {
);
};
export default InputBox;
export default InputComposer;

@ -25,7 +25,7 @@ const MessagesHeader = () => {
<Flex gap={16} className='p-1 flex-auto'>
{/* {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'>
<Flex vertical={false} gap={12} justify='space-between'>
<Typography.Text strong>{currentConversation.customer_name}</Typography.Text>
<Typography.Text>{currentConversation.whatsapp_phone_number}</Typography.Text>
</Flex>

@ -44,4 +44,5 @@
margin-top: 0;
margin-left: 5px;
justify-content: flex-start;
font-size: 12px;
}

Loading…
Cancel
Save