主文件 chat.nvue
<template>
<view>
<!-- 导航栏 -->
<free-nav-bar title="呵呵呵呵" :noreadnum="1" showBack>
<free-icon-button slot="right"><text class="iconfont font-md"></text></free-icon-button>
</free-nav-bar>
<!-- 聊天内容区域 -->
<scroll-view scroll-y="true" class="bg-light position-fixed left-0 right-0" style="bottom: 105rpx;"
:style="chatBodyBottom" :show-scrollbar="false">
<block v-for="(item,index) in list" :key="index">
<!-- 聊天信息列表组件 -->
<free-chat-item :item="item" :index="index" :pretime="index>0 ? list[index-1].create_time : 0"
@long="long" ref="chatItem" @preview="previewImage"></free-chat-item>
</block>
<!-- 右边 -->
<!-- <view class="flex align-start justify-end position-relative">
<div class="bg-chat-item p-2 rounded mr-3" style="max-width:500rpx;">
<text class="font-md">你好你好你好你好你好你好你好你好你好你好你好</text>
</div>
<view class="iconfont font-md position-absolute chat-right-icon"><text class="text-chat-item iconfont font-md"></text></view>
<free-avatar size="75" src="/static/images/demo/demo6.jpg"></free-avatar>
</view> -->
</scroll-view>
<!-- 扩展菜单 -->
<free-popup ref="action" bottom transformOrigin="center bottom" @hide="keyBoardHeight = 0" :mask="false">
<view style="height: 580rpx;" class="border-top border-light-secondary">
<swiper :indicator-dots="emoticonOrActionList.length>1" style="height:510rpx;">
<swiper-item class="row" v-for="(item,index) in emoticonOrActionList" :key="index" >
<view class="col-3 flex flex-column align-center justify-center" style="height: 255rpx;" v-for="(item2,index2) in item" :key="index2" @click="actionEvent(item2)">
<image :src="item2.icon" mode="widthFix"
style="width: 100rpx;height: 100rpx;"></image>
<text class="font-sm text-muted mt-2">{{item2.name}}</text>
</view>
</swiper-item>
</swiper>
</view>
</free-popup>
<!-- 弹出层 -->
<free-popup ref="extend" maskColor bottom :bodyWidth="240" :bodyHeight="geMenusHeight" :tabbarHeight="105">
<view class="flex flex-column" style="width:240rpx;" :style="getMenusStyle">
<view v-for="(item,index) in menusList" :key="index" class="flex-1 flex align-center"
hover-class="bg-light" @click="clickEvent(item.event)">
<text class="font-md pl-3">{{item.name}}</text>
</view>
</view>
</free-popup>
<!-- #ifdef APP-PLUS-NVUE -->
<div class="position-fixed top-0 right-0 left-0 bottom-0" v-if="mode==='action' || mode==='emoticon'" @click="clickPage" :style="'bottom:'+maskBottom+'px;'"></div>
<!-- #endif -->
<!-- 底部输入框 -->
<view class="position-fixed left-0 right-0 border-top flex align-center"
style="background-color: #F7F7F6;height: 105rpx;" :style="'bottom:'+keyBoardHeight+'px;'">
<free-icon-button @click="changeVoiceOrText">
<block v-if="mode === 'audio'">
<text class="iconfont font-lg"></text>
</block>
<block v-else>
<text class="iconfont font-lg"></text>
</block>
</free-icon-button>
<view class="flex-1">
<view v-if="mode==='audio'" class="rounded flex align-center justify-center" style="height: 80rpx;" :class="isRecording?'bg-hover-light':'bg-white'" @touchstart="voiceTouchStart" @touchend="voiceTouchEnd" @touchmove="voiceTouchMove" @touchcancel="voiceTouchCancel">
<text class="font">{{isRecording ? '松开 结束' : '按住 说话'}}</text>
</view>
<textarea v-else class="bg-white rounded p-1 font-md" style="height: 75rpx;" :adjust-position="false"
v-model="text" @click="onInputClick" />
</view>
<!-- 表情 -->
<free-icon-button><text class="iconfont font-lg" @click="openActionOrEmoticon('emoticon')"></text></free-icon-button>
<template v-if="text.length === 0">
<!-- 扩展菜单 -->
<free-icon-button @click="openActionOrEmoticon('action')"><text class="iconfont font-lg"></text></free-icon-button>
</template>
<template v-else>
<!-- 发送按钮 -->
<view class="main-bg-color rounded flex align-center justify-center mr-2 px-2 pt-4" style="height: 70rpx;" @click="send('text')">发送
</view>
</template>
</view>
<!-- 录音提示 -->
<view v-if="isRecording" class="position-fixed top-0 left-0 right-0 flex align-center justify-center" style="bottom: 105rpx;">
<view class="rounded flex flex-column align-center justify-center" style="width: 360rpx;height: 360rpx;background-color: rgba(0,0,0,0.5);">
<image src="/static/images/audio/audio/recording.gif" style="width: 150rpx;height: 150rpx;"></image>
<text class="font text-white mt-3">{{unRecord?'松开手指,取消发送':'手指上滑,取消发送'}}</text>
</view>
</view>
</view>
</template>
<script>
// #ifdef APP-NVUE
const domModule = weex.requireModule('dom');
// #endif
import freeNavBar from '@/components/free-ui/free-nav-bar.vue';
import freeIconButton from '@/components/free-ui/free-icon-button.vue';
import freeChatItem from '@/components/free-ui/free-chat-item.vue';
import freePopup from '@/components/free-ui/free-popup.vue';
import { mapState,mapMutations } from 'vuex';
export default {
components: {
freeNavBar,
freeIconButton,
freeChatItem,
freePopup
},
watch:{
mode(newValue,oldValue){
if(newValue !== 'action' && newValue !== 'emoticon'){
this.$refs.action.hide();
}
if(newValue !== 'text'){
uni.hideKeyboard()
}
}
},
// 生命周期
mounted() {
// 获取任务栏高度
// #ifdef APP-PLUS-NVUE
this.statusBarHeight = plus.navigator.getStatusbarHeight()
// #endif
this.navBarHeight = this.statusBarHeight + uni.upx2px(90)
// 监听键盘高度变化
uni.onKeyboardHeightChange((res) => {
if(this.mode !== 'action' && this.mode !== 'emoticon'){
this.keyBoardHeight = res.height;
}
if (this.keyBoardHeight>0) {
this.pageToBottom()
}
})
// 注册发送音频事件
this.regSendVoiceEvent((url)=>{
if(!this.unRecord){
// 发送
this.send('audio',url,{
time:this.RecordTime
})
}
});
},
computed: {
...mapState({
RECORD:state=>state.audio.RECORD,
RecordTime:state=>state.audio.RecordTime
}),
// 所有信息的图片地址
imageList(){
var arr = [];
this.list.forEach((item)=>{
if(item.type === 'emoticon' || item.type === 'image'){
arr.push(item.data)
}
})
return arr;
},
// 获取蒙版的位置
maskBottom(){
return this.keyBoardHeight + uni.upx2px(105)
},
// 动态获取菜单高度
geMenusHeight() {
let H = 100;
return this.menus.length * H;
},
// 获取菜单的样式
getMenusStyle() {
return `height:${this.geMenusHeight}rpx;`;
},
// 是否是本人
isDoSelf() {
// 获取本人id(假设拿到了)
let id = 1;
let user_id = this.propIndex > -1 ? this.list[this.propIndex].user_id : 0;
return user_id === id;
},
// 获取操作菜单
menusList() {
return this.menus.filter(v => {
if (v.name === '撤回' && this.isDoSelf) {
return false;
} else {
return true;
}
})
},
// 聊天区域bottom
chatBodyBottom() {
return `bottom:${uni.upx2px(105) + this.keyBoardHeight}px;top:${this.navBarHeight}px`;
},
// 获取操作或者表情列表
emoticonOrActionList(){
if(this.mode === 'emoticon' || this.mode === 'action'){
return this[this.mode+'List'];
}else{
return [];
}
}
},
data() {
return {
// 模式 text输入文字 emoticon表情 action操作 audio音频
mode:'audio',
// 扩展菜单列表
actionList: [
[{
name: "相册",
icon: "/static/images/extends/pic.png",
event: "uploadImage"
},
{
name: "拍摄",
icon: "/static/images/extends/video.png",
event: "uploadVideo"
},
{
name: "收藏",
icon: "/static/images/extends/shoucan.png",
event: ""
},
{
name: "名片",
icon: "/static/images/extends/man.png",
event: ""
},
{
name: "语音通话",
icon: "/static/images/extends/phone.png",
event: ""
},
{
name: "位置",
icon: "/static/images/extends/path.png",
event: ""
}
]
],
// 表情包
emoticonList:[
[{
name: "沮丧",
icon: "/static/images/emoticon/5497/0.gif",
event: ""
},
{
name: "沮丧",
icon: "/static/images/emoticon/5497/0.gif",
event: ""
}]
],
// 输入文字
text: "",
// 音频录制中
isRecording:false,
// 取消录音
unRecord:false,
// 当前气泡索引
keyBoardHeight: 0,
propIndex: 1,
navBarHeight: 0,
list: [{
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 1,
type: "text", // image,audio,video,file,share
data: "你好你好你好你好你好你好你好你好你好你好你好",
create_time: 1628233521,
isremove: false
}, {
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 2,
type: "text", // image,audio,video,file,share
data: "11你好你好你好你好你好你好你好你好你好你好你好",
create_time: 1628234896,
isremove: false
},
{
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 1,
type: "text", // image,audio,video,file,share
data: "你好你好你好你好你好你好你好你好你好你好你好",
create_time: 1628233521,
isremove: false
},{
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 2,
type: "text", // image,audio,video,file,share
data: "11你好你好你好你好你好你好你好你好你好你好你好",
create_time: 1628234896,
isremove: false
},
{
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 1,
type: "audio", // image,audio,video,file,share
data: "/static/images/audio/audio/1.mp3",
options:{
time:10
},
create_time: 1628234896,
isremove: false
},
{
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 2,
type: "audio", // image,audio,video,file,share
options:{
time:20
},
data: "/static/images/audio/audio/2.mp3",
create_time: 1628234896,
isremove: false
},
{
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 1,
type: "audio", // image,audio,video,file,share
data: "/static/images/audio/audio/3.mp3",
options:{
time:60
},
create_time: 1628234896,
isremove: false
},
{
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 1,
type: "video", // image,audio,video,file,share
data: "/static/video/demo.mp4",
options:{
poster:"/static/video/demo.jpg"
},
create_time: 1628234896,
isremove: false
}
],
RecordingStartY:0,
menus: [{
name: '复制',
event: ""
},
{
name: '发送给朋友',
event: ""
},
{
name: '收藏',
event: ""
},
{
name: '删除',
event: ""
},
{
name: '多选',
event: ""
},
{
name: '撤回',
event: "removeChatItem"
}],
}
},
created(){
// 初始化
this.__init();
},
methods: {
...mapMutations(['regSendVoiceEvent']),
__init(){
var total = 24;
var page = Math.ceil(total/8);
var arr = [];
for (var i = 0;i<page;i++) {
var start = i*8;
arr[i] = [];
for(var j=0;j<=8;j++){
arr[i].push({
name:'表情'+(start+j),
icon:'/static/images/emoticon/5497/'+(start+j)+'.gif',
event:'sendEmoticon'
})
}
}
this.emoticonList = arr;
},
// 点击区域
clickPage(){
// 隐藏操作菜单
this.$refs.action.hide();
this.mode = 'text';
},
// 输入框聚焦
onInputClick(event){
this.mode = 'text';
},
// 事件分发
actionEvent(e){
switch(e.event){
case 'uploadImage':
uni.chooseImage({
count: 9, //默认9
success:(res)=>{
res.tempFilePaths.forEach((item)=>{
// 发送到服务器
// 渲染到页面
this.send('image',item);
})
}
});
break;
case 'sendEmoticon': // 发送表情包
this.send('emoticon',e.icon);
break;
case 'uploadVideo': // 发送短视频
uni.chooseVideo({
maxDuration:10,
success: (res) => {
// 渲染页面
this.send('video',res.tempFilePath);
// 发送到服务端(获取视频封面,返回url)
// 修改本地发送状态
}
})
break;
}
},
// 打开扩展菜单或者表情包
openActionOrEmoticon(model = 'action') {
this.mode = model;
this.$refs.action.show();
uni.hideKeyboard();
this.keyBoardHeight = uni.upx2px(580);
},
// 发送
send(type,data='',options={}) {
var time = (new Date()).getTime();
var obj = {
avatar: "/static/images/demo/demo6.jpg",
nickname: "昵称",
user_id: 1,
type: type, // image,audio,video,emoticon
data: data,
create_time: time,
options:options,
isremove: false
};
switch (type) {
case 'text':
obj.data = this.text;
this.text = "";
break;
case 'image':
obj.data = data;
break;
case 'audio':
break;
case 'video':
break;
case 'file':
break;
case 'share':
break;
default:
obj.data=data;
break;
}
this.list.push(obj);
// 置于底部
var pageToBottomTimer = setTimeout(()=>{
this.pageToBottom();
clearTimeout(pageToBottomTimer);
},200);
},
// 长按消息气泡
long(e) {
this.propIndex = e.index;
this.$refs.extend.show(e.x, e.y);
},
// 回到底部
pageToBottom() {
let chatItem = this.$refs.chatItem;
let lastIndex = chatItem.length > 0 ? chatItem.length - 1 : 0;
let last = chatItem[lastIndex];
if (chatItem[lastIndex]) {
domModule.scrollToElement(last, {});
}
},
// 操作菜单方法分发
clickEvent(event) {
switch (event) {
case 'removeChatItem': // 撤回消息
// 拿到当前被操作的信息
this.list[this.propIndex].isremove = true;
break;
default:
break;
}
// 关闭菜单
this.$refs.extend.hide();
},
// 预览图片
previewImage(url){
// 预览图片
uni.previewImage({
current:url,
urls:this.imageList,
indicator:"default",
longPressActions: {
itemList: ['发送给朋友', '保存图片', '收藏'],
success: function(data) {
console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
},
fail: function(err) {
console.log(err.errMsg);
}
}
});
},
// 切换音频录制喝文本输入
changeVoiceOrText(){
this.mode = this.mode !== 'audio' ? 'audio' : 'text';
},
// 录音相关
// 录音开始
voiceTouchStart(e){
this.isRecording = true;
this.RecordingStartY = e.changedTouches[0].screenY;
this.unRecord = false;
// 开始录音
this.RECORD.start({
format:'mp3'
})
},
// 录音结束
voiceTouchEnd(){
this.isRecording = false;
// 停止录音
this.RECORD.stop();
},
// 录音打断
voiceTouchCancel(){
this.isRecording = false;
this.unRecord = true;
// 停止录音
this.RECORD.stop();
},
voiceTouchMove(e){
var Y = Math.abs(e.changedTouches[0].screenY-this.RecordingStartY);
this.unRecord = (Y>=80);
}
}
}
</script>
<style>
</style>
插件 free-nav-bar.vue free-icon-button.vue free-chat-item.vue free-popup.vue
free-nav-bar.vue
<template>
<view class="flex align-center justify-center"
hover-class="bg-hover-light" @click="$emit('click')"
style="height: 90rpx;width: 90rpx;">
<slot></slot>
</view>
</template>
<script>
export default {
props: {
icon: {
type: String,
default: ''
},
},
mounted() {
// #ifdef APP-PLUS-NVUE
// 加载公共图标库
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
'fontFamily': "iconfont",
'src': "url('/static/font_1365296_2ijcbdrmsg.ttf')"
});
// #endif
}
}
</script>
<style>
</style>
free-icon-button.vue
<template>
<view class="flex align-center justify-center"
hover-class="bg-hover-light" @click="$emit('click')"
style="height: 90rpx;width: 90rpx;">
<slot></slot>
</view>
</template>
<script>
export default {
props: {
icon: {
type: String,
default: ''
},
},
mounted() {
// #ifdef APP-PLUS-NVUE
// 加载公共图标库
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
'fontFamily': "iconfont",
'src': "url('/static/font_1365296_2ijcbdrmsg.ttf')"
});
// #endif
}
}
</script>
<style>
</style>
free-chat-item.vue
<template>
<view @longpress="long">
<!-- 时间显示 -->
<view v-if="showTime" class="flex align-center justify-center pb-4 pt-2">
<text class="font-sm text-light-muted">{{showTime}}</text>
</view>
<!-- 撤回消息 -->
<view class="flex align-center justify-center pb-4 pt-1 chat-animate" v-if="item.isremove">
<text class="font-sm text-light-muted">你撤回了一条消息</text>
</view>
<!-- 气泡 -->
<view v-else class="flex align-start position-relative mb-3" :class="!isself ? 'justify-start' : 'justify-end'">
<!-- 头像 -->
<template v-if="!isself">
<free-avatar size="75" :src="item.avatar"></free-avatar>
<view class="iconfont font-md position-absolute chat-left-icon bg-light"><text
class="iconfont font-md text-white"></text></view>
<!-- 聊天气泡 -->
<div class="p-2 rounded" :class="labelClass" style="max-width:500rpx;">
<!-- 文字 -->
<text v-if="item.type === 'text'" class="font-md">{{item.data}}</text>
<!-- 表情包||图片 -->
<free-image @click="preview(item.data)" v-if="item.type==='emoticon' || item.type==='image'" :src="item.data"></free-image>
<!-- 音频 -->
<view v-if="item.type === 'audio'" class="flex align-center" @click="openAudio(item.data)">
<image :src="audioPlaying ?'/static/images/audio/audio/play.gif' : '/static/images/audio/audio/audio3.png'" style="width: 50rpx;height: 50rpx;" class="font"></image>
<text class="font">{{item.options.time + '"'}}</text>
</view>
</div>
</template>
<template v-if="isself">
<div class="p-2 rounded" :class="labelClass" style="max-width:500rpx;" :style="labelStyle">
<!-- 文字 -->
<text v-if="item.type === 'text'" class="font-md">{{item.data}}</text>
<!-- 表情包||图片 -->
<free-image @click="preview(item.data)" v-if="item.type==='emoticon' || item.type==='image'" :src="item.data"></free-image>
<!-- 音频 -->
<view v-if="item.type === 'audio'" class="flex align-center" @click="openAudio(item.data)">
<image :src="audioPlaying ?'/static/images/audio/audio/play.gif' : '/static/images/audio/audio/audio3.png'" style="width: 50rpx;height: 50rpx;" class="mx-1"></image>
<text class="font">{{item.options.time + '"'}}</text>
</view>
<!-- 视频 -->
<view v-if="item.type === 'video'" class="position-relative rounded">
<free-image :src="item.options.poster" @click="openVideo"
imageClass="rounded" :maxWidth="350" :maxHeight="300" @load="loadPoster"></free-image>
<text class="iconfont text-white position-absolute" style="font-size: 80rpx;width: 80rpx;height: 80rpx;" :style="posterTextStyle"></text>
</view>
</div>
<view class="iconfont font-md position-absolute" :class="(item.type==='text')?'chat-right-icon' : '' ">
<text class="text-chat-item iconfont"></text></view>
<free-avatar size="75" :src="item.avatar"></free-avatar>
</template>
</view>
</view>
</template>
<script>
import freeAvatar from "@/components/free-ui/free-avatar.vue";
import $T from '@/common/free-lib/time.js';
import freeImage from './free-image.vue';
import { mapState,mapActions } from 'vuex';
export default {
components: {
freeAvatar,
freeImage
},
props: {
item: Object,
index: Number,
// 上一条消息的时间戳
pretime: [Number, String]
},
data(){
return {
innerAudioContext:null,
audioPlaying:false,
// 默认封面宽高
poster:{
w:100,
h:100
}
}
},
mounted() {
// 注册全局事件
if(this.item.type === 'audio'){
this.audioOn(this.onPlayAudio)
}
// 监听是否撤回消息
// #ifdef APP-PLUS-NVUE
this.$watch('item.isremove', (newVal, oldVal) => {
if (newVal) {
const animation = weex.requireModule('animation');
this.$nextTick(() => {
animation.transition(this.$refs.isremove, {
styles: {
opacity: 1
},
duration: 100, //ms
timingFunction: 'ease',
}, () => {
console.log('动画执行结束');
})
})
}
})
// #endif
},
computed: {
...mapState({
ceshi:state=>state.audio.ceshi
}),
labelStyle(){
if(this.item.type === 'audio'){
let time = this.item.options.time || 0;
let width = parseInt(time) / (60/500);
width = width < 150 ? 150 : width;
return `width:${width}rpx;`;
}
},
// 视频封面图标
posterTextStyle(){
let w = (this.poster.w-uni.upx2px(80))/2;
let h = (this.poster.h-uni.upx2px(80))/2;
return `left:${w}px;top:${h}px;`;
},
// 是否是本人
isself() {
// 获取本人id(假设拿到了)
let id = 1;
return this.item.user_id === id;
},
// 显示时间
showTime() {
return $T.getChatTime(this.item.create_time, this.pretime);
},
// 是否需要气泡样式
hasLabelClass() {
return this.item.type === 'text' || this.item.type === 'audio';
},
// 气泡打样式
labelClass() {
let lable = this.hasLabelClass ? 'bg-chat-item mr-3' : 'mr-3';
return this.isself ? lable : 'bg-white ml-3';
}
},
// 组件销毁
destroyed() {
if(this.item.type === 'audio'){
this.audioOff(this.onPlayAudio)
}
// 销毁
if(this.innerAudioContext != null){
this.innerAudioContext.destroy();
this.innerAudioContext = null;
}
},
methods: {
...mapActions(['audioOn','audioEmit','audioOff']),
// 加载封面
loadPoster(e){
this.poster.w = e.w;
this.poster.h = e.h;
},
// 打开视频
openVideo(){
uni.navigateTo({
url:'/pages/chat/video/video?url='+this.item.data,
})
},
// 监听音频播放全局事件
onPlayAudio(index){
if(this.innerAudioContext){
if(this.index !== index){
this.innerAudioContext.pause();
}
}
},
// 播放音频
openAudio(url){
// 通知其他音频停止
this.audioEmit(this.index);
if(this.innerAudioContext==null){
this.innerAudioContext = uni.createInnerAudioContext();
this.innerAudioContext.autoplay = true;
this.innerAudioContext.src = url;
this.innerAudioContext.play();
// 监听播放
this.innerAudioContext.onPlay(()=>{
this.audioPlaying = true
})
// 监听暂停
this.innerAudioContext.onPause(()=>{
this.audioPlaying = false
})
// 监听停止
this.innerAudioContext.onStop(()=>{
this.audioPlaying = false
})
// 监听错误
this.innerAudioContext.onError(()=>{
this.audioPlaying = false
})
}else{
this.innerAudioContext.pause();
this.innerAudioContext.play();
}
},
// 预览图片
preview(url) {
this.$emit('preview',url);
},
// 长按事件
long(e) {
let x = 0,
y = 0;
// #ifdef APP-PLUS-NVUE
if (Array.isArray(e.changedTouches) && e.changedTouches.length > 0) {
x = e.changedTouches[0].screenX;
y = e.changedTouches[0].screenY;
}
// #endif
// #ifdef MP
x = e.detail.x;
y = e.detail.y;
// #endif
this.$emit('long', {
x,
y,
index: this.index
});
}
}
}
</script>
<style>
.chat-left-icon {
left: 80rpx;
top: 20rpx;
}
.chat-right-icon {
right: 80rpx;
top: 20rpx;
}
.chat-animate {
/* #ifndef APP-PLUS-NVUE */
opacity: 0;
/* #endif */
}
</style>
free-popup.vue
<template>
<div style="z-index:9999;overflow:hidden;" v-if="status">
<!-- 蒙版 -->
<view v-if="mask" class="position-fixed top-0 left-0 right-0 bottom-0" :style="getMaskColor" @click="hide"></view>
<!-- 弹出框内容 -->
<div ref="popup" class="position-fixed free-animated" :class="getBodyClass" :style="getBodyStyle">
<slot></slot>
</div>
</div>
</template>
<script>
// #ifdef APP-PLUS-NVUE
const animation = weex.requireModule('animation');
// #endif
export default {
props: {
// 是否开启蒙版颜色
maskColor: {
type: Boolean,
default: false
},
// 是否开启蒙版
mask:{
type:Boolean,
default:true
},
// 是否处于底部
bottom:{
type:Boolean,
default:false
},
// 弹出层内容高度
bodyHeight:{
type:Number,
default:0
},
// 弹出层内容宽度
bodyWidth:{
type:Number,
default:0
},
bodyBgColor:{
type:String,
default:'bg-white'
},
transformOrigin:{
type:String,
default:'left top'
},
// tabbar高度
tabbarHeight:{
type:Number,
default:0
}
},
data() {
return {
status: false,
x:-1,
y:1,
maxX:0,
maxY:0
}
},
mounted() {
try {
const res = uni.getSystemInfoSync();
this.maxX = res.windowWidth - uni.upx2px(this.bodyWidth)
this.maxY = res.windowHeight - uni.upx2px(this.bodyHeight) - uni.upx2px(this.tabbarHeight)
} catch (e) {
// error
}
},
computed: {
getMaskColor() {
let i = this.maskColor ? 0.5 : 0
return `background-color: rgba(0,0,0,${i});`
},
getBodyClass(){
if(this.center){
return 'left-0 right-0 bottom-0 top-0 flex align-center justify-center';
}
let bottom = this.bottom ? 'left-0 right-0 bottom-0' : 'rounded border'
return `${this.bodyBgColor} ${bottom}`;
},
getBodyStyle(){
let left = this.x > -1 ? `left:${this.x}px;` : ''
let top = this.y > -1 ? `top:${this.y}px;` : ''
return left + top
}
},
methods:{
show(x=-1,y=-1){
if(this.status){
return;
}
this.x = (x > this.maxX) ? this.maxX : x;
this.y = (y > this.maxY) ? this.maxY : y;
this.status = true
// #ifdef APP-PLUS-NVUE
this.$nextTick(()=>{
animation.transition(this.$refs.popup,{
styles:{
transform:'scale(1,1)',
transformOrigin:this.transformOrigin,
opacity:1
},
duration:100, //ms
timingFunction:'ease',
},()=>{
console.log('动画执行结束');
})
})
// #endif
},
hide(){
this.$emit('hide');
// #ifdef APP-PLUS-NVUE
this.$nextTick(()=>{
animation.transition(this.$refs.popup,{
styles:{
transform:'scale(0,0)',
transformOrigin:this.transformOrigin,
opacity:0
},
duration:100, //ms
timingFunction:'ease',
},()=>{
this.status = false;
})
})
// #endif
}
}
}
</script>
<style scoped>
.free-animated{
/* #ifdef APP-PLUS-NVUE */
/* transform: scale(0,0);
opacity: 0; */
/* #endif */
}
.z-index{
/* #ifndef APP-NVUE */
z-index: 9999;
/* #endif */
}
</style>
还有我们在chat.nvue中引入了vuex
/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
import audio from '@/store/modules/audio.js';
export default new Vuex.Store({
modules:{
audio
}
})
/store/modules/audio.js
export default{
state:{
// 存放全局事件
events:[],
RECORD:null,
RecordTime:0,
RECORDTIMER:null,
sendVoice:null
},
mutations:{
// 初始化录音管理器
initRECORD(state){
state.RECORD = uni.getRecorderManager();
// 监听录音开始
state.RECORD.onStart(()=>{
state.RecordTime = 0;
state.RECORDTIMER = setInterval(()=>{
state.RecordTime++
},1000);
})
// 监听录音结束
state.RECORD.onStop((e)=>{
if(state.RECORDTIMER){
clearInterval(state.RECORDTIMER)
state.RECORDTIMER = null
}
// if(!state.unRecord){
// this.send('audio',e.tempFilePath,{time:state.RecordTime});
// }
// 执行发送
if(typeof state.sendVoice === 'function'){
state.sendVoice(e.tempFilePath)
}
})
},
// 注册发送音频事件
regSendVoiceEvent(state,event){
state.sendVoice = event
},
// 注册全局事件
regEvent(state,event){
console.log('注册事件')
state.events.push(event)
},
// 执行全局事件
doEvent(state,params){
state.events.forEach(e=>{
console.log('执行事件'+state.events.length)
e(params)
})
},
// 注销事件
removeEvent(state,event){
let index = state.events.findIndex(item=>{
return item === event
})
if(index !== -1){
state.events.splice(index,1);
}
}
},
actions:{
// 分发注册全局事件
audioOn({commit},event){
commit('regEvent',event);
},
// 分发执行全局事件
audioEmit({commit},params){
commit('doEvent',params);
},
// 分发注销全局事件
audioOff({commit},event){
commit('removeEvent',event);
}
}
}
页面是如下所示
感谢大家观看,我们下期再见。