0


uniapp仿微信聊天界面(vue3组合式版本)

先看效果图:

消息格式参照下方:

<template>
    <view class="chat-index">
        <scroll-view
            id="scrollview"
            class="scroll-style"
            :style="{height: `${windowHeight - inputHeight}rpx`}"
            scroll-y="true" 
            :scroll-top="conf.scrollTop"
            @scrolltoupper="topRefresh"
            @click="touchClose"
        >
            <view id="msglistview" class="chat-body">
                <view v-for="item,index in data.msgInfoList" :key="index">
                    
                    <!-- 消息发送时间 -->
                    <view class="time-box" v-if="item.showTime">
                        <view class="time-style">
                            <view>
                                {{ timeFormat(item.sendTime) }}
                            </view>
                        </view>
                    </view>
                    
                    <!-- 自己 -->
                    <view class="item self" v-if="item.scid == userInfo.scid">
                        
                        <!-- 文本消息 -->
                        <view class="content-text right" v-if="item.type=='text'">
                            {{item.content}}
                        </view>
                        
                        <!-- 语音消息 -->
                        <view class="content-text right" v-else-if="item.type=='voice'">
                            <view style="display: flex;" @click="playSound(item.content)">
                                <text>{{ item.voiceLength }}''</text>
                                <image v-if="conf.playVoice" style="width: 42rpx;height: 42rpx;" src="../../static/icon/voice_play_on.png"/>
                                <image v-else style="width: 42rpx;height: 42rpx;" src="../../static/icon/voice_play.png"/>
                            </view>
                        </view>
                        
                        <!-- 图片消息 -->
                        <view class="content-img" v-else-if="item.type=='img'">
                            <image class="img-style" :src="item.content"  mode="widthFix" :lazy-load="true"/>
                        </view>
                        
                        <!-- 视频消息 -->
                        <view class="content-video" v-else>
                            <video class="video-style" :src="item.content" />
                        </view>
                        
                        <!-- 头像 -->
                        <image class="avatar" :src="userInfo.s_avatar" />
                    </view>
                    
                    <!-- 好友 -->
                    <view class="item Ai" v-else>
                        
                        <!-- 头像 -->
                        <image class="avatar" :src="userInfo.r_avatar" />
                        
                        <!-- 文本消息 -->
                        <view class="content-text left" v-if="item.type=='text'">
                            {{item.content}}
                        </view>
                        
                        <!-- 语音消息 -->
                        <view class="content-text left" v-else-if="item.type=='voice'">
                            <view style="display: flex;" @click="playSound(item.content)">
                                <text>{{ item.voiceLength }}''</text>
                                <image v-if="conf.playVoice" style="width: 42rpx;height: 42rpx;" src="../../static/icon/voice_play_on.png"/>
                                <image v-else style="width: 42rpx;height: 42rpx;" src="../../static/icon/voice_play.png"/>
                            </view>
                        </view>
                        
                        <!-- 图片消息 -->
                        <view class="content-img" v-else-if="item.type=='img'">
                            <image class="img-style" :src="item.content"  mode="widthFix" :lazy-load="true"/>
                        </view>
                        
                        <!-- 视频消息 -->
                        <view class="content-video" v-else>
                            <video class="video-style" :src="item.content" />
                        </view>

                    </view>
                </view>
            </view>
        </scroll-view>
        
        <!-- 消息发送框 -->
        <view class="chat-bottom" :style="{height:`${inputHeight}rpx`}">
            <view class="input-msg-box" :style="{bottom:`${conf.keyboardHeight}rpx`}">
                
                <!-- 输入框区域 -->
                <view class="textarea-style">
                    <!-- 语音/文字输入 -->
                    <view class="voice-btn" @click="isVoice">
                        <image class="icon-style"  v-if="conf.isVoice" src="../../static/icon/keyboard.png" />
                        <image class="icon-style" v-else src="../../static/icon/voice.png" />    
                    </view>
                    
                    <!-- textarea输入框 -->
                    <view class="out_textarea_box" @click="() => conf.showMoreMenu=false">
                        <textarea
                            placeholder-class="textarea_placeholder"
                            :style="{textAlign:(conf.textAreaDisabled?'center':'')}"
                            v-model="sendMsg.text"
                            maxlength="250"
                            confirm-type="send"
                            auto-height
                            :placeholder="conf.textAreaText"
                            :show-confirm-bar="false"
                            :adjust-position="false"
                            :disabled="conf.textAreaDisabled"
                            @confirm="handleSend"
                            @linechange="listenTextAreaHeight"
                            @focus="scrollToBottom" 
                            @blur="scrollToBottom"
                            @touchstart="handleTouchStart"
                            @touchmove="handleTouchMove"
                            @touchend="handleTouchEnd"
                           />
                    </view>    
                        
                    <!-- 输入菜单 -->
                    <view class="more-btn">
                        <image class="icon-style" src="../../static/icon/emoji.png" @click="handleSend"/>
                        <image class="icon-style" style="margin-left: 20rpx;" src="../../static/icon/more.png" @click="showMoreMenuFunc"/>                
                    </view>
            
                </view>
                
                <!-- 更多菜单 -->
                <view :class="{'more-menu-box-max': conf.showMoreMenu,'more-menu-box-min': !conf.showMoreMenu}">
                    <view class="inner-menu-box">
                        <view class="menu-box" @click="sendFile('choose','')">
                            <view class="out-icon-area">
                                <image class="i-style" src="../../static/icon/photo.png" />
                            </view>
                            <view class="t-style">照片</view>
                        </view>
                        <view class="menu-box" @click="sendFile('shoot','')">
                            <view class="out-icon-area">
                                <image class="i-style" src="../../static/icon/takePhoto.png" />
                            </view>
                            <view class="t-style">拍摄</view>
                        </view>
                    </view>
                </view>
                
            </view>
        </view>
        
        <!-- 语音输入 -->
        <view class="voice-mask" v-show="voice.mask">
            <view class="inner-mask">
                <view class="voice-progress-box" :style="{width:`${progressNum}`+'rpx'}">
                    <view class="third-icon"/>
                    <view class="progress-num">
                        {{ voice.length }}s
                    </view>
                </view>
                <view class="cancel-btn" :class="{cancelBtn : voice.cancel}">
                    <image style="width: 60rpx;height: 60rpx;" src="../../static/icon/cancel-voice.png"></image>
                </view>
                <view class="show-tips">
                    上滑取消发送
                </view>
                <view class="bottom-area">
                    <image class="img-style" src="../../static/icon/icon-voice.png" />
                </view>
            </view>
        </view>
    </view>
