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

 更新时间:2024年10月28日 09:15:34   作者:二九筒  
这篇文章主要介绍了uniapp仿微信聊天界面的相关资料,这里提及了一个时间工具包timeMethod.js,该工具包可能提供了一系列时间处理的功能,如格式化日期、计算时间差等,以便在消息格式中正确展示时间信息,使用此类工具包可以大大提高开发效率,需要的朋友可以参考下

先看效果图:

消息格式参照下方:

<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();

总结 

到此这篇关于uniapp仿微信聊天界面的文章就介绍到这了,更多相关uniapp仿微信聊天界面内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Vue之TodoList案例详解

    Vue之TodoList案例详解

    这篇文章主要为大家介绍了Vue之TodoList的案例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助<BR>
    2021-11-11
  • Vue 3.0 中 hooks 的概念示例详解

    Vue 3.0 中 hooks 的概念示例详解

    在Vue3.0框架中,hooks函数允许将组件逻辑抽离复用,提高代码的可维护性和复用性,通过封装逻辑如获取数据、处理状态等,hooks使得组件开发更加高效和清晰,示例中,useDog.ts用于获取狗狗图片,展示了hooks封装数据和逻辑、响应式数据和异步操作的能力
    2024-10-10
  • 一文带你搞懂Vue中Vuex的使用

    一文带你搞懂Vue中Vuex的使用

    ​ Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态。本文会通过一些简单的示例,为大家详细讲解Vuex的使用,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-11-11
  • vant-Dialog 弹出框的使用小结

    vant-Dialog 弹出框的使用小结

    这篇文章主要介绍了vant-Dialog 弹出框的使用小结,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-02-02
  • Vue使用codemirror实现一个可插入自定义标签的textarea

    Vue使用codemirror实现一个可插入自定义标签的textarea

    这篇文章主要为大家详细介绍了Vue如何使用codemirror实现一个可插入自定义标签的textarea,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-02-02
  • Vue使用Echarts实现数据可视化的方法详解

    Vue使用Echarts实现数据可视化的方法详解

    这篇文章主要为大家详细介绍了Vue使用Echarts实现数据可视化的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • this在vue和小程序中的使用详解

    this在vue和小程序中的使用详解

    这篇文章主要介绍了this在vue和小程序中的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-01-01
  • vue3中form对象无法赋值的问题解决

    vue3中form对象无法赋值的问题解决

    这篇文章给大家介绍了vue3中form对象无法赋值的问题解决,有时候对象的值死活赋不上值,这时候可以看下赋值的对象变量名是否和页面组件中的ref相同,文中给出了代码示例供大家参考,需要的朋友可以参考下
    2024-01-01
  • 浅谈vue 组件中的setInterval方法和window的不同

    浅谈vue 组件中的setInterval方法和window的不同

    这篇文章主要介绍了浅谈vue 组件中的setInterval方法和window的不同,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • vue 添加和编辑用同一个表单,el-form表单提交后清空表单数据操作

    vue 添加和编辑用同一个表单,el-form表单提交后清空表单数据操作

    这篇文章主要介绍了vue 添加和编辑用同一个表单,el-form表单提交后清空表单数据操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08

最新评论