Merge branch 'main' of github.com:hainatravel/global-sales

hotfix/new-conversation
Jimmy Liow 1 year ago
commit 1fd2a13a40

@ -19,3 +19,6 @@ npm version minor
[聊天式销售平台需求文档](https://www.kdocs.cn/l/calaUjgmCmDA?from=docs&reqtype=kdocs&startTime=1703645330177&createDirect=true&newFile=true) [聊天式销售平台需求文档](https://www.kdocs.cn/l/calaUjgmCmDA?from=docs&reqtype=kdocs&startTime=1703645330177&createDirect=true&newFile=true)
## vonage语音视频
安装模块 npm i @vonage/client-sdk

@ -11,6 +11,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@vonage/client-sdk": "^1.6.0",
"antd": "^5.14.0", "antd": "^5.14.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
@ -35,6 +36,7 @@
"eslint-plugin-react-refresh": "^0.4.3", "eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.33", "postcss": "^8.4.33",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"@vonage/client-sdk": "^1.6.0",
"vite": "^4.5.1", "vite": "^4.5.1",
"vite-plugin-css-modules": "^0.0.1", "vite-plugin-css-modules": "^0.0.1",
"vite-plugin-windicss": "^1.9.3", "vite-plugin-windicss": "^1.9.3",

@ -411,9 +411,11 @@ export const whatsappMsgTypeMapped = {
type: 'audio', type: 'audio',
data: (msg) => ({ data: (msg) => ({
id: msg.wamid, id: msg.wamid,
audioProps: { preload: 'auto' },
data: { data: {
audioURL: msg.audio.link, audioURL: msg.audio.link,
audioType: msg.audio?.mime_type || 'audio/ogg', audioType: msg.audio?.mime_type || 'audio/ogg',
controlsList: 'nofullscreen',
}, },
}), }),
renderForReply: (msg) => ({ renderForReply: (msg) => ({
@ -607,10 +609,12 @@ export const parseRenderMessageList = (messages) => {
export const whatsappError = { export const whatsappError = {
'BAD_REQUEST': ' ', 'BAD_REQUEST': ' ',
'PARAM_INVALID': '参数错误, 请联系技术组', 'PARAM_INVALID': '参数错误, 请联系技术组',
'INTERNAL_SERVER_ERROR': '无法连接WhatsApp.\n请稍重试', 'INTERNAL_SERVER_ERROR': '无法连接WhatsApp.\n请稍重试',
'2': '无法连接WhatsApp.\n请稍候重试', // (#2) Service temporarily unavailable '2': '[2] 无法连接WhatsApp.\n请稍后重试', // (#2) Service temporarily unavailable
'INVALID_PHONE_NUMBER': '无效号码, 请修正号码后重新从订单进入会话', 'INVALID_PHONE_NUMBER': '无效号码, 请修正号码后重新从订单进入会话',
'100': '参数错误, 请联系技术组', '100': '参数错误, 请联系技术组',
'FORBIDDEN': '[FORBIDDEN] ',
'4': '[4] 无法连接WhatsApp.\n请稍后重试', // (#4) Application request limit reached
'131026': '[131026] 消息无法投递(未同意WhatsApp 的隐私政策).\n请使用邮件联系', '131026': '[131026] 消息无法投递(未同意WhatsApp 的隐私政策).\n请使用邮件联系',
'131047': '[131047] 会话未激活. \n请使用模板消息💬发送', '131047': '[131047] 会话未激活. \n请使用模板消息💬发送',
'131053': '[131053] 文件上传失败.', '131053': '[131053] 文件上传失败.',

@ -6,6 +6,7 @@
export const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_server'; export const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_server';
export const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_server'; // prod: export const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_server'; // prod:
export const VONAGE_URL = 'https://p9axztuwd7x8a7.mycht.cn/vonage-server'; // 语音和视频接口:
export const DATE_FORMAT = 'YYYY-MM-DD'; export const DATE_FORMAT = 'YYYY-MM-DD';
export const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; export const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';

@ -16,6 +16,7 @@ import ErrorPage from '@/components/ErrorPage'
import ChatWindow from '@/views/ChatWindow' import ChatWindow from '@/views/ChatWindow'
import MobileConversation from '@/views/mobile/Conversation' import MobileConversation from '@/views/mobile/Conversation'
import MobileChat from '@/views/mobile/Chat' import MobileChat from '@/views/mobile/Chat'
import CallCenter from '@/views/CallCenter'
import MobileSecondHeader from '@/views/mobile/SecondHeaderWrapper'; import MobileSecondHeader from '@/views/mobile/SecondHeaderWrapper';
import CustomerProfile from '@/views/Conversations/Online/order/CustomerProfile'; import CustomerProfile from '@/views/Conversations/Online/order/CustomerProfile';
@ -52,6 +53,8 @@ const router = createBrowserRouter([
element: <MobileSecondHeader />, element: <MobileSecondHeader />,
children: [ children: [
{ path: 'm/order', element: <CustomerProfile /> }, { path: 'm/order', element: <CustomerProfile /> },
{ path: 'callcenter/call', element: <CallCenter /> },
{ path: 'callcenter/call/:phonenumber', element: <CallCenter /> },
], ],
}, },
] ]
@ -68,6 +71,8 @@ const router = createBrowserRouter([
{ path: 'account/profile', element: <AccountProfile /> }, { path: 'account/profile', element: <AccountProfile /> },
{ path: 'chat/unassign/:whatsappid', element: <ChatAssign /> }, { path: 'chat/unassign/:whatsappid', element: <ChatAssign /> },
{ path: 'chat/unassign', element: <Unassign /> }, { path: 'chat/unassign', element: <Unassign /> },
{ path: 'callcenter/call', element: <CallCenter /> },
{ path: 'callcenter/call/:phonenumber', element: <CallCenter /> },
], ],
}, },
], ],

@ -0,0 +1,104 @@
import { create } from "zustand";
import { VonageClient } from "@vonage/client-sdk";
import { fetchJSON } from "@/utils/request";
import { prepareUrl, isNotEmpty } from "@/utils/commons";
import { VONAGE_URL, DATETIME_FORMAT } from "@/config";
import dayjs from "dayjs";
const callCenterStore = create((set, get) => ({
client: new VonageClient({ apiUrl: "https://api-ap-3.vonage.com", websocketUrl: "wss://ws-ap-3.vonage.com" }),
call_id: 0,
loading: false,
logs: "",
//初始化 Vonage
init_vonage: user_id => {
const { client, log } = get();
set({ loading: true });
const fetchUrl = prepareUrl(VONAGE_URL + "/jwt")
.append("user_id", user_id)
.build();
return fetchJSON(fetchUrl).then(json => {
if (json.status === 200) {
let jwt = json.token;
client
.createSession(jwt)
.then(sessionId => {
log("Id of created session: ", sessionId);
})
.catch(error => {
log("Error creating session: ", error);
});
client.on("sessionError", reason => {
// After creating a session
log("Session error reason: ", reason);
});
client.on("legStatusUpdate", (callId, legId, status) => {
// After creating a session
log({ callId, legId, status });
});
client.on("callInvite", (callId, from, channelType) => {
log({ callId, from, channelType }); // Answer / Reject Call
});
client.on("callHangup", (callId, callQuality, reason) => {
log(`Call ${callId} has hung up, callQuality:${callQuality}, reason:${reason}`);
set({ call_id: 0 });
});
client.on("sessionError", error => {
log({ error });
});
} else {
throw new Error("请求jwt失败");
}
set({ loading: false });
});
},
log: (...message) => {
const { logs } = get();
console.log(message);
set({ logs: [...logs, dayjs().format(DATETIME_FORMAT) + " : " + JSON.stringify(message)] });
},
// 创建一个语音通话
make_call: phone_number => {
const { client, log } = get();
if (!isNotEmpty(phone_number)) {
log("请输入电话号码");
return;
}
log("开始拨号:" + phone_number);
if (client) {
set({ loading: true });
client
.serverCall({ to: phone_number })
.then(callId => {
log("Id of created call: ", callId);
set({ call_id: callId });
set({ loading: false });
})
.catch(error => {
log("Error making call: ", error);
set({ loading: false });
});
}
},
// 挂断语音通话
hang_up: () => {
const { client, call_id, log } = get();
log("挂断电话");
if (call_id) {
client.hangup(call_id);
set({ call_id: 0 });
}
},
}));
export default callCenterStore;

@ -0,0 +1,61 @@
import { useCallback, useState, useEffect } from "react";
import { Grid, Divider, Layout, Flex, Spin, Input, Col, Row, List, Typography } from "antd";
import { PhoneOutlined, CustomerServiceOutlined, AudioOutlined } from "@ant-design/icons";
import { useParams, useHref, useNavigate } from "react-router-dom";
import { isEmpty } from "@/utils/commons";
import callCenterStore from "@/stores/CallCenterStore";
import useAuthStore from "@/stores/AuthStore";
const CallCenter = props => {
const href = useHref();
const navigate = useNavigate();
const { phonenumber } = useParams();
const [init_vonage, make_call, hang_up, logs, call_id, loading] = callCenterStore(state => [state.init_vonage, state.make_call, state.hang_up, state.logs, state.call_id, state.loading]);
const [loginUser] = useAuthStore(state => [state.loginUser]);
const [phone_number, setPhone_number] = useState(phonenumber);
useEffect(() => {
if (loginUser.userId === -1 && href.indexOf("/p/") === -1) {
navigate("/p/dingding/login?origin_url=" + href);
} else {
init_vonage(loginUser.userId);
}
}, [href, navigate, init_vonage, loginUser]);
const oncall = () => {
if (isEmpty(call_id)) {
make_call(phone_number);
} else {
hang_up();
}
};
return (
<>
<br />
<Row gutter={16}>
<Col md={24} lg={8} xxl={9}></Col>
<Col md={24} lg={8} xxl={6}>
<Input.Search
type="tel"
size="large"
defaultValue={phone_number}
placeholder="电话号码"
prefix={<AudioOutlined />}
suffix={loading ? <Spin /> : ""}
enterButton={call_id ? "挂断" : "拨号"}
onSearch={oncall}
onChange={e => {
setPhone_number(e.target.value);
}}></Input.Search>
</Col>
<Col md={24} lg={8} xxl={9}></Col>
</Row>
<Divider plain orientation="left" className="mb-0"></Divider>
<List header={<Typography.Text strong>Console Logs</Typography.Text>} bordered dataSource={logs} renderItem={item => <List.Item>{item}</List.Item>} />
</>
);
};
export default CallCenter;

@ -1,7 +1,7 @@
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState, useRef } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { Dropdown, Input, Button, Empty, Tooltip, Tag, Select } from 'antd'; import { Dropdown, Input, Button, Empty, Tooltip, Tag, Select } from 'antd';
import { PlusOutlined, WhatsAppOutlined, LoadingOutlined, HistoryOutlined, FireOutlined, } from '@ant-design/icons'; import { PlusOutlined, WhatsAppOutlined, LoadingOutlined, HistoryOutlined, FireOutlined,AudioTwoTone } from '@ant-design/icons';
import { fetchConversationsList, fetchOrderConversationsList, fetchConversationItemClose, fetchConversationsSearch, postNewConversationItem, fetchConversationItemUnread, UNREAD_MARK } from '@/actions/ConversationActions'; import { fetchConversationsList, fetchOrderConversationsList, fetchConversationItemClose, fetchConversationsSearch, postNewConversationItem, fetchConversationItemUnread, UNREAD_MARK } from '@/actions/ConversationActions';
import { ChatItem } from 'react-chat-elements'; import { ChatItem } from 'react-chat-elements';
import ConversationsNewItem from './ConversationsNewItem'; import ConversationsNewItem from './ConversationsNewItem';
@ -263,6 +263,7 @@ const Conversations = ({ mobile }) => {
type='text' type='text'
/> />
</Tooltip> </Tooltip>
{ mobile === undefined?'':<AudioTwoTone onClick={()=>{navigate(`/callcenter/call`)}} />}
</div> </div>
<div className='flex-1 overflow-x-hidden overflow-y-auto relative'> <div className='flex-1 overflow-x-hidden overflow-y-auto relative'>
{conversationsListLoading && dataSource.length === 0 ? ( {conversationsListLoading && dataSource.length === 0 ? (

@ -1,190 +1,231 @@
import { LinkOutlined, MailOutlined, PhoneOutlined, UserOutlined, WhatsAppOutlined } from '@ant-design/icons' import { LinkOutlined, MailOutlined, PhoneOutlined, UserOutlined, WhatsAppOutlined } from "@ant-design/icons";
import { App, Button, Card, Empty, Flex, Select, Spin, Typography, Divider, Modal } from 'antd' import { App, Button, Card, Empty, Flex, Select, Spin, Typography, Divider, Modal } from "antd";
import { useEffect, useState, useRef } from 'react' import { useEffect, useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { copy, isEmpty } from '@/utils/commons' import { copy, isEmpty } from "@/utils/commons";
import { Conditional } from '@/components/Conditional' import { Conditional } from "@/components/Conditional";
import useConversationStore from '@/stores/ConversationStore' import useConversationStore from "@/stores/ConversationStore";
import { useOrderStore, OrderLabelDefaultOptions, OrderStatusDefaultOptions } from '@/stores/OrderStore' import { useOrderStore, OrderLabelDefaultOptions, OrderStatusDefaultOptions } from "@/stores/OrderStore";
import useAuthStore from '@/stores/AuthStore' import useAuthStore from "@/stores/AuthStore";
import QuotesHistory from './QuotesHistory' import QuotesHistory from "./QuotesHistory";
import ConversationBind from './../ConversationBind'; import ConversationBind from "./../ConversationBind";
import ConversationsNewItem from './../ConversationsNewItem'; import ConversationsNewItem from "./../ConversationsNewItem";
import { useConversationNewItem } from '@/hooks/useConversation'; import { useConversationNewItem } from "@/hooks/useConversation";
const CustomerProfile = (() => { const CustomerProfile = () => {
const { notification, message } = App.useApp() const { notification, message } = App.useApp();
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false) const [isModalOpen, setIsModalOpen] = useState(false);
const orderCommentRef = useRef(null) const orderCommentRef = useRef(null);
const currentOrder = useConversationStore((state) => state.currentConversation?.coli_sn || '') const currentOrder = useConversationStore(state => state.currentConversation?.coli_sn || "");
const currentConversationID = useConversationStore((state) => state.currentConversation?.sn || '') const currentConversationID = useConversationStore(state => state.currentConversation?.sn || "");
const [updateCurrentConversation] = useConversationStore(((state) => [state.updateCurrentConversation])); const [updateCurrentConversation] = useConversationStore(state => [state.updateCurrentConversation]);
const loginUser = useAuthStore((state) => state.loginUser) const loginUser = useAuthStore(state => state.loginUser);
const { orderDetail, customerDetail, lastQuotation, quotationList, const { orderDetail, customerDetail, lastQuotation, quotationList, fetchOrderDetail, setOrderPropValue, appendOrderComment } = useOrderStore();
fetchOrderDetail, setOrderPropValue, appendOrderComment
} = useOrderStore()
const orderLabelOptions = copy(OrderLabelDefaultOptions) const navigate = useNavigate();
orderLabelOptions.unshift({ value: 0, label: '未设置', disabled: true, }) const orderLabelOptions = copy(OrderLabelDefaultOptions);
orderLabelOptions.unshift({ value: 0, label: "未设置", disabled: true });
const orderStatusOptions = copy(OrderStatusDefaultOptions) const orderStatusOptions = copy(OrderStatusDefaultOptions);
useEffect(() => { useEffect(() => {
if (currentOrder) { if (currentOrder) {
setLoading(true) setLoading(true);
fetchOrderDetail(currentOrder) fetchOrderDetail(currentOrder)
.finally(() => setLoading(false)) .finally(() => setLoading(false))
.catch(reason => { .catch(reason => {
notification.error({ notification.error({
message: '查询出错', message: "查询出错",
description: reason.message, description: reason.message,
placement: 'top', placement: "top",
duration: 60, duration: 60,
}) });
}) });
} }
}, [currentOrder]) }, [currentOrder]);
let regularText = '' let regularText = "";
if (orderDetail.buytime > 0) regularText = '(R' + orderDetail.buytime + ')' if (orderDetail.buytime > 0) regularText = "(R" + orderDetail.buytime + ")";
const { openOrderContactConversation } = useConversationNewItem(); const { openOrderContactConversation } = useConversationNewItem();
const [newChatModalVisible, setNewChatModalVisible] = useState(false); const [newChatModalVisible, setNewChatModalVisible] = useState(false);
const [newChatFormValues, setNewChatFormValues] = useState({}); const [newChatFormValues, setNewChatFormValues] = useState({});
const handleNewChat = async (values) => { const handleNewChat = async values => {
const newContact = { wa_id: values.wa_id }; const newContact = { wa_id: values.wa_id };
openOrderContactConversation(newContact.wa_id); openOrderContactConversation(newContact.wa_id);
setNewChatModalVisible(false); setNewChatModalVisible(false);
} };
if (currentOrder) { if (currentOrder) {
return ( return (
<div className='divide-x-0 divide-y divide-dashed divide-gray-300'> <div className="divide-x-0 divide-y divide-dashed divide-gray-300">
<Spin spinning={loading}> <Spin spinning={loading}>
<Card className='p-2 ' <Card
className="p-2 "
bordered={false} bordered={false}
title={orderDetail.order_no} title={orderDetail.order_no}
actions={[ actions={[
<Select key={'orderlabel'} size='small' <Select
key={"orderlabel"}
size="small"
style={{ style={{
width: '100%' width: "100%",
}} }}
variant='borderless' variant="borderless"
onSelect={(value) => { onSelect={value => {
setOrderPropValue(currentOrder, 'orderlabel', value) setOrderPropValue(currentOrder, "orderlabel", value)
.then(() => { .then(() => {
message.success('设置成功') message.success("设置成功");
}) })
.catch(reason => { .catch(reason => {
notification.error({ notification.error({
message: '设置出错', message: "设置出错",
description: reason.message, description: reason.message,
placement: 'top', placement: "top",
duration: 60, duration: 60,
}) });
}) });
}} }}
value={orderDetail.tags} value={orderDetail.tags}
options={orderLabelOptions} options={orderLabelOptions}
/>, />,
<Select key={'orderstatus'} size='small' <Select
key={"orderstatus"}
size="small"
style={{ style={{
width: '100%' width: "100%",
}} }}
variant='borderless' variant="borderless"
onSelect={(value) => { onSelect={value => {
setOrderPropValue(currentOrder,'orderstatus', value) setOrderPropValue(currentOrder, "orderstatus", value)
.then(() => { .then(() => {
message.success('设置成功') message.success("设置成功");
}) })
.catch(reason => { .catch(reason => {
notification.error({ notification.error({
message: '设置出错', message: "设置出错",
description: reason.message, description: reason.message,
placement: 'top', placement: "top",
duration: 60, duration: 60,
}) });
}) });
}} }}
value={orderDetail.states} value={orderDetail.states}
options={orderStatusOptions} options={orderStatusOptions}
/> />,
]} ]}>
>
<Flex gap={10}> <Flex gap={10}>
<Flex vertical={true} justify='space-between'> <Flex vertical={true} justify="space-between">
<Typography.Text ><UserOutlined className=' pr-1' />{customerDetail.name + regularText}</Typography.Text> <Typography.Text>
<Typography.Text ><PhoneOutlined className=' pr-1' />{customerDetail.phone}</Typography.Text> <UserOutlined className=" pr-1" />
<Typography.Text ><MailOutlined className=' pr-1' />{customerDetail.email}</Typography.Text> {customerDetail.name + regularText}
</Typography.Text>
<Typography.Text>
<PhoneOutlined className=" pr-1" />
<Button
type="link"
size={"small"}
onClick={() => {
navigate(`/callcenter/call/` + customerDetail.phone);
}}>
{customerDetail.phone}
</Button>
</Typography.Text>
<Typography.Text>
<MailOutlined className=" pr-1" />
{customerDetail.email}
</Typography.Text>
<Typography.Text> <Typography.Text>
<WhatsAppOutlined className='pr-1' /> <WhatsAppOutlined className="pr-1" />
<Button type='link' size={'small'} onClick={() => { <Button
type="link"
size={"small"}
onClick={() => {
setNewChatModalVisible(true); setNewChatModalVisible(true);
setNewChatFormValues(prev => ({...prev, phone_number: customerDetail.whatsapp_phone_number, is_current_order: true, })) setNewChatFormValues(prev => ({ ...prev, phone_number: customerDetail.whatsapp_phone_number, is_current_order: true }));
}} >{customerDetail.whatsapp_phone_number}</Button> }}>
{customerDetail.whatsapp_phone_number}
</Button>
</Typography.Text> </Typography.Text>
</Flex> </Flex>
</Flex> </Flex>
</Card> </Card>
<Divider orientation='left'><Typography.Text strong>最新报价</Typography.Text></Divider> <Divider orientation="left">
<Flex vertical={true} className='p-2 '> <Typography.Text strong>最新报价</Typography.Text>
</Divider>
<Flex vertical={true} className="p-2 ">
<Conditional <Conditional
condition={quotationList.length > 0} condition={quotationList.length > 0}
whenFalse={<Empty description={<span>暂无报价</span>}></Empty>} whenFalse={<Empty description={<span>暂无报价</span>}></Empty>}
whenTrue={ whenTrue={
<> <>
<p className='m-0 py-2 line-clamp-2 '><a target='_blank' href={lastQuotation.letterurl}><LinkOutlined />&nbsp;{lastQuotation.lettertitle}</a></p> <p className="m-0 py-2 line-clamp-2 ">
<Flex justify={'space-between'} > <a target="_blank" href={lastQuotation.letterurl}>
<LinkOutlined />
&nbsp;{lastQuotation.lettertitle}
</a>
</p>
<Flex justify={"space-between"}>
<QuotesHistory dataSource={quotationList} /> <QuotesHistory dataSource={quotationList} />
</Flex> </Flex>
</> </>
}/> }
/>
</Flex> </Flex>
<Divider orientation='left'><Typography.Text strong>表单信息</Typography.Text></Divider> <Divider orientation="left">
<p className='p-2 overflow-auto m-0 break-words whitespace-pre-wrap' dangerouslySetInnerHTML={{__html: orderDetail.order_detail}}></p> <Typography.Text strong>表单信息</Typography.Text>
<Modal title='添加备注' open={isModalOpen} </Divider>
<p className="p-2 overflow-auto m-0 break-words whitespace-pre-wrap" dangerouslySetInnerHTML={{ __html: orderDetail.order_detail }}></p>
<Modal
title="添加备注"
open={isModalOpen}
onOk={() => { onOk={() => {
const orderCommnet = orderCommentRef.current.value const orderCommnet = orderCommentRef.current.value;
if (isEmpty(orderCommnet)) { if (isEmpty(orderCommnet)) {
message.warning('请输入备注后再提交。') message.warning("请输入备注后再提交。");
} else { } else {
appendOrderComment(loginUser.userId, currentOrder, orderCommnet) appendOrderComment(loginUser.userId, currentOrder, orderCommnet)
.then(() => { .then(() => {
message.success('添加成功') message.success("添加成功");
setIsModalOpen(false) setIsModalOpen(false);
}) })
.catch(reason => { .catch(reason => {
notification.error({ notification.error({
message: '添加出错', message: "添加出错",
description: reason.message, description: reason.message,
placement: 'top', placement: "top",
duration: 60, duration: 60,
}) });
}) });
} }
orderCommentRef.current.value = '' orderCommentRef.current.value = "";
}} }}
onCancel={() => {setIsModalOpen(false)}}> onCancel={() => {
<textarea ref={orderCommentRef} className='w-full' rows={4}></textarea> setIsModalOpen(false);
}}>
<textarea ref={orderCommentRef} className="w-full" rows={4}></textarea>
</Modal> </Modal>
<Button size={'small'} onClick={() => { <Button
setIsModalOpen(true) size={"small"}
}}>添加备注</Button> onClick={() => {
setIsModalOpen(true);
}}>
添加备注
</Button>
</Spin> </Spin>
<ConversationsNewItem initialValues={newChatFormValues} open={newChatModalVisible} onCreate={handleNewChat} onCancel={() => setNewChatModalVisible(false)} /> <ConversationsNewItem initialValues={newChatFormValues} open={newChatModalVisible} onCreate={handleNewChat} onCancel={() => setNewChatModalVisible(false)} />
</div> </div>
) );
} else { } else {
return ( return (
<Empty <Empty description={<span>暂无相关订单</span>}>
description={<span>暂无相关订单</span>} <ConversationBind currentConversationID={currentConversationID} onBoundSuccess={coli_sn => updateCurrentConversation({ coli_sn })} />
>
<ConversationBind currentConversationID={currentConversationID} onBoundSuccess={(coli_sn) => updateCurrentConversation({coli_sn})} />
</Empty> </Empty>
) );
} }
}) };
export default CustomerProfile export default CustomerProfile;

@ -31,7 +31,7 @@ function DesktopApp() {
let defaultPath = '/order/follow' let defaultPath = '/order/follow'
if (href !== '/') { if (href !== '/') {
const splitPath = href.split('/') const splitPath = href.split('/');
if (splitPath.length > 2) { if (splitPath.length > 2) {
defaultPath = '/' + splitPath[1] + '/' + splitPath[2] defaultPath = '/' + splitPath[1] + '/' + splitPath[2]
} }
@ -94,6 +94,7 @@ function DesktopApp() {
</Link> </Link>
), ),
}, },
{ key: '/callcenter/call', label: <Link to='/callcenter/call'>语音通话</Link> },
{ key: '/chat/history', label: <Link to='/chat/history'>聊天记录</Link> }, { key: '/chat/history', label: <Link to='/chat/history'>聊天记录</Link> },
]} ]}
/> />

Loading…
Cancel
Save