Commit 2eae9797 authored by 罗超's avatar 罗超

调整IM列表,补充聊天界面和功能

parent 10d51510
...@@ -54,7 +54,8 @@ App( ...@@ -54,7 +54,8 @@ App(
emitter: null, emitter: null,
netcallController: null, netcallController: null,
ENVIRONMENT_CONFIG, ENVIRONMENT_CONFIG,
PAGE_CONFIG PAGE_CONFIG,
queryUser:{}
}, },
onShow: function (e) { onShow: function (e) {
if (e.scene == 1007 || e.scene == 1008) { if (e.scene == 1007 || e.scene == 1008) {
......
...@@ -7,8 +7,11 @@ ...@@ -7,8 +7,11 @@
"pages/validateForm/validateEnd/validateEnd", "pages/validateForm/validateEnd/validateEnd",
"pages/logs/logs", "pages/logs/logs",
"pages/me/index", "pages/me/index",
"partials/chating/chating",
"pages/video/index" "pages/video/index",
"components/emoji/emoji",
"components/inputclear/inputclear",
"components/inputmodal/inputmodal"
], ],
"window": { "window": {
"backgroundTextStyle": "light", "backgroundTextStyle": "light",
......
import EmojiObj from '../../utils/emojimap.js'
Component({
/**
* 组件的属性列表
*/
properties: {
},
/**
* 组件的初始数据
*/
data: {
localAlbumImages: ['/images/album-emoji.png', '/images/album-ajtd.png', '/images/album-xxy.png', '/images/album-lt.png'],
albumArr: [],
currentAlbum: 'emoji',
emojiList: {},
currentAlbumKeys: [] //存储每一类别的key
},
attached: function() {
let currentAlbumKeys = this.splitAlbumKeys(Object.keys(EmojiObj.emojiList[this.data.currentAlbum]), this.data.currentAlbum == 'emoji' ? 23 : 10)
this.setData({
albumArr: EmojiObj.albumArr,
emojiList: EmojiObj.emojiList,
currentAlbumKeys
})
},
/**
* 组件的方法列表
*/
methods: {
/**
* 切换emoji类别
*/
switchAlbum: function(e) {
let currentAlbum = e.currentTarget.dataset.album
// 提前跟新一次,下面分类需要用到
this.setData({
currentAlbum
})
let currentAlbumKeys = this.splitAlbumKeys(Object.keys(this.data.emojiList[currentAlbum]), currentAlbum == 'emoji' ? 23 : 10, currentAlbum)
this.setData({
currentAlbumKeys
})
},
/**
* 每页显示固定个数
* arr数据源数组,space每个数组最大容量
* [[], [], []]
*/
splitAlbumKeys: function (arr, space, currentAlbum) {
const delta = space || 23
let result = [],
factor = Math.ceil(arr.length / delta),
begin = 0,
end = 1
if (factor == 1) {
result = [[...arr]]
} else {
for (let i = 1; i < factor; i++) {
let temp = []
temp = [...arr.slice(begin, i * delta)]
begin = i * delta
result.push(temp)
}
result.push([...arr.slice(delta * (factor - 1), arr.length)])
}
if (currentAlbum == 'emoji' || this.data.currentAlbum == 'emoji') { // 只有emoji才添加删除按钮
result.map((cata, index) => {
if(index != (result.length-1)) {
cata.push('[删除]')
}
})
// console.log(result)
}
return result
},
/**
* 单击emoji
*/
emojiTap: function(e) {
let emoji = e.target.dataset.emoji
if (!emoji) return
// console.log(emoji)
this.triggerEvent("EmojiClick", emoji)
},
/**
* 发送emoji
*/
emojiSend: function () {
this.triggerEvent("EmojiSend")
}
},
})
{
"component": true
}
\ No newline at end of file
<view class='emoji-wrapper'>
<view class='emoji-content'>
<swiper indicator-dots='true' class='emoji-content-swiper'>
<block>
<view style='display: inline-block;' wx:for="{{currentAlbumKeys}}" wx:for-item="currentEmojiArr" wx:key="{{Math.random()}}" bindtap='emojiTap'>
<swiper-item>
<view class='emoji-content-item' wx:for="{{currentEmojiArr}}" wx:for-item="currentEmojiKey" wx:key="{{currentEmojiKey}}">
<image src="{{emojiList[currentAlbum][currentEmojiKey].img}}" class='{{currentAlbum == "emoji" ? "emoji-content-img-emoji" : "emoji-content-img-other"}}' data-emoji='{{currentEmojiKey}}'></image>
</view>
</swiper-item>
</view>
</block>
</swiper>
</view>
<view class='emoji-album'>
<!-- <view class='emoji-album-left' wx:for="{{albumArr}}" wx:for-item="album" wx:key="{{album.album}}" data-album="{{album.album}}" bindtap='switchAlbum'>
<image src='{{album.img}}' class='{{album.album == currentAlbum ? "emoji-album-left-img album-active" : "emoji-album-left-img"}}'></image>
</view> -->
<view class='emoji-album-left' wx:for="{{albumArr}}" wx:for-item="album" wx:for-index="index" wx:key="{{album.album}}" data-album="{{album.album}}" bindtap='switchAlbum'>
<image src='{{localAlbumImages[index]}}' class='{{album.album == currentAlbum ? "emoji-album-left-img album-active" : "emoji-album-left-img"}}'></image>
</view>
<view class='emoji-send' bindtap='emojiSend'>发送</view>
</view>
</view>
\ No newline at end of file
.emoji-wrapper {
width: 100%;
height: 100%;
background-color: #fff;
}
/*内容 */
.emoji-content {
width: 100%;
height: 400rpx;
/* padding-top: 30rpx; */
padding-left: 20rpx;
box-sizing:border-box;
}
.emoji-content-swiper {
width: 100%;
height: 100%;
}
.emoji-content-item {
display: inline-block;
margin: 17rpx;
}
.emoji-content-img-emoji {
width: 56rpx;
height: 56rpx;
}
.emoji-content-img-other {
width: 100rpx;
height: 100rpx;
}
/*底部类别 */
.emoji-album {
width: 100%;
height: 88rpx;
border: 2rpx solid #999;
box-sizing: border-box;
background-color: #fff;
}
.emoji-album-left {
display: inline-block;
height: 100%;
}
.emoji-album-left-img {
width:88rpx;
height:100%;
padding:8rpx 10rpx;
box-sizing:border-box;
border-right:2rpx soild #999
}
.emoji-send {
width: 88rpx;
height: 100%;
line-height:88rpx;
background-color: #0091e4;
text-align: center;
float: right;
color: #fff;
}
.album-active {
background-color: #aaa;
}
// components/inputclear/inputclear.js
Component({
/**
* 组件的属性列表
*/
properties: {
type: {
type: String,
value: 'text'
},
placeholder: {
type: String,
value: '请输入内容'
},
maxlength: {
type: Number,
value: 10
}
},
/**
* 组件的初始数据
*/
data: {
inputVal: ''
},
/**
* 组件的方法列表
*/
methods: {
/**
* 输入响应
*/
bindInput(e) {
this.setData({
inputVal: e.detail.value
})
this.triggerEvent('inputClearChange', { data: this.data.inputVal }, {})
},
/**
* 清除输入框
*/
clearInput() {
this.setData({
inputVal: ''
})
},
/**
* 确定
*/
confirmHandler() {
this.triggerEvent('inputClearFinish', { data: this.data.inputVal }, {})
}
}
})
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<view class='input-clear-wrapper'>
<input type='{{type}}' value='{{inputVal}}' placeholder='{{placeholder}}' maxlength='{{maxlength}}' bindinput='bindInput' bindconfirm='confirmHandler'></input>
<icon wx:if="{{inputVal.length != 0}}" catchtap='clearInput' type='clear' size='17' class='clear-icon'></icon>
</view>
\ No newline at end of file
.input-clear-wrapper {
position: relative;
width: 100%;
}
.input-clear-wrapper input {
background-color: #fff;
width: 100%;
padding: 15rpx 0 15rpx 20rpx;
}
.input-clear-wrapper .clear-icon {
position: absolute;
top:50%;
right:17rpx;
margin-top:-17rpx;
z-index: 100;
}
\ No newline at end of file
// components/inputmodal/inputmodal.js
Component({
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: '默认标题'
}
},
/**
* 组件的初始数据
*/
data: {
},
attached: function () {
// let systemInfo = wx.getSystemInfoSync()
// console.log(systemInfo)
},
/**
* 组件的方法列表
*/
methods: {
cancel() {
this.triggerEvent('inputModalClick', {data: 'cancel'}, {})
},
confirm() {
this.triggerEvent('inputModalClick', { data: 'confirm' }, {})
}
}
})
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<view class='inputmodal-wrapper'>
<view class='content'>
<view class='title'>{{title}}</view>
<view class='slot'>
<slot></slot>
</view>
<view class='button-group'>
<view class='btn' hover-class='btn-hover' style='border-right: 2rpx solid #ccc;' bindtap='cancel'>取消</view>
<view class='btn' hover-class='btn-hover' style='color: rgb(99,181,130);' bindtap='confirm'>确定</view>
</view>
</view>
</view>
\ No newline at end of file
.inputmodal-wrapper {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background-color: rgba(0, 0, 0, .5);
z-index: 9998;
}
.inputmodal-wrapper .content {
width: 540rpx;
height: 300rpx;
background-color: #F3F4F6;
position: absolute;
top: 50%;
left: 50%;
margin-left: -270rpx;
margin-top: -150rpx;
border-radius: 24rpx;
z-index: 9999;
}
.inputmodal-wrapper .content .title {
width:100%;
text-align:center;
line-height:114rpx;
font-size:34rpx;
color: #030303;
}
.inputmodal-wrapper .content .slot {
height: 110rpx;
display: flex;
flex-direction: column;
/* justify-content: center; */
}
.inputmodal-wrapper .content input{
margin: 0 auto;
width: 90%;
height: 64rpx;
padding: 0 8rpx;
line-height: 64rpx;
background-color: #fff;
border: 0 solid #4D4D4D;
font-size: 30rpx;
}
.button-group {
position: absolute;
bottom: 0;
width: 100%;
height: 84rpx;
display: flex;
flex-direction: row;
border-top: 2rpx solid #ccc;
font-size: 34rpx;
color: #007AFF;
}
.button-group .btn {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.button-group .btn-hover {
background-color: rgba(0, 0, 0, .1);
}
let startX = 0
Component({
/**
* 组件的初始数据
*/
data: {
translateX: 0
},
/**
* 组件的方法列表
*/
methods: {
deleteItem: function (e) {
this.setData({
translateX: 0
})
this.triggerEvent('deleteChatItem', {}, {bubbles: true})
},
/**
* 滑动删除事件-滑动开始
*/
touchStartHandler: function(e) {
startX = e.touches[0].pageX
},
/**
* 滑动删除事件-滑动
*/
touchMoveHandler: function(e) {
let pageX = e.touches[0].pageX
let moveX = pageX - startX
if(Math.abs(moveX) < 40) {
return
}
// e.target.style.WebkitTransform = `translateX(${moveX}px)`
if (moveX > 0) { // 右滑 隐藏删除
if (Math.abs(this.data.translateX) == 0) {
return
} else {
this.setData({
translateX: 0
})
}
} else { // 左滑 显示删除
if (Math.abs(this.data.translateX) >= 80) {
return
} else {
this.setData({
translateX: -80
})
}
}
}
}
})
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<view class='swipedelete-wrapper' bindtouchmove='touchMoveHandler' bindtouchstart='touchStartHandler' style="transform:translateX({{translateX}}px)">
<slot></slot>
<view class='swipedelete-btn' bindtap='deleteItem'>删除</view>
</view>
\ No newline at end of file
.swipedelete-wrapper {
transition: all .4s ease;
}
.swipedelete-btn {
position:absolute;
top:0;
right:-180rpx;
text-align:center;
background: #f00;
color:#fff;
width:160rpx;
height:100%;
display:flex;
justify-content:center;
align-items:center;
}
\ No newline at end of file
Component({
properties: {
config: {
type: Object,
value: {
x: 0,
y: 0,
width: 0,
height: 0
}
},
debug: {
type: Boolean,
value: false
},
/**
* 加载状态:loading、ready、error
*/
status: {
type: String,
value: 'loading',
observer: function (newVal, oldVal, changedPath) {
console.log(`player status changed from ${oldVal} to ${newVal}`)
}
},
/**
* 画面方向,可选值有 vertical,horizontal
*/
orientation: {
type: String,
value: 'vertical'
},
objectFit: {
type: String,
value: 'fillCrop'
},
name: {
type: String,
value: ''
},
uid: {
type: String,
value: ''
},
coverText: {
type: String,
value: ''
},
url: {
type: String,
value: '',
observer: function (newVal, oldVal, changedPath) {
}
}
},
data: {
livePlayerContext: null,
detached: false
},
/**
* 组件的方法列表
*/
methods: {
/**
* 组件生命周期:在组件布局完成后执行,此时可以获取节点信息
*/
ready() {
console.log(`yunxinplayer-${this.data.uid} ready`)
if (this.data.livePlayerContext) {
this.data.livePlayerContext = wx.createLivePlayerContext(`yunxinplayer-${this.data.uid}`, this)
}
if (this.data.url) {
this.start()
}
},
/**
* 组件生命周期:在组件实例被从页面节点树移除时执行
*/
detached() {
console.log(`yunxinplayer-${this.data.uid} detached`)
wx.createLivePlayerContext(`yunxinplayer-${this.data.uid}`, this).stop()
this.data.detached = true
},
/**
* 开始拉流播放
*/
start() {
const uid = this.data.uid
if (this.data.status === 'ready') {
console.log(`player ${uid} already started`)
return
}
if (this.data.detached) {
console.log(`try to start player while component already detached`)
return
}
console.log(`starting player ${uid}`)
this.data.livePlayerContext.play()
},
/**
* 停止拉流播放
*/
stop() {
console.log(`stopping player ${this.data.uid}`)
wx.createLivePlayerContext(`yunxinplayer-${this.data.uid}`, this).stop()
},
/**
* 切换画面方向
* true为 horizontal,false为 vertical
*/
changeOrientation(isHorizontal) {
let orientation = isHorizontal ? 'horizontal' : 'vertical'
this.setData({
orientation: orientation
})
},
/**
* 切换填充模式
* true为 fillCrop,false为 contain
*/
changeObjectFit(isFillCrop) {
let objectFit = isFillCrop ? 'fillCrop' : 'contain'
this.setData({
objectFit: objectFit
})
},
/**
* 播放器状态更新回调
*/
stateChangeHandler(e) {
console.warn(`yunxin-player code: ${e.detail.code} - ${e.detail.message}`)
let uid = parseInt(e.target.id.split('-')[1])
if (e.detail.code === 2004) {
console.log(`live-player ${uid} started playing`)
if (this.data.status === 'loading') {
this.setData({
status: 'ready'
})
}
} else if (e.detail.code === -2301) {
console.log(`live-player ${uid} stopped`, 'error')
this.setData({
status: 'error'
})
this.triggerEvent('pullfailed');
}
},
/**
* 改变画面蒙面
*/
changeStatus(status) {
switch(status) {
case 'leave':
case 'notConnected': {
break
}
default: {
status = this.data.status
}
}
// console.error(status)
this.setData({
status
})
},
/**
* 开启调试
*/
toggleDebug(isDebug) {
this.setData({
debug: isDebug
})
}
}
})
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<!--components/yunxin-player/yunxin-player.wxml-->
<view class="play-container" style="left:{{config.x}}px; top:{{config.y}}px; width: {{config.width}}px; height: {{config.height}}px; ">
<live-player
id="yunxinplayer-{{uid}}"
src="{{url}}"
mode="RTC"
class="player"
orientation="{{orientation}}"
min-cache="0.2"
max-cache="0.8"
bindstatechange="stateChangeHandler"
object-fit="{{objectFit}}"
autoplay
style="height: {{config.height}}px; position: absolute; width: 100%; top: 0; left: 0;background-color: transparent;"
debug="{{debug}}">
<slot />
<cover-view
wx-if="{{status !== 'ready'}}"
class="sud flex-center-column"
style="display:none;">
<!-- style="position: absolute; width: 100%; height:100%;display:flex;justify-content:center;align-items:center;"> -->
<cover-image style="width: 182rpx;height:240rpx" src="../../images/yunxin/{{status}}.png"></cover-image>
</cover-view>
<cover-view style="position: absolute;top:10px;left:10px;font-size: 28rpx; right: 10px;color:#ccc;" wx-if="{{coverText.length != 0}}">
{{coverText}}
</cover-view>
</live-player>
</view>
\ No newline at end of file
/* components/yunxin-player/yunxin-player.wxss */
.play-container{
background: black;
display: block;
position: absolute;
}
.sud{
background-color: #1B2A38;
opacity:0.65;
}
\ No newline at end of file
Component({
properties: {
config: {
type: Object,
value: {
x: 0,
y: 0,
width: 0,
height: 0
}
},
debug: {
type: Boolean,
value: false
},
minBitrate: {
type: Number,
value: 200
},
maxBitrate: {
type: Number,
value: 500
},
enableCamera: {
type: Boolean,
value: true
},
muted: {
type: Boolean,
value: false
},
beauty: {
type: String,
value: 0
},
aspect: {
type: String,
value: "3:4"
},
/**
* 加载状态:loading、ready、error
*/
status: {
type: String,
value: "loading",
observer: function (newVal, oldVal, changedPath) {
console.log(`yunxin-pusher status changed from ${oldVal} to ${newVal}`);
}
},
coverText: {
type: String,
value: ''
},
url: {
type: String,
value: "",
observer: function (newVal, oldVal, changedPath) {
}
}
},
/**
* 组件的初始数据
*/
data: {
livePusherContext: null, // 组件操作上下文
detached: false // 组件是否被移除标记
},
/**
* 组件生命周期
*/
lifetimes: {
/**
* 在组件实例被从页面节点树移除时执行
*/
detached: function () {
console.log("yunxin-pusher detached");
// auto stop yunxin-pusher when detached
// this.stop()
this.setData({
detached: true
})
},
/**
* 在组件布局完成后执行,此时可以获取节点信息
*/
attached: function () {
console.log("yunxin-pusher ready")
this.start()
this.setData({
detached: false
})
},
},
/**
* 组件的方法列表
*/
methods: {
/**
* 播放推流
* 一般情况下不应手动调用,在推流组件预备好后会自动被调用
*/
start(options = {}) {
if (!this.livePusherContext) {
this.livePusherContext = wx.createLivePusherContext()
}
console.log(`starting yunxin-pusher`);
this.livePusherContext.start(options)
},
/**
* 停止推流
*/
stop(options = {}) {
if (this.livePusherContext) {
console.log(`stopping yunxin-pusher`);
this.livePusherContext.stop(options)
}
},
/**
* 切换前后摄像头
*/
switchCamera() {
this.livePusherContext.switchCamera()
},
/**
* 快照
*/
snapshot() {
this.livePusherContext.snapshot()
},
/**
* 推流状态变化事件回调
*/
stateChangeHandler(e) {
console.warn(`yunxin-pusher code: ${e.detail.code} - ${e.detail.message}`)
if (e.detail.code === -1307) { // 网络断连,且经多次重连抢救无效,更多重试请自行重启推
console.log('yunxin-pusher stopped', `code: ${e.detail.code}`);
this.setData({
status: "error"
})
this.livePusherContext.stop({
complete: () => {
this.livePusherContext.start()
}
})
this.triggerEvent('pushfailed');
} else if (e.detail.code === 1008) { // 编码器启动
console.log(`yunxin-pusher started`, `code: ${e.detail.code}`);
if (this.data.status === "loading") {
this.setData({
status: "ready"
})
}
}
},
/**
* 网络状态通知回调
*/
netChangeHandler(e) {
// console.log(`network: ${JSON.stringify(e.detail)}`);
},
/**
* 开启调试
*/
toggleDebug(isDebug) {
this.setData({
debug: isDebug
})
}
}
})
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<!--components/yunxin-pusher/yunxin-pusher.wxml-->
<view class="pusher-container" id="rtcpusher" style="left: {{config.x}}px; top: {{config.y}}px; width: {{config.width}}px; height: {{config.height}}px; position: absolute;">
<live-pusher
style="height:{{config.height}}px; position: absolute; width: 100%; "
url="{{url}}"
wx:if="{{url.length !== 0}}"
mode="RTC"
aspect="{{aspect}}"
class="camera"
bindstatechange="stateChangeHandler"
bindnetstatus="netChangeHandler"
background-mute="true"
enable-camera="{{enableCamera}}"
muted="{{muted}}"
beauty="{{beauty}}"
max-bitrate="500"
min-bitrate="200"
debug="{{debug}}"
autopush="true">
<slot />
<cover-view
wx-if="{{status !== 'ready'}}"
class="sud flex-center-column"
style="display:flex;position: absolute; width: 100%; height: 100%;justify-content:center;align-items:center;">
<cover-image style="width: 182rpx;height:240rpx" src="../../images/yunxin/{{status}}.png"></cover-image>
</cover-view>
<cover-view style="position: absolute;top:10px;left:10px;font-size: 28rpx; right: 10px;color:#ccc;" wx-if="{{coverText.length != 0}}">
{{coverText}}
</cover-view>
</live-pusher>
</view>
/* components/yunxin-pusher/yunxin-pusher.wxss */
.pusher-container{
background: black;
display: block;
position: absolute;
}
.sud{
background-color: #1B2A38;
opacity:0.65;
}
\ No newline at end of file
...@@ -18,7 +18,7 @@ let configMap = { ...@@ -18,7 +18,7 @@ let configMap = {
url: 'http://preapp.netease.im:8184' url: 'http://preapp.netease.im:8184'
}, },
online: { online: {
appkey: '45c6af3c98409b18a84451215d0bdd6e', appkey: 'b612b31e837c79c68f141aeb719d2b20',
url: 'https://app.netease.im' url: 'https://app.netease.im'
}, },
}; };
......
...@@ -15,7 +15,7 @@ export default class IMController { ...@@ -15,7 +15,7 @@ export default class IMController {
// 初始化SDk // 初始化SDk
// debug: true, // debug: true,
appKey: app.globalData.ENVIRONMENT_CONFIG.appkey, appKey: app.globalData.ENVIRONMENT_CONFIG.appkey,
token: MD5(headers.token), token: headers.token,
account: headers.account, account: headers.account,
promise: true, promise: true,
transports: ['websocket'], transports: ['websocket'],
......
import IMController from '../../../controller/im.js' import IMController from '../../../controller/im.js'
import { connect } from '../../../redux/index.js' import { connect } from '../../../redux/index.js'
import { showToast, calcTimeHeader, clickLogoJumpToCard } from '../../../utils/util.js' import { showToast, calcTimeHeader, clickLogoJumpToCard, getUsers, formatDate } from '../../../utils/util.js'
import { iconNoMessage } from '../../../utils/imageBase64.js' import { iconNoMessage } from '../../../utils/imageBase64.js'
let app = getApp() let app = getApp()
let store = app.store let store = app.store
let queryUserQueuee = app.globalData.queryUser
let startX = 0 let startX = 0
...@@ -206,7 +207,7 @@ let pageConfig = { ...@@ -206,7 +207,7 @@ let pageConfig = {
app.globalData.nim.resetSessionUnread(session) app.globalData.nim.resetSessionUnread(session)
// 跳转 // 跳转
wx.navigateTo({ wx.navigateTo({
url: `../../partials/chating/chating?chatTo=${account}&type=${chatType}`, url: `../../../partials/chating/chating?chatTo=${account}&type=${chatType}`,
}) })
}, },
/** /**
...@@ -259,17 +260,19 @@ let pageConfig = { ...@@ -259,17 +260,19 @@ let pageConfig = {
/** /**
* 将原生消息转化为最近会话列表渲染数据 * 将原生消息转化为最近会话列表渲染数据
*/ */
convertRawMessageListToRenderChatList(rawMessageList, friendCard, groupList, unreadInfo) { convertRawMessageListToRenderChatList(rawMessageList, friendCard, groupList, unreadInfo,userCard) {
let chatList = [] let chatList = []
let sessions = Object.keys(rawMessageList) let sessions = Object.keys(rawMessageList)
let index = 0 let index = 0
let unQueryUsers=[]
sessions.map(session => { sessions.map(session => {
let account = session.indexOf('team-') === 0 ? session.slice(5, session.length) : session.slice(4, session.length) let account = session.indexOf('team-') === 0 ? session.slice(5, session.length) : session.slice(4, session.length)
let isP2p = session.indexOf('p2p-') === 0 let isP2p = session.indexOf('p2p-') === 0
let chatType = isP2p ? 'p2p' : (groupList[account] && groupList[account].type) let chatType = isP2p ? 'p2p' : (groupList[account] && groupList[account].type)
let sessionCard = (isP2p ? friendCard[account] : groupList[account]) || {} let sessionCard = (isP2p ? friendCard[account] : groupList[account]) || {}
let ucard = (isP2p ? userCard[account] : {}) || {}
let unixtimeList = Object.keys(rawMessageList[session]) let unixtimeList = Object.keys(rawMessageList[session])
if (!unixtimeList) { if (!unixtimeList || account =='684cb79fe92f46877777') {
return return
} }
let maxTime = Math.max(...unixtimeList) let maxTime = Math.max(...unixtimeList)
...@@ -278,8 +281,17 @@ let pageConfig = { ...@@ -278,8 +281,17 @@ let pageConfig = {
let msgType = this.judgeMessageType(msg) let msgType = this.judgeMessageType(msg)
let lastestMsg = msgType let lastestMsg = msgType
let status = isP2p ? (sessionCard.status || '离线') : '' let status = isP2p ? (sessionCard.status || '离线') : ''
let nick = isP2p ? (sessionCard.nick || '非好友') : sessionCard.name let nick = isP2p ? (sessionCard.nick || ucard.nick || '非好友') : sessionCard.name
let avatar = isP2p ? (sessionCard.avatar || app.globalData.PAGE_CONFIG.defaultUserLogo) : (sessionCard.avatar || app.globalData.PAGE_CONFIG.defaultUserLogo) if(nick=='非好友'){
for (var key in rawMessageList[session]) {
nick = rawMessageList[session][key].fromNick;
}
if (!queryUserQueuee[account]){
queryUserQueuee[account]=account
getUsers(account, store)
}
}
let avatar = isP2p ? (sessionCard.avatar || ucard.avatar || app.globalData.PAGE_CONFIG.defaultUserLogo) : (sessionCard.avatar || app.globalData.PAGE_CONFIG.defaultUserLogo)
chatList.push({ chatList.push({
chatType, chatType,
session, session,
...@@ -291,7 +303,7 @@ let pageConfig = { ...@@ -291,7 +303,7 @@ let pageConfig = {
type: msgType || msg.type, type: msgType || msg.type,
timestamp: msg.time, timestamp: msg.time,
unread: unreadInfo[session] || 0, unread: unreadInfo[session] || 0,
displayTime: msg.time ? calcTimeHeader(msg.time) : '' displayTime: msg.time ? formatDate(msg.time) : ''
}) })
} }
}) })
...@@ -301,6 +313,11 @@ let pageConfig = { ...@@ -301,6 +313,11 @@ let pageConfig = {
}) })
return chatList return chatList
}, },
getUserNick(obj) { //obj为我们的对象
var n = {};
return n.fromNick||"";
},
/** /**
* 计算最近一条发送的通知消息列表 * 计算最近一条发送的通知消息列表
*/ */
...@@ -331,7 +348,7 @@ let pageConfig = { ...@@ -331,7 +348,7 @@ let pageConfig = {
} }
} }
let mapStateToData = (state) => { let mapStateToData = (state) => {
let chatList = pageConfig.convertRawMessageListToRenderChatList(state.rawMessageList, state.friendCard, state.groupList, state.unreadInfo) let chatList = pageConfig.convertRawMessageListToRenderChatList(state.rawMessageList, state.friendCard, state.groupList, state.unreadInfo, state.userCard)
let latestNotification = pageConfig.caculateLastestNotification(state.notificationList) let latestNotification = pageConfig.caculateLastestNotification(state.notificationList)
return { return {
rawMessageList: state.rawMessageList, rawMessageList: state.rawMessageList,
......
...@@ -9,24 +9,8 @@ ...@@ -9,24 +9,8 @@
<input type='text' bindinput='searchInput' class="search-input {{showSearchBox==1?'':'hide'}}" bindblur="changeSearchBox" data-type="0" focus='{{isFocus}}'/> <input type='text' bindinput='searchInput' class="search-input {{showSearchBox==1?'':'hide'}}" bindblur="changeSearchBox" data-type="0" focus='{{isFocus}}'/>
</view> </view>
<view class="msg-box"> <view class="msg-box">
<view class="msg-item">
<image class="avater" src="http://imgfile.oytour.com/New/Upload/User/20191018150051176.png"></image> <view class="msg-item" wx:if="{{chatList.length != 0}}" wx:for="{{chatList}}" wx:for-item="message" wx:key="message.time" data-session='{{message.session}}' data-account='{{message.account}}' data-session='{{message.session}}' bindtap='switchToChating'>
<view class="msg-content">
<view class="item-name">
罗超
</view>
<view class="lst-msg">
如果来不及的话,就直接使用Vue-table来做,这样会快一点
</view>
</view>
<view class="times">
<view class="timer">16:23</view>
<view>
<text class="pops">9</text>
</view>
</view>
</view>
<view class="msg-item" wx:if="{{chatList.length != 0}}" wx:for="{{chatList}}" wx:for-item="message" wx:key="message.time" data-session='{{message.session}}'>
<image class="avater" src="{{message.avatar}}"></image> <image class="avater" src="{{message.avatar}}"></image>
<view class="msg-content"> <view class="msg-content">
<view class="item-name"> <view class="item-name">
...@@ -37,7 +21,7 @@ ...@@ -37,7 +21,7 @@
</view> </view>
</view> </view>
<view class="times"> <view class="times">
<view class="timer">昨天</view> <view class="timer">{{message.displayTime}}</view>
<view wx:if="{{message.unread}}"> <view wx:if="{{message.unread}}">
<text class="pops">{{message.unread || ''}}</text> <text class="pops">{{message.unread || ''}}</text>
</view> </view>
......
...@@ -70,8 +70,9 @@ page{ ...@@ -70,8 +70,9 @@ page{
flex: 1; flex: 1;
} }
.msg-box .msg-item .msg-content .item-name{ .msg-box .msg-item .msg-content .item-name{
font-size: 36rpx; font-size: 32rpx;
font-weight: 600; font-weight: 600;
margin-top: 10rpx;
} }
.msg-box .msg-item .msg-content .lst-msg{ .msg-box .msg-item .msg-content .lst-msg{
font-size: 28rpx; font-size: 28rpx;
......
import { connect } from '../../redux/index.js'
import { generateFingerGuessImageFile, generateBigEmojiImageFile, generateRichTextNode, generateImageNode, calcTimeHeader } from '../../utils/util.js'
import { showToast, deepClone, clickLogoJumpToCard } from '../../utils/util.js'
import * as iconBase64Map from '../../utils/imageBase64.js'
let app = getApp()
let store = app.store
let self = this
let pageConfig = {
data: {
defaultUserLogo: app.globalData.PAGE_CONFIG.defaultUserLogo,
videoContext: null, // 视频操纵对象
isVideoFullScreen: false, // 视频全屏控制标准
videoSrc: '', // 视频源
recorderManager: null, // 微信录音管理对象
recordClicked: false, // 判断手指是否触摸录音按钮
iconBase64Map: {}, //发送栏base64图标集合
isLongPress: false, // 录音按钮是否正在长按
chatWrapperMaxHeight: 0,// 聊天界面最大高度
chatTo: '', //聊天对象account
chatType: '', //聊天类型 advanced 高级群聊 normal 讨论组群聊 p2p 点对点聊天
loginAccountLogo: '', // 登录账户对象头像
focusFlag: false,//控制输入框失去焦点与否
emojiFlag: false,//emoji键盘标志位
moreFlag: false, // 更多功能标志
tipFlag: false, // tip消息标志
tipInputValue: '', // tip消息文本框内容
sendType: 0, //发送消息类型,0 文本 1 语音
messageArr: [], //[{text, time, sendOrReceive: 'send', displayTimeHeader, nodes: []},{type: 'geo',geo: {lat,lng,title}}]
inputValue: '',//文本框输入内容
from: ''
},
onUnload() {
// 更新当前会话对象账户
store.dispatch({
type: 'CurrentChatTo_Change',
payload: ''
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
let chatWrapperMaxHeight = wx.getSystemInfoSync().windowHeight - 52 - 35
// 初始化聊天对象
let self = this
let tempArr = []
let chatTo = options.chatTo
let chatType = options.type || 'p2p'
let from = options.from || ''
let loginAccountLogo = this.data.userInfo.avatar || this.data.defaultUserLogo
// 设置顶部标题
if (chatTo === this.data.userInfo.account) {
wx.setNavigationBarTitle({
title: '我的电脑',
})
} else if (chatType === 'advanced' || chatType === 'normal') {
if (this.data.currentGroup.teamId === chatTo && this.data.currentGroup.isCurrentNotIn) {
showToast('error', '您已离开该群组')
}
let card = this.data.currentGroup || this.data.groupList[chatTo] || {}
let memberNum = card.memberNum || 0
let title = card.name || chatTo
wx.setNavigationBarTitle({
title: (title.length > 8 ? title.slice(0, 8) + '…' : title) + '(' + memberNum + ')',
})
if (!this.data.groupMemberList[chatTo] || !this.data.groupMemberList[chatTo].allMembers) { // 当前群组的成员不全时获取成员列表 并 更新当前成员是否在群聊的标志
this.getMemberList(chatTo)
}
} else { // p2p
let card = this.data.friendCard[chatTo] || {}
let ucard = this.data.userCard[chatTo] ||{}
wx.setNavigationBarTitle({
title: card.nick || ucard.nick || chatTo,
})
}
this.setData({
chatTo,
chatType,
loginAccountLogo,
iconBase64Map: iconBase64Map,
chatWrapperMaxHeight,
})
// 重新计算所有时间
self.reCalcAllMessageTime()
// 滚动到底部
self.scrollToBottom()
app.globalData.emitter.on('callRejected', (data) => {
console.log('对方拒绝了111')
console.log(data)
})
},
/**
* 生命周期函数--监听页面展示
*/
onShow: function () {
let chatType = this.data.chatType
if (chatType === 'advanced' || chatType === 'normal') {
let card = this.data.currentGroup
let memberNum = card.memberNum || 0
let title = card.name
wx.setNavigationBarTitle({
title: (title.length > 8 ? title.slice(0, 8) + '…' : title) + '(' + memberNum + ')',
})
}
},
/**
* 获取群组成员列表
*/
getMemberList(teamId) {
app.globalData.nim.getTeamMembers({
teamId: teamId,
done: (error, obj) => {
if (error) {
console.log(error, '获取群成员失败')
return
}
store.dispatch({
type: 'Get_Group_Members_And_Set_Current',
payload: obj
})
}
})
},
/**
* 文本框输入事件
*/
inputChange(e) {
this.setData({
inputValue: e.detail.value
})
},
/**
* 键盘单击发送,发送文本
*/
inputSend(e) {
this.sendRequest(e.detail.value)
},
/**
* emoji组件回调
*/
emojiCLick(e) {
let val = e.detail
// 单击删除按钮,,删除emoji
if (val == '[删除]') {
let lastIndex = this.data.inputValue.lastIndexOf('[')
if (lastIndex != -1) {
this.setData({
inputValue: this.data.inputValue.slice(0, lastIndex)
})
}
return
}
if (val[0] == '[') { // emoji
this.setData({
inputValue: this.data.inputValue + val
})
} else {//大图
this.sendBigEmoji(val)
}
},
/**
* emoji点击发送
*/
emojiSend(e) {
let val = this.data.inputValue
this.sendRequest(val)
this.setData({
emojiFlag: false
})
},
/**
* 发送网络请求:发送文本消息(包括emoji)
*/
sendRequest(text) {
let self = this
app.globalData.nim.sendText({
scene: self.data.chatType === 'p2p' ? 'p2p' : 'team',
to: this.data.chatTo,
text,
done: (err, msg) => {
// 判断错误类型,并做相应处理
if (self.handleErrorAfterSend(err)) {
return
}
// 存储数据到store
self.saveChatMessageListToStore(msg)
self.setData({
inputValue: '',
focusFlag: false
})
// 滚动到底部
self.scrollToBottom()
}
})
},
/**
* 发送大的emoji:实际上是type=3的自定义消息
* {"type":3,"data":{"catalog":"ajmd","chartlet":"ajmd010"}}
*/
sendBigEmoji(val) {
wx.showLoading({
title: '发送中...',
})
let self = this
let catalog = ''
if (val[0] === 'a') {
catalog = 'ajmd'
} else if (val[0] === 'x') {
catalog = 'xxy'
} else if (val[0] === 'l') {
catalog = 'lt'
}
let content = {
type: 3,
data: {
catalog,
chartlet: val
}
}
app.globalData.nim.sendCustomMsg({
scene: self.data.chatType === 'p2p' ? 'p2p' : 'team',
to: self.data.chatTo,
content: JSON.stringify(content),
done: function (err, msg) {
wx.hideLoading()
// 判断错误类型,并做相应处理
if (self.handleErrorAfterSend(err)) {
return
}
// 存储数据到store
self.saveChatMessageListToStore(msg)
// 隐藏发送栏
self.setData({
focusFlag: false,
emojiFlag: false,
tipFlag: false,
moreFlag: false
})
// 滚动到底部
self.scrollToBottom()
}
})
},
/**
* 发送自定义消息-猜拳
*/
sendFingerGuess() {
let self = this
self.setData({
moreFlag: false
})
let content = {
type: 1,
data: {
value: Math.ceil(Math.random() * 3)
}
}
app.globalData.nim.sendCustomMsg({
scene: self.data.chatType === 'p2p' ? 'p2p' : 'team',
to: self.data.chatTo,
content: JSON.stringify(content),
done: function (err, msg) {
// 判断错误类型,并做相应处理
if (self.handleErrorAfterSend(err)) {
return
}
// 存储数据到store
self.saveChatMessageListToStore(msg)
// 滚动到底部
self.scrollToBottom()
}
})
},
/**
* 点击发送tip按钮
*/
tipInputConfirm() {
let self = this
if (self.data.tipInputValue.length !== 0) {
app.globalData.nim.sendTipMsg({
scene: self.data.chatType === 'p2p' ? 'p2p' : 'team',
to: self.data.chatTo,
tip: self.data.tipInputValue,
done: function (err, msg) {
// 判断错误类型,并做相应处理
if (self.handleErrorAfterSend(err)) {
return
}
// 存储数据到store
self.saveChatMessageListToStore(msg)
self.setData({
tipInputValue: '',
tipFlag: false
})
// 滚动到底部
self.scrollToBottom()
}
})
} else {
showToast('text', '请输入内容')
}
},
/**
* 发送语音消息
*/
sendAudioMsg(res) {
wx.showLoading({
title: '发送中...',
})
let tempFilePath = res.tempFilePath
let self = this
// console.log(tempFilePath)
app.globalData.nim.sendFile({
scene: self.data.chatType === 'p2p' ? 'p2p' : 'team',
to: self.data.chatTo,
type: 'audio',
wxFilePath: tempFilePath,
done: function (err, msg) {
wx.hideLoading()
// 判断错误类型,并做相应处理
if (self.handleErrorAfterSend(err)) {
return
}
// 存储数据到store
self.saveChatMessageListToStore(msg)
// 滚动到底部
self.scrollToBottom()
}
})
},
/**
* 发送位置消息
*/
sendPositionMsg(res) {
let self = this
let { address, latitude, longitude } = res
app.globalData.nim.sendGeo({
scene: self.data.chatType === 'p2p' ? 'p2p' : 'team',
to: self.data.chatTo,
geo: {
lng: longitude,
lat: latitude,
title: address
},
done: function (err, msg) {
// 判断错误类型,并做相应处理
if (self.handleErrorAfterSend(err)) {
return
}
// 存储数据到store
self.saveChatMessageListToStore(msg)
// 滚动到底部
self.scrollToBottom()
}
})
},
/**
* 发送视频文件到nos
*/
sendVideoToNos(res) {
wx.showLoading({
title: '发送中...',
})
// {duration,errMsg,height,size,tempFilePath,width}
let self = this
let tempFilePath = res.tempFilePath
// 上传文件到nos
app.globalData.nim.sendFile({
type: 'video',
scene: self.data.chatType === 'p2p' ? 'p2p' : 'team',
to: self.data.chatTo,
wxFilePath: tempFilePath,
done: function (err, msg) {
wx.hideLoading()
// file: {dur, ext,h,md5,name,size,url,w}
// 判断错误类型,并做相应处理
if (self.handleErrorAfterSend(err)) {
return
}
// 存储数据到store
self.saveChatMessageListToStore(msg)
// 滚动到底部
self.scrollToBottom()
}
})
},
/**
* 发送图片到nos
*/
sendImageToNOS(res) {
wx.showLoading({
title: '发送中...',
})
let self = this
let tempFilePaths = res.tempFilePaths
for (let i = 0; i < tempFilePaths.length; i++) {
// 上传文件到nos
app.globalData.nim.sendFile({
// app.globalData.nim.previewFile({
type: 'image',
scene: self.data.chatType === 'p2p' ? 'p2p' : 'team',
to: self.data.chatTo,
wxFilePath: tempFilePaths[i],
done: function (err, msg) {
wx.hideLoading()
// 判断错误类型,并做相应处理
if (self.handleErrorAfterSend(err)) {
return
}
// 存储数据到store
self.saveChatMessageListToStore(msg)
// 滚动到底部
self.scrollToBottom()
}
})
}
},
/**
* 统一发送消息后打回的错误信息
* 返回true表示有错误,false表示没错误
*/
handleErrorAfterSend(err) {
if (err) {
if (err.code == 7101) {
showToast('text', '你已被对方拉黑')
}
console.log(err)
return true
}
return false
},
/**
* 滚动页面到底部
*/
scrollToBottom() {
wx.pageScrollTo({
scrollTop: 999999,
duration: 100
})
},
/**
* 保存数据到store
*/
saveChatMessageListToStore(rawMsg, handledMsg) {
store.dispatch({
type: 'RawMessageList_Add_Msg',
payload: { msg: rawMsg }
})
},
/**
* 收起所有输入框
*/
chatingWrapperClick(e) {
this.foldInputArea()
},
/**
* 收起键盘
*/
foldInputArea() {
this.setData({
focusFlag: false,
emojiFlag: false,
tipFlag: false,
moreFlag: false
})
},
/**
* 阻止事件冒泡空函数
*/
stopEventPropagation() {
},
/**
* 全屏播放视频
*/
requestFullScreenVideo(e) {
let video = e.currentTarget.dataset.video
let videoContext = wx.createVideoContext('videoEle')
this.setData({
isVideoFullScreen: true,
videoSrc: video.url,
videoContext
})
videoContext.requestFullScreen()
videoContext.play()
},
/**
* 视频播放结束钩子
*/
videoEnded() {
this.setData({
isVideoFullScreen: false,
videoSrc: ''
})
},
/**
* 播放音频
*/
playAudio(e) {
showToast('text', '播放中', {
duration: 120 * 1000,
mask: true
})
let audio = e.currentTarget.dataset.audio
const audioContext = wx.createInnerAudioContext()
if (audio.ext === 'mp3') { // 小程序发送的
audioContext.src = audio.url
} else {
audioContext.src = audio.mp3Url
}
audioContext.play()
audioContext.onPlay(() => {
})
audioContext.onEnded(() => {
wx.hideToast()
})
audioContext.onError((res) => {
showToast('text', res.errCode)
})
},
/**
* 重新计算时间头
*/
reCalcAllMessageTime() {
let tempArr = [...this.data.messageArr]
if (tempArr.length == 0) return
// 计算时差
tempArr.map((msg, index) => {
if (index === 0) {
msg['displayTimeHeader'] = calcTimeHeader(msg.time)
} else {
let delta = (msg.time - tempArr[index - 1].time) / (120 * 1000)
if (delta > 1) { // 距离上一条,超过两分钟重新计算头部
msg['displayTimeHeader'] = calcTimeHeader(msg.time)
}
}
})
this.setData({
messageArr: tempArr
})
},
/**
* 切换发送文本类型
*/
switchSendType() {
this.setData({
sendType: this.data.sendType == 0 ? 1 : 0,
focusFlag: false,
emojiFlag: false
})
},
/**
* 获取焦点
*/
inputFocus(e) {
this.setData({
emojiFlag: false,
focusFlag: true
})
},
/**
* 失去焦点
*/
inputBlur() {
this.setData({
focusFlag: false
})
},
/**
* tip输入
*/
tipInputChange(e) {
this.setData({
tipInputValue: e.detail.value
})
},
/**
* 组件按钮回调
*/
tipClickHandler(e) {
let data = e.detail.data
if (data === 'confirm') {
if (this.data.tipInputValue.length === 0) {
showToast('text', '请输入内容')
} else {
this.tipInputConfirm()
this.setData({
tipFlag: false
})
}
} else if (data === 'cancel') {
this.setData({
tipFlag: false
})
}
},
/**
* 切换出emoji键盘
*/
toggleEmoji() {
this.setData({
sendType: 0,
// focusFlag: this.data.emojiFlag ? true : false,
emojiFlag: !this.data.emojiFlag,
moreFlag: false
})
},
/**
* 切出更多
*/
toggleMore() {
this.setData({
moreFlag: !this.data.moreFlag,
emojiFlag: false,
focusFlag: false
})
},
/**
* 调出tip发送面板
*/
showTipMessagePanel() {
this.setData({
tipFlag: true,
moreFlag: false
})
},
/**
* 微信按钮长按,有bug,有时候不触发
*/
voiceBtnLongTap(e) {
let self = this
self.setData({
isLongPress: true
})
wx.getSetting({
success: (res) => {
let recordAuth = res.authSetting['scope.record']
if (recordAuth == false) { //已申请过授权,但是用户拒绝
wx.openSetting({
success: function (res) {
let recordAuth = res.authSetting['scope.record']
if (recordAuth == true) {
showToast('success', '授权成功')
} else {
showToast('text', '请授权录音')
}
self.setData({
isLongPress: false
})
}
})
} else if (recordAuth == true) { // 用户已经同意授权
self.startRecord()
} else { // 第一次进来,未发起授权
wx.authorize({
scope: 'scope.record',
success: () => {//授权成功
showToast('success', '授权成功')
}
})
}
},
fail: function () {
showToast('error', '鉴权失败,请重试')
}
})
},
/**
* 手动模拟按钮长按,
*/
longPressStart() {
let self = this
self.setData({
recordClicked: true
})
setTimeout(() => {
if (self.data.recordClicked == true) {
self.executeRecord()
}
}, 350)
},
/**
* 语音按钮长按结束
*/
longPressEnd() {
this.setData({
recordClicked: false
})
// 第一次授权,
if (!this.data.recorderManager) {
this.setData({
isLongPress: false
})
return
}
if (this.data.isLongPress === true) {
this.setData({
isLongPress: false
})
wx.hideToast()
this.data.recorderManager.stop()
}
},
/**
* 执行录音逻辑
*/
executeRecord() {
let self = this
self.setData({
isLongPress: true
})
wx.getSetting({
success: (res) => {
let recordAuth = res.authSetting['scope.record']
if (recordAuth == false) { //已申请过授权,但是用户拒绝
wx.openSetting({
success: function (res) {
let recordAuth = res.authSetting['scope.record']
if (recordAuth == true) {
showToast('success', '授权成功')
} else {
showToast('text', '请授权录音')
}
self.setData({
isLongPress: false
})
}
})
} else if (recordAuth == true) { // 用户已经同意授权
self.startRecord()
} else { // 第一次进来,未发起授权
wx.authorize({
scope: 'scope.record',
success: () => {//授权成功
showToast('success', '授权成功')
}
})
}
},
fail: function () {
showToast('error', '鉴权失败,请重试')
}
})
},
/**
* 开始录音
*/
startRecord() {
let self = this
showToast('text', '开始录音', { duration: 120000 })
const recorderManager = self.data.recorderManager || wx.getRecorderManager()
const options = {
duration: 120 * 1000,
format: 'mp3'
}
recorderManager.start(options)
self.setData({
recorderManager
})
recorderManager.onStop((res) => {
if (res.duration < 2000) {
showToast('text', '录音时间太短')
} else {
self.sendAudioMsg(res)
}
})
},
/**
* 选择相册图片
*/
chooseImageToSend(e) {
let type = e.currentTarget.dataset.type
let self = this
self.setData({
moreFlag: false
})
wx.chooseImage({
sourceType: ['album'],
success: function (res) {
self.sendImageToNOS(res)
},
})
},
/**
* 选择拍摄视频或者照片
*/
chooseImageOrVideo() {
let self = this
self.setData({
moreFlag: false
})
wx.showActionSheet({
itemList: ['照相', '视频'],
success: function (res) {
if (res.tapIndex === 0) { // 相片
wx.chooseImage({
sourceType: ['camera'],
success: function (res) {
self.sendImageToNOS(res)
},
})
} else if (res.tapIndex === 1) { // 视频
wx.chooseVideo({
sourceType: ['camera', 'album'],
success: function (res) {
if (res.duration > 60) {
showToast('text', '视频时长超过60s,请重新选择')
return
}
console.log(res);
// {duration,errMsg,height,size,tempFilePath,width}
self.sendVideoToNos(res)
}
})
}
}
})
},
/**
* 选取位置
*/
choosePosition() {
let self = this
self.setData({
moreFlag: false
})
wx.getSetting({
success: (res) => {
let auth = res.authSetting['scope.userLocation']
if (auth == false) { //已申请过授权,但是用户拒绝
wx.openSetting({
success: function (res) {
if (res.authSetting['scope.userLocation'] == true) {
showToast('success', '授权成功')
} else {
showToast('text', '请授权地理位置')
}
}
})
} else if (auth == true) { // 用户已经同意授权
self.callSysMap()
} else { // 第一次进来,未发起授权
wx.authorize({
scope: 'scope.userLocation',
success: () => {//授权成功
self.callSysMap()
}
})
}
},
fail: (res) => {
showToast('error', '鉴权失败,请重试')
}
})
},
/**
* 视频通话
*/
videoCall() {
if (app.globalData.waitingUseVideoCall) {
showToast('text', '请勿频繁操作', {duration: 2000})
return
}
if (this.data.chatType === 'advanced' || this.data.chatType === 'normal') { // 群组
if (this.data.currentGroup.memberNum.length < 2) {
showToast('text', '无法发起,人数少于2人')
} else {
wx.navigateTo({
url: `../forwardMultiContact/forwardMultiContact?teamId=${this.data.currentGroup.teamId}`,
})
}
} else { // p2p
console.log(`正在发起对${this.data.chatTo}的视频通话`)
wx.navigateTo({
url: `../videoCall/videoCall?callee=${this.data.chatTo}`,
})
}
},
/**
* 调用系统地图界面
*/
callSysMap() {
let self = this
wx.chooseLocation({
success: function (res) {
let { address, latitude, longitude } = res
self.sendPositionMsg(res)
},
})
},
/**
* 查看全屏地图
*/
fullScreenMap(e) {
let geo = e.currentTarget.dataset.geo
wx.openLocation({
latitude: geo.lat,
longitude: geo.lng,
})
},
/**
* 切换到个人介绍页
*/
switchToMyTab() {
wx.switchTab({
url: '../../pages/setting/setting',
})
},
/**
* 切换到对方介绍页
*/
switchPersonCard(data) {
if (this.data.chatType === 'p2p') {
if (this.data.chatTo === this.data.userInfo.account || this.data.chatTo === 'ai-assistant') {
return
}
// 重定向进入account介绍页
clickLogoJumpToCard(this.data.friendCard, this.data.chatTo, false)
} else if (this.data.chatType === 'advanced') {
wx.navigateTo({
url: '../../partials/advancedGroupMemberCard/advancedGroupMemberCard?account=' + data.target.dataset.account + '&teamId=' + this.data.chatTo
})
}
},
/**
* 查看云端历史消息、查看群信息、查看讨论组信息
*/
lookMessage() {
let self = this
let actionArr = ['清空本地聊天记录', '查看云消息记录']
let actionFn = [self.sureToClearAllMessage, self.lookAllMessage]
if (this.data.currentGroup.isCurrentNotIn) {
actionArr.pop()
}
if (self.data.chatType === 'advanced') {
actionArr.unshift('群信息')
actionFn.unshift(self.lookAdvancedGroupInfo)
} else if (self.data.chatType === 'normal') {
actionArr.unshift('讨论组信息')
actionFn.unshift(self.lookNormalGroupInfo)
}
wx.showActionSheet({
itemList: actionArr,
success: (res) => {
(actionFn[res.tapIndex])()
}
})
},
/**
* 查看群信息
*/
lookAdvancedGroupInfo() {
store.dispatch({
type: 'Set_Current_Group_And_Members',
payload: this.data.chatTo
})
wx.navigateTo({
url: `../advancedGroupCard/advancedGroupCard?teamId=${this.data.chatTo}&from=${this.data.from}`,
})
},
/**
* 查看讨论组信息
*/
lookNormalGroupInfo() {
store.dispatch({
type: 'Set_Current_Group_And_Members',
payload: this.data.chatTo
})
wx.navigateTo({
url: `../normalGroupCard/normalGroupCard?teamId=${this.data.chatTo}&from=${this.data.from}`,
})
},
/**
* 弹框 确认 清除本地记录
*/
sureToClearAllMessage() {
let self = this
wx.showActionSheet({//二次确认
itemList: ['清空'],
itemColor: '#f00',
success: (res) => {
if (res.tapIndex == 0) {
self.clearAllMessage()
}
}
})
},
/**
* 查看云消息记录
*/
lookAllMessage() {
wx.navigateTo({
url: `../historyFromCloud/historyFromCloud?account=${this.data.chatTo}&chatType=${this.data.chatType}`,
})
},
/**
* 清除本地记录
*/
clearAllMessage() {
// 刷新本地视图
this.setData({
messageArr: []
})
store.dispatch({
type: 'Delete_All_MessageByAccount',
payload: (this.data.chatType === 'p2p' ? 'p2p-' : 'team-') + this.data.chatTo
})
},
/**
* 展示编辑菜单
*/
showEditorMenu(e) {
let message = e.currentTarget.dataset.message
if (message.type === 'tip') {
return
}
let paraObj = {
time: message.time,
chatTo: this.data.chatTo,
chatType: this.data.chatType
}
let self = this
if (message.sendOrReceive === 'send') { // 自己消息
wx.showActionSheet({
itemList: ['删除', '转发', '撤回'],
success: function (res) {
switch (res.tapIndex) {
case 0:
self.deleteMessageRecord(message)
break
case 1:
self.forwardMessage(paraObj)
break
case 2:
wx.showActionSheet({
itemList: ['确定'],
itemColor: '#ff0000',
success: function (res) {
if (res.tapIndex === 0) {
self.recallMessage(message)
}
}
})
break
default:
break
}
}
})
} else {// 对方消息
wx.showActionSheet({
itemList: ['删除', '转发'],
success: function (res) {
switch (res.tapIndex) {
case 0:
self.deleteMessageRecord(message)
break
case 1:
self.forwardMessage(paraObj)
break
default:
break
}
}
})
}
},
/**
* 转发消息
*/
forwardMessage(paramObj) {
let str = encodeURIComponent(JSON.stringify(paramObj))
wx.redirectTo({
url: '../forwardContact/forwardContact?data=' + str,
})
},
/**
* 撤回消息
*/
recallMessage(message) {
let self = this
let sessionId = (self.data.chatType === 'p2p' ? 'p2p-' : 'team-') + self.data.chatTo
let rawMessage = self.data.rawMessageList[sessionId][message.time]
app.globalData.nim.deleteMsg({
msg: rawMessage,
done: function (err, { msg }) {
if (err) { // 撤回失败
console.log(err)
showToast('text', '消息已超过2分钟,不能撤回')
return
} else {// 撤回成功
store.dispatch({
type: 'RawMessageList_Recall_Msg',
payload: msg
})
// 滚动到底部
self.scrollToBottom()
}
}
})
},
/**
* 删除消息
* {displayTimeHeader,nodes,sendOrReceive,text,time,type}
*/
deleteMessageRecord(msg) {
let sessionId = (this.data.chatType === 'p2p' ? 'p2p-' : 'team-') + this.data.chatTo
// 从全局记录中删除
store.dispatch({
type: 'Delete_Single_MessageByAccount',
payload: { sessionId: sessionId, time: msg.time }
})
},
/**
* 距离上一条消息是否超过两分钟
*/
judgeOverTwoMinute(time, messageArr) {
let displayTimeHeader = ''
let lastMessage = messageArr[messageArr.length - 1]
if (lastMessage) {//拥有上一条消息
let delta = time - lastMessage.time
if (delta > 2 * 60 * 1000) {//两分钟以上
displayTimeHeader = calcTimeHeader(time)
}
} else {//没有上一条消息
displayTimeHeader = calcTimeHeader(time)
}
return displayTimeHeader
},
/**
* 原始消息列表转化为适用于渲染的消息列表
* {unixtime1: {flow,from,fromNick,idServer,scene,sessionId,text,target,to,time...}, unixtime2: {}}
* =>
* [{text, time, sendOrReceive: 'send', displayTimeHeader, nodes: []},{type: 'geo',geo: {lat,lng,title}}]
*/
convertRawMessageListToRenderMessageArr(rawMsgList) {
let messageArr = []
for(let time in rawMsgList) {
let rawMsg = rawMsgList[time]
let msgType = ''
if (rawMsg.type === 'custom' && JSON.parse(rawMsg['content'])['type'] === 1) {
msgType = '猜拳'
} else if (rawMsg.type === 'custom' && JSON.parse(rawMsg['content'])['type'] === 3) {
msgType = '贴图表情'
} else {
msgType = rawMsg.type
}
let displayTimeHeader = this.judgeOverTwoMinute(rawMsg.time, messageArr)
let sendOrReceive = rawMsg.flow === 'in' ? 'receive' : 'send'
let specifiedObject = {}
switch(msgType) {
case 'text': {
specifiedObject = {
nodes: generateRichTextNode(rawMsg.text)
}
break
}
case 'image': {
specifiedObject = {
nodes: generateImageNode(rawMsg.file)
}
break
}
case 'geo': {
specifiedObject = {
geo: rawMsg.geo
}
break
}
case 'audio': {
specifiedObject = {
audio: rawMsg.file
}
break
}
case 'video': {
specifiedObject = {
video: rawMsg.file
}
break
}
case '猜拳': {
let value = JSON.parse(rawMsg['content']).data.value
specifiedObject = {
nodes: generateImageNode(generateFingerGuessImageFile(value))
}
break
}
case '贴图表情': {
let content = JSON.parse(rawMsg['content'])
specifiedObject = {
nodes: generateImageNode(generateBigEmojiImageFile(content))
}
break
}
case 'tip': {
specifiedObject = {
text: rawMsg.tip,
nodes: [{
type: 'text',
text: rawMsg.tip
}]
}
break
}
case '白板消息':
case '阅后即焚': {
specifiedObject = {
nodes: [{
type: 'text',
text: `[${msgType}],请到手机或电脑客户端查看`
}]
}
break
}
case 'file':
case 'robot': {
let text = msgType === 'file' ? '文件消息' : '机器人消息'
specifiedObject = {
nodes: [{
type: 'text',
text: `[${text}],请到手机或电脑客户端查看`
}]
}
break
}
case 'custom':
specifiedObject = {
nodes: [{
type: 'text',
text: '自定义消息'
}]
}
break;
case 'notification':
specifiedObject = {
// netbill的text为空
text: rawMsg.groupNotification || (rawMsg.text.length == 0 ? '通知' : rawMsg.text),
nodes: [{
type: 'text',
text: rawMsg.groupNotification || (rawMsg.text.length == 0 ? '通知' : rawMsg.text)
}]
}
break;
default: {
break
}
}
messageArr.push(Object.assign({}, {
from: rawMsg.from,
type: msgType,
text: rawMsg.text || '',
time,
sendOrReceive,
displayTimeHeader
}, specifiedObject))
}
return messageArr
},
}
let mapStateToData = (state) => {
let sessionId = state.currentChatTo
let messageArr = pageConfig.convertRawMessageListToRenderMessageArr(state.rawMessageList[sessionId])
return {
friendCard: state.friendCard,
personList: state.personList,
userInfo: state.userInfo,
currentGroup: state.currentGroup,
groupList: state.groupList,
groupMemberList: state.groupMemberList,
rawMessageList: state.rawMessageList,
messageArr: messageArr,
userCard:state.userCard
}
}
const mapDispatchToPage = (dispatch) => {}
let connectedPageConfig = connect(mapStateToData, mapDispatchToPage)(pageConfig)
Page(connectedPageConfig)
{
"navigationBarTitleText": "",
"navigationStyle":"default",
"usingComponents": {
"component-emoji": "/components/emoji/emoji",
"input-modal": "/components/inputmodal/inputmodal"
}
}
<view class='chating-wrapper' catchtap='chatingWrapperClick'>
<!-- 视频全屏播放区 -->
<view wx:if="{{isVideoFullScreen}}" style='position:fixed;top:0;bottom:0;right:0;left:0;z-index:999;background-color:#000;'>
<video id="videoEle" src="{{videoSrc}}" bindended="videoEnded" show-fullscreen-btn="{false}" controls style='width:100%;height:100%;'></video>
</view>
<!-- tip消息 -->
<view catchtap='stopEventPropagation'>
<input-modal wx:if="{{tipFlag}}" title="输入提醒" catch:inputModalClick="tipClickHandler">
<input placeholder='请输入文本' type='text' confirm-type='send' class='modal-input' bindinput='tipInputChange' bindconfirm='tipInputConfirm'></input>
</input-modal>
</view>
<!-- 历史消息 -->
<!-- <view wx:if="{{chatType === 'p2p'}}" class='chating-history' catchtap='lookMessage'>
<text class='chating-history-left'>历史消息</text>
<text class='chating-history-right'>></text>
</view> -->
<!-- 群信息 -->
<view wx:if="{{!currentGroup.isCurrentNotIn && chatType === 'advanced'}}" class='chating-history' catchtap='lookMessage'>
<text class='chating-history-left'>群信息</text>
<text class='chating-history-right'>></text>
</view>
<!-- 讨论组信息 -->
<view wx:if="{{!currentGroup.isCurrentNotIn && chatType === 'normal'}}" class='chating-history' catchtap='lookMessage'>
<text class='chating-history-left'>讨论组信息</text>
<text class='chating-history-right'>></text>
</view>
<!-- 消息记录 -->
<view class='record-wrapper' id="recordWrapper">
<view wx:for="{{messageArr}}" wx:for-item="message" wx:key="{{message.time}}">
<view class='record-item-time-wrapper' wx:if="{{message.displayTimeHeader != ''}}">
<text class='record-item-time'>{{message.displayTimeHeader}}</text>
</view>
<view wx:if="{{message.sendOrReceive == 'send'}}" class='{{message.sendOrReceive == "send" ? "record-chatting-item self" : ""}}' style='justify-content: {{message.type === "tip" || message.type === "notification" ? "center" : "flex-end"}}' data-message="{{message}}" bindlongpress='showEditorMenu'>
<view wx:if="{{message.type === 'geo'}}" data-geo='{{message.geo}}' class='small-map-wrapper' catchtap='fullScreenMap'>
<image src="../../images/map.png" class='small-geo-img'></image>
<text class='text'>{{message.geo.title}}</text>
</view>
<view wx:if="{{message.type === 'video'}}" data-video="{{message.video}}" catchtap='requestFullScreenVideo' class='small-video-wrapper'>
<view class='video-triangle'></view>
<view style='color: #000;'>{{message.video.dur / 1000 << 1 >> 1}}''</view>
</view>
<view wx:if="{{message.type === 'audio'}}" class='audio-wrapper' data-audio="{{message.audio}}" catchtap='playAudio' style='background-color:#3387FF;color: #000;'>
<image src='{{iconBase64Map.iconVoiceWhite}}' class='image'></image>
<text class='text'>{{message.audio.dur / 1000 << 1 >> 1}}''</text>
</view>
<rich-text wx:if="{{message.type === 'image'}}" class='record-chatting-item-text nobg' nodes="{{message.nodes}}" ></rich-text>
<rich-text wx:if="{{message.type === '猜拳' || message.type === '贴图表情'}}" class='record-chatting-item-text nobg' nodes="{{message.nodes}}" ></rich-text>
<rich-text wx:if="{{message.type === 'text' || message.type === 'file' || message.type === '白板消息' || message.type === '阅后即焚' || message.type === 'robot'}}" class='record-chatting-item-text' nodes="{{message.nodes}}"></rich-text>
<rich-text wx:if="{{message.type === 'tip' || message.type === 'notification'}}" class='tip-rich-text' nodes="{{message.nodes}}"></rich-text>
<text wx:if="{{message.type !== 'tip' && message.type !== 'notification' && message.type !== 'image' && message.type !== 'video' && message.type !== 'geo' && message.type !== '猜拳' && message.type !== '贴图表情' }}" class='right-triangle'></text>
<image wx:if="{{message.type !== 'tip' && message.type !== 'notification'}}" src='{{loginAccountLogo}}' catchtap='switchToMyTab' class='record-chatting-item-img'></image>
</view>
<view wx:if="{{message.sendOrReceive == 'receive'}}" class='{{message.sendOrReceive == "receive" ? "record-chatting-item other" : ""}}' style='justify-content: {{message.type === "tip" || message.type === "notification" ? "center" : "flex-start"}}' data-message="{{message}}" bindlongpress='showEditorMenu'>
<image wx:if="{{message.type !== 'tip' && message.type !== 'notification'}}" catchtap='switchPersonCard' data-account='{{message.from}}' src='{{personList[message.from] && personList[message.from].avatar || defaultUserLogo}}' class='record-chatting-item-img'></image>
<text wx:if="{{message.type !== 'tip' && message.type !== 'notification' && message.type !== 'image' && message.type !== 'video' && message.type !== 'geo' && message.type !== '猜拳' && message.type !== '贴图表情' }}" class='left-triangle'></text>
<view wx:if="{{message.type === 'geo'}}" data-geo='{{message.geo}}' class='small-map-wrapper' catchtap='fullScreenMap'>
<image src="../../images/map.png" class='small-geo-img'></image>
<text class='text'>{{message.geo.title}}</text>
</view>
<view wx:if="{{message.type === 'video'}}" data-video="{{message.video}}" catchtap='requestFullScreenVideo' class='small-video-wrapper'>
<view class='video-triangle'></view>
<view style='color: #000;'>{{message.video.dur / 1000 << 1 >> 1}}''</view>
</view>
<view wx:if="{{message.type === 'audio'}}" data-audio="{{message.audio}}" catchtap='playAudio' class='audio-wrapper'>
<image src='{{iconBase64Map.iconVoiceGrey}}' class='image'></image>
<text class='text' style='color:#000;'>{{message.audio.dur / 1000 << 1 >> 1}}''</text>
</view>
<rich-text wx:if="{{message.type === 'image'}}" class='record-chatting-item-text nobg' nodes="{{message.nodes}}"></rich-text>
<rich-text wx:if="{{message.type === '猜拳' || message.type === '贴图表情'}}" class='record-chatting-item-text nobg' nodes="{{message.nodes}}"></rich-text>
<rich-text wx:if="{{message.type === 'text' || message.type === 'file' || message.type === '白板消息' || message.type === '阅后即焚' || message.type === 'robot'|| message.type === 'custom'}}" class='record-chatting-item-text' style='color:#000;background-color:#fff;' nodes="{{message.nodes}}"></rich-text>
<rich-text wx:if="{{message.type === 'tip' || message.type === 'notification'}}" class='tip-rich-text' nodes="{{message.nodes}}"></rich-text>
</view>
</view>
</view>
<!--底部输入框 -->
<view wx:if="{{chatType === 'p2p' || !currentGroup.isCurrentNotIn}}" class='chatinput-wrapper' style='margin-bottom: {{focusFlag ? 20 : 0}}rpx;' catchtap='stopEventPropagation'>
<view class='chatinput-content'>
<image src='{{sendType == 0 ? "../../images/voice.png" : "../../images/keyboard.png"}}' class='chatinput-img' catchtap='switchSendType'></image>
<input style='margin-bottom: 20rpx;' wx:if="{{sendType == 0}}" value='{{inputValue}}' focus='{{focusFlag}}' bindinput='inputChange' bindfocus='inputFocus' bindblur='inputBlur' bindconfirm='inputSend' class='chatinput-input' placeholder="输入文字" confirm-type='send'></input>
<!-- <button wx:if="{{sendType == 1}}" class="{{ isLongPress ? 'chatinput-voice-mask chatinput-voice-mask-hover' : 'chatinput-voice-mask' }}" hover-class="none" catchtouchstart='longPressStart' catchlongpress='voiceBtnLongTap' catchtouchend='longPressEnd'>按住说话</button> -->
<button wx:if="{{sendType == 1}}" class="{{ isLongPress ? 'chatinput-voice-mask chatinput-voice-mask-hover' : 'chatinput-voice-mask' }}" hover-class="none" catchtouchstart='longPressStart' catchtouchend='longPressEnd'>
{{isLongPress ? '松开结束' : '按住说话'}}
</button>
<image src='../../images/more.png' catchtap='toggleMore' class='chatinput-img fr'></image>
<image src='../../images/emoji.png' catchtap='toggleEmoji' class='chatinput-img fr emoji'></image>
</view>
<view wx:if="{{emojiFlag}}" class='chatinput-subcontent'>
<component-emoji bind:EmojiClick="emojiCLick" bind:EmojiSend="emojiSend"></component-emoji>
</view>
<view wx:if="{{moreFlag}}" class='more-subcontent'>
<view style='display:flex;justify-content: space-between;'>
<view class='more-subcontent-item' catchtap='chooseImageToSend'>
<image src="../../images/photo.png" class='image'></image>
<text class='text'>相册</text>
</view>
<view class='more-subcontent-item' catchtap='chooseImageOrVideo'>
<image src="../../images/shoot.png" class='image'></image>
<text class='text'>拍摄</text>
</view>
<!-- <view class='more-subcontent-item'><view class='image'></view><text class='text'>文件</text></view> -->
<view class='more-subcontent-item' catchtap='showTipMessagePanel'>
<image src="../../images/tip.png" class='image'></image>
<text class='text'>Tip</text>
</view>
<view class='more-subcontent-item' catchtap='sendFingerGuess'>
<image src="../../images/morra.png" class='image'></image>
<text class='text'>猜拳</text>
</view>
</view>
<view style='display:flex;justify-content: space-between;'>
<view class='more-subcontent-item' catchtap='choosePosition'>
<image src="../../images/location.png" class='image'></image>
<text class='text'>位置</text>
</view>
<view class='more-subcontent-item' catchtap='videoCall'>
<image src="../../images/location.png" class='image'></image>
<text class='text'>视频通话</text>
</view>
<view class='more-subcontent-item'><view class='image' style='background-color: transparent;'></view><text class='text'></text></view>
<view class='more-subcontent-item'><view class='image' style='background-color: transparent;'></view><text class='text'></text></view>
</view>
</view>
</view>
</view>
page{
height: 100%;
}
.chating-wrapper {
width: 100%;
min-height: 100%;
position: relative;
/* margin: 70rpx 0 100rpx; */
box-sizing: border-box;
/* overflow: hidden; */
background: #F3F4F6;
}
/* 收起键盘热区 */
.fold-keyboad-wrapper {
position: fixed;
top: 70rpx;
width: 100%;
height: 500rpx;
}
/*历史消息 */
.chating-history {
position: fixed;
padding: 0 20rpx;
width:100%;
height:80rpx;
line-height:80rpx;
box-sizing: border-box;
border: 1px solid #E7E7E7;
background-color: #E7E7E7;
z-index: 1;
color: #888888;
font-size: 30rpx;
}
.chating-history-left {
float: left;
color: #666;
}
.chating-history-right {
float: right;
}
/*聊天输入框 */
.chatinput-wrapper {
width: 100%;
background-color: #fff;
border: 2rpx solid #ccc;
position: fixed;
bottom: 0;
left: 0;
}
.chatinput-content {
width: 100%;
height: 100rpx;
}
.chatinput-img{
width: 60rpx;
height: 60rpx;
border-radius: 100%;
margin: 20rpx 20rpx;
display: inline-block;
}
.chatinput-img.emoji{
margin-right: 0;
}
.chatinput-img:active {
opacity: .6;
}
.chatinput-input {
width: 466rpx;
min-height: 72rpx;
border-radius: 12rpx;
border: 1px solid #ccc;
margin-top: 15rpx;
display: inline-block;
vertical-align:top;
box-sizing:border-box;
padding-left: 20rpx;
font-size: 30rpx;
}
.chatinput-voice-mask {
width: 466rpx;
height: 76rpx;
line-height: 76rpx;
display: inline-block;
border-radius: 12rpx;
border: 1px solid #ccc;
margin-top: 12rpx;
vertical-align:top;
box-sizing:border-box;
/* padding-left: 20rpx; */
font-size: 30rpx;
text-align: center;
color: #333336;
background-color: #fff;
letter-spacing: 4rpx;
}
.chatinput-voice-mask-hover {
background-color: #cecece;
color: #333336;
}
/*subcontent wrapper */
.chatinput-subcontent {
width: 100%;
height: 470rpx;
background-color: #999;
}
.fr {
float: right;
}
/* more-subcontent */
.more-subcontent {
padding: 40rpx 30rpx;
border:2rpx solid #ccc;
}
.more-subcontent .more-subcontent-item {
display:flex;
flex-direction:column;
margin-bottom: 30rpx;
}
.more-subcontent .more-subcontent-item .image {
width:112rpx;
height:112rpx;
border-radius:50%;
margin-bottom: 12rpx;
}
.more-subcontent .more-subcontent-item .text {
font-size: 24rpx;
text-align:center;
color: #6C7074;
}
/*聊天记录 */
.record-wrapper {
width: 100%;
padding-bottom: 100rpx;
padding-top:80rpx;
}
.record-chatting-item {
width: 100%;
padding: 20rpx 20rpx;
box-sizing: border-box;
}
.record-item-time-wrapper {
display:flex;
flex-direction:row;
justify-content:center;
}
.record-item-time {
padding:4rpx 10rpx;
background: #D6D6D6;
border-radius: 8rpx;
font-size: 24rpx;
color: #fff;
}
.record-chatting-item-img {
width: 80rpx;
height: 80rpx;
border-radius: 100%;
display: inline-block;
}
.record-chatting-item-text {
max-width: 70%;
border-radius: 8rpx;
background-color: #3387FF;
padding: 16rpx;
box-sizing:border-box;
word-wrap:break-word;
overflow: hidden;
font-size: 32rpx;
line-height: 48rpx;
}
.record-chatting-item-text.nobg {
background: transparent;
margin-left: 20rpx;
margin-right: 20rpx;
}
/* tip消息富文本 */
.tip-rich-text {
background-color:#ccc;
text-align:center;
align-self:center;
min-height:40rpx;
word-break:break-word;
font-size:26rpx;
padding:0 20rpx;
color:#000;
border-radius: 10rpx;
}
.self {
display: flex;
flex-direction: row;
justify-content: flex-end;
color: #fff;
}
.other {
display: flex;
flex-direction: row;
justify-content: flex-start;
color: #222;
}
.left-triangle{
height:0px;
width:0px;
border-width:20rpx;
border-style:solid;
border-color:transparent #fff transparent transparent;
margin-top: 20rpx;
}
.right-triangle{
height:0px;
width:0px;
border-width:20rpx;
border-style:solid;
border-color:transparent transparent transparent #3387FF;
margin-top: 20rpx;
}
.video-triangle {
height:0px;
width:0px;
border-width:30rpx;
border-style:solid;
border-color:transparent transparent transparent #777;
margin-top: 20rpx;
margin-left: 30rpx;
}
.small-map-wrapper {
overflow: hidden;
width: 452rpx;
height:300rpx;
position:relative;
border-radius:8rpx;
}
.self .small-map-wrapper {
margin-right: 20rpx;
}
.other .small-map-wrapper {
margin-left: 20rpx;
}
.small-map-wrapper .small-geo-img {
width: 100%;
height: 100%;
background-color: pink;
}
.small-map-wrapper .text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
position: absolute;
bottom: 0;
left: 0;
right: 0;
box-sizing: border-box;
width: 100%;
padding: 10rpx;
min-height: 70rpx;
/* line-height: 70rpx; */
opacity: 0.75;
font-size: 28rpx;
/* white-space: nowrap; */
text-align: center;
text-overflow: ellipsis;
background-color:#444;
color:#fff;
}
.small-video-wrapper {
margin-left: 20rpx;
margin-right: 20rpx;
width:200rpx;
height:200rpx;
padding:10rpx;
background-color:#fff;
border-radius:20rpx;
display:flex;
justify-content:center;
flex-direction:column;
align-items:center;
}
.small-video-wrapper .video {
max-width:200px;
max-height:300px;
}
.audio-wrapper {
background-color:#fff;
border-radius:28rpx;
display:flex;
justify-content:space-between;
padding:0 20rpx;
min-width: 30%;
box-sizing:border-box;
margin-left:-2px;
}
.audio-wrapper .image {
width:70rpx;
height:70rpx;
align-self:center;
}
.audio-wrapper .text {
align-self:center;
color:#fff;
}
...@@ -78,6 +78,7 @@ function shallowEqual(objA, objB) { ...@@ -78,6 +78,7 @@ function shallowEqual(objA, objB) {
} }
return true return true
} }
module.exports = { module.exports = {
isPlainObject, isPlainObject,
ActionTypes, ActionTypes,
......
...@@ -635,6 +635,15 @@ let indexReducer = (state = INITIAL_STATE, action) => { ...@@ -635,6 +635,15 @@ let indexReducer = (state = INITIAL_STATE, action) => {
tempState.netcallGroupCallInfo = Object.assign({}, groupCall) tempState.netcallGroupCallInfo = Object.assign({}, groupCall)
return Object.assign({}, state, tempState) return Object.assign({}, state, tempState)
} }
case 'Update_Im_Usercard':{
let tempState = Object.assign({}, state)
let card=action.payload.user
console.log(card)
tempState.userCard = Object.assign({}, tempState.userCard)
tempState.userCard[card.account] = Object.assign({}, tempState.userCard[card.account], card)
console.log(tempState.userCard)
return Object.assign({}, state, tempState)
}
default: default:
return state return state
} }
......
...@@ -5,6 +5,7 @@ const INITIAL_STATE = { ...@@ -5,6 +5,7 @@ const INITIAL_STATE = {
currentChatTo: '', // 正在聊天 sessionId currentChatTo: '', // 正在聊天 sessionId
friendCard: {}, //好友列表,含名片信息,额外添加在线信息 friendCard: {}, //好友列表,含名片信息,额外添加在线信息
onlineList: {}, // 在线好友列表 onlineList: {}, // 在线好友列表
userCard:{},//非好友用户资料
// messageListToRender: {}, // messageListToRender: {},
currentGroup: {}, currentGroup: {},
currentGroupMembers: [], currentGroupMembers: [],
......
...@@ -470,6 +470,24 @@ function clickLogoJumpToCard(friendsCard, account, isPush) { ...@@ -470,6 +470,24 @@ function clickLogoJumpToCard(friendsCard, account, isPush) {
}) })
} }
} }
/**
* 获取指定数组内的用户资料
*/
function getUsers(account,store){
app.globalData.nim.getUser({
account,
done: function(error,user){
if(user){
store.dispatch({
type: 'Update_Im_Usercard',
payload: {
user
}
})
}
}
});
}
/** /**
* 获取格式化后的好友列表 * 获取格式化后的好友列表
* friendCard: 好友列表(含名片信息) * friendCard: 好友列表(含名片信息)
...@@ -913,6 +931,63 @@ function dealMsg(msg, store, app) { ...@@ -913,6 +931,63 @@ function dealMsg(msg, store, app) {
}) })
} }
} }
function stringifyDate (datetime, simple = false) {
// let weekMap = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
let weekMap = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
datetime = new Date(datetime)
let year = datetime.getFullYear()
let simpleYear = datetime.getYear() - 100
let month = datetime.getMonth() + 1
month = month > 9 ? month : '0' + month
let day = datetime.getDate()
day = day > 9 ? day : '0' + day
let hour = datetime.getHours()
hour = hour > 9 ? hour : '0' + hour
let min = datetime.getMinutes()
min = min > 9 ? min : '0' + min
let week = datetime.getDay()
week = weekMap[week]
let thatDay = (new Date(year, month - 1, day, 0, 0, 0)).getTime()
if (simple) {
return {
withYear: `${day}/${month}/${simpleYear}`,
withMonth: `${month}-${day}`,
withDay: `${week}`,
withLastDay: `昨天`,
withHour: `${hour}:${min}`,
thatDay
}
} else {
return {
withYear: `${year}-${month}-${day} ${hour}:${min}`,
withMonth: `${month}-${day} ${hour}:${min}`,
withDay: `${week} ${hour}:${min}`,
withLastDay: `昨天 ${hour}:${min}`,
withHour: `${hour}:${min}`,
thatDay
}
}
}
function formatDate(datetime, simple = true) {
let tempDate = (new Date()).getTime()
let result = stringifyDate(datetime, simple)
let thatDay = result.thatDay
let deltaTime = (tempDate - thatDay) / 1000
if (deltaTime < 3600 * 24) {
return result.withHour
} else if (deltaTime < 3600 * 24 * 2) {
return result.withLastDay
} else if (deltaTime < 3600 * 24 * 7) {
return result.withDay
} else if (deltaTime < 3600 * 24 * 30) {
return result.withMonth
} else {
return result.withYear
}
}
module.exports = { module.exports = {
calculateMeetingPosition, calculateMeetingPosition,
formatDate, formatDate,
...@@ -929,10 +1004,12 @@ module.exports = { ...@@ -929,10 +1004,12 @@ module.exports = {
correctData, correctData,
deepClone, deepClone,
clickLogoJumpToCard, clickLogoJumpToCard,
getUsers,
generateRichTextNode, generateRichTextNode,
generateFingerGuessImageFile, generateFingerGuessImageFile,
generateBigEmojiImageFile, generateBigEmojiImageFile,
generateImageNode, generateImageNode,
getFormatFriendList, getFormatFriendList,
dealMsg dealMsg,
formatDate
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment