import { BaseEventOrig, Button, Image, ScrollView, ScrollViewProps, Textarea, TextareaProps } from '@tarojs/components'; import Taro, { NodesRef, useDidHide, useDidShow, useLoad, useUnload } from '@tarojs/taro'; import classNames from 'classnames'; import { useCallback, useEffect, useRef, useState } from 'react'; import { ContactMessage, JobMessage, LocationMessage, MaterialMessage, TextMessage, TimeMessage, } from '@/components/message-chat'; import PageLoading from '@/components/page-loading'; import SafeBottomPadding from '@/components/safe-bottom-padding'; import { EventName } from '@/constants/app'; import { CollectEventName } from '@/constants/event'; import { ChatWatchType, MessageType, PULL_NEW_MESSAGES_TIME } from '@/constants/message'; import useListHeight, { IUseListHeightProps } from '@/hooks/use-list-height'; import { RESPONSE_ERROR_CODE } from '@/http/constant'; import { HttpError } from '@/http/error'; import { IChatUser, IChatInfo, IChatMessage, IJobMessage, ILocationMessage, IMaterialMessage, IMessageStatus, PostMessageRequest, } from '@/types/message'; import { isAnchorMode } from '@/utils/app'; import { getScrollItemId, last, logWithPrefix } from '@/utils/common'; import { collectEvent } from '@/utils/event'; import { isExchangeMessage, isJobMessage, isLocationMessage, isMaterialMessage, isTextMessage, isTimeMessage, openLocationSelect, postAddMessageTimes, postChatRejectWatch, postSendMessage, requestActionDetail, requestChatDetail, requestChatWatch, requestMessageStatusList, requestNewChatMessages, } from '@/utils/message'; import { getPageQuery, parseQuery } from '@/utils/route'; import Toast from '@/utils/toast'; import { getUserId } from '@/utils/user'; import './index.less'; const PREFIX = 'page-message-chat'; const LIST_CONTAINER_CLASS = `${PREFIX}__chat-list`; const CALC_LIST_PROPS: IUseListHeightProps = { selectors: [`.${LIST_CONTAINER_CLASS}`], calc: (rects: [NodesRef.BoundingClientRectCallbackResult]) => { const [rect] = rects; return rect.height; }, }; const log = logWithPrefix(PREFIX); const chooseLocation = Taro.requirePlugin('chooseLocation'); interface ILoadProps { chatId: string; jobId?: string; job?: string; material?: string; } const getHeaderLeftButtonText = (job?: IJobMessage, material?: IMaterialMessage) => { if (job) { return '不感兴趣'; } if (material) { return '标记为不合适'; } return isAnchorMode() ? '不感兴趣' : '标记为不合适'; }; export default function MessageChat() { const listHeight = useListHeight(CALC_LIST_PROPS); const [input, setInput] = useState(''); const [showMore, setShowMore] = useState(false); const [chat, setChat] = useState(null); const [reject, setReject] = useState(false); const [receiver, setReceiver] = useState(null); const [messages, setMessages] = useState([]); const [messageStatusList, setMessageStatusList] = useState([]); const [jobId, setJobId] = useState(); const [resumeId, setResumeId] = useState(); const [job, setJob] = useState(); const [material, setMaterial] = useState(); const [scrollItemId, setScrollItemId] = useState(); const scrollToLowerRef = useRef(false); const autoSendRef = useRef({ sendJob: false, sendMaterial: false }); const loadMoreRef = useRef(async (chatId: string, currentMessages: IChatMessage[], forceScroll?: boolean) => { try { const lastMsgId = last(currentMessages)?.msgId; const newMessages = await requestNewChatMessages({ chatId: chatId, lastMsgId }); log('requestNewChatMessages', newMessages, forceScroll); if (newMessages.length) { setMessages([...currentMessages, ...newMessages]); (forceScroll || scrollToLowerRef.current) && setScrollItemId(getScrollItemId(last(newMessages)?.msgId)); } } catch (e) { console.error(e); } }); const handleInput = useCallback((e: BaseEventOrig) => { const value = e.detail.value || ''; setInput(value); }, []); const handleClickExpand = useCallback(() => setShowMore(true), []); const handleScroll = useCallback( (e: BaseEventOrig) => { // log('handleScroll', e); const { scrollTop, scrollHeight } = e.detail; scrollToLowerRef.current = listHeight + scrollTop >= scrollHeight - 40; }, [listHeight] ); const handleClickSendLocation = useCallback((e: React.MouseEvent) => { e.stopPropagation(); openLocationSelect(); }, []); const handleClickMoreOuter = () => showMore && setShowMore(false); const handleClickContactButton = useCallback(async () => { if (!chat) { return; } await loadMoreRef.current(chat.chatId, messages, true); }, [chat, messages]); const handleSendMessage = useCallback( async (newMessage: Omit) => { if (!chat) { return; } try { Taro.showLoading(); await postSendMessage({ chatId: chat.chatId, bizId: jobId || chat.lastJobId, ...newMessage }); await loadMoreRef.current(chat.chatId, messages, true); Taro.hideLoading(); } catch (error) { const e = error as HttpError; const errorCode = e.errorCode; collectEvent(CollectEventName.MESSAGE_DEV_LOG, { action: 'send-message', e, message: newMessage }); let tips = '发送失败请重试'; let duration = 1500; if ( errorCode === RESPONSE_ERROR_CODE.INSUFFICIENT_BALANCE && newMessage.type === MessageType.RequestCompanyContact ) { tips = '今日申请交换联系方式次数已用完,当前每日限制为5次'; duration = 3000; } tips.length > 7 ? Toast.info(tips, duration) : Toast.error(tips, duration); } }, [chat, jobId, messages] ); const handleClickReject = useCallback(async () => { if (!chat || !receiver || reject) { return; } const watchType = isAnchorMode() ? ChatWatchType.AnchorReject : ChatWatchType.CompanyReject; await postChatRejectWatch({ type: watchType, toUserId: receiver.userId, jobId: jobId || chat.lastJobId, status: false, }); setReject(true); }, [jobId, chat, receiver, reject]); const handleSendExchangeContact = useCallback(async () => { postAddMessageTimes('click_request_exchange_contact'); const type = isAnchorMode() ? MessageType.RequestCompanyContact : MessageType.RequestAnchorContact; handleSendMessage({ type, actionObject: '' }); }, [handleSendMessage]); const handleSendJobMessage = useCallback(async () => { if (!job || !receiver || autoSendRef.current.sendJob) { return; } const detail = await requestActionDetail({ type: MessageType.Job, bizId: job.id, toUserId: receiver.userId }); if (!detail) { handleSendMessage({ type: MessageType.Job, actionObject: JSON.stringify(job) }); } autoSendRef.current.sendJob = true; }, [job, receiver, handleSendMessage]); const handleSendMaterialMessage = useCallback(async () => { if (!material || !receiver || autoSendRef.current.sendMaterial) { return; } const detail = await requestActionDetail({ type: MessageType.Material, bizId: material.id, toUserId: receiver.userId, }); if (!detail) { handleSendMessage({ type: MessageType.Material, actionObject: JSON.stringify(material) }); } autoSendRef.current.sendMaterial = true; }, [material, receiver, handleSendMessage]); const handleSendLocationMessage = useCallback( (location: Omit) => { setShowMore(false); handleSendMessage({ type: MessageType.Location, actionObject: JSON.stringify(location) }); }, [handleSendMessage] ); const handleSendTextMessage = useCallback(async () => { if (!input) { return; } postAddMessageTimes('send_message_button'); await handleSendMessage({ type: MessageType.Text, content: input }); setInput(''); }, [input, handleSendMessage]); // useEffect(() => { // loadMoreRef.current = async (chatId: string, currentMessages: IChatMessage[], forceScroll: boolean) => { // try { // const lastMsgId = last(currentMessages)?.msgId; // const newMessages = await requestNewChatMessages({ chatId: chatId, lastMsgId }); // log('requestNewChatMessages', newMessages); // if (newMessages.length) { // setMessages([...currentMessages, ...newMessages]); // (forceScroll || scrollToLowerRef.current) && setScrollItemId(getScrollItemId(last(newMessages)?.msgId)); // } // } catch (e) { // console.error(e); // } // }; // }, []); useEffect(() => { if (!chat) { return; } const intervalId = setInterval(async () => { loadMoreRef.current(chat.chatId, messages); const statusList = await requestMessageStatusList(chat.chatId); setMessageStatusList(statusList); }, PULL_NEW_MESSAGES_TIME); return () => { clearInterval(intervalId); }; }, [chat, messages]); useEffect(() => { if (!chat) { return; } job && handleSendJobMessage(); material && handleSendMaterialMessage(); }, [chat, job, material, handleSendJobMessage, handleSendMaterialMessage]); useLoad(async () => { const query = getPageQuery(); const chatId = query.chatId; if (!chatId) { return; } try { const currentUserId = getUserId(); const watchType = isAnchorMode() ? ChatWatchType.AnchorReject : ChatWatchType.CompanyReject; const chatDetail = await requestChatDetail(chatId); const toUserInfo = chatDetail.participants.find(u => u.userId !== currentUserId); if (!toUserInfo) { throw new Error('not receiver'); } const watchStatus = await requestChatWatch({ type: watchType, toUserId: toUserInfo.userId, jobId: query.jobId || chatDetail.lastJobId, }); const parseJob = query.job ? parseQuery(query.job) : null; const parseMaterial = query.material ? parseQuery(query.material) : null; // log('requestChatDetail', chatDetail, parseJob, parseMaterial); setChat(chatDetail); setResumeId(chatDetail.participants.find(u => u.userId !== currentUserId)?.resumeId); setJobId(query.jobId); setMessages(chatDetail.messages); setScrollItemId(getScrollItemId(last(chatDetail.messages)?.msgId)); parseJob && setJob(parseJob); parseMaterial && setMaterial(parseMaterial); Taro.setNavigationBarTitle({ title: toUserInfo.nickName }); setReceiver(toUserInfo); setReject(!watchStatus); } catch (e) { console.error(e); collectEvent(CollectEventName.MESSAGE_DEV_LOG, { action: 'init-chat-message', e }); Toast.error('加载失败请重试'); } }); useDidShow(() => { const location = chooseLocation?.getLocation() as Omit; log('useDidShow', location); if (!location) { return; } // 发送定位消息 handleSendLocationMessage(location); chooseLocation?.setLocation(null); }); useDidHide(() => chooseLocation?.setLocation(null)); useUnload(() => { chooseLocation?.setLocation(null); Taro.eventCenter.trigger(EventName.EXIT_CHAT_PAGE); }); log('render', scrollItemId, scrollToLowerRef.current); return (
{!chat && }
{messages.map((message: IChatMessage) => { if (isTextMessage(message)) { return ( m.msgId === message.msgId && !!m.isRead)} /> ); } else if (isTimeMessage(message)) { return ; } else if (isJobMessage(message)) { return ; } else if (isMaterialMessage(message)) { return ; } else if (isExchangeMessage(message)) { return ( ); } else if (isLocationMessage(message)) { return ( m.msgId === message.msgId && !!m.isRead)} /> ); } })}
e.stopPropagation()} onClick={handleClickExpand} />