feat: 在消息窗口打开新会话, 已存在则切换

dev/timezone
Lei OT 1 year ago
parent b4fe62343d
commit 714718053c

@ -672,3 +672,48 @@ export const handleNotification = (title, _options) => {
};
}
};
/**
* WhatsApp 国际号码
*
* - Make sure to remove any leading 0s or special calling codes.
* - All phone numbers in Argentina (country code "54") should have a "9" between the country code and area code. The prefix "15" must be removed so the final number will have 13 digits total: +54 9 XXX XXX XXXX
* - Phone numbers in Mexico (country code "52") need to have "1" after "+52", even if they're Nextel numbers.
*/
export const phoneNumberToWAID = (input) => {
// Remove any non-digit characters except '+'
const cleanedInput = input.replace(/[^\d+]/g, '');
// Check if the number starts with a valid country code
const countryCode = cleanedInput.slice(0, 2);
const isArgentina = countryCode === '54';
const isMexico = countryCode === '52';
if (!isArgentina && !isMexico) {
return cleanedInput; // Return the cleaned input as is
}
// Remove leading 0s and special calling codes
let formattedNumber = cleanedInput.replace(/^0+/, '');
if (isArgentina) {
// Remove the prefix "15" if present
// formattedNumber = formattedNumber.replace(/^15/, '');
formattedNumber = formattedNumber.replace(/^54 ?15/, '54');
// Remove leading '0' after country code
formattedNumber = formattedNumber.replace(/^54 ?0/, '54');
// Insert "9" between the country code and area code
formattedNumber = `54 9 ${formattedNumber.slice(2)}`;
} else if (isMexico) {
// Remove leading '0' after country code
formattedNumber = formattedNumber.replace(/^52 ?0/, '52');
// Insert "1" after the country code
formattedNumber = `52 1 ${formattedNumber.slice(2)}`;
}
// Remove any non-digit characters except '+'
formattedNumber = formattedNumber.replace(/[^\d+]/g, '');
return formattedNumber;
}

