时间显示处理方法:
// 格式化时间段 const toTimeSolt = (h: number) => { let bt = ''; if (0 <= h && h <= 3) bt = '凌晨'; if (4 <= h && h <= 8) bt = '早上'; if (9 <= h && h <= 11) bt = '上午'; if (12 == h) bt = '中午'; if (13 <= h && h <= 17) bt = '下午'; if (18 <= h && h <= 23) bt = '晚上'; return bt; } // 格式星期 const toWeek = (w: number) => { let week = ''; switch (w) { case 0: week = '星期日'; break; case 1: week = '星期一'; break; case 2: week = '星期二'; break; case 3: week = '星期三'; break; case 4: week = '星期四'; break; case 5: week = '星期五'; break; case 6: week = '星期六'; break; } return week; } export const formatChatListTime = (timestamp: string, type: number = 1) => { let oldtime = new Date(Number(timestamp) * 1000); let date = new Date(); let today = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); //今天凌晨 let yestday = new Date(today - 24 * 3600 * 1000).getTime(); // let beforeYestday = new Date(today - 24 * 3600 * 1000 * 2).getTime(); let beforeWeek = new Date(today - 24 * 3600 * 1000 * 7).getTime(); let Y = oldtime.getFullYear(); //年份 let M = oldtime.getMonth() + 1; //月份 let d = oldtime.getDate(); //日 // let h = oldtime.getHours() % 12 == 0 ? 12 : oldtime.getHours() % 12; //12小时 let H = oldtime.getHours(); //24小时 let m = oldtime.getMinutes(); //分 let w = toWeek(oldtime.getUTCDay()); //星期 let timesolt = toTimeSolt(oldtime.getHours()); //时间段 let timeStr = ''; //当天 if (oldtime.getTime() > yestday) { timeStr = H + ':' + m; } //昨天 if (oldtime.getTime() < today && yestday <= oldtime.getTime()) { timeStr = '昨天 ' + (type == 1 ? H + ':' + m : ''); } // 一周内 if (oldtime.getTime() < yestday && beforeWeek <= oldtime.getTime()) { timeStr = w + (type == 1 ? ' ' + H + ':' + m : ''); } // 一周前 if (oldtime.getTime() < beforeWeek) { timeStr = type == 1 ? Y + '年' + M + '月' + d + '日 ' + timesolt + ' ' + H + ':' + m : Y + '/' + M + '/' + d; } // 比当前时间晚 if (oldtime.getTime() > date.getTime()) { timeStr = '手动修改'; } return timeStr; }
主文件index.tsx:
import React, { useEffect, useRef, useState } from 'react' import Taro, { getCurrentInstance } from '@tarojs/taro' import { View, Text, ScrollView, Input, Image } from "@tarojs/components" import classnames from 'classnames' import api from '@services/api' import app from '@services/request' import NavBar from '@components/navbar' import useNavData from '@hooks/useNavData' import { PRICE_TYPE } from '@constants/house' import { formatChatListTime } from '@utils/index' import { getTotalPage, INIT_PAGE, IPage } from '@utils/page' import './index.scss' interface IParam { currentPage: number } const INIT_PARAM = { currentPage: 1 } const MESSAGE_TYPE = { text: '1', image: '2', image_text: '3' } const ChatRoom = () => { const PAGE_LIMIT: number = 10 const router: any = getCurrentInstance().router const fromUserId: string = router?.params.fromUserId const user: any = JSON.parse(router?.params.user) || {} const toUser: any = JSON.parse(router?.params.toUser) || {} const { contentHeight } = useNavData() const [param, setParam] = useState<IParam>(INIT_PARAM) const [page, setPage] = useState<IPage>(INIT_PAGE) const [chatData, setChatData] = useState<any[]>([]) const [bottom, setBottom] = useState<number>(0) const [toView, setToView] = useState<string>('') const [isPhoto, setIsPhoto] = useState<boolean>(false) const [inputData, setInputData] = useState<any>({ value: '', send: false }) const ref = useRef<string>('') useEffect(() => { fetchChatData() }, [param]) const fetchChatData = () => { app.request({ url: app.testApiUrl(api.getChatData), data: { page: param.currentPage, limit: PAGE_LIMIT, from_user_id: fromUserId, } }, { loading: false }).then((result: any) => { if (param.currentPage === INIT_PARAM.currentPage) { setChatData(result.data) } else { setChatData([...result.data, ...chatData]) } setToView(`toView_${result.data[result.data.length - 1].id}`) setPage({ ...page, totalCount: result.pagination.totalCount, totalPage: getTotalPage(PAGE_LIMIT, result.pagination.totalCount) }) }) } const handleScrollToUpper = () => { if (page.totalPage > param.currentPage) { setParam({ currentPage: param.currentPage + 1 }) } } const handleInputFocus = (e: any) => { setIsPhoto(false) setBottom(e.detail.height) setToView(`toView_${chatData[chatData.length - 1].id}`) } const handleInputChange = (e: any) => { const value = e.detail.value setInputData({ value, send: !!value }) } const handlePhotoClick = (type: any) => { Taro.chooseImage({ count: 9, sourceType: [type], success: (res: any) => { app.uploadFile(res).then((result: any) => { sendMessage(MESSAGE_TYPE.image, result) }) } }) } const sendMessage = (type: string, content: any) => { app.request({ url: app.apiUrl(api.postChatSend), method: 'POST', data: { to_user_id: toUser.id, message_type: type, content } }).then(() => { setIsPhoto(false) setInputData({ value: '', send: false }) fetchChatData() }) } const renderContentByType = (chatItem: any, isMine: boolean = false) => { const content = { '1': <Text className={classnames('text', isMine && 'text-primary')}>{chatItem.content}</Text>, '2': <Image className="image" src={chatItem.content} mode="widthFix" />, '3': ( <View className="content"> <View className="content-image"> <Image src={chatItem.content.image_path} mode="aspectFit" /> <View className="tag">新房</View> </View> <View className="content-title">{chatItem.content.title}</View> <View className="content-text">{chatItem.content.price}{PRICE_TYPE[chatItem.content.price_type]}</View> </View> ) } return content[chatItem.message_type] } const renderTime = (time: string) => { if (ref.current !== time) { ref.current = time return ( <View className="item-tip"> <Text className="text">{formatChatListTime(time)}</Text> </View> ) } } const renderChatList = () => { return chatData.map((item: any, index: number) => ( <View key={index} id={`toView_${item.id}`}> { item.to_user_id !== toUser.id ? ( <View className="msg-item"> {renderTime(item.time)} <View className="item-content"> <View className="photo"> <Image src={toUser.avatar} /> </View> <View className="message"> {renderContentByType(item)} </View> </View> </View> ) : ( <View className="msg-item"> {renderTime(item.time)} <View className="item-content item-content-reverse"> <View className="photo"> <Image src={user.avatar} /> </View> <View className="message"> {renderContentByType(item, true)} </View> </View> </View> ) } </View> )) } return ( <View className="chat-room"> <NavBar title={toUser.nickname} back={true}></NavBar> <View className="chat-room-content"> <ScrollView scrollY className="msg-box" style={{ maxHeight: contentHeight - 52 }} upperThreshold={40} onScrollToUpper={handleScrollToUpper} onClick={() => setIsPhoto(false)} scrollIntoView={toView} scrollWithAnimation={false} > {renderChatList()} </ScrollView> <View className="send-box" style={{ bottom }}> <View className="send-content"> <Input adjustPosition={false} onFocus={handleInputFocus} onBlur={() => setBottom(0)} value={inputData.value} onInput={handleInputChange} /> { inputData.send ? <View className="btn btn-primary" onClick={() => sendMessage(MESSAGE_TYPE.text, inputData.value)} > <Text className="text">发送</Text> </View> : <View className="icon-btn" onClick={() => setIsPhoto(true)}> <Text className="iconfont iconadd"></Text> </View> } </View> { isPhoto && <View className="photo-content"> <View className="photo-item photo-album" onClick={() => handlePhotoClick('album')}> <View className="iconfont iconphoto"></View> <View className="text">照片</View> </View> <View className="photo-item photograph" onClick={() => handlePhotoClick('camera')}> <View className="iconfont iconphotograph"></View> <View className="text">拍照</View> </View> </View> } </View> </View> </View> ) } export default ChatRoom
解决问题有:
1. 默认滚动位置在做底部,根据列表id的不同时设置scrollIntoView实现。
2.预览或真机时,自定义导航栏会往上顶,设置adjustPosition={false}当输入键盘出来时,输入框不自动调整位置
样式:
.chat-room { position: relative; width: 100%; height: 100vh; background-color: $bg2-color; &-content { .msg-box { width: 100%; .msg-item { padding: 0 30px 30px; .item-content { display: flex; align-items: center; &.item-content-reverse { flex-direction: row-reverse; } .photo { width: 80px; height: 80px; background-color: $bg-color; border-radius: 50%; overflow: hidden; Image { width: 100%; height: 100%; } } .message { margin: 0 20px; .text { padding: 15px 30px; border-radius: $border-radius-10; background-color: $white; } .text-primary { background-color: $primary-color; color: $white; } .image { max-width: 300px; } .content { width: 400px; padding: 20px; background-color: $white; border-radius: $border-radius-base; .content-image { position: relative; width: 100%; height: 200px; background-color: $bg-color; Image { width: 100%; height: 100%; } .tag { position: absolute; top: 20px; left: 20px; background-color: $white; padding: 4px 12px; font-size: $font-26; border-radius: $border-radius-base; } } .content-title { margin-top: 10px; font-size: $font-30; } .content-text { margin-top: 10px; font-size: $font-26; color: $desc-color; } } } } .item-tip { text-align: center; margin-bottom: 30px; .text { padding: 4px 10px; font-size: $font-26; border-radius: $border-radius-10; background: rgba($color: $black, $alpha: 0.2); color: $white; } } } } .send-box { position: absolute; bottom: 0; width: 100%; font-size: $font-basic; padding: 20px 0; background-color: $bg-color; z-index: 99; box-shadow: 0 -1px 6px 0 rgba(0, 0, 0, 0.08); .send-content { display: flex; align-items: center; justify-content: space-between; padding: 0 30px; Input { flex: 1; height: 60px; line-height: 60px; border: $border; border-radius: $border-radius-10; margin-right: 20px; padding: 0 20px; background-color: $white; } .btn { width: 100px; height: 60px; } .icon-btn { font-size: 40px; color: $text-color; } } .photo-content { display: flex; margin: 30px; .photo-item { text-align: center; margin-right: 30px; font-size: $font-basic; .iconfont { border: $border; border-radius: $border-radius-10; padding: 20px; font-size: 50px; margin-bottom: 8px; color: $text-color; } } } } } }