tiefen_space_h5/app/messageDetail/page.js

614 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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({
list: [],
isGetHistory: false,
});
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 [sendding, setSendding] = 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(() => {
// debugger;
// console.log("oldMessages[0]", oldMessages[0]);
if (oldMessages.list[0]) {
updateMessages(oldMessages.list[0]?.id, 0, oldMessages.list);
// toScrollBottom.current = 1;
}
}, 3000); // 间隔时间为3秒
// 在组件卸载时清除定时器
return () => {
clearInterval(intervalId);
};
}, [oldMessages.list]);
useEffect(() => {
// debugger;
if (toScrollBottom.current && !oldMessages.isGetHistory) {
scrollBox.current?.scrollTo(0, scrollBox.current.scrollHeight + 50);
toScrollBottom.current = 0;
}
}, [oldMessages.list, oldMessages.isGetHistory]);
useEffect(() => {
if (typeof sessionId === "number" && 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;
}
const data2 = await requireAPI(
"POST",
"/api/contact_customer_service/read_all",
{
body: {
session_id: sessionId,
},
}
);
if (data2.ret === -1) {
Toast.show({
icon: "fail",
content: data2.msg,
position: "top",
});
return;
}
setOffset(data.data.offset);
setMore(data.data.more);
let mathNewMessages = handleData(
[...oldMessages.list, ...data.data.list],
oldMessages.list.length > 0
);
// 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);
//请求接口发送私信
setSendding(true);
try {
const data = await requireAPI(
"POST",
"/api/contact_customer_service/create",
{
body: {
session_id: sessionId,
predicate: 0,
message,
},
}
);
setSendding(false);
if (data.ret === -1) {
Toast.show({
icon: "error",
content: data.msg,
position: "top",
});
return;
}
// updateLatestHistory();
updateMessages(lastId, 0, oldArr).then((res) => {
setNewMessage("");
toScrollBottom.current = 1;
});
} catch (error) {
// console.error(error);
setSendding(false);
}
// //每次发送都缓存信息到本地
// 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 (typeof sessionId === "number" && userInfo.mid && offset && more) {
const append = await loadEarlierHistory();
if (append) {
// setMessages((val) => [...val, ...append]);
setMessages(append);
// setHasMore(append.length > 0);
}
}
}
const handleData = (list, isGetHistory) => {
// 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,
m_type: item.m_type,
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,
m_type: item.m_type,
user: {
_id: 0,
name: "客服",
avatar:
process.env.NEXT_PUBLIC_WEB_ASSETS_URL +
"/images/icon_border.png",
},
};
}
});
// console.log("handledmessages......", handledmessages);
// console.log("[...messages, ...temMessages]", temMessages);
setHandledmessages(temMessages);
setOldMessages((old) => {
// debugger;
if (old.list.length === list.length) {
return old;
} else {
// debugger;
return { list, isGetHistory: !!isGetHistory };
}
});
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, oldArr);
const newMessages = [...newData, ...oldArr].filter(
(item, index, self) => index === self.findIndex((t) => t.id === item.id)
);
let mathNewMessages = handleData([...newMessages]);
setMessages((old) => {
toScrollBottom.current = 1;
return mathNewMessages;
});
if (mathNewMessages.length > messages.length) {
const data2 = await requireAPI(
"POST",
"/api/contact_customer_service/read_all",
{
body: {
session_id: sessionId,
},
}
);
if (data2.ret === -1) {
Toast.show({
icon: "fail",
content: data2.msg,
position: "top",
});
return;
}
}
return;
} catch (error) {
console.error(error);
}
};
const getAutoMessage = useCallback(
(data) => {
if (data.m_type === 1) {
const autoAnswerStrs = data.text.split("|");
const btns = autoAnswerStrs[1].split(",");
return (
<div>
<pre>{autoAnswerStrs[0]}</pre>
<div className="my-2">
<ul className="grid grid-cols-2 gap-2 mt-2">
{btns.map((item, index) => {
return (
<li
key={index}
onClick={() => {
onSend(
item,
oldMessages[0]?.id || -1,
oldMessages.list
);
}}
className="bg-[#3333331A] rounded text-center px-4 py-1"
>
{item}
</li>
);
})}
</ul>
</div>
</div>
);
} else {
return data?.text;
}
// data.m_type===1?it?.text.split("|")it?.text
},
[sessionId, oldMessages.list]
);
return (
<div className="bg-[#13121F] h-screen overflow-y-auto" ref={scrollBox}>
<div className="p-4 fixed top-0 z-10 w-full bg-deepBg">
<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-2 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 items-start w-full">
<Avatar
className="mr-2 w-[44px] h-[44px]"
style={{
"--border-radius": "50px",
"--size": "44px",
}}
width={32}
height={32}
src={it?.user.avatar}
/>
<div
className="rounded-lg py-2 px-3 text-base bg-white text-black break-words"
style={{
borderTopLeftRadius: 0,
maxWidth: "calc(100% - 44px - 0.75rem)",
}}
>
{getAutoMessage(it)}
</div>
</div>
) : (
<div className="flex justify-end items-start w-full">
<div
className="max-w-full rounded-lg py-2 px-3 text-base bg-blue-500 break-words"
style={{
borderTopRightRadius: 0,
maxWidth: "calc(100% - 44px - 0.75rem)",
}}
>
{it?.text}
</div>
<Avatar
className="ml-2 w-[44px] h-[44px]"
style={{
"--border-radius": "50px",
"--size": "44px",
}}
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-[10px] bg-[#222036] px-4 py-2 mr-2">
<Input
placeholder="输入新消息"
className=""
value={newMessage}
onChange={setNewMessage}
style={{ "--font-size": "16px" }}
/>
</div>
<Button
size="middle"
block
disabled={sendding}
onClick={() => {
if (newMessage.includes("|")) {
Toast.show({
icon: "fail",
content: "消息不能包含|",
position: "top",
});
return;
}
onSend(
newMessage,
typeof oldMessages.list[0]?.id == "number"
? oldMessages.list[0]?.id
: -1,
oldMessages.list
);
}}
style={{
"--background-color": "#FF669E",
"--border-radius": "10px",
color: "#FFFFFF",
}}
>
发送
</Button>
</div>
)}
</div>
</div>
);
}