</template>

<script setup>
import { computed, getCurrentInstance, reactive, ref, onUpdated } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import properties from '@/properties/index.js';
import timeMethod from '@/utils/timeMethod.js';

const { proxy } = getCurrentInstance();
const _this = proxy;
const sendMsg = reactive({
    text: ''
})
/* 接口数据 */
const data = reactive({
    msgInfoList: [],
    pageNum: 1,
    pageSize: 20,
    pageNumCount: 0
})
/* 用户信息 */
const userInfo = reactive({
    scid: null,
    rcid: null,
    s_avatar: '',
    r_avatar: ''
})
/* 配置项 */
const conf = reactive({
    keyboardHeight: 0,
    bottomHeight: 150,
    scrollTop: 0,
    moreMenuHeight: 0,
    judgeScrollToBottom: true,
    showMoreMenu: false,
    loading: false,
    showMsgMenuBoxId: null,
    showMoreMenu: false,
    textAreaDisabled: false,
    textAreaText: '',
    isVoice: false,
    showMoreMenu: false,
    playVoice: false
})
/* 语音输入配置项 */
const voice = reactive({
    mask: false,
    length: 0,
    cancel: false,
    startX: "",
    startY: "",
    timer: "",
    recordInstance: "",
    finished: false,
})
 /* msg配置项 */        