@ -1,22 +1,57 @@
import { useState, useEffect } from 'react';
import { Form, Input, Modal } from 'antd';
import { isEmpty } from '@/utils/commons';
import { isEmpty, pick } from '@/utils/commons';
import useConversationStore from '@/stores/ConversationStore';
import { phoneNumberToWAID } from '@/channel/whatsappUtils';
export const ConversationItemForm = ({ initialValues, onFormInstanceReady }) => {
const [currentConversation] = useConversationStore((state) => [state.currentConversation]);
const fromCurrent = initialValues?.is_current_order ? pick(currentConversation, ['coli_sn', 'coli_id', 'opi_sn']) : {};
const [form] = Form.useForm();
const [formatPhone, setFormatPhone] = useState('');
const [formatVisible, setFormatVisible] = useState(false);
useEffect(() => {
onFormInstanceReady(form);
setFormatPhone(phoneNumberToWAID(initialValues.phone_number));
}, []);
useEffect(() => {
form.setFieldValue('wa_id', formatPhone);
return () => {};
}, [formatPhone]);
const onValuesChange = (changeValues, allValues) => {
if ('phone_number' in changeValues) {
const _t = phoneNumberToWAID(changeValues.phone_number);
setFormatPhone(_t);
setFormatVisible(_t !== changeValues.phone_number);
}
};
return (
<Form layout='horizontal' form={form} name='form_in_modal' initialValues={initialValues}>
<Form.Item name={'phone_number'} label='WhatsApp号码' rules={[{ required: true, message: '请输入联系人手机号' }]}>
<Form layout='horizontal' form={form} name='form_in_modal' initialValues={{ ...fromCurrent, ...initialValues, wa_id: formatPhone }} onValuesChange={onValuesChange}>
<Form.Item
name={'phone_number'}
label='WhatsApp号码'
validateStatus={formatVisible ? 'warning' : undefined}
help={formatVisible ? `WhatsApp格式: ${formatPhone}` : undefined}
rules={[{ required: true, message: '请输入联系人手机号' }]}>
<Input placeholder='请输入联系人手机号' />
</Form.Item>
{/* <Form.Item name={'coli_id'} label={!isEmpty(initialValues?.coli_id) ? '' : ''} rules={[{ required: true, message: '' }]}>
<Input placeholder='关联的订单' />
</Form.Item> */}
<Form.Item name={'coli_sn'} label='订单SN' rules={[{ required: true, message: '订单SN' }]}>
<Input placeholder='订单SN' />
{/* hidden */}
<Form.Item hidden name={'wa_id'} dependencies={['phone_number']}>
<Input />
</Form.Item>
<Form.Item
name={'coli_id'}
label={initialValues?.is_current_order ? '关联当前订单' : '关联订单'}
rules={[{ required: true, message: '关联的订单' }]}
validateStatus='warning'
help='请务必确认关联的订单是否正确'>
<Input placeholder='关联的订单' disabled={initialValues?.is_current_order || false} />
</Form.Item>
<Form.Item name={'coli_sn'} label='订单SN' hidden={initialValues?.is_current_order || false} rules={[{ required: true, message: '订单SN' }]}>
<Input placeholder='订单SN' />
</Form.Item>
{/* <Form.Item name={'name'} label='' rules={[{ required: true, message: '' }]}>
<Input placeholder='请输入联系人名称' />
@ -29,13 +64,16 @@ export const ConversationItemFormModal = ({ open, onCreate, onCancel, initialVal
return (
<Modal
open={open}
title='新建会话'
okText='创建'
title='新建/切换会话'
okText='确认'
// cancelText='Cancel'
okButtonProps={{
autoFocus: true,
}}
onCancel={() => { onCancel(); formInstance?.resetFields();}}
onCancel={() => {
onCancel();
formInstance?.resetFields();
}}
destroyOnClose
onOk={async () => {
try {

@ -5,6 +5,7 @@ import { DownOutlined, LoadingOutlined } from '@ant-design/icons';
import { useShallow } from 'zustand/react/shallow';
import useConversationStore from '@/stores/ConversationStore';
import { groupBy, isEmpty, } from '@/utils/commons';
import ConversationsNewItem from './ConversationsNewItem';
const MessagesList = ({ messages, handlePreview, reference, longListLoading, getMoreMessages, shouldScrollBottom, loadNextPage, handleContactClick, ...props }) => {
@ -50,13 +51,16 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
buttonsArr = componentsObj?.buttons?.reduce((r, c) => r.concat(c.buttons), []);
}
const parts = str.split(/(https?:\/\/[^\s]+|\p{Emoji_Presentation})/gmu).filter((s) => s !== '');
const parts = str.split(/(https?:\/\/[^\s]+|\p{Emoji_Presentation}|\d{4,})/gmu).filter((s) => s !== '');
const links = str.match(/https?:\/\/[\S]+/gi) || [];
const numbers = str.match(/\d{4,}/g) || [];
const emojis = str.match(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g) || [];
const extraClass = isEmpty(emojis) ? '' : '';
const objArr = parts.reduce((prev, curr, index) => {
if (links.includes(curr)) {
prev.push({ type: 'link', key: curr });
} else if (numbers.includes(curr)) {
prev.push({ type: 'number', key: curr });
} else if (emojis.includes(curr)) {
prev.push({ type: 'emoji', key: curr });
} else {
@ -84,6 +88,13 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
{part.key}
</a>
);
} else if (part.type === 'number') {
return (
<a key={`${part.key}${index}`} className='text-sm' onClick={() => {setNewChatModalVisible(true);
setNewChatFormValues(prev => ({...prev, phone_number: part.key, is_current_order: true, }))}}>
{part.key}
</a>
);
} else {
// if (part.type === 'emoji')
return part.key;
@ -123,6 +134,15 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
</div>
));
const [newChatModalVisible, setNewChatModalVisible] = useState(false);
const [newChatFormValues, setNewChatFormValues] = useState({});
const handleNewChat = async (values) => {
const newContact = { wa_id: values.wa_id };
await handleContactClick([newContact]);
setNewChatModalVisible(false);
}
return (
<div className='relative h-full overflow-y-auto overflow-x-hidden flex flex-1'>
<div ref={reference} className='relative overflow-y-auto overflow-x-hidden block flex-1'>
@ -188,6 +208,7 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
))}
</div>
<Button onClick={() => scrollToBottom(true)} ghost type={'dashed'} shape={'circle'} className=' absolute bottom-1 right-4' icon={<DownOutlined />} />
<ConversationsNewItem initialValues={newChatFormValues} open={newChatModalVisible} onCreate={handleNewChat} onCancel={() => setNewChatModalVisible(false)} />
</div>
);
};

Loading…
Cancel
Save