• 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;
                        }
                    }
                }
            }
        }
    }
  • 相关阅读:
    End of 2007, where are you?
    2007年,前进!
    PHP框架Yii快速搭建高并发网站
    需要 gmail 与 wallop 邀请的 请留言给 email
    开始关注Mono了
    百度,阿里巴巴,google
    重返 cnblogs.com
    解决VMware虚拟机桥接不能上网的问题
    SecureCRT
    linux的ssh和sshd配置
  • 原文地址:https://www.cnblogs.com/Nyan-Workflow-FC/p/13921944.html
Copyright © 2020-2023  润新知