项目初始化
This commit is contained in:
41
pages/im/components/cardNotice.vue
Normal file
41
pages/im/components/cardNotice.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="card-notice">
|
||||
<u-notice-bar mode="horizontal" :list="list"></u-notice-bar>
|
||||
<view class="btn">投诉举报</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [
|
||||
'平台绝对不会收取实名认证等任何费用。让你加微信支付或者点链接、扫二维码的都是骗子!平台绝对不会收取实名认证等任何费用。让你加微信支付或者点链接、扫二维码的都是骗子!平台绝对不会收取实名认证等任何费用。让你加微信支付或者点链接、扫二维码的都是骗子!平台绝对不会收取实名认证等任何费用。让你加微信支付或者点链接、扫二维码的都是骗子!平台绝对不会收取实名认证等任何费用。让你加微信支付或者点链接、扫二维码的都是骗子!平台绝对不会收取实名认证等任何费用。让你加微信支付或者点链接、扫二维码的都是骗子!平台绝对不会收取实名认证等任何费用。让你加微信支付或者点链接、扫二维码的都是骗子!',
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onBanner(index) {
|
||||
console.log(22222, index);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-notice {
|
||||
background-color: #fff;
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
color: #ffa31a;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
225
pages/im/components/cardSwiper.vue
Normal file
225
pages/im/components/cardSwiper.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view>
|
||||
<uni-swiper-dot class="uni-swiper-dot-box" @clickItem=clickItem :info="info" :current="current" :mode="mode" :dots-styles="dotsStyles" field="content">
|
||||
<swiper class="swiper-box" @change="change" :current="swiperDotIndex">
|
||||
<swiper-item v-for="(item, index) in 3" :key="index">
|
||||
<view class="swiper-item">
|
||||
<view class="card">
|
||||
<image class="logo" src="http://rbtnet.oss-cn-hangzhou.aliyuncs.com/test/20240726/98a24075202b429d9c9722df541621c6.jpg"></image>
|
||||
<view class="content-box">
|
||||
<view class="left-box">
|
||||
<view class="title">15开黑卡/局</view>
|
||||
<view class="text-gray">LOL手游</view>
|
||||
<view class="text-gray">下单前聊一聊</view>
|
||||
</view>
|
||||
<view class="btn">找TA开黑</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</uni-swiper-dot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
info: [{
|
||||
colorClass: 'uni-bg-red',
|
||||
url: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/shuijiao.jpg',
|
||||
content: '内容 A'
|
||||
},
|
||||
{
|
||||
colorClass: 'uni-bg-green',
|
||||
url: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/shuijiao.jpg',
|
||||
content: '内容 B'
|
||||
},
|
||||
{
|
||||
colorClass: 'uni-bg-blue',
|
||||
url: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/shuijiao.jpg',
|
||||
content: '内容 C'
|
||||
}
|
||||
],
|
||||
dotStyle: [{
|
||||
backgroundColor: '#00c8b0',
|
||||
border: '1px rgba(0, 0, 0, .3) solid',
|
||||
color: '#fff',
|
||||
selectedBackgroundColor: 'rgba(0, 0, 0, .9)',
|
||||
selectedBorder: '1px rgba(0, 0, 0, .9) solid'
|
||||
},
|
||||
{
|
||||
backgroundColor: 'rgba(255, 90, 95,0.3)',
|
||||
border: '1px rgba(255, 90, 95,0.3) solid',
|
||||
color: '#fff',
|
||||
selectedBackgroundColor: 'rgba(255, 90, 95,0.9)',
|
||||
selectedBorder: '1px rgba(255, 90, 95,0.9) solid'
|
||||
},
|
||||
{
|
||||
backgroundColor: 'rgba(83, 200, 249,0.3)',
|
||||
border: '1px rgba(83, 200, 249,0.3) solid',
|
||||
color: '#fff',
|
||||
selectedBackgroundColor: 'rgba(83, 200, 249,0.9)',
|
||||
selectedBorder: '1px rgba(83, 200, 249,0.9) solid'
|
||||
}
|
||||
],
|
||||
modeIndex: -1,
|
||||
styleIndex: -1,
|
||||
current: 0,
|
||||
mode: 'default',
|
||||
dotsStyles: {
|
||||
selectedBackgroundColor: '#00c8b0',
|
||||
},
|
||||
swiperDotIndex: 0,
|
||||
current: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(e) {
|
||||
this.current = e.detail.current
|
||||
},
|
||||
selectStyle(index) {
|
||||
this.dotsStyles = this.dotStyle[index]
|
||||
this.styleIndex = index
|
||||
},
|
||||
selectMode(mode, index) {
|
||||
this.mode = mode
|
||||
this.modeIndex = index
|
||||
this.styleIndex = -1
|
||||
this.dotsStyles = this.dotStyle[0]
|
||||
},
|
||||
clickItem(e) {
|
||||
this.swiperDotIndex = e
|
||||
},
|
||||
onBanner(index) {
|
||||
console.log(22222, index);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.swiper-box {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 750rpx;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
::v-deep .image img {
|
||||
-webkit-user-drag: none;
|
||||
-khtml-user-drag: none;
|
||||
-moz-user-drag: none;
|
||||
-o-user-drag: none;
|
||||
user-drag: none;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
.uni-bg-red {
|
||||
background-color: #ff5a5f;
|
||||
}
|
||||
|
||||
.uni-bg-green {
|
||||
background-color: #09bb07;
|
||||
}
|
||||
|
||||
.uni-bg-blue {
|
||||
background-color: #007aff;
|
||||
}
|
||||
|
||||
.example-body {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.example-body-item {
|
||||
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 15rpx;
|
||||
padding: 15rpx;
|
||||
height: 60rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
padding: 0 15rpx;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
border-color: #e5e5e5;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.example-body-item-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.example-body-dots {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50px;
|
||||
background-color: #333333;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.active {
|
||||
border-style: solid;
|
||||
border-color: #007aff;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
padding: 10px;
|
||||
|
||||
.logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 100%;;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
|
||||
.title {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
font-size: 24rpx;
|
||||
color: #aaaaaa;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #00c8b0;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
border-radius: 45px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
1452
pages/im/components/chatBar.vue
Normal file
1452
pages/im/components/chatBar.vue
Normal file
File diff suppressed because it is too large
Load Diff
336
pages/im/components/layout.vue
Normal file
336
pages/im/components/layout.vue
Normal file
@@ -0,0 +1,336 @@
|
||||
<!-- 聊天记录模式+虚拟列表演示(vue),加载更多聊天记录无闪动&支持渲染大量数据 -->
|
||||
<!-- 注意:虚拟列表缓存高度默认仅在cell初始化时获取一次,若您的聊天列表中带有图片,强烈建议给图片一个固定的高度,如果高度根据内容动态撑高,可能导致虚拟列表抖动 -->
|
||||
<!-- 如果必须实现高度根据内容动态撑高+虚拟列表功能,可监听图片加载完毕事件,并在其中调用z-paging的didUpdateVirtualListCell刷新缓存高度 -->
|
||||
<template>
|
||||
<view class="content">
|
||||
<!-- use-chat-record-mode:开启聊天记录模式 -->
|
||||
<!-- use-virtual-list:开启虚拟列表模式 -->
|
||||
<!-- cell-height-mode:设置虚拟列表模式高度不固定 -->
|
||||
<!-- safe-area-inset-bottom:开启底部安全区域适配 -->
|
||||
<!-- bottom-bg-color:设置slot="bottom"容器的背景色,这里设置为和chat-input-bar的背景色一致 -->
|
||||
<z-paging ref="paging" v-model="dataList" :paging-style="{ paddingTop: paddingTop + 'px' }" default-page-size="20" :innerListStyle="innerListStyle" use-chat-record-mode use-virtual-list cell-height-mode="dynamic" :auto-clean-list-when-reload="false" safe-area-inset-bottom bottom-bg-color="#fafafa"
|
||||
|
||||
@query="queryList" :back-to-top-style="backToTopStyle" :auto-show-back-to-top="showNewMessageTip" @scrolltoupper="onScrollToUpper" @backToTopClick="onBackToTopClick" @keyboardHeightChange="keyboardHeightChange" @hidedKeyboard="hidedKeyboard">
|
||||
<!-- 顶部提示文字 -->
|
||||
<template #top>
|
||||
<navigation-Bar :isReconnecting="isReconnecting" :user="user" @initNav="initNav" @more="navMore">
|
||||
<template #card-swiper>
|
||||
<card-swiper v-if="navSwiper"></card-swiper>
|
||||
</template>
|
||||
<template #card-notice>
|
||||
<card-notice v-if="navNotice"></card-notice>
|
||||
</template>
|
||||
</navigation-Bar>
|
||||
</template>
|
||||
|
||||
<!-- style="transform: scaleY(-1)"必须写,否则会导致列表倒置!!! -->
|
||||
<!-- 注意不要直接在chat-item组件标签上设置style,因为在微信小程序中是无效的,请包一层view -->
|
||||
<template #cell="{item,index}">
|
||||
<view style="transform: scaleY(-1)">
|
||||
<view-item @playAudio="playAudio" :playId="playId" :messageList="dataList" :messageIndex="index" :item="item"></view-item>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 底部聊天输入框 -->
|
||||
<template #bottom>
|
||||
<slot name="chat-bar" :user="user" :keyboardHeight="keyboardHeight"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 查看最新消息 -->
|
||||
<template #backToTop>
|
||||
<text>有新消息</text>
|
||||
</template>
|
||||
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
dayjs.extend(relativeTime);
|
||||
import NavigationBar from '@/pages/im/components/navigationBar.vue';
|
||||
import cardSwiper from '@/pages/im/components/cardSwiper.vue';
|
||||
import cardNotice from '@/pages/im/components/cardNotice.vue';
|
||||
import ViewItem from '@/pages/im/components/viewItem.vue';
|
||||
import ImMessageApi from '@/sheep/api/im/memberMessage';
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
export default {
|
||||
components: {
|
||||
NavigationBar,
|
||||
cardSwiper,
|
||||
cardNotice,
|
||||
ViewItem,
|
||||
},
|
||||
props: {
|
||||
groupId: 0,
|
||||
userId: 0,
|
||||
isReconnecting: false,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataList: [],
|
||||
keyboardHeight: -1,
|
||||
paddingTop: 0,
|
||||
navSwiper: false,
|
||||
navSwiperH: 80,
|
||||
navNotice: false,
|
||||
navNoticeH: 90,
|
||||
height: 0,
|
||||
|
||||
queryParams: {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
groupId: 0,
|
||||
},
|
||||
|
||||
showNewMessageTip: false,
|
||||
backToTopStyle: {
|
||||
'width': '100px',
|
||||
'background-color': '#fff',
|
||||
'border-radius': '30px',
|
||||
'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)',
|
||||
'display': 'flex',
|
||||
'justifyContent': 'center',
|
||||
'alignItems': 'center',
|
||||
},
|
||||
|
||||
innerListStyle: {
|
||||
borderBottom: '20px solid #fafafa',
|
||||
},
|
||||
|
||||
audioList: [],
|
||||
voiceIndex: -1,
|
||||
playId: null,
|
||||
|
||||
user: {},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getUserInfo();
|
||||
},
|
||||
methods: {
|
||||
getUserInfo() {
|
||||
UserApi.getUserInfoById(this.userId).then(res => {
|
||||
this.user = res.data;
|
||||
this.user.time = this.showDayTime(this.user.updateTime);
|
||||
});
|
||||
},
|
||||
showDayTime(datetime) {
|
||||
if (!datetime) return "";
|
||||
return dayjs(datetime).fromNow();
|
||||
},
|
||||
initNav(e) {
|
||||
this.height = e.height;
|
||||
this.paddingTop = this.height;
|
||||
|
||||
if(this.navSwiper){
|
||||
this.paddingTop = this.paddingTop + this.navSwiperH;
|
||||
}
|
||||
|
||||
if(this.navNotice){
|
||||
this.paddingTop = this.paddingTop + this.navNoticeH;
|
||||
}
|
||||
},
|
||||
navMore() {
|
||||
this.navNotice = !this.navNotice;
|
||||
|
||||
if(this.navNotice){
|
||||
this.paddingTop = this.paddingTop + this.navNoticeH;
|
||||
}else{
|
||||
this.paddingTop = this.paddingTop - this.navNoticeH;
|
||||
}
|
||||
},
|
||||
queryList(pageNo, pageSize) {
|
||||
// 组件加载时会自动触发此方法,因此默认页面加载时会自动触发,无需手动调用
|
||||
// 这里的pageNo和pageSize会自动计算好,直接传给服务器即可
|
||||
// 模拟请求服务器获取分页数据,请替换成自己的网络请求
|
||||
this.queryParams.pageNo = pageNo;
|
||||
this.queryParams.pageSize = pageSize;
|
||||
this.queryParams.groupId = this.groupId;
|
||||
|
||||
/* this.$refs.paging.complete([
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'message',
|
||||
message: {
|
||||
time: false,
|
||||
src: 'https://rbtnet.oss-cn-hangzhou.aliyuncs.com/laohu.png',
|
||||
position: 'left',
|
||||
isRead: true,
|
||||
src: '',
|
||||
sec: '30',
|
||||
msgType: 'voice',
|
||||
},
|
||||
},
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'info',
|
||||
message: {
|
||||
title: '温馨提示',
|
||||
content: '请礼貌用语友好沟通,如遇骚扰等不文明行为,可以将对方屏蔽并投诉。',
|
||||
msgType: 'card',
|
||||
},
|
||||
},
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'message',
|
||||
message: {
|
||||
time: true,
|
||||
timeDesc: '14:52',
|
||||
src: 'https://rbtnet.oss-cn-hangzhou.aliyuncs.com/laohu.png',
|
||||
position: 'right',
|
||||
isRead: true,
|
||||
msgType: 'emoji',
|
||||
},
|
||||
},
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'message',
|
||||
message: {
|
||||
time: true,
|
||||
timeDesc: '14:52',
|
||||
src: 'http://rbtnet.oss-cn-hangzhou.aliyuncs.com/test/20240726/98a24075202b429d9c9722df541621c6.jpg',
|
||||
position: 'right',
|
||||
isRead: true,
|
||||
msgType: 'img',
|
||||
},
|
||||
},
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'message',
|
||||
message: {
|
||||
time: false,
|
||||
src: 'https://rbtnet.oss-cn-hangzhou.aliyuncs.com/laohu.png',
|
||||
position: 'right',
|
||||
isRead: true,
|
||||
src: '',
|
||||
sec: '130',
|
||||
msgType: 'voice',
|
||||
},
|
||||
},
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'message',
|
||||
message: {
|
||||
time: false,
|
||||
position: 'right',
|
||||
content: '地址:广东省广州市番禺区祈福新村129号',
|
||||
msgType: 'map',
|
||||
},
|
||||
},
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'message',
|
||||
message: {
|
||||
time: false,
|
||||
position: 'right',
|
||||
content: '吃饭了吗',
|
||||
msgType: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'info',
|
||||
message: {
|
||||
content: '长按图片、文字或视频,支持撤回消息哦~',
|
||||
msgType: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
dd:'2',
|
||||
msgType: 'message',
|
||||
message: {
|
||||
time: true,
|
||||
timeDesc: '14:52',
|
||||
src: 'https://rbtnet.oss-cn-hangzhou.aliyuncs.com/laohu.png',
|
||||
position: 'left',
|
||||
isRead: true,
|
||||
msgType: 'emoji',
|
||||
},
|
||||
},
|
||||
]);
|
||||
*/
|
||||
|
||||
ImMessageApi.getMemberMessagePage(this.queryParams).then(res => {
|
||||
// 将请求的结果数组传递给z-paging
|
||||
this.$refs.paging.complete(res.data.list);
|
||||
}).catch(res => {
|
||||
// 如果请求失败写this.$refs.paging.complete(false);
|
||||
// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
|
||||
// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
|
||||
this.$refs.paging.complete(false);
|
||||
})
|
||||
},
|
||||
/** 监听滚动到底部事件(因为 scroll 翻转了顶就是底) */
|
||||
onScrollToUpper() {
|
||||
// 若已是第一页则不做处理
|
||||
if (this.queryParams.pageNo === 1) {
|
||||
return;
|
||||
}
|
||||
this.showNewMessageTip = false;
|
||||
// 到底重置消息列表
|
||||
this.reload();
|
||||
},
|
||||
// 监听键盘高度改变,请不要直接通过uni.onKeyboardHeightChange监听,否则可能导致z-paging内置的键盘高度改变监听失效(如果不需要切换表情面板则不用写)
|
||||
keyboardHeightChange(res) {
|
||||
this.keyboardHeight = res.height;
|
||||
},
|
||||
// 用户尝试隐藏键盘,此时如果表情面板在展示中,应当通知chatInputBar隐藏表情面板(如果不需要切换表情面板则不用写)
|
||||
hidedKeyboard() {
|
||||
this.$emit('hidedKeyboard');
|
||||
},
|
||||
reload(message) {
|
||||
if(message){
|
||||
if(this.queryParams.pageNo > 1){
|
||||
this.showNewMessageTip = true;
|
||||
}
|
||||
if(message.readStatus){
|
||||
this.updateReadStatus(message);
|
||||
}else{
|
||||
this.$refs.paging.addChatRecordData([message]);
|
||||
}
|
||||
}else{
|
||||
this.$refs.paging.reload();
|
||||
}
|
||||
},
|
||||
updateReadStatus(message) {
|
||||
this.dataList.forEach(item => {
|
||||
if (item.id == message.id) {
|
||||
item.readStatus = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 滚动到最新消息 */
|
||||
onBackToTopClick(event) {
|
||||
event(false); // 禁用默认操作
|
||||
this.$refs.paging.scrollToBottom();
|
||||
},
|
||||
playAudio(e) {
|
||||
this.playId = e.id;
|
||||
this.clearAllAudio();
|
||||
var audio = uni.createInnerAudioContext();
|
||||
this.audioList.push(audio);
|
||||
//语音自然播放结束
|
||||
audio.onEnded((res) => {
|
||||
this.playId = null;
|
||||
});
|
||||
|
||||
audio.src = e.content;
|
||||
audio.play();
|
||||
},
|
||||
clearAllAudio() {
|
||||
this.audioList.forEach(function(e){
|
||||
e.destroy();
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content {
|
||||
background-color: #fafafa;
|
||||
height: calc(100vh);
|
||||
}
|
||||
</style>
|
194
pages/im/components/navigationBar.vue
Normal file
194
pages/im/components/navigationBar.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<view>
|
||||
<tui-navigation-bar :isOpacity="false" @init="initNavigation" @change="opacityChange" :scrollTop="scrollTop" backgroundColor="#fff" color="#333">
|
||||
<view>
|
||||
<view class="nav-box">
|
||||
<view class="title">
|
||||
<text class="nickname">{{user.nickname}}</text>
|
||||
<view class="sex-badge" v-if="user.sex == 2">
|
||||
<u-icon name="ziyuan2" custom-prefix="iconfont"></u-icon>
|
||||
</view>
|
||||
<view class="sex-man" v-if="user.sex == 1">
|
||||
<u-icon name="ziyuan3" custom-prefix="iconfont"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="online" v-if="user.online">
|
||||
<view v-if="isReconnecting">会话重连中</view>
|
||||
<view class="text" v-else>
|
||||
<tui-badge :scaleRatio="0.8" type="green" dot></tui-badge>
|
||||
<text class="time">在线</text>
|
||||
<!-- <u-icon name="arrow-right" size="16"></u-icon> -->
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="online" v-else>
|
||||
<view v-if="isReconnecting">会话重连中</view>
|
||||
<view v-else>
|
||||
<text class="time">{{user.time}}</text>
|
||||
</view>
|
||||
</view> -->
|
||||
<view class="action" @click="goBack">
|
||||
<u-icon name="nav-back" size="44"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view>
|
||||
<slot name="card-swiper"></slot>
|
||||
</view>
|
||||
<view>
|
||||
<slot name="card-notice"></slot>
|
||||
</view>
|
||||
<!-- <view class="more">
|
||||
<view class="more-btn" @click="more">
|
||||
<view class="line"></view>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</tui-navigation-bar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tuiBadge from "@/components/thorui/tui-badge/tui-badge.vue";
|
||||
import tuiNavigationBar from "@/components/thorui/tui-navigation-bar/tui-navigation-bar.vue";
|
||||
export default {
|
||||
components: {
|
||||
tuiBadge,
|
||||
tuiNavigationBar,
|
||||
},
|
||||
props: {
|
||||
user: {},
|
||||
isReconnecting: false,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
top: 0, //标题图标距离顶部距离
|
||||
opacity: 1,
|
||||
height: 0,
|
||||
scrollTop: 0.5,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initNavigation(e) {
|
||||
this.height = e.height;
|
||||
this.opacity = e.opacity;
|
||||
this.top = e.top;
|
||||
this.$emit('initNav', e);
|
||||
},
|
||||
opacityChange(e) {
|
||||
this.opacity = e.opacity;
|
||||
},
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
more() {
|
||||
this.$emit('more');
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-box {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
flex-direction: column;
|
||||
font-size: 22rpx;
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin-bottom: 3px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.nickname {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: calc(100% - 440rpx);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.online {
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.time {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
padding: 0 10px;
|
||||
align-items: center;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.sex-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #e03997;
|
||||
color: #ffffff;
|
||||
border-radius: 3px;
|
||||
font-size: 8px;
|
||||
height: 30rpx;
|
||||
width: 30rpx;
|
||||
margin: 0 10rpx;
|
||||
}
|
||||
|
||||
.sex-man {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #0081ff;
|
||||
color: #ffffff;
|
||||
border-radius: 3px;
|
||||
font-size: 8px;
|
||||
height: 30rpx;
|
||||
width: 30rpx;
|
||||
margin: 0 10rpx;
|
||||
}
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.more-btn {
|
||||
background-color: #fff;
|
||||
padding: 0 7px;
|
||||
width: 31px;
|
||||
height: 15px;
|
||||
border-radius: 0 0 3px 3px;
|
||||
|
||||
.line {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
margin: 3px 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
385
pages/im/components/viewItem.vue
Normal file
385
pages/im/components/viewItem.vue
Normal file
@@ -0,0 +1,385 @@
|
||||
<template>
|
||||
<view v-if="userId > 0">
|
||||
<view v-if="item.sendType == 1">
|
||||
<view class="tip-box" v-if="item.contentType == 1">
|
||||
<text class="time">{{item.content}}</text>
|
||||
</view>
|
||||
<view class="tip-card" v-if="item.contentType == 2">
|
||||
<!-- <image class="icon" src="@/static/images/help.png"></image> -->
|
||||
<view class="content-box">
|
||||
<view class="title">温馨提示</view>
|
||||
<view class="content">{{item.content}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="item.sendType == 2">
|
||||
<view class="tip-box" v-if="showTime(item, messageIndex)">
|
||||
<text class="time">{{showDayTime(item.createTime)}}</text>
|
||||
</view>
|
||||
<view class="chat-box left" v-if="item.sendUserId != userId">
|
||||
<view class="avatar" @click="detail(item)">
|
||||
<u-avatar width="80rpx" height="80rpx" :src="item.sendAvatar"></u-avatar>
|
||||
</view>
|
||||
|
||||
<view class="content-box" v-if="item.contentType == 1">
|
||||
<view class="content" >{{item.content}}</view>
|
||||
</view>
|
||||
<view class="content-box" v-if="item.contentType == 2">
|
||||
<view class="content" >{{item.content}}</view>
|
||||
<u-icon name="map-fill" color="#3D7EFF" size="40"></u-icon>
|
||||
</view>
|
||||
<view class="content voice" :class="item.id == playId ?'play':''" @click="playAudio(item)" v-if="item.contentType == 3">
|
||||
<view class="icon">
|
||||
<u-icon name="voice1" custom-prefix="iconfont" color="#080808" size="40"></u-icon>
|
||||
</view>
|
||||
<text>{{item.voiceSec}}”</text>
|
||||
</view>
|
||||
<view v-if="item.contentType == 4" @click="showPic(item)" class="msg-img">
|
||||
<u-image width="420rpx" border-radius="5px" height="220rpx" :fade="false" :src="item.content"></u-image>
|
||||
</view>
|
||||
<view v-if="item.contentType == 5" class="msg-img">
|
||||
<u-image width="200rpx" height="200rpx" :fade="false" :src="item.content"></u-image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="chat-box right" v-if="item.sendUserId == userId">
|
||||
<view class="content-box" v-if="item.contentType == 1">
|
||||
<u-icon v-if="item.readStatus" name="checkmark-circle-fill" color="#00c8b0" size="40"></u-icon>
|
||||
<u-icon v-else name="checkmark-circle" color="#00c8b0" size="40"></u-icon>
|
||||
<view class="content" >{{item.content}}</view>
|
||||
</view>
|
||||
|
||||
<view class="content-box" v-if="item.contentType == 2">
|
||||
<u-icon name="map-fill" color="#3D7EFF" size="40"></u-icon>
|
||||
<view class="content" >{{item.content}}</view>
|
||||
</view>
|
||||
|
||||
<view class="content-box" v-if="item.contentType == 3">
|
||||
<u-icon v-if="item.readStatus" name="checkmark-circle-fill" color="#00c8b0" size="40"></u-icon>
|
||||
<u-icon v-else name="checkmark-circle" color="#00c8b0" size="40"></u-icon>
|
||||
<view class="content voice" :class="item.id == playId ?'play':''" @click="playAudio(item)">
|
||||
<text>{{item.voiceSec}}”</text>
|
||||
<view class="icon">
|
||||
<u-icon name="chat_voice" custom-prefix="iconfont" color="#fff" size="40"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-box" v-if="item.contentType == 4">
|
||||
<u-icon v-if="item.readStatus" name="checkmark-circle-fill" color="#00c8b0" size="40"></u-icon>
|
||||
<u-icon v-else name="checkmark-circle" color="#00c8b0" size="40"></u-icon>
|
||||
<view class="msg-img" @click="showPic(item)">
|
||||
<u-image width="420rpx" border-radius="5px" height="220rpx" :fade="false" :src="item.content"></u-image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-box" v-if="item.contentType == 5">
|
||||
<u-icon v-if="item.readStatus" name="checkmark-circle-fill" color="#00c8b0" size="40"></u-icon>
|
||||
<u-icon v-else name="checkmark-circle" color="#00c8b0" size="40"></u-icon>
|
||||
<view class="msg-img">
|
||||
<u-image width="200rpx" height="200rpx" :fade="false" :src="item.content"></u-image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="avatar" @click="detail(item)">
|
||||
<u-avatar width="80rpx" height="80rpx" :src="item.sendAvatar"></u-avatar>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from 'dayjs';
|
||||
import isToday from "dayjs/plugin/isToday";
|
||||
import isYesterday from "dayjs/plugin/isYesterday";
|
||||
dayjs.extend(isToday);
|
||||
dayjs.extend(isYesterday);
|
||||
import sheep from '@/sheep';
|
||||
export default {
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: {
|
||||
createTime: 0,
|
||||
},
|
||||
},
|
||||
// 消息索引
|
||||
messageIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// 消息列表
|
||||
messageList:{
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
playId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userId: 0,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
var user = sheep.$store('user').userInfo;
|
||||
this.userId = user.id;
|
||||
},
|
||||
methods: {
|
||||
// 预览图片
|
||||
showPic(e) {
|
||||
uni.previewImage({
|
||||
indicator: "none",
|
||||
current: e.content,
|
||||
urls: [e.content],
|
||||
});
|
||||
},
|
||||
detail(e) {
|
||||
sheep.$router.go('/pages/user/detail/index',{id: e.sendUserId});
|
||||
},
|
||||
playAudio(e) {
|
||||
this.$emit('playAudio', e);
|
||||
},
|
||||
formatDate(date, format= 'YYYY-MM-DD HH:mm:ss') {
|
||||
// 日期不存在,则返回空
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
// 日期存在,则进行格式化
|
||||
if (format === undefined) {
|
||||
format = 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
return dayjs(date).format(format);
|
||||
},
|
||||
showDayTime(datetime) {
|
||||
if (!datetime) return "";
|
||||
const weeks = [
|
||||
"星期日",
|
||||
"星期一",
|
||||
"星期二",
|
||||
"星期三",
|
||||
"星期四",
|
||||
"星期五",
|
||||
"星期六",
|
||||
];
|
||||
// 当天 显示时分
|
||||
if (dayjs(datetime).isToday()) {
|
||||
return dayjs(datetime).format("HH:mm");
|
||||
}
|
||||
// 昨天 昨天+时分
|
||||
if (dayjs(datetime).isYesterday()) {
|
||||
return `昨天 ${dayjs(datetime).format("HH:mm")}`;
|
||||
}
|
||||
// 本周 显示周几+时分
|
||||
if (dayjs().isSame(datetime, "week")) {
|
||||
const weekIndex = dayjs().day();
|
||||
return `${weeks[weekIndex]} ${dayjs(datetime).format("HH:mm")}`;
|
||||
}
|
||||
// 本周之前 显示年月日
|
||||
if (dayjs(datetime).isBefore(new Date(), "week")) {
|
||||
return dayjs(datetime).format("YYYY-MM-DD");
|
||||
}
|
||||
},
|
||||
showTime(item, index) {
|
||||
// 最后一个
|
||||
var createTime = new Date().getTime();
|
||||
if(index > 0){
|
||||
var message = this.messageList[index + 1];
|
||||
if(message){
|
||||
createTime = message.createTime;
|
||||
}
|
||||
}
|
||||
|
||||
let dateString = dayjs(createTime).fromNow();
|
||||
let nowDateString = dayjs(item.createTime).fromNow();
|
||||
return dateString !== nowDateString;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chat-box {
|
||||
padding: 6px 12px;
|
||||
display: flex;
|
||||
|
||||
.content-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.left {
|
||||
.content {
|
||||
border-radius: 0px 8px 8px;
|
||||
width: fit-content;
|
||||
background-color: #fff;
|
||||
color: #080808;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
text-align: justify;
|
||||
max-width: 470rpx;
|
||||
font-size: 11px;
|
||||
font-family: Helvetica Neue, Helvetica, sans-serif;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.msg-img {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.play{
|
||||
|
||||
.icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.icon:after
|
||||
{
|
||||
border-right: solid 3px #fff;
|
||||
animation: other-play 1s linear infinite;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.content {
|
||||
border-radius: 8px 0px 8px 8px;
|
||||
width: fit-content;
|
||||
background-color: #00c8b0;
|
||||
color: #080808;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
color: #fff;
|
||||
text-align: justify;
|
||||
max-width: 470rpx;
|
||||
font-size: 11px;
|
||||
font-family: Helvetica Neue, Helvetica, sans-serif;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.msg-img {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.play{
|
||||
|
||||
.icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.icon:after
|
||||
{
|
||||
border-left: solid 3px #00c8b0;
|
||||
animation: my-play 1s linear infinite;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes my-play {
|
||||
0% {
|
||||
transform: translateX(80%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes other-play {
|
||||
0% {
|
||||
transform: translateX(-80%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
|
||||
.voice{
|
||||
.icon{
|
||||
font-size: 40upx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icon:after
|
||||
{
|
||||
content:" ";
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.avatar {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tip-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 7px 0;
|
||||
padding: 7px 20px;
|
||||
text-align: center;
|
||||
|
||||
.time {
|
||||
color: #aaaaaa;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.tip-card {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 7px;
|
||||
margin: 12px;
|
||||
text-align: justify;
|
||||
|
||||
.icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
|
||||
.content-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
margin-left: 7px;
|
||||
|
||||
.title {
|
||||
font-size: 11px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 3px;
|
||||
color: #aaaaaa;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user