const msgConf = reactive({
    timeSpace: 120,
    initMsgTime: '',
    msgId: 0,
    latestTime: ''
})    

/**
 * 页面加载时调用
 */
onLoad((e) => {
    userInfo.scid =parseInt(uni.getStorageSync('cid'));
    userInfo.rcid = parseInt(e.rcid);
    voice.recordInstance = uni.getRecorderManager();
    keyboardHeightChange();
    listenMsg();
    getAiUserInfo(parseInt(e.rcid));
    getSelfUserInfo(uni.getStorageSync('cid'));
    getAllMsg(parseInt(e.rcid));
    readMsg(parseInt(e.rcid))
})

/**
 * 数据更新时调用
 */
onUpdated(() => {
    /* 页面更新时调用聊天消息定位到最底部 */
    if (conf.judgeScrollToBottom) scrollToBottom();

})

/**
 * 计算属性
 */
const windowHeight = computed(() => rpxTopx(uni.getSystemInfoSync().windowHeight))
const inputHeight = computed(() => conf.bottomHeight + conf.keyboardHeight + conf.moreMenuHeight)
const progressNum = computed(() => voice.length * 2 + 250)

/**
 * px 转换 rpx
 */  
const rpxTopx = (px) => {
    const deviceWidth = uni.getSystemInfoSync().windowWidth;
    let rpx = ( 750 / deviceWidth ) * Number(px);
    return Math.floor(rpx);
}

/**
 * 监听聊天发送栏高度
 */
const listenTextAreaHeight = () => {
    setTimeout(()=>{
        let query = uni.createSelectorQuery();
        query.select('.input-msg-box').boundingClientRect();
        query.exec(res =>{
            conf.bottomHeight = rpxTopx(res[0].height);
        })
    },200)
}

/**
 * 监听键盘高度
 */
const keyboardHeightChange = () => {
    uni.onKeyboardHeightChange(res => {
        conf.keyboardHeight = rpxTopx(res.height);
        if(conf.keyboardHeight <= 0) {
            conf.keyboardHeight = 0;
            conf.showMoreMenu = false;
        }
    })
}

/**
 * 滑动到底部
 */
const scrollToBottom = (e) => {
    setTimeout(()=>{
        let query = uni.createSelectorQuery().in(_this);
        query.select('#scrollview').boundingClientRect();
        query.select('#msglistview').boundingClientRect();
        query.exec((res) =>{
            if(res[1].height > res[0].height){
                conf.scrollTop = rpxTopx(res[1].height - res[0].height);
            }
        })
    },200);
}

/**
 * 弹出更多菜单弹窗
 */
const showMoreMenuFunc = () => {
    conf.showMoreMenu = true;
    conf.isVoice = false;
    conf.textAreaText = '';
    conf.moreMenuHeight = 350;
}

/**
 * websocket监听
 */
const listenMsg = () => {
    uni.onSocketMessage((res)=>{
        let resData = JSON.parse(res.data);
        data.msgInfoList.push(resData);
    })
}

/**
 * 语音与输入切换
 */
const isVoice = () => {
    if (conf.isVoice) {
        conf.isVoice = false;
        conf.textAreaDisabled = false;
        conf.textAreaText = '';
    } else {
        conf.isVoice = true;
        conf.textAreaDisabled = true;
        conf.textAreaText = '按住 说话';
        conf.showMoreMenu = false;
        conf.moreMenuHeight = 0;
    }
        
}

/**
 * 获取用户信息(自己)
 */
const getSelfUserInfo = (cid) => _this.$http('/user/getUserInfo','GET',{'cid':cid}).then(res => {
    userInfo.scid = cid;
    userInfo.s_avatar = res.data.avatarUrl;
})

/**
 * 获取用户信息(好友)
 */
