496 lines
15 KiB
JavaScript
496 lines
15 KiB
JavaScript
"use client";
|
||
|
||
import React, { useState, useRef, useEffect, useCallback } from "react";
|
||
|
||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||
import { faAngleLeft } from "@fortawesome/free-solid-svg-icons";
|
||
import { Input, Button, Toast, Avatar, DotLoading } from "antd-mobile";
|
||
import { useRouter, useSearchParams } from "next/navigation";
|
||
import { get } from "@/utils/storeInfo";
|
||
import requireAPI from "@/utils/requireAPI";
|
||
import { formatDeadline } from "@/utils/tools";
|
||
import { getStreamerDetailInfo } from "@/api/space";
|
||
/*
|
||
params格式:
|
||
{
|
||
mid: item.mid,
|
||
}
|
||
*/
|
||
|
||
export default function MessageDetail({}) {
|
||
// const [hasMore, setHasMore] = useState(true);
|
||
const router = useRouter();
|
||
const searchParams = useSearchParams();
|
||
const [oldMessages, setOldMessages] = useState([]);
|
||
const [messages, setMessages] = useState([]);
|
||
const [handledmessages, setHandledmessages] = useState([]);
|
||
const [sessionId, setSessionId] = useState();
|
||
const [userInfo, setUserInfo] = useState(null);
|
||
const [newMessage, setNewMessage] = useState("");
|
||
const [loading, setLoading] = useState(false);
|
||
const [offset, setOffset] = useState(0);
|
||
const [more, setMore] = useState(1);
|
||
const scrollBox = useRef();
|
||
const toScrollBottom = useRef(0);
|
||
useEffect(() => {
|
||
const userData = get("account");
|
||
const mid = searchParams.get("mid");
|
||
if (mid) {
|
||
getStreamerDetailInfo(Number(mid)).then((data) => {
|
||
setMessages([
|
||
[
|
||
{
|
||
predicate: 1,
|
||
_id: 1,
|
||
createdAt: new Date() / 1000,
|
||
text: data?.streamer_ext.auto_response_message,
|
||
user: {
|
||
_id: 0,
|
||
name: data?.streamer_ext.name,
|
||
avatar: data?.streamer_ext?.avatar?.images[0]?.urls[0],
|
||
},
|
||
},
|
||
],
|
||
]);
|
||
});
|
||
} else {
|
||
setUserInfo(userData);
|
||
getSession(userData.mid);
|
||
}
|
||
}, []);
|
||
useEffect(() => {
|
||
const intervalId = setInterval(() => {
|
||
// console.log("oldMessages[0]", oldMessages[0]);
|
||
if (oldMessages[0]) {
|
||
updateMessages(oldMessages[0]?.id, 0, oldMessages);
|
||
// toScrollBottom.current = 1;
|
||
}
|
||
}, 3000); // 间隔时间为3秒
|
||
|
||
// 在组件卸载时清除定时器
|
||
return () => {
|
||
clearInterval(intervalId);
|
||
};
|
||
}, [oldMessages]);
|
||
useEffect(() => {
|
||
if (toScrollBottom.current) {
|
||
scrollBox.current?.scrollTo(0, scrollBox.current.scrollHeight + 50);
|
||
toScrollBottom.current = 0;
|
||
}
|
||
}, [messages]);
|
||
// useEffect(() => {
|
||
// if (offset == 12) {
|
||
// console.log("offset--------", offset);
|
||
|
||
// scrollBox.current?.scrollTo(0, scrollBox.current.scrollHeight);
|
||
|
||
// // console.log("scrollBox.current",scrollBox.current.scrollHeight,scrollBox.current.scrollTop)
|
||
// }
|
||
// }, [offset]);
|
||
useEffect(() => {
|
||
if (sessionId && userInfo.mid) {
|
||
loadEarlierHistory().then((res) => {
|
||
setMessages((old) => {
|
||
toScrollBottom.current = 1;
|
||
return res;
|
||
});
|
||
});
|
||
}
|
||
}, [sessionId]);
|
||
//查询session
|
||
const getSession = async (mid) => {
|
||
setLoading(true);
|
||
try {
|
||
const data = await requireAPI(
|
||
"POST",
|
||
"/api/contact_customer_service_session/list_by_mid",
|
||
{
|
||
body: {
|
||
mid: Number(mid),
|
||
},
|
||
}
|
||
);
|
||
if (data.ret === -1) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: data.msg,
|
||
position: "top",
|
||
});
|
||
}
|
||
setLoading(false);
|
||
if (data.data.session) {
|
||
setSessionId(data.data.session.id);
|
||
} else {
|
||
//如果是第一次发送,需要创建session
|
||
createSession(mid);
|
||
}
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
};
|
||
//创建session
|
||
const createSession = async (mid) => {
|
||
setLoading(true);
|
||
try {
|
||
const data = await requireAPI(
|
||
"POST",
|
||
"/api/contact_customer_service_session/create",
|
||
{
|
||
body: {
|
||
sub_mid: Number(mid),
|
||
obj_mid: 0,
|
||
},
|
||
}
|
||
);
|
||
|
||
if (data.ret === -1) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: data.msg,
|
||
position: "top",
|
||
});
|
||
}
|
||
setSessionId(data.data.session_id);
|
||
setLoading(false);
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
};
|
||
//请求历史记录
|
||
const loadEarlierHistory = async () => {
|
||
if (!more) return;
|
||
try {
|
||
setLoading(true);
|
||
const data = await requireAPI(
|
||
"POST",
|
||
"/api/contact_customer_service/list_by_session_id",
|
||
{
|
||
body: {
|
||
session_id: sessionId,
|
||
offset: offset,
|
||
limit: 12,
|
||
},
|
||
}
|
||
);
|
||
if (data.ret === -1) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: data.msg,
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
setOffset(data.data.offset);
|
||
setMore(data.data.more);
|
||
let mathNewMessages = handleData([...oldMessages, ...data.data.list]);
|
||
// setMessages((prev) => [...prev, ...temMessages]);
|
||
setLoading(false);
|
||
return mathNewMessages;
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
};
|
||
//发送私信功能
|
||
const onSend = useCallback(
|
||
async (message, lastId, oldArr) => {
|
||
if (message == "") {
|
||
// Toast.show({
|
||
// icon: "error",
|
||
// content: "不可发送空内容",
|
||
// position: "top",
|
||
// });
|
||
return;
|
||
}
|
||
|
||
//查询历史记录的时候后移一位,防止记录重复
|
||
setOffset((prev) => prev + 1);
|
||
//请求接口发送私信
|
||
try {
|
||
const data = await requireAPI(
|
||
"POST",
|
||
"/api/contact_customer_service/create",
|
||
{
|
||
body: {
|
||
session_id: sessionId,
|
||
predicate: 0,
|
||
message,
|
||
},
|
||
}
|
||
);
|
||
if (data.ret === -1) {
|
||
Toast.show({
|
||
icon: "error",
|
||
content: data.msg,
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
// updateLatestHistory();
|
||
// console.log("oldArr", oldArr);
|
||
updateMessages(lastId, 0, oldArr).then((res) => {
|
||
setNewMessage("");
|
||
toScrollBottom.current = 1;
|
||
});
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
// //每次发送都缓存信息到本地
|
||
// addArr(`${selfData.mid}_to_${params.mid}_messages`, messages);
|
||
},
|
||
[userInfo, sessionId]
|
||
);
|
||
// async function doRefresh() {
|
||
// await sleep(1000);
|
||
// Toast.show({
|
||
// icon: "fail",
|
||
// content: "刷新失败",
|
||
// });
|
||
// throw new Error("刷新失败");
|
||
// }
|
||
async function loadMore() {
|
||
if (sessionId && userInfo.mid && offset && more) {
|
||
const append = await loadEarlierHistory();
|
||
if (append) {
|
||
// setMessages((val) => [...val, ...append]);
|
||
setMessages(append);
|
||
// setHasMore(append.length > 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleData = (list) => {
|
||
// console.log("list", list);
|
||
const account = get("account");
|
||
const temMessages = list.map((item) => {
|
||
if (item.predicate === 0) {
|
||
return {
|
||
predicate: item.predicate,
|
||
_id: item.id,
|
||
// createdAt: new Date(item.ct * 1000).toISOString(),
|
||
createdAt: item.ct,
|
||
text: item.message,
|
||
user: {
|
||
_id: account?.mid,
|
||
name: account?.name,
|
||
avatar: account?.avatar?.images[0]?.urls[0],
|
||
},
|
||
};
|
||
} else {
|
||
return {
|
||
predicate: item.predicate,
|
||
_id: item.id,
|
||
createdAt: item.ct,
|
||
text: item.message,
|
||
user: {
|
||
_id: 0,
|
||
name: "客服",
|
||
avatar: process.env.NEXT_PUBLIC_WEB_ASSETS_URL + "/images/icon.png",
|
||
},
|
||
};
|
||
}
|
||
});
|
||
// console.log("handledmessages......", handledmessages);
|
||
// console.log("[...messages, ...temMessages]", temMessages);
|
||
setHandledmessages(temMessages);
|
||
setOldMessages(list);
|
||
let newMessages = temMessages.reverse();
|
||
let mathNewMessages = newMessages.reduce(
|
||
(accumulator, currentValue, index, sourceArray) => {
|
||
// console.log(
|
||
// accumulator.time,
|
||
// "----",
|
||
// currentValue.createdAt,
|
||
// "---",
|
||
// index
|
||
// );
|
||
if (accumulator.time > currentValue.createdAt) {
|
||
let newData = {
|
||
...accumulator,
|
||
time: accumulator.time,
|
||
messages: [...accumulator.messages, currentValue],
|
||
totalArr:
|
||
index == sourceArray.length - 1
|
||
? [
|
||
...accumulator.totalArr,
|
||
[...accumulator.messages, currentValue],
|
||
]
|
||
: [...accumulator.totalArr],
|
||
};
|
||
return newData;
|
||
} else {
|
||
let newData = {
|
||
...accumulator,
|
||
time: currentValue.createdAt + 60 * 3,
|
||
messages: [currentValue],
|
||
totalArr:
|
||
index == sourceArray.length - 1
|
||
? [
|
||
...accumulator.totalArr,
|
||
accumulator.messages,
|
||
[currentValue],
|
||
]
|
||
: [...accumulator.totalArr, accumulator.messages],
|
||
};
|
||
return newData;
|
||
}
|
||
},
|
||
{ time: newMessages[0]?.createdAt + 60 * 3, messages: [], totalArr: [] }
|
||
);
|
||
return mathNewMessages.totalArr;
|
||
};
|
||
|
||
const updateMessages = async (lastId, currentOffset, oldArr) => {
|
||
// console.log("lastId", lastId);
|
||
try {
|
||
const data = await requireAPI(
|
||
"POST",
|
||
"/api/contact_customer_service/list_by_session_id",
|
||
{
|
||
body: {
|
||
session_id: sessionId,
|
||
offset: currentOffset,
|
||
limit: 12,
|
||
},
|
||
}
|
||
);
|
||
if (data.ret === -1) {
|
||
Toast.show({
|
||
icon: "fail",
|
||
content: data.msg,
|
||
position: "top",
|
||
});
|
||
return;
|
||
}
|
||
let newData = data.data.list.filter((element) => {
|
||
return element.id > lastId;
|
||
});
|
||
// console.log("[...messages,...newData]", [...newData]);
|
||
let mathNewMessages = handleData([...newData, ...oldArr]);
|
||
setMessages((old) => {
|
||
toScrollBottom.current = 1;
|
||
return mathNewMessages;
|
||
});
|
||
return;
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
};
|
||
return (
|
||
<div className="bg-[#13121F] h-screen overflow-y-auto" ref={scrollBox}>
|
||
<div className="p-4 fixed top-0 z-10 w-full bg-black">
|
||
<div className="flex items-center justify-center absolute">
|
||
<FontAwesomeIcon
|
||
icon={faAngleLeft}
|
||
style={{ maxWidth: "12px" }}
|
||
size="xl"
|
||
onClick={() => {
|
||
router.back();
|
||
}}
|
||
/>
|
||
</div>
|
||
<p className="text-base text-center">
|
||
{!searchParams.get("mid") ? "在线客服" : messages?.[0]?.[0].user.name}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<div className="my-[57px]">
|
||
{!searchParams.get("mid") && (
|
||
<div className="flex justify-center py-2">
|
||
<div className="px-3 py-2 rounded-full bg-[#FFFFFF1A]">
|
||
{loading ? (
|
||
<DotLoading />
|
||
) : more ? (
|
||
<span onClick={loadMore}>查看更早</span>
|
||
) : (
|
||
<span>无更早消息</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
<ul className="py-2">
|
||
{messages?.map((item, index) => (
|
||
<li key={index}>
|
||
<p className="my-2 text-center">
|
||
{formatDeadline(item[0].createdAt)}
|
||
</p>
|
||
<ul className="px-4 overflow-y-auto scrollbarBox_hidden">
|
||
{item.map((it) => (
|
||
<li key={it?._id} className="py-3 rounded-lg ">
|
||
<div className="flex w-full">
|
||
{it?.predicate == 1 ? (
|
||
<div className="flex justify-start w-full">
|
||
<Avatar
|
||
className="mr-2 w-[32px] h-[32px]"
|
||
style={{ "--border-radius": "50px" }}
|
||
width={32}
|
||
height={32}
|
||
src={it?.user.avatar}
|
||
/>
|
||
<div
|
||
className="rounded-lg py-2 px-3 bg-blue-500 break-words"
|
||
style={{
|
||
borderTopLeftRadius: 0,
|
||
maxWidth: "calc(100% - 32px - 0.75rem)",
|
||
}}
|
||
>
|
||
{it?.text}
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="flex justify-end w-full">
|
||
<div
|
||
className="max-w-full rounded-lg py-2 px-3 bg-blue-500 break-words"
|
||
style={{
|
||
borderTopRightRadius: 0,
|
||
maxWidth: "calc(100% - 32px - 0.75rem)",
|
||
}}
|
||
>
|
||
{it?.text}
|
||
</div>
|
||
<Avatar
|
||
className="ml-2 w-[32px] h-[32px]"
|
||
style={{ "--border-radius": "50px" }}
|
||
width={32}
|
||
height={32}
|
||
src={it?.user?.avatar}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</li>
|
||
))}
|
||
{}
|
||
{/* <InfiniteScroll loadMore={loadMore} hasMore={more} /> */}
|
||
</ul>
|
||
</div>
|
||
{!searchParams.get("mid") && (
|
||
<div className="w-full h-16 fixed bottom-0 grid grid-cols-[1fr_68px] bg-black items-center p-2 border-t-2 border-[#ffffff2a]">
|
||
<div className="rounded bg-[#222036] px-4 py-2 mr-2">
|
||
<Input
|
||
placeholder="输入新消息"
|
||
className=""
|
||
value={newMessage}
|
||
onChange={setNewMessage}
|
||
style={{ "--font-size": "16px" }}
|
||
/>
|
||
</div>
|
||
<Button
|
||
size="middle"
|
||
block
|
||
onClick={() =>
|
||
onSend(newMessage, oldMessages[0]?.id || -1, oldMessages)
|
||
}
|
||
style={{ "--background-color": "#FF669E", color: "#FFFFFF" }}
|
||
>
|
||
发送
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|