tiefen_space_app/screeens/Messages/MessageDetail/index.jsx

590 lines
16 KiB
React
Raw Normal View History

2023-12-29 00:27:44 +08:00
import { View, Text, Platform } from "react-native";
import React, { useState, useCallback, useEffect } from "react";
import {
GiftedChat,
Send,
InputToolbar,
Composer,
Bubble,
Day,
LoadEarlier,
} from "react-native-gifted-chat";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTailwind } from "tailwind-rn";
import "dayjs/locale/zh-cn";
import dayjs from "dayjs";
import { Image } from "expo-image";
import { get } from "../../../utils/storeInfo";
import Toast from "react-native-toast-message";
import baseRequest from "../../../utils/baseRequest";
import { generateSignature } from "../../../utils/crypto";
const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk";
/*
params格式
{
mid: item.mid,
}
*/
export default function MessageDetail({ navigation, route }) {
const tailwind = useTailwind();
const insets = useSafeAreaInsets();
const params = route.params;
const [messages, setMessages] = useState([]);
//获取本地自身数据
const [selfData, setSelfData] = useState({});
useEffect(() => {
async function getData() {
try {
const account = await get("account");
if (account) {
setSelfData(account);
}
} catch (e) {
console.error(e);
}
}
getData();
}, []);
//获取主播数据发送自动回复信息并设置页面标题
useEffect(() => {
const getData = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
mid: params.mid,
...base,
});
const detailResponse = await fetch(
`${apiUrl}/api/streamer/list_ext_by_mid?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mid: params.mid,
...base,
}),
}
);
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
Toast.show({
type: "error",
text1: detailData.msg,
topOffset: 60,
});
return;
}
navigation.setOptions({
title: detailData.data.streamer_ext.name,
});
await sendAutoMessages(
detailData.data.streamer_ext?.name,
detailData.data.streamer_ext?.avatar?.images[0]?.urls[0],
detailData.data.streamer_ext?.auto_response_message
);
} catch (error) {
console.error(error);
}
};
if (params?.mid === 1) {
navigation.setOptions({
title: "在线客服",
});
} else {
getData();
}
}, []);
//显示主播自动回复
const sendAutoMessages = async (name, avatar, content) => {
const temMessages = [
{
_id: 1,
text: content,
createdAt: new Date(),
user: {
_id: 1,
name: name,
avatar: avatar,
},
},
];
setMessages(temMessages);
};
//读取本地缓存的聊天记录
// const loadMessages = async () => {
// const accout = await get("account");
// const selfMid = accout.mid;
// const messagesCache = await get(`${selfMid}_to_${params.mid}_messages`);
// if (messagesCache) {
// setMessages(messagesCache);
// }
// };
//查询session
const [sessionId, setSessionId] = useState();
useEffect(() => {
const getSession = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const account = await get("account");
const signature = await generateSignature({
mid: account.mid,
...base,
});
const detailResponse = await fetch(
`${apiUrl}/api/contact_customer_service_session/list_by_mid?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mid: account.mid,
...base,
}),
}
);
const detailData = await detailResponse.json();
if (detailData.ret === -1) {
Toast.show({
type: "error",
text1: detailData.msg,
topOffset: 60,
});
return;
}
if (detailData.data.session) {
setSessionId(detailData.data.session.id);
return;
}
} catch (error) {
console.error(error);
}
};
getSession();
}, []);
//创建session
const createSession = async () => {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const account = await get("account");
const signature = await generateSignature({
sub_mid: account.mid,
obj_mid: 0,
...base,
});
const createResponse = await fetch(
`${apiUrl}/api/contact_customer_service_session/create?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
sub_mid: account.mid,
obj_mid: 0,
...base,
}),
}
);
const createData = await createResponse.json();
if (createData.ret === -1) {
Toast.show({
type: "error",
text1: createData.msg,
topOffset: 60,
});
return;
}
setSessionId(createData.data.session_id);
} catch (error) {
console.error(error);
}
};
//请求历史记录
const [offset, setOffset] = useState(1);
const [more, setMore] = useState(1);
const loadEarlierHistory = async () => {
if (params?.mid !== 1) return;
if (!more) return;
if (sessionId === undefined) return;
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
session_id: sessionId,
offset: offset,
limit: 12,
...base,
});
const response = await fetch(
`${apiUrl}/api/contact_customer_service/list_by_session_id?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
session_id: sessionId,
offset: offset,
limit: 12,
...base,
}),
}
);
const data = await response.json();
if (data.ret === -1) {
Toast.show({
type: "error",
text1: data.msg,
topOffset: 60,
});
return;
}
setOffset(data.data.offset);
setMore(data.data.more);
const account = await get("account");
const temMessages = data.data.list.map((item) => {
if (item.predicate === 0) {
return {
_id: item.id,
createdAt: new Date(item.ct * 1000).toISOString(),
text: item.message,
user: {
_id: account?.mid,
name: account?.name,
avatar: account?.avatar?.images[0]?.urls[0],
},
};
} else {
return {
_id: item.id,
createdAt: new Date(item.ct * 1000).toISOString(),
text: item.message,
user: {
_id: 0,
name: "客服",
avatar: require("../../../assets/icon.png"),
},
};
}
});
setMessages((prev) => [...prev, ...temMessages]);
} catch (error) {
console.error(error);
}
};
//请求最新1条历史记录如果与之前不同则加在第1条
const updateLatestHistory = async () => {
if (params?.mid !== 1) return;
if (sessionId === undefined) return;
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
session_id: sessionId,
offset: 0,
limit: 1,
...base,
});
const response = await fetch(
`${apiUrl}/api/contact_customer_service/list_by_session_id?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
session_id: sessionId,
offset: 0,
limit: 1,
...base,
}),
}
);
const data = await response.json();
if (data.ret === -1) {
Toast.show({
type: "error",
text1: data.msg,
topOffset: 60,
});
return;
}
const account = await get("account");
const temMessages = data.data.list.map((item) => {
if (item.predicate === 0) {
return {
_id: item.id,
createdAt: new Date(item.ct * 1000).toISOString(),
text: item.message,
user: {
_id: account.mid,
name: account.name,
avatar: account.avatar.images[0].urls[0],
},
};
} else {
return {
_id: item.id,
createdAt: new Date(item.ct * 1000).toISOString(),
text: item.message,
user: {
_id: 1,
name: "客服",
avatar: require("../../../assets/icon.png"),
},
};
}
});
setMessages((prev) => {
if (prev[0]?._id === temMessages[0]?._id) {
return prev;
} else {
return [...temMessages, ...prev];
}
});
} catch (error) {
console.error(error);
}
};
// 轮询更新历史记录
useEffect(() => {
loadEarlierHistory();
updateLatestHistory();
// 设置轮询请求,每隔一定时间执行一次
const intervalId = setInterval(() => {
updateLatestHistory();
}, 3000); // 间隔时间为3秒
// 在组件卸载时清除定时器
return () => {
clearInterval(intervalId);
};
}, [sessionId]);
//发送私信功能
const onSend = useCallback(
async (messages = []) => {
if (!messages[0].text) {
Toast.show({
type: "error",
text1: "不可发送空内容",
topOffset: 60,
});
return;
}
//如果是第一次发送需要创建session
if (!sessionId) await createSession();
//查询历史记录的时候后移一位,防止记录重复
setOffset((prev) => prev + 1);
//请求接口发送私信
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
try {
const base = await baseRequest();
const signature = await generateSignature({
session_id: sessionId,
predicate: 0,
message: messages[0].text,
...base,
});
const response = await fetch(
`${apiUrl}/api/contact_customer_service/create?signature=${signature}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
session_id: sessionId,
predicate: 0,
message: messages[0].text,
...base,
}),
}
);
const data = await response.json();
if (data.ret === -1) {
Toast.show({
type: "error",
text1: data.msg,
topOffset: 60,
});
return;
}
updateLatestHistory();
} catch (error) {
console.error(error);
}
// //每次发送都缓存信息到本地
// addArr(`${selfData.mid}_to_${params.mid}_messages`, messages);
},
[selfData, sessionId]
);
//发送按钮样式
const renderSend = useCallback((props) => {
return (
<Send
{...props}
disabled={!props.text}
containerStyle={tailwind(
"px-4 bg-[#FF669E] justify-center items-center rounded-lg h-10 ml-2"
)}
>
<Text style={tailwind("text-white font-medium text-sm")}>发送</Text>
</Send>
);
}, []);
//日期格式
const renderDay = useCallback((props) => {
const now = dayjs();
const isToday = now.diff(props.currentMessage.createdAt, "day") === 0;
const isYesterday = now.diff(props.currentMessage.createdAt, "day") === 1;
if (isToday) {
return <Day {...props} dateFormat="HH:mm" />;
} else if (isYesterday) {
return <Day {...props} dateFormat="昨天 HH:mm" />;
} else {
return <Day {...props} dateFormat="YYYY/MM/DD HH:mm" />;
}
}, []);
//时间格式
const renderTime = useCallback((props) => {
return <></>;
}, []);
//输入框样式
const renderComposer = useCallback((props) => {
return (
<Composer
{...props}
textInputStyle={tailwind(
"bg-[#FFFFFF1A] text-white px-4 py-2 rounded-lg text-sm ml-0"
)}
textInputProps={{ maxLength: 140 }}
/>
);
}, []);
//整个输入栏样式
const renderInputToolbar = useCallback((props) => {
if (params?.mid === 1) {
return (
<InputToolbar
renderComposer={() => renderComposer(props)}
renderSend={() => renderSend(props)}
containerStyle={tailwind("p-2 bg-[#13121F] border-[#FFFFFF26]")}
primaryStyle={tailwind("items-center")}
/>
);
} else {
return <View style={tailwind("flex-1")}></View>;
}
}, []);
//头像样式
const renderAvatar = useCallback((props) => {
return (
<Image
style={tailwind("w-10 h-10 rounded-full")}
source={props.currentMessage.user.avatar}
placeholder={blurhash}
contentFit="cover"
transition={1000}
cachePolicy="disk"
/>
);
}, []);
//气泡样式
const renderBubble = useCallback((props) => {
return (
<Bubble
{...props}
wrapperStyle={{
left: tailwind("bg-white p-1"),
right: tailwind("p-1"),
}}
/>
);
}, []);
//加载更早信息样式
const renderLoadEarlier = useCallback(
(props) => {
return (
<LoadEarlier
{...props}
label={more === 0 || params.mid !== 1 ? "无更早消息" : "查看更早"}
wrapperStyle={tailwind("bg-[#FFFFFF1A]")}
/>
);
},
[more]
);
return (
<View
style={{
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
...tailwind("flex-1 bg-[#13121F]"),
}}
>
<View style={tailwind("flex-1")}>
<GiftedChat
placeholder={params?.mid === 1 ? "输入新消息" : "爱就大胆说出来!"}
alwaysShowSend
locale={"zh-cn"}
keyboardShouldPersistTaps="never"
alignTop={false}
listViewProps={{
contentContainerStyle: { flexGrow: 1, justifyContent: "flex-end" },
}}
onLoadEarlier={() => loadEarlierHistory()}
showUserAvatar
showAvatarForEveryMessage
renderAvatarOnTop
messagesContainerStyle={tailwind("bg-[#13121F] pb-4")}
renderAvatar={renderAvatar}
renderDay={renderDay}
renderInputToolbar={renderInputToolbar}
renderBubble={renderBubble}
loadEarlier
renderLoadEarlier={renderLoadEarlier}
renderTime={renderTime}
messages={messages}
onSend={(messages) => onSend(messages)}
user={{
_id: selfData?.mid,
name: selfData?.name,
avatar: selfData?.avatar?.images[0]?.urls[0],
}}
/>
</View>
</View>
);
}