const getAiUserInfo = (cid) => _this.$http('/user/getUserInfo','GET',{'cid':cid}).then(res => {
    userInfo.rcid = cid;
    userInfo.r_avatar = res.data.avatarUrl;
    uni.setNavigationBarTitle({title:res.data.name});
})

/**
 * 上拉加载消息
 */
const topRefresh = () => {
    if (data.pageNum < data.pageNumCount) {
        data.pageNum++;
        conf.judgeScrollToBottom = false;
        conf.loading = true;
        getAllMsg(userInfo.rcid);
    }
}

/**
 * 获取消息
 */
const getAllMsg = (rcid) => {
    _this.$http('/msg/getChatMsg','POST',{'scid':uni.getStorageSync('cid'),'rcid':rcid,'pageNum':data.pageNum,'pageSize':data.pageSize}).then(res => {
        data.pageNumCount = res.data.pagesNum;
        showMsgTime(res.data.list);
        msgConf.latestTime = data.msgInfoList.slice(-1)[0].sendTime; 
    })
}

/**
 * 已读消息
 */
const readMsg = (rcid) => {
    _this.$http('/msg/readMsg','POST',{'scid':rcid,'rcid':uni.getStorageSync('cid')})
}

/**
 * 控制消息时间是否展示
 */
const showMsgTime = (msgData) => {
    msgData.forEach(e => {
        e.showTime = false;
        data.msgInfoList.unshift(e);
        if (msgConf.msgId !== 0) {
            if (timeMethod.calculateTime(msgConf.initMsgTime,e.sendTime)/1000 > msgConf.timeSpace) {
                data.msgInfoList.slice(0 - msgConf.msgId)[0].showTime = true;
            }
        }
        msgConf.initMsgTime = e.sendTime;
        msgConf.msgId++;
    });
    data.msgInfoList.slice(0 - (msgConf.msgId + 1))[0].showTime = true;
}

/**
 * 日期转换
 */
const timeFormat = (time) => {
    //时间格式化
    const Time = timeMethod.getTime(time).split("T");
    //当前消息日期属于周
    const week = timeMethod.getDateToWeek(time); 
    //当前日期0时
    const nti = timeMethod.setTimeZero(timeMethod.getNowTime());
    //消息日期当天0时
    const mnti = timeMethod.setTimeZero(timeMethod.getTime(time));
    //计算日期差值
    const diffDate = timeMethod.calculateTime(nti,mnti);
    //本周一日期0时 (后面+1是去除当天时间)
    const fwnti = timeMethod.setTimeZero(timeMethod.countDateStr(-timeMethod.getDateToWeek(timeMethod.getNowTime()).weekID + 1));
    //计算周日期差值
    const diffWeek = timeMethod.calculateTime(mnti,fwnti);
    
    if (diffDate === 0) {                 //消息发送日期减去当天日期如果等于0则是当天时间
        return Time[1].slice(0,5);
    } else if (diffDate < 172800000) { //当前日期减去消息发送日期小于2天(172800000ms)则是昨天-  一天最大差值前天凌晨00:00:00到今天晚上23:59:59
        return "昨天 " + Time[1].slice(0,5);
    } else if (diffWeek >= 0) {         //消息日期减去本周一日期大于0则是本周
        return week.weekName;
    } else {                             //其他时间则是日期
        return Time[0].slice(5,10);
    }
}

/**
 * 关闭消息操作菜单
 */
const touchClose = () => {
    conf.showBoxId = null;
    conf.showMoreMenu = false;
    conf.keyboardHeight = 0;
    conf.moreMenuHeight = 0;
}

/**
 * 发送消息
 */
const handleSend = () => {
    conf.judgeScrollToBottom = true;
    data.pageNum = 1;
    /* 如果消息不为空 */
    if(sendMsg.text.length !== 0){
        _this.$http("/msg/sendMsg","POST",{
            "scid":userInfo.scid,
            "rcid":userInfo.rcid,
            "type": "text",
            "content":sendMsg.text}).then(res => {
            if (res.status) {
                if (timeMethod.calculateTime(res.data.sendTime,msgConf.latestTime)/1000 > msgConf.timeSpace) {
                    res.data.showTime = true;
                } else {
                    res.data.showTime = false;
                }
                data.msgInfoList.push(res.data);
                sendMsg.text = '';
            } 
        })
    }
}

