石小疯 发表于 2024-10-20 01:46:52

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

先看效果图:
https://i-blog.csdnimg.cn/direct/c241e72d589749f7a46587aea25bdbd9.pnghttps://i-blog.csdnimg.cn/direct/24bd3bb386714cf7a335af8096d3fed8.png
https://i-blog.csdnimg.cn/direct/a1e601d15fc34846a1410572dbdd4f18.png

消息格式参照下方:
https://i-blog.csdnimg.cn/direct/f3369dbb40214754b92ceab6da0e95be.png
<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.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.height > res.height){
                                conf.scrollTop = rpxTopx(res.height - res.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).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).showTime = true;
                        }
                }
                msgConf.initMsgTime = e.sendTime;
                msgConf.msgId++;
        });
        data.msgInfoList.slice(0 - (msgConf.msgId + 1)).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.slice(0,5);
        } else if (diffDate < 172800000) { //当前日期减去消息发送日期小于2天(172800000ms)则是昨天-一天最大差值前天凌晨00:00:00到今天晚上23:59:59
                return "昨天 " + Time.slice(0,5);
        } else if (diffWeek >= 0) {                 //消息日期减去本周一日期大于0则是本周
                return week.weekName;
        } else {                                                         //其他时间则是日期
                return Time.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.pageX;
                                        voice.startY = e.touches.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.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.fileType === 'image') {
                                        type = 'img'
                                } else {
                                        type = 'video'
                                }
                                uploadFile(res.tempFiles.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.fileType === 'image') {
                                        type = 'img'
                                } else {
                                        type = 'video'
                                }
                                uploadFile(res.tempFiles.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
        }
       
        /**
       * @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();



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: uniapp仿微信聊天界面(vue3组合式版本)