项目初始化

This commit is contained in:
jerry
2025-01-21 01:46:34 +08:00
parent 364021b042
commit 48153e7761
962 changed files with 172070 additions and 0 deletions

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

View 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>

View 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>

204
pages/im/index - 副本.vue Normal file
View File

@@ -0,0 +1,204 @@
<template>
<view>
<message-list @hidedKeyboard="hidedKeyboard">
<template #chat-bar="{keyboardHeight}">
<chat-bar ref="chatBar" :keyboardHeight="keyboardHeight" @openGift="openGift"></chat-bar>
</template>
</message-list>
<gift-bar ref="gift" @sendGift="sendGift"></gift-bar>
<view class="svga-box" :class="giftFlag ? 'svga-show': 'svga-hide'">
<c-svga ref="cSvgaRef" :canvasId='canvasId' :src="src" :loops='1' :auto-play="true" @frame='onFrame' @finished='onFinished' @percentage='onPercentage' @loaded='onLoaded'></c-svga>
<view class="close-btn" @click="closeSvga"></view>
</view>
</view>
</template>
<script>
import MessageList from '@/pages/im/components/messageList.vue';
import ChatBar from "@/components/chat-bar/chat-bar.vue"
import GiftBar from "@/components/gift-bar/gift-bar.vue"
export default {
components: {
MessageList,
ChatBar,
GiftBar,
},
data() {
return {
giftFlag: false,
src: 'https://cos.duopei.feiniaowangluo.com/1866/reward/gift/171241866882267KgIoSuLt.svga',
canvasId: 'myCanvas',
modal: true,
current: 0, // tabs组件的current值表示当前活动的tab选项
swiperCurrent: 0,
typeidx: 0,
currentTab: 1,
showMoreSheet: false,
modalButton: [{
text: "置顶该聊天",
type: "green",
plain: false
},
{
text: "删除该聊天",
type: "green",
plain: false
},
{
text: "设置免打扰",
type: "green",
plain: false
}],
itemList: [{
text: "删除",
color: "#2B2B2B"
}, {
text: "举报",
color: "#2B2B2B"
}, {
text: "分享",
color: "#2B2B2B"
}],
}
},
created() {
},
computed: {
swiperHeight: {
get() {
let that = this;
return ' calc( 100vh - 100rpx )';
},
set(v) {}
},
},
methods: {
hidedKeyboard() {
this.$refs.chatBar.hideDrawer();
},
toChat() {
this.$u.route({
url: 'pages/chat/index',
});
},
//调用此方法显示组件
showModal() {
this.modal = true;
},
//隐藏组件
hideModal() {
this.modal = false;
},
handleClick(e){
let index = e.index;
if (index === 0) {
} else {
}
this.hideModal();
},
openGift() {
this.$refs.gift.open();
},
sendGift(e) {
this.src = e.src;
this.$refs.cSvgaRef.call('startAnimation');
this.giftFlag = true;
},
onFinished() {
this.giftFlag = false;
console.log('动画停止播放时回调');
},
onFrame(frame) {//动画播放至某帧后回调
// console.log(frame);
},
onPercentage(percentage) { //动画播放至某进度后回调
// console.log(percentage);
},
onLoaded() {
this.$refs.cSvgaRef.call('setContentMode', 'AspectFill');
console.log('加载完成');
},
closeSvga() {
this.$refs.cSvgaRef.call('stopAnimation');
this.giftFlag = false;
}
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: #fafafa;
height: calc(100vh);
padding-bottom: env(safe-area-inset-bottom);
}
.nav-box {
display: flex;
justify-content: center;
align-items: center;
}
.tab-box {
display: flex;
justify-content: center;
align-items: center;
}
.tab-box .title-label {
height: 44px;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
color: #333333;
font-size: 28rpx;
}
.tab-box .title-label text {
font-weight: bold;
transition: all 0.3s;
}
.svga-box {
position: fixed;
top: 0;
left: 0;
z-index: 999999999;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .7);
.close-btn {
color: #fff;
font-size: 14px;
position: absolute;
z-index: 999999999;
//background: rgba(0, 0, 0, .5);
padding: 5px 10px;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
}
.svga-hide {
transform: translate(-100%, 0);
}
.svga-show {
transform: translate(0, 0);
}
</style>

194
pages/im/index.vue Normal file
View File

@@ -0,0 +1,194 @@
<template>
<view class="container page-app theme-light main-green font-1">
<layout v-if="groupId > 0 && receiveUserId > 0" @hidedKeyboard="hidedKeyboard" :isReconnecting="isReconnecting" :groupId="groupId" :userId="receiveUserId" ref="im">
<template #chat-bar="{keyboardHeight, user}">
<chat-bar ref="chatBar" :user="user" :keyboardHeight="keyboardHeight" @send="sendMessage" @openGift="openGift" @voiceOk="voiceOk" @getWeixin="getWeixin" @imageOk="imageOk"></chat-bar>
</template>
</layout>
<s-auth-modal />
<qrcode-modal />
</view>
</template>
<script>
import Layout from '@/pages/im/components/layout.vue';
import ChatBar from "@/pages/im/components/chatBar.vue";
import ImMessageApi from '@/sheep/api/im/memberMessage';
import qrcodeModal from '@/components/qrcode-modal/qrcode-modal.vue';
import FileApi from '@/sheep/api/infra/file';
import UserApi from '@/sheep/api/member/user';
import sheep from '@/sheep';
import { WxaSubscribeTemplate, WebSocketMessageTypeConstants } from '@/sheep/util/const';
import { useWebSocket } from '@/sheep/hooks/useWebSocket';
export default {
components: {
Layout,
ChatBar,
qrcodeModal,
},
data() {
return {
groupId: 0,
receiveUserId: 0,
Reconnect: {},
}
},
onLoad(option) {
this.groupId = option.groupId;
this.receiveUserId = option.receiveUserId;
},
onShow() {
//======================= 聊天工具相关 end =======================
const { options } = useWebSocket({
// 连接成功
onConnected: async () => {
console.log('连接成功');
},
// 收到消息
onMessage: async (data) => {
console.log('接收消息');
const type = data.type;
if (!type) {
console.error('未知的消息类型:' + data.value);
return;
}
if (type == WebSocketMessageTypeConstants.IM_MESSAGE_READ) {
console.log('刷新消息');
var that = this;
setTimeout(function(){
var message = JSON.parse(data.content);
if(message.groupId != that.groupId){
return;
}
that.$nextTick(function() {
that.$refs.im.reload(message);
});
},1000);
return;
}
if (type == WebSocketMessageTypeConstants.IM_MESSAGE_NEWS) {
var message = JSON.parse(data.content);
if(message.groupId != this.groupId){
return;
}
ImMessageApi.updateReadStatus(message.id);
this.$nextTick(function() {
this.$refs.im.reload(message);
});
return;
}
// 2.2 消息类型KEFU_MESSAGE_TYPE
if (type == WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
console.log('刷新消息');
// 刷新消息列表
//await messageListRef.value.refreshMessageList(JSON.parse(data.content));
return;
}
// 2.3 消息类型KEFU_MESSAGE_ADMIN_READ
if (type == WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
console.log('管理员已读消息');
}
},
});
this.Reconnect = options; // 重连状态
},
computed: {
isReconnecting() {
return this.Reconnect.isReconnecting;
},
},
onHide() {
},
methods: {
hidedKeyboard() {
this.$refs.chatBar.hideDrawer();
},
sendMessage(e) {
// #ifdef MP
// 订阅只能由用户主动触发,只能包一层 showModal 诱导用户点击
this.autoSubscribeMessage();
// #endif
ImMessageApi.sendImMessage({
groupId: this.groupId,
receiveUserId: this.receiveUserId,
contentType: e.contentType,
content: e.content,
voiceSec: e.voiceSec,
imgWidth: e.imgWidth,
imgHeight: e.imgHeight,
}).then(res => {
this.$refs.im.reload();
});
},
async imageOk(e) {
const res = await FileApi.uploadFile(e.path);
var data = {
contentType: 4,
content: res.data,
imgWidth: e.imgWidth,
imgHeight: e.imgHeight,
}
this.sendMessage(data);
},
openGift() {
sheep.$router.go('/pages/user/detail/index',{id: this.receiveUserId});
},
getWeixin() {
UserApi.getQrcodeByUserId(this.receiveUserId).then(res => {
if(res.data){
uni.previewImage({
current: 0, //预览图片的下标
urls: [res.data], //预览图片的地址,必须要数组形式,如果不是数组形式就转换成数组形式就可以
indicator: 'number',
loop: true
});
}
});
},
showDayTime(datetime) {
if (!datetime) return "";
return dayjs(datetime).fromNow();
},
async voiceOk(e) {
const res = await FileApi.uploadFile(e.path);
var data = {
contentType: 3,
content: res.data,
voiceSec: e.sec,
}
this.sendMessage(data);
},
subscribeMessage() {
const event = [WxaSubscribeTemplate.UNREAD_MESSAGE];
event.push(WxaSubscribeTemplate.CLERK_BLIND);
event.push(WxaSubscribeTemplate.CLERK_ORDER);
sheep.$platform.useProvider('wechat').subscribeMessage(event, () => {
// 订阅后记录一下订阅状态
uni.removeStorageSync(WxaSubscribeTemplate.UNREAD_MESSAGE);
uni.setStorageSync(WxaSubscribeTemplate.UNREAD_MESSAGE, '已订阅');
});
},
async autoSubscribeMessage() {
// 2. 订阅消息
this.subscribeMessage();
},
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: #fafafa;
height: calc(100vh);
padding-bottom: env(safe-area-inset-bottom);
}
</style>

33
pages/im/util/emoji.js Normal file
View File

@@ -0,0 +1,33 @@
export default [
"😀", "😁", "😃", "😄", "😅", "😆", "😉", "😊", "😋", "😎", "😍",
"😘", "😗", "😙", "😚", "😇", "😐", "😑", "😶", "😏", "😣", "😥", "😮", "😯", "😪",
"😫", "😴", "😌", "😛", "😜", "😝", "😒", "😓", "😔", "😕", "😲", "😷", "😖", "😞", "😟",
"😤", "😢", "😭", "😦", "😧", "😨", "😬", "😰", "😱", "😳", "😵", "😡", "😠",
"👦", "👧", "👨", "👩", "👴", "👵", "👶", "👱", "👮", "👲", "👳", "👷", "👸", "💂", "🎅", "👰", "👼",
"💆", "💇", "🙍", "🙎", "🙅", "🙆", "💁", "🙋", "🙇", "🙌", "🙏", "👤", "👥", "🚶", "🏃", "👯",
"💃", "👫", "👬", "👭", "💏", "💑", "👪", "💪", "👈", "👉", "☝", "👆", "👇", "✌", "✋", "👌",
"👍", "👎", "✊", "👊", "👋", "👏", "👐", "✍", "👣", "👀", "👂", "👃", "👅", "👄", "💋", "👓",
"👔", "👙", "👛", "👜", "👝", "🎒", "💼", "👞", "👟", "👠", "👡", "👢", "👑",
"👒", "🎩", "🎓", "💄", "💅", "💍", "🌂", "📶", "📳", "📴", "♻", "🏧","🚮", "🚰", "♿", "🚹", "🚺",
"🚻", "🚼", "🚾", "⚠", "🚸", "⛔", "🚫", "🚳", "🚭", "🚯", "🚱", "🚷", "🔞", "💈",
"🙈", "🐒", "🐶", "🐕", "🐩", "🐺", "🐱","🐈", "🐯", "🐅", "🐆", "🐴", "🐎", "🐮", "🐂",
"🐃","🐄","🐷","🐖","🐗","🐽","🐏","🐑","🐐","🐪","🐫","🐘","🐭",
"🐁","🐀","🐹","🐰","🐇","🐻","🐨","🐼","🐾","🐔","🐓","🐣","🐤","🐥",
"🐦", "🐧", "🐸", "🐊","🐢", "🐍", "🐲", "🐉", "🐳", "🐋", "🐬", "🐟", "🐠", "🐡",
"🐙", "🐚", "🐌", "🐛", "🐜", "🐝", "🐞", "🦋", "💐", "🌸", "💮", "🌹", "🌺",
"🌻", "🌼", "🌷", "🌱", "🌲", "🌳", "🌴", "🌵", "🌾", "🌿", "🍀", "🍁", "🍂", "🍃",
"🌍","🌎","🌏","🌐","🌑","🌒","🌓","🌔","🌕","🌖","🌗","🌘","🌙","🌚",
"🌛","🌜","☀","🌝","🌞","⭐","🌟","🌠","☁","⛅","☔","⚡","❄","🔥","💧","🌊",
"🏀", "🏈", "🏉", "🎾", "🎱", "🎳", "⛳", "🎣", "🎽", "🎿",
"😈", "👿", "👹", "👺", "💀", "☠", "👻", "👽", "👾", "💣",
"🌋", "🗻", "🏠", "🏡", "🏢", "🏣", "🏤", "🏥", "🏦", "🏨",
"⛲", "🌁", "🌃", "🌆", "🌇", "🎠", "🎡", "🎢", "🚂",
"🚌", "🚍", "🚎", "🚏", "🚐", "🚑", "🚒", "🚓", "🚔", "🚕", "🚖", "🚗", "🚘",
"💌", "💎", "🔪", "💈", "🚪", "🚽", "🚿", "🛁", "⌛", "⏳", "⌚", "⏰", "🎈", "🎉",
"💤", "💢", "💬", "💭", "♨", "🌀", "🔔", "🔕", "✡", "✝", "🔯", "📛", "🔰", "🔱", "⭕", "✅",
"☑", "✔", "✖", "❌", "❎", "", "", "➗", "➰", "➿", "〽", "✳", "✴", "❇", "‼", "⁉", "❓", "❔", "❕", "❗",
"🕛", "🕧", "🕐", "🕜", "🕑", "🕝", "🕒", "🕞", "🕓", "🕟", "🕔", "🕠", "🕕", "🕡",
"🕖", "🕢", "🕗", "🕣", "🕘", "🕤", "🕙", "🕥", "🕚", "🕦", "⏱", "⏲", "🕰",
"💘", "❤", "💓", "💔", "💕", "💖", "💗", "💙", "💚", "💛", "💜", "💝", "💞", "💟❣",
"🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "🍎", "🍏", "🍐", "🍑", "🍒", "🍓",
]