Browse Source

弹出菜单优化

master
xsx 2 years ago
parent
commit
258c8bee81
  1. 10
      im-uniapp/common/recorder-app.js
  2. 8
      im-uniapp/common/recorder-h5.js
  3. 11
      im-uniapp/components/chat-item/chat-item.vue
  4. 79
      im-uniapp/components/chat-message-item/chat-message-item.vue
  5. 3
      im-uniapp/components/chat-record/chat-record.vue
  6. 52
      im-uniapp/components/pop-menu/pop-menu.vue
  7. 48
      im-uniapp/pages/chat/chat.vue
  8. 3
      im-uniapp/pages/login/login.vue
  9. 2
      im-uniapp/pages/register/register.vue

10
im-uniapp/common/recorder-app.js

@ -39,12 +39,18 @@ let upload = () => {
filePath: wavFile.tempFilePath, filePath: wavFile.tempFilePath,
name: 'file', name: 'file',
success: (res) => { success: (res) => {
const duration = (new Date().getTime() - startTime.getTime()) / 1000 let r = JSON.parse(res.data);
if (r.code != 200) {
reject(r.message);
} else {
const duration = (new Date().getTime() - startTime.getTime()) /
1000
const data = { const data = {
duration: Math.round(duration), duration: Math.round(duration),
url: JSON.parse(res.data).data url: r.data
} }
resolve(data); resolve(data);
}
}, },
fail: (e) => { fail: (e) => {
reject(e); reject(e);

8
im-uniapp/common/recorder-h5.js

@ -33,11 +33,17 @@ let upload = () => {
file: file, file: file,
name: 'file', name: 'file',
success: (res) => { success: (res) => {
let r = JSON.parse(res.data);
if(r.code != 200){
console.log(res)
reject(r.message);
}else {
const data = { const data = {
duration: parseInt(rc.duration), duration: parseInt(rc.duration),
url: JSON.parse(res.data).data url: r.data
} }
resolve(data); resolve(data);
}
}, },
fail: (e) => { fail: (e) => {
reject(e); reject(e);

11
im-uniapp/components/chat-item/chat-item.vue

@ -8,9 +8,12 @@
</view> </view>
<view class="chat-right"> <view class="chat-right">
<view class="chat-name"> <view class="chat-name">
<view class="chat-tag" v-if="chat.type=='GROUP'">
<uni-tag circle text="群" size="mini" type="primary"></uni-tag>
</view>
<view class="chat-name-text"> <view class="chat-name-text">
{{chat.showName}} {{chat.showName}}
<uni-tag v-if="chat.type=='GROUP'" disabled text="群" size="mini" type="primary"></uni-tag>
</view> </view>
<view class="chat-time">{{$date.toTimeText(chat.lastSendTime,true)}}</view> <view class="chat-time">{{$date.toTimeText(chat.lastSendTime,true)}}</view>
</view> </view>
@ -122,6 +125,12 @@
line-height: 44rpx; line-height: 44rpx;
height: 44rpx; height: 44rpx;
.chat-tag {
display: flex;
align-items: center;
margin-right: 3rpx;
}
.chat-name-text { .chat-name-text {
flex: 1; flex: 1;
font-size: 30rpx; font-size: 30rpx;

79
im-uniapp/components/chat-message-item/chat-message-item.vue

@ -2,62 +2,68 @@
<view class="chat-msg-item"> <view class="chat-msg-item">
<view class="chat-msg-tip" <view class="chat-msg-tip"
v-if="msgInfo.type==$enums.MESSAGE_TYPE.RECALL||msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT"> v-if="msgInfo.type==$enums.MESSAGE_TYPE.RECALL||msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">
{{msgInfo.content}}</view> {{msgInfo.content}}
</view>
<view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME"> <view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME">
{{$date.toTimeText(msgInfo.sendTime)}} {{$date.toTimeText(msgInfo.sendTime)}}
</view> </view>
<view class="chat-msg-normal" v-if="isNormal" :class="{'chat-msg-mine':msgInfo.selfSend}">
<view class="chat-msg-normal" v-if="isNormal"
:class="{'chat-msg-mine':msgInfo.selfSend}">
<head-image class="avatar" @longpress.prevent="$emit('longPressHead')" :id="msgInfo.sendId" :url="headImage" <head-image class="avatar" @longpress.prevent="$emit('longPressHead')" :id="msgInfo.sendId" :url="headImage"
:name="showName" :size="80"></head-image> :name="showName" :size="80"></head-image>
<view class="chat-msg-content"> <view class="chat-msg-content">
<view v-if="msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top"> <view v-if="msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top">
<text>{{showName}}</text> <text>{{showName}}</text>
</view> </view>
<view class="chat-msg-bottom">
<view class="chat-msg-bottom" @touchmove="onHideMenu()"> <view v-if="msgInfo.type==$enums.MESSAGE_TYPE.TEXT">
<view v-if="msgInfo.type==$enums.MESSAGE_TYPE.TEXT" @longpress.native="onShowMenu($event)"> <pop-menu :items="menuItems" @select="onSelectMenu">
<rich-text class="chat-msg-text" <rich-text class="chat-msg-text" :nodes="$emo.transform(msgInfo.content)"></rich-text>
:nodes="$emo.transform(msgInfo.content)" </pop-menu>
></rich-text>
</view> </view>
<view class="chat-msg-image" v-if="msgInfo.type==$enums.MESSAGE_TYPE.IMAGE"> <view class="chat-msg-image" v-if="msgInfo.type==$enums.MESSAGE_TYPE.IMAGE">
<view class="img-load-box" @longpress="onShowMenu($event)"> <pop-menu :items="menuItems" @select="onSelectMenu">
<view class="img-load-box">
<image class="send-image" mode="heightFix" :src="JSON.parse(msgInfo.content).thumbUrl" <image class="send-image" mode="heightFix" :src="JSON.parse(msgInfo.content).thumbUrl"
lazy-load="true" @click.stop="onShowFullImage()"> lazy-load="true" @click.stop="onShowFullImage()">
</image> </image>
<loading v-if="loading"></loading> <loading v-if="loading"></loading>
</view> </view>
</pop-menu>
<text title="发送失败" v-if="loadFail" @click="onSendFail" <text title="发送失败" v-if="loadFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></text> class="send-fail iconfont icon-warning-circle-fill"></text>
</view> </view>
<view class="chat-msg-file" v-if="msgInfo.type==$enums.MESSAGE_TYPE.FILE"> <view class="chat-msg-file" v-if="msgInfo.type==$enums.MESSAGE_TYPE.FILE">
<view class="chat-file-box" @longpress="onShowMenu($event)"> <pop-menu :items="menuItems" @select="onSelectMenu">
<view class="chat-file-box">
<view class="chat-file-info"> <view class="chat-file-info">
<uni-link class="chat-file-name" :text="data.name" showUnderLine="true" color="#007BFF" <uni-link class="chat-file-name" :text="data.name" showUnderLine="true"
:href="data.url"></uni-link> color="#007BFF" :href="data.url"></uni-link>
<view class="chat-file-size">{{fileSize}}</view> <view class="chat-file-size">{{fileSize}}</view>
</view> </view>
<view class="chat-file-icon iconfont icon-file"></view> <view class="chat-file-icon iconfont icon-file"></view>
<loading v-if="loading"></loading> <loading v-if="loading"></loading>
</view> </view>
</pop-menu>
<text title="发送失败" v-if="loadFail" @click="onSendFail" <text title="发送失败" v-if="loadFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></text> class="send-fail iconfont icon-warning-circle-fill"></text>
</view> </view>
<view class="chat-msg-audio chat-msg-text" v-if="msgInfo.type==$enums.MESSAGE_TYPE.AUDIO" <pop-menu v-if="msgInfo.type==$enums.MESSAGE_TYPE.AUDIO" :items="menuItems" @select="onSelectMenu">
@click="onPlayAudio()" @longpress="onShowMenu($event)"> <view class="chat-msg-audio chat-msg-text" @click="onPlayAudio()">
<text class="iconfont icon-voice-play"></text> <text class="iconfont icon-voice-play"></text>
<text class="chat-audio-text">{{JSON.parse(msgInfo.content).duration+'"'}}</text> <text class="chat-audio-text">{{JSON.parse(msgInfo.content).duration+'"'}}</text>
<text v-if="audioPlayState=='PAUSE'" class="iconfont icon-play"></text> <text v-if="audioPlayState=='PAUSE'" class="iconfont icon-play"></text>
<text v-if="audioPlayState=='PLAYING'" class="iconfont icon-pause"></text> <text v-if="audioPlayState=='PLAYING'" class="iconfont icon-pause"></text>
</view> </view>
<view class="chat-realtime chat-msg-text" v-if="isAction" </pop-menu>
@click="$emit('call')" @longpress="onShowMenu($event)"> <pop-menu v-if="isAction" :items="menuItems" @select="onSelectMenu">
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VOICE" class="iconfont icon-chat-voice"></text> <view class="chat-realtime chat-msg-text" @click="$emit('call')">
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VIDEO" class="iconfont icon-chat-video"></text> <text v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VOICE"
class="iconfont icon-chat-voice"></text>
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VIDEO"
class="iconfont icon-chat-video"></text>
<text>{{msgInfo.content}}</text> <text>{{msgInfo.content}}</text>
</view> </view>
</pop-menu>
<view class="chat-msg-status" v-if="!isAction"> <view class="chat-msg-status" v-if="!isAction">
<text class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId <text class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status==$enums.MESSAGE_STATUS.READED">已读</text> && msgInfo.status==$enums.MESSAGE_STATUS.READED">已读</text>
@ -72,8 +78,7 @@
</view> </view>
</view> </view>
<chat-group-readed ref="chatGroupReaded" :groupMembers="groupMembers" :msgInfo="msgInfo"></chat-group-readed> <chat-group-readed ref="chatGroupReaded" :groupMembers="groupMembers" :msgInfo="msgInfo"></chat-group-readed>
<pop-menu v-if="menu.show" :menu-style="menu.style" :items="menuItems" @close="onHideMenu()"
@select="onSelectMenu"></pop-menu>
</view> </view>
</template> </template>
@ -106,36 +111,8 @@
style: "" style: ""
} }
} }
}, },
methods: { methods: {
onShowMenu(e) {
uni.getSystemInfo({
success: (res) => {
let touches = e.touches[0];
let style = "";
/* 因 非H5端不兼容 style 属性绑定 Object ,所以拼接字符 */
if (touches.clientY > (res.windowHeight / 2)) {
style = `bottom:${res.windowHeight-touches.clientY}px;`;
} else {
style = `top:${touches.clientY}px;`;
}
if (touches.clientX > (res.windowWidth / 2)) {
style += `right:${res.windowWidth-touches.clientX}px;`;
} else {
style += `left:${touches.clientX}px;`;
}
this.menu.style = style;
//
this.$nextTick(() => {
this.menu.show = true;
});
}
})
},
onHideMenu(){
this.menu.show = false;
},
onSendFail() { onSendFail() {
uni.showToast({ uni.showToast({
title: "该文件已发送失败,目前不支持自动重新发送,建议手动重新发送", title: "该文件已发送失败,目前不支持自动重新发送,建议手动重新发送",
@ -148,6 +125,7 @@
this.innerAudioContext = uni.createInnerAudioContext(); this.innerAudioContext = uni.createInnerAudioContext();
let url = JSON.parse(this.msgInfo.content).url; let url = JSON.parse(this.msgInfo.content).url;
this.innerAudioContext.src = url; this.innerAudioContext.src = url;
console.log(url);
this.innerAudioContext.onEnded((e) => { this.innerAudioContext.onEnded((e) => {
console.log('停止') console.log('停止')
this.audioPlayState = "STOP" this.audioPlayState = "STOP"
@ -484,6 +462,7 @@
padding-right: 0; padding-right: 0;
padding-left: 8px; padding-left: 8px;
} }
.icon-voice-play { .icon-voice-play {
transform: rotateY(180deg); transform: rotateY(180deg);
} }

3
im-uniapp/components/chat-record/chat-record.vue

@ -99,7 +99,6 @@
}) })
}).finally(() => { }).finally(() => {
this.$rc.close(); this.$rc.close();
console.log("录音完成")
}) })
}, },
startTimer() { startTimer() {
@ -132,7 +131,7 @@
return `bottom:${bottom}px;` return `bottom:${bottom}px;`
}, },
recordBarStyle() { recordBarStyle() {
const bgColor = this.recording ? "royalblue" : "#f8f8f8"; const bgColor = this.recording ? "royalblue" : "white";
const textColor = this.recording ? "white" : "black"; const textColor = this.recording ? "white" : "black";
return `background-color:${bgColor}; return `background-color:${bgColor};
color:${textColor};` color:${textColor};`

52
im-uniapp/components/pop-menu/pop-menu.vue

@ -1,7 +1,11 @@
<template> <template>
<view> <view>
<view class="pop-menu" @tap="onClose()" @contextmenu.prevent=""></view> <view @longpress.stop="onLongPress($event)" @touchmove="onTouchMove" @touchend="onTouchEnd">
<view class="menu" :style="menuStyle"> <slot></slot>
</view>
<view v-if="isShowMenu" class="pop-menu" @tap="onClose()" @contextmenu.prevent=""></view>
<view v-if="isShowMenu" class="menu" :style="menuStyle">
<view class="menu-item" v-for="(item) in items" :key="item.key" @click.prevent="onSelectMenu(item)"> <view class="menu-item" v-for="(item) in items" :key="item.key" @click.prevent="onSelectMenu(item)">
<uni-icons :type="item.icon" :style="itemStyle(item)" size="22"></uni-icons> <uni-icons :type="item.icon" :style="itemStyle(item)" size="22"></uni-icons>
<text :style="itemStyle(item)"> {{item.name}}</text> <text :style="itemStyle(item)"> {{item.name}}</text>
@ -14,22 +18,56 @@
export default { export default {
name: "pop-menu", name: "pop-menu",
data() { data() {
return {} return {
isShowMenu : false,
style : ""
}
}, },
props: { props: {
menuStyle: {
type: String
},
items: { items: {
type: Array type: Array
} }
}, },
methods: { methods: {
onLongPress(e){
if(this.isTouchMove){
return;
}
uni.getSystemInfo({
success: (res) => {
let touches = e.touches[0];
let style = "";
/* 因 非H5端不兼容 style 属性绑定 Object ,所以拼接字符 */
if (touches.clientY > (res.windowHeight / 2)) {
style = `bottom:${res.windowHeight-touches.clientY}px;`;
} else {
style = `top:${touches.clientY}px;`;
}
if (touches.clientX > (res.windowWidth / 2)) {
style += `right:${res.windowWidth-touches.clientX}px;`;
} else {
style += `left:${touches.clientX}px;`;
}
this.menuStyle = style;
//
this.$nextTick(() => {
this.isShowMenu = true;
});
}
})
},
onTouchMove(){
this.isTouchMove = true;
},
onTouchEnd(){
this.isTouchMove = false;
},
onSelectMenu(item) { onSelectMenu(item) {
this.$emit("select", item); this.$emit("select", item);
this.isShowMenu = false;
}, },
onClose() { onClose() {
this.$emit("close"); this.isShowMenu = false;
}, },
itemStyle(item){ itemStyle(item){
if(item.color){ if(item.color){

48
im-uniapp/pages/chat/chat.vue

@ -16,14 +16,13 @@
</view> </view>
<scroll-view class="scroll-bar" v-else scroll-with-animation="true" scroll-y="true"> <scroll-view class="scroll-bar" v-else scroll-with-animation="true" scroll-y="true">
<view v-for="(chatPos,i) in chatsPos" :key="i"> <view v-for="(chatPos,i) in chatsPos" :key="i">
<chat-item v-if="isShowChat(chatStore.chats[chatPos.idx])" :chat="chatStore.chats[chatPos.idx]" <pop-menu v-if="isShowChat(chatStore.chats[chatPos.idx])" :items="menu.items"
:active="menu.chatIdx==chatPos.idx" :index="chatPos.idx" @select="onSelectMenu($event,chatPos.idx)">
@longpress.native="onShowMenu($event,chatPos.idx)"></chat-item> <chat-item :chat="chatStore.chats[chatPos.idx]"
:active="menu.chatIdx==chatPos.idx" :index="chatPos.idx"></chat-item>
</pop-menu>
</view> </view>
</scroll-view> </scroll-view>
<pop-menu v-show="menu.show" :menu-style="menu.style" :items="menu.items" @close="onCloseMenu()"
@select="onSelectMenu"></pop-menu>
</view> </view>
</template> </template>
@ -36,6 +35,7 @@
show: false, show: false,
style: "", style: "",
chatIdx: -1, chatIdx: -1,
isTouchMove: false,
items: [{ items: [{
key: 'DELETE', key: 'DELETE',
name: '删除该聊天', name: '删除该聊天',
@ -52,49 +52,19 @@
} }
}, },
methods: { methods: {
onSelectMenu(item) { onSelectMenu(item,chatIdx) {
switch (item.key) { switch (item.key) {
case 'DELETE': case 'DELETE':
this.removeChat(this.menu.chatIdx); this.removeChat(chatIdx);
break; break;
case 'TOP': case 'TOP':
this.moveToTop(this.menu.chatIdx); this.moveToTop(chatIdx);
break; break;
default: default:
break; break;
} }
this.menu.show = false; this.menu.show = false;
}, },
onShowMenu(e, chatIdx) {
this.menu.chatIdx = chatIdx;
uni.getSystemInfo({
success: (res) => {
let touches = e.touches[0];
let style = "";
/* 因 非H5端不兼容 style 属性绑定 Object ,所以拼接字符 */
if (touches.clientY > (res.windowHeight / 2)) {
style = `bottom:${res.windowHeight-touches.clientY}px;`;
} else {
style = `top:${touches.clientY}px;`;
}
if (touches.clientX > (res.windowWidth / 2)) {
style += `right:${res.windowWidth-touches.clientX}px;`;
} else {
style += `left:${touches.clientX}px;`;
}
this.menu.style = style;
this.menu.chatIdx = chatIdx;
//
this.$nextTick(() => {
this.menu.show = true;
});
}
})
},
onCloseMenu() {
this.menu.chatIdx = -1;
this.menu.show = false;
},
removeChat(chatIdx) { removeChat(chatIdx) {
this.$store.commit("removeChat", chatIdx); this.$store.commit("removeChat", chatIdx);
}, },

3
im-uniapp/pages/login/login.vue

@ -10,8 +10,7 @@
</uni-forms-item> </uni-forms-item>
<button class="btn-submit" @click="submit" type="primary">登录</button> <button class="btn-submit" @click="submit" type="primary">登录</button>
</uni-forms> </uni-forms>
<navigator class="nav-register" url="/pages/register/register" render-link="true" <navigator class="nav-register" url="/pages/register/register">
hover-class="other-navigator-hover">
没有账号,前往注册 没有账号,前往注册
</navigator> </navigator>

2
im-uniapp/pages/register/register.vue

@ -16,7 +16,7 @@
</uni-forms-item> </uni-forms-item>
<button class="btn-submit" @click="submit" type="warn">注册并登陆</button> <button class="btn-submit" @click="submit" type="warn">注册并登陆</button>
</uni-forms> </uni-forms>
<navigator class="nav-login" url="/pages/login/login" render-link="true" hover-class="other-navigator-hover"> <navigator class="nav-login" url="/pages/login/login" >
返回登陆页面 返回登陆页面
</navigator> </navigator>
</view> </view>

Loading…
Cancel
Save