123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- <template>
- <view class="container">
- <view id="topnav" class="topnav flex-start"
- :style="{ 'height': `${topbarOffsetHeight - statusBarHeight}px`, 'padding-top': `${statusBarHeight}px` }">
- <view class="nav-item flex-center" @click="back">
- <image :src="`${assetsUrl}back.png`" mode="widthFix" class="nav-img"></image>
- </view>
- <view class="nav-center flex-center">
- <view class="nav-text font32 fw600">
- {{ userInfo.nick }}
- </view>
- <view class="nav-tip font20 fw400">
- <text></text>{{ onlineState }} · {{ userInfo.distance }}
- </view>
- </view>
- <view class="nav-item"></view>
- </view>
- <view class="message-list" :style="[{ 'height': `${scrollHeight}px`, 'margin-top': `${topbarOffsetHeight}px` }]">
- <!-- <view class="message-list" :style="[messageStyle]"> -->
- <TUI-message-list id="message-list" ref="messageList" :conversation="conversation" :scrollHeight="scrollHeight"
- :topbarOffsetHeight="topbarOffsetHeight" />
- </view>
- <view class="talk-box" :style="[{ 'bottom': `${bottomVal}px` }]" id="talk-box" @touchmove="prevent()">
- <view class="input-box flex-between">
- <!-- <van-field v-model="inputText" label="文本" placeholder="请输入消息…" /> -->
- <!-- <input type="text" class="input" confirm-type="send" placeholder="请输入消息…" :adjust-position="true" v-model="inputText" placeholder-style="color:#7D7DA4 ;font-size:24rpx" @confirm="sendTextMessage"> -->
- <input class="TUI-message-input-area input" :adjust-position="false" cursor-spacing="20" v-model="inputText"
- @confirm="sendTextMessage" maxlength="140" type="text" placeholder-style="color:#7D7DA4 ;font-size:24rpx"
- placeholder="请输入消息…" @focus="inputBindFocus" @blur="inputBindBlur" />
- <view class="input-btn font22 fw400" @click="sendTextMessage">
- 发送
- </view>
- </view>
- <view class="action-box flex-between">
- <image :src="`${assetsUrl}talk-voice.png`" mode="aspectFit" class="act-img" @click="showActionPanel(0)"></image>
- <image :src="`${assetsUrl}talk-pic.png`" mode="aspectFit" class="act-img" @click="showActionPanel(1)"></image>
- <image :src="`${assetsUrl}talk-video.png`" mode="aspectFit" class="act-img" @click="showActionPanel(2)"></image>
- <image :src="`${assetsUrl}talk-emo.png`" mode="aspectFit" class="act-img" @click="showActionPanel(3)"></image>
- </view>
- <view class="action-panel" :style="{ 'padding-bottom': `${showActionIndex === -1 ? '68rpx' : '20rpx'}` }">
- <view class="voice-panel flex-center" v-if="showActionIndex === 0">
- <view class="voice-text font22 fw400">
- {{ voiceText }}
- </view>
- <image :src="`${assetsUrl}talk-voice-${isRecording ? 'delete' : 'open'}.png`" mode="aspectFill" class="voice-img"
- @longpress="handleLongPress" @touchmove="handleTouchMove" @touchend="handleTouchEnd"></image>
- </view>
- <view class="emoji-box" v-if="showActionIndex === 3">
- <TUI-Emoji @enterEmoji="appendMessage"></TUI-Emoji>
- </view>
- </view>
- </view>
- <view v-if="videoPlay" class="container-box" @tap.stop="stopVideoHander">
- <video v-if="videoPlay" class="video-message" :src="videoMessage.payload.videoUrl || videoMessage.videoUrl"
- :poster="videoMessage.payload.thumbUrl || videoMessage.videoCover" object-fit="cover" error="videoError"
- autoplay="true" direction="0" show-fullscreen-btn="false"
- :style="{ 'width': `${videoMessage.payload.thumbWidth || videoMessage.width}px`, 'height': `${videoMessage.payload.thumbHeight || videoMessage.height}px` }" />
- </view>
- </view>
- </template>
- <script>
- import TUIEmoji from '../../components/tui-chat/message-elements/emoji/index';
- import TUIMessageList from '../../components/tui-chat/message-list/index';
- export default {
- components: {
- TUIMessageList,
- TUIEmoji
- },
- data() {
- return {
- assetsUrl: this.$util.assetsUrl,
- scrollHeight: 0,
- topNavHeight: 0,
- userInfo: null,
- messageList: [],
- message: null,//消息实例
- onlineState: '',
- scrollRefreshing: false,
- scrollTriggered: true,
- conversation: '',
- conversationID: '',
- inputText: '',
- showSendBtn: false,
- showActionIndex: -1,
- recorderManager: null,
- voiceText: '按住说话,松手发送',
- recordTimeTotal: 60,//最大长度,30秒
- isRecording: false,
- canSend: false,
- startPoint: 0,
- videoPlay: false,
- videoMessage: {},
- bottomVal: 0 // 底部bottom
- }
- },
- computed: {
- statusBarHeight() {
- return this.$store.state.statusBarHeight;
- },
- topbarOffsetHeight() {
- return this.$store.state.topbarOffsetHeight;
- },
- messageStyle() {
- let obj = {
- 'height': `${this.scrollHeight}px`,
- 'margin-top': `${this.topbarOffsetHeight}px`
- }
- return obj
- // [{,'margin-top':`${topbarOffsetHeight}px`}]
- }
- },
- created() {
- uni.$on('videoPlayerHandler', value => {
- this.videoPlay = value.isPlay;
- this.videoMessage = value.message;
- });
- uni.authorize({
- scope: 'scope.record',
- success: () => {
- this.startRecordManage();
- },
- fail: err => {
- uni.showModal({
- content: '检测到您没打开录音功能权限,是否去设置打开?',
- confirmText: "确认",
- cancelText: '取消',
- success: res => {
- if (res.confirm) {
- uni.openSetting({
- success: ress => {
- if (ress.authSetting) {
- this.startRecordManage();
- }
- }
- })
- }
- }
- })
- }
- })
- },
- onLoad(options) {
- this.conversationID = options.conversationid;
- uni.$TUIKit.setMessageRead({
- conversationID: this.conversationID
- })
- uni.$TUIKit.getConversationProfile(this.conversationID).then(res => {
- const { conversation } = res.data;
- this.conversation = conversation;
- console.log(this.conversation)
- });
- let pages = getCurrentPages();
- let prePage = pages[pages.length - 2];
- this.userInfo = prePage.$vm.userInfo;
- },
- mounted() {
- if (this.userInfo.lastActiveTime === 0 || this.userInfo.online) {
- this.onlineState = '在线';
- }
- else if (this.userInfo.lastActiveTime < 30 && !this.userInfo.online) {
- this.onlineState = '刚刚';
- }
- else {
- this.onlineState = '离线';
- }
- setTimeout(() => {
- this.computedScollviewHeight();
- }, 500)
- },
- methods: {
- inputBindFocus(e) {
- // 获取手机键盘的高度,赋值给input 所在盒子的 bottom 值
- // 注意!!! 这里的 px 至关重要!!! 我搜到的很多解决方案都没有说这里要添加 px
- // this.$emit('changeBottomVal', e.detail.height + 'px')
- this.scrollHeight = e.detail.height
- this.bottomVal = e.detail.height
- console.log(e.detail.height, 'e.detail.height', e, 'this.scrollHeight', this.scrollHeight)
- },
- inputBindBlur() {
- // input 失去焦点,键盘隐藏,设置 input 所在盒子的 bottom 值为0
- // this.$emit('changeBottomVal', 0)
- // this.scrollHeight = 0
- this.computedScollviewHeight()
- this.bottomVal = 0
- },
- back() {
- uni.navigateBack({
- delta: 1
- })
- },
- prevent() {
- return false;
- },
- stopVideoHander() {
- this.videoPlay = false;
- },
- /**
- * 计算scroll高度
- */
- computedScollviewHeight() {
- let query = uni.createSelectorQuery().in(this);
- let heightLeaf = 0;
- query.selectAll('#topnav,#talk-box').boundingClientRect(data => {
- data.forEach(item => {
- heightLeaf += item.height;
- })
- }).exec(() => {
- let sysInfo = uni.getSystemInfoSync();
- this.scrollHeight = sysInfo.windowHeight - heightLeaf;
- this.$refs.messageList.scrollToButtom();
- });
- },
- /**
- * 推荐下拉刷新、加载更多
- */
- scrollRefresh() {
- if (this.scrollRefreshing) {
- return;
- }
- this.scrollRefreshing = true;
- setTimeout(() => {
- this.scrollTriggered = false;
- this.scrollRefreshing = false;
- }, 1000);
- this.isLoadPreData = true;
- },
- scrollPulling(e) { },
- scrollRestore() { this.scrollTriggered = true; },
- scrollAbort() { },
- scrollToBottom() { },
- showActionPanel(index) {
- if (this.showActionIndex === index) {
- this.showActionIndex = -1;
- setTimeout(() => {
- this.computedScollviewHeight();
- }, 50)
- return;
- }
- switch (index) {
- case 0:
- this.showActionIndex = index;
- setTimeout(() => {
- this.computedScollviewHeight();
- }, 50)
- break;
- case 1:
- this.showActionIndex = -1;
- uni.chooseImage({
- count: 1,
- sizeType: ['original', 'compressed'],
- sourceType: ['album', 'camera'],
- success: res => {
- console.log(res)
- const message = uni.$TUIKit.createImageMessage({
- to: String(this.userInfo.id),
- conversationType: this.conversation.type,
- payload: {
- file: res
- },
- onProgress: event => { }
- });
- this.$sendTIMMessage(message);
- }
- })
- break;
- case 2:
- uni.chooseVideo({
- sourceType: ['album', 'camera'],
- maxDuration: 60,
- camera: 'back',
- success: res => {
- const message = uni.$TUIKit.createVideoMessage({
- to: String(this.userInfo.id),
- conversationType: this.conversation.type,
- payload: {
- file: res
- },
- onProgress: event => { }
- });
- this.$sendTIMMessage(message);
- }
- })
- break;
- case 3:
- this.showActionIndex = index;
- setTimeout(() => {
- this.computedScollviewHeight();
- }, 50)
- break;
- }
- },
- appendMessage(e) {
- this.inputText += e.detail.message;
- },
- startRecordManage() {
- // 加载声音录制管理器
- this.recorderManager = uni.getRecorderManager();
- console.log(this.recorderManager)
- this.recorderManager.onStop(res => {
- console.log(res)
- clearInterval(this.recordTimer);
- // 兼容 uniapp 打包app,duration 和 fileSize 需要用户自己补充
- // 文件大小 = (音频码率) x 时间长度(单位:秒) / 8
- let msg = {
- duration: res.duration ? res.duration : this.recordTime * 1000,
- tempFilePath: res.tempFilePath,
- fileSize: res.fileSize ? res.fileSize : ((48 * this.recordTime) / 8) * 1024
- };
- uni.hideLoading();
- // 兼容 uniapp 语音消息没有duration
- if (this.canSend) {
- if (msg.duration < 1000) {
- uni.showToast({
- title: '录音时间太短',
- icon: 'none'
- });
- } else {
- // res.tempFilePath 存储录音文件的临时路径
- const message = uni.$TUIKit.createAudioMessage({
- to: String(this.userInfo.id),
- conversationType: this.conversation.type,
- payload: {
- file: msg
- }
- });
- this.$sendTIMMessage(message);
- }
- }
- this.startPoint = 0;
- this.isRecording = false;
- this.canSend = true;
- this.voiceText = '按住说话,松手发送';
- });
- this.recorderManager.onError(err => {
- console.log(err)
- })
- },
- handleLongPress(e) {
- uni.vibrateShort();
- this.recorderManager.start({
- duration: 60000,
- // 录音的时长,单位 ms,最大值 600000(10 分钟)
- sampleRate: 44100,
- // 采样率
- numberOfChannels: 1,
- // 录音通道数
- encodeBitRate: 192000,
- // 编码码率
- format: 'aac' // 音频格式,选择此格式创建的音频消息,可以在即时通信 IM 全平台(Android、iOS、微信小程序和Web)互通
- });
- this.startPoint = e.touches[0];
- this.voiceText = '正在录音,上划可取消';
- this.isRecording = true;
- this.recordTime = 0;
- this.recordTimer = setInterval(() => {
- this.recordTime++;
- if (this.recorTime >= this.recordTimeTotal) {
- this.recorderManager.stop();
- clearInterval(this.recordTimer);
- this.recordTimer = null;
- }
- }, 1000);
- },
- // 录音时的手势上划移动距离对应文案变化
- handleTouchMove(e) {
- if (this.isRecording) {
- if (this.startPoint.clientY - e.touches[e.touches.length - 1].clientY > 100) {
- this.voiceText = '松开手指,取消发送';
- this.canSend = false;
- } else if (this.startPoint.clientY - e.touches[e.touches.length - 1].clientY > 20) {
- this.voiceText = '上划可取消';
- this.canSend = true;
- } else {
- this.voiceText = '抬起停止';
- this.canSend = true;
- }
- }
- },
- // 手指离开页面滑动
- handleTouchEnd() {
- this.isRecording = false;
- this.voiceText = '按住说话,松手发送';
- uni.hideLoading();
- this.recorderManager.stop();
- },
- sendTextMessage() {
- const to = String(this.userInfo.id);
- const text = this.inputText;
- const message = uni.$TUIKit.createTextMessage({
- to,
- conversationType: this.conversation.type,
- payload: {
- text
- }
- });
- this.inputText = '';
- this.$sendTIMMessage(message);
- },
- $sendTIMMessage(message) {
- uni.$TUIKit.sendMessage(message).then((res) => {
- this.$EventBus.$emit('sendMessage', message)
- this.$refs.messageList.scrollToButtom();
- }).catch((error) => {
- uni.showToast({
- title: '发送消息失败',
- icon: "none"
- })
- })
- },
- }
- }
- </script>
- <style lang="scss" scoped>
- .container {
- width: 100vw;
- height: 100vh;
- background-color: $bgcolor1;
- position: relative;
- overflow: hidden;
- .topnav {
- padding: 0 10rpx;
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- z-index: 100;
- background-color: $bgcolor1;
- .nav-item {
- width: 40rpx;
- height: 40rpx;
- margin-left: 16rpx;
- .nav-img {
- width: 40rpx;
- height: 40rpx;
- }
- }
- .nav-center {
- flex: 1;
- flex-direction: column;
- .nav-text {
- color: $fontcolor5;
- height: 40rpx;
- text-align: center;
- }
- .nav-tip {
- color: $fontcolor2;
- }
- }
- }
- .talk-box {
- position: fixed;
- bottom: 0;
- left: 0;
- width: 100vw;
- padding-top: 16rpx;
- background-color: $bgcolor1;
- .input-box {
- margin: 0rpx 32rpx;
- height: 80rpx;
- border-radius: 80rpx;
- background-color: $bgcolor4;
- padding: 0 24rpx;
- .input {
- width: 100%;
- height: 100%;
- color: #ffffff;
- font-size: 22rpx;
- }
- .input-btn {
- background-color: $primary;
- color: #ffffff;
- border-radius: 16rpx;
- height: 56rpx;
- line-height: 56rpx;
- width: 120rpx;
- text-align: center;
- }
- }
- .action-box {
- padding: 0rpx 0rpx 20rpx 0rpx;
- margin: 0 90rpx;
- margin-top: 36rpx;
- .act-img {
- width: 56rpx;
- height: 56rpx;
- }
- }
- .action-panel {
- width: 100vw;
- .voice-panel {
- height: 400rpx;
- flex-direction: column;
- .voice-text {
- color: #7D7DA4;
- text-align: center;
- }
- .voice-img {
- width: 200rpx;
- height: 200rpx;
- margin-top: 40rpx;
- }
- }
- }
- .emoji-box {
- padding: 0 11rpx;
- box-sizing: border-box;
- height: 400rpx;
- flex-wrap: wrap;
- transition: height .3s;
- .emoji-item {
- width: 64rpx;
- height: 64rpx;
- padding: 20rpx;
- .emoji-img {
- width: 64rpx;
- height: 64rpx;
- }
- }
- }
- }
- .container-box {
- position: fixed;
- display: flex;
- justify-content: center;
- align-items: center;
- left: 0;
- right: 0;
- bottom: 0;
- top: 0;
- background-color: rgba(0, 0, 0, 0.5);
- .video-message {
- width: 90vw;
- height: auto;
- }
- }
- }</style>
|