/**
 * 长按开始录制语音
 */
const handleTouchStart = (e) => {
    if (conf.textAreaDisabled) {
        voice.finished = false;
        uni.getSetting({
            success(res) {
                if (res.authSetting['scope.record'] === undefined) {
                    console.log("第一次授权")
                } else if (!res.authSetting['scope.record']) {
                    uni.showToast({
                        icon: "none",
                        title: "点击右上角···进入设置开启麦克风授权!",
                        duration: 2000
                    })
                } else {                        
                    voice.recordInstance.start();
                    voice.mask = true;
                    voice.isRecord = true;
                    voice.length = 1;
                    voice.startX = e.touches[0].pageX;
                    voice.startY = e.touches[0].pageY;
                    voice.timer = setInterval(() => {
                        voice.length += 1;
                        if(voice.length >= 60) {
                            clearInterval(voice.timer);
                            handleTouchEnd();
                        }
                    },1000)    
                    //判断先结束按钮但是录制才开始时不会结束录制的条件;因为获取授权这儿存在延时;所以结束录制时可能还没开始录制
                    if (voice.finished && voice.mask) {
                        handleTouchEnd();
                    }
                }
            }
        })
    }            
}

/**
 * 长按滑动
 */
const handleTouchMove = (e) => {
    if (conf.textAreaDisabled) {
        if (voice.startY - e.touches[0].pageY > 80) {
            voice.cancel = true;
        }else {
            voice.cancel = false;
        }
    }
}

/**
 * 语音录制结束
 */
const handleTouchEnd = () => {
    if (conf.textAreaDisabled) {
        voice.finished = true;
        voice.mask = false;
        clearInterval(voice.timer);
        voice.recordInstance.stop();
        voice.recordInstance.onStop((res) => {
            const message = {
                voice:res.tempFilePath,
                length:voice.length
            }
            if (!voice.cancel) {
                if (voice.length>1) {
                    sendFile("voice",message);
                } else {
                    uni.showToast({
                        icon: 'none',
                        title: "语音时间太短",
                        duration: 1000
                    })
                }
            }else {
                voice.cancel = false;
            }
        })                                                    
    }
}

/**
 * 语音播放
 */
const playSound = (url) => {
    conf.playVoice = true;
    let music = null;
    music = uni.createInnerAudioContext(); 
    music.src = url;
    music.play(); 
    music.onEnded(()=>{
        music = null;
        conf.playVoice = false;
    })
}

/**
 * 发送文件
 */
const sendFile = (type,data) => {
    if (type === "choose") {
        uni.chooseMedia({
            count: 1,
            mediaType: ['image', 'video'],
            sourceType: ['album'],
            maxDuration: 30,
            success(res) {
                let type = 'img';
                if (res.tempFiles[0].fileType === 'image') {
                    type = 'img'
                } else {
                    type = 'video'
                }
                uploadFile(res.tempFiles[0].tempFilePath,type)
            }
        })    
    } else if (type === "shoot") {
        uni.chooseMedia({
            count: 1,
            mediaType: ['image', 'video'],
            sourceType: ['camera'],
            maxDuration: 30,
            success(res) {
                let type = 'img';
                if (res.tempFiles[0].fileType === 'image') {
                    type = 'img'
                } else {
                    type = 'video'
                }
                uploadFile(res.tempFiles[0].tempFilePath,type)
            }
        })    
    } else {
        uploadFile(data.voice,'voice')
    }
}

/**
 * 上传文件
 */
const uploadFile = (path,type) => {
    let param = {"scid":userInfo.scid,"rcid":userInfo.rcid,"type":type};
    if (type=='voice') {
        param = {"scid":userInfo.scid,"rcid":userInfo.rcid,"type":type,"voiceLength":voice.length};
    }
    uni.uploadFile({
        url: properties.appConf.url + "/msg/sendFileMsg",
        filePath: path,
        name: 'file',
        formData: param,
        header: {"Authorization": uni.getStorageSync('Authorization')},
        success(res) {
            let newMsg = JSON.parse(res.data)
            if (newMsg.status) {
                if (timeMethod.calculateTime(newMsg.data.sendTime,msgConf.latestTime)/1000 > msgConf.timeSpace) {
                    newMsg.data.showTime = true;
                } else {
                    newMsg.data.showTime = false;
                }
                data.msgInfoList.push(newMsg.data)
            }
        }
    })
}

</script>

<style lang="scss">
    
$chatContentbgc: #00ff7f;
$chatBackground: #f0f0f0;

center {
    display: flex;
    align-items: center;
    justify-content: center;
}
    
.chat-index {
    height: 100vh;
    background-color: $chatBackground;
    
    .scroll-style {
        
        .chat-body {
            display: flex;
            flex-direction: column;
            padding-top: 23rpx;
            
            .time-box {
                width: 100%;
                height: 100rpx;
                display: flex;
                justify-content: center;
                align-items: center;
                
                .time-style {
                    font-size: 22rpx;
                    background-color: rgba(213, 213, 213, 0.3);;
                    padding: 5rpx 10rpx;
                    border-radius: 8rpx;
                    color: black;
                }
            }
            
            .self {
                justify-content: flex-end;
                position: relative;
            }
            
            .Ai {
                position: relative;
            }
            
            .item {
                display: flex;
                padding: 23rpx 30rpx;
                
                .right {
                    background-color: $chatContentbgc;
                }
                
                .left {
                    background-color: #FFFFFF;
                }
                
                .right::after {
                    position: absolute;
                    display: inline-block;
                    content: '';
                    width: 0;
                    height: 0;
                    left: 100%;
                    top: 10px;
                    border: 12rpx solid transparent;
                    border-left: 12rpx solid $chatContentbgc;
                }
                
                .left::after {
                    position: absolute;
                    display: inline-block;
                    content: '';
                    width: 0;
                    height: 0;
                    top: 10px;
                    right: 100%;
                    border: 12rpx solid transparent;
                    border-right: 12rpx solid #FFFFFF;
                }
                
                .content-text {
                    position: relative;
                    max-width: 486rpx;
                    border-radius: 8rpx;
                    word-wrap: break-word;
                    padding: 24rpx 24rpx;
                    margin: 0 24rpx;
                    border-radius: 5px;
                    font-size: 32rpx;
                    font-family: PingFang SC;
                    font-weight: 500;
                    color: #333333;
                    line-height: 42rpx;    
                }
                
                .content-img {
                    margin: 0 24rpx;
                }
                
                .content-video {
                    margin: 0 24rpx;
                }
                
                .img-style {
                    width: 400rpx;
                    height: auto;
                    border-radius: 10rpx;
                }
                
                .video-style {
                    width: 400rpx;
                    height: 400rpx;
                }
                
                .avatar {
                    display: flex;
                    justify-content: center;
                    width: 78rpx;
                    height: 78rpx;
                    background: #fff;
                    border-radius: 50rpx;
                    overflow: hidden;
                    
                    image {
                        align-self: center;
                    }
                }
            }
        }
    }
    
    .chat-bottom {
        width: 100%;
        
        .input-msg-box {
            width: 100% ;
            min-height: 150rpx;
            position: fixed;
            bottom: 0;
            background: #e6e6e6;
            
            .textarea-style {
                width: 100%;
                padding-top: 20rpx;
                display: flex;
                
                .out_textarea_box {
                    width:65%;
                    min-height: 70rpx;
                    border-radius: 10rpx;
                    margin-left: 10rpx;
                    background: #f0f0f0;
                    display: flex;
                    align-items: center;
                    
                    textarea {
                        width: 94%;
                        padding: 0 3%;
                        min-height: 42rpx;
                        max-height: 200rpx;
                        font-size: 32rpx;
                        font-family: PingFang SC;
                        color: #333333;
                    }
                }
                
                .voice-btn {
                    width: 10%;
                    @extend center;
                }
                
                .more-btn {
                    width: calc(25% - 25rpx);
                    margin-left: 10rpx;
                    @extend center;
                }
                
                .icon-style {
                    width: 50rpx;
                    height: 50rpx;
                }
            }
            
            .more-menu-box-min {
                width: 100%;
                height: 0rpx;
                display: none;
            }
            
            .more-menu-box-max {
                height: 400rpx;
                margin-top: 10rpx;
                border-top: 1rpx solid #d6d6d6;
                transition: height 1ms linear;
                display: block;
                
                .inner-menu-box {
                    width: calc(100% - 20rpx);
                    height: calc(360rpx - 10rpx);
                    padding: 10rpx;
                    
                    .menu-box {
                        width: 150rpx;
                        height: 150rpx;
                        margin: 12rpx calc((100% - 600rpx) / 8);
                        float: left;
                        
                        .out-icon-area {
                            width: 110rpx;
                            height: 110rpx;
                            background-color: #fff;
                            border-radius: 20rpx;
                            margin: 0 20rpx;
                            @extend center;
                            
                            .i-style {
                                width: 60rpx;
                                height: 60rpx;
                            }
                        }
                        
                        .t-style {
                            font-size: 24rpx;
                            font-weight: 400;
                            text-align: center;
                            margin-top: 10rpx;
                            color: #717171;
                        }
                    }
                }
            }
            
        }
    }
    
    .voice-mask{
        position:fixed;
        top:0;
        right:0;
        bottom:0;
        left:0;
        background-color: rgba(0,0,0,0.8);
    
        .inner-mask {
            display: flex;
            flex-direction: column;
            align-items: center;
            
            .voice-progress-box {
                min-width: 250rpx;
                height: 150rpx;
                margin-top: 60%;
                border-radius: 50rpx;
                background: #4df861;
                position: relative;
                @extend center; 
                
                .third-icon {
                    width: 0;
                    height: 0;
                    border: 15rpx solid transparent;
                    border-top: 15rpx solid #4df861;
                    position: absolute;
                    top: 100%;
                    left: 45%;
                    
                    .progress-num {
                        font-size: 50rpx;
                        font-weight: 600;
                    }
                }
            
            }
            
            .cancel-btn {
                width: 120rpx;
                height: 120rpx;
                clip-path: circle();
                margin-top: 50%;
                background: #080808;
                @extend center;
            }
            
            .cancelBtn {
                width: 150rpx;
                height: 150rpx;
                background-color: #ff0004;
                
            }
            
            .show-tips {
                width: 100%;
                margin-top: 50rpx;
                text-align: center;
                color: white;
                animation: 4s opacity2 1s infinite; 
                font-size: 30rpx;
                font-weight: 400;
                font-family: sans-serif;
            }
            
            @keyframes opacity2{
                0%{opacity:0}
                50%{opacity:.8;}
                100%{opacity:0;}
            }
            
            .bottom-area {
                position: fixed;
                bottom: 0rpx;
                width: 100%;
                height:190rpx;
                border-top: #BABABB 8rpx solid;
                border-radius: 300rpx 300rpx 0 0;
                background-image: linear-gradient(#949794,#e1e3e1);
                @extend center;
                
                .img-style {
                    width: 50rpx;
                    height: 50rpx;
                }
            }    
        }
    }
}
</style>

导入的时间工具包 timeMethod.js

class TimeMethod {
    
    constructor() {}
    
    //日期格式化
    addZero(data) {
        if (parseInt(data) < 10) {
            return "0" + String(data);
        }
        return data;
    }    
    
    /**
     * 获取当前日期
     */
    getNowTime() {
        const myDate = new Date();
        const year = myDate.getFullYear();
        const mouth = this.addZero(myDate.getMonth() + 1);
        const day = this.addZero(myDate.getDate());
        const hour = this.addZero(myDate.getHours());
        const minute = this.addZero(myDate.getMinutes());
        const second = this.addZero(myDate.getSeconds());
        return year + '-' + mouth + '-' + day + 'T' + hour+ ':' + minute+ ':' + second
    }
    
    /**
     * 根据时间返回标准字符串时间
     * @param {Object} time
     */
    getTime(time) {
        const myDate = new Date(time);
        const year = myDate.getFullYear();
        const mouth = this.addZero(myDate.getMonth() + 1);
        const day = this.addZero(myDate.getDate());
        const hour = this.addZero(myDate.getHours());
        const minute = this.addZero(myDate.getMinutes());
        const second = this.addZero(myDate.getSeconds());
        return year + '-' + mouth + '-' + day + 'T' + hour+ ':' + minute+ ':' + second
    }
    
    /**
     * @param {Object} timestamp
     * @param {Object} type
     * 时间戳转时间
     */
    timestampToTime(timestamp,type) {
            if(String(timestamp).length===10) {
                //时间戳为10位需*1000
                var date = new Date(timestamp * 1000);
            }else {
                var date = new Date(timestamp);
            }
            const Y = date.getFullYear() + '-';    
            const M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';    
            const D = date.getDate() + ' ';    
            const h = date.getHours() + ':';    
            const m = date.getMinutes() + ':';    
            const s = date.getSeconds();
            if(type==="date") {
                return Y+M+D;
            }else {
                return Y+M+D+h+m+s;
            }
        }
        
        
    /**
     * @param {Object} time
     * 时间转时间戳
     */
    timeToTimestamp(time) {
        //精确到秒,毫秒用000代替 :Date.parse(date); 
        return new Date(time).getTime(); 
    }
    
    
    /**
     * @param {Object} startTime
     * @param {Object} endTime
     * 日期计算
     */
    calculateTime(startTime,endTime) {
        return new Date(startTime) - new Date(endTime)
    }
    
    /**
     * @param {Object} time
     * 日期转星期
     */
    getDateToWeek(time) {
        let weekArrayList = [
        {"weekID":7,"weekName":"星期日"},
        {"weekID":1,"weekName":"星期一"},
        {"weekID":2,"weekName":"星期二"},
        {"weekID":3,"weekName":"星期三"},
        {"weekID":4,"weekName":"星期四"},
        {"weekID":5,"weekName":"星期五"},
        {"weekID":6,"weekName":"星期六"}];
        return weekArrayList[new Date(time).getDay()]
    }
    
    /**
     * @param {Object} date
     *  yyyy-MM-dd HH:mm:ss转为   yyyy-MM-ddTHH:mm:ss
     */
    timeFormat(date,type) {
        if (type == "T")
            return date.replace(" ","T")
        else
            return date.replace("T"," ")
    }
    
    /**
     * @param {Object} time
     * 定时器
     */
    timeSleep(time) {
        return new Promise((resolve) => setTimeout(resolve,time))
    }
    
    
    /**
     * 根据日期加减计算日期
     * @param dayCount
     */
    countDateStr(dayCount) {
        let dd = new Date();
        dd.setDate(dd.getDate()+ dayCount);
        let ms = dd.getTime();
        return new Date(ms).toJSON();
    }
    
    /**
     * @param {Object} time
     * 日期中时间置0
     */
    setTimeZero(time) {
        return time.slice(0,10) + 'T00:00:00.000+00:00';
    }

}

export default new TimeMethod();
标签: uni-app 微信 前端

本文转载自: https://blog.csdn.net/qq_53266140/article/details/141108984
版权归原作者 二九筒 所有, 如有侵权,请联系我们删除。

“uniapp仿微信聊天界面(vue3组合式版本)”的评论:

还没有评论