taro3.x: 微聊简单实现

时间显示处理方法:

// 格式化时间段
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;
                    }
                }
            }
        }
    }
}
原文地址:https://www.cnblogs.com/Nyan-Workflow-FC/p/13921944.html