Files
jurong_circle_front_app/pages/message/chat.vue
2025-09-28 17:30:20 +08:00

535 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="chat-container">
<u-navbar title="聊天" id="navBarId" :background="{background: 'transparent' }" :border-bottom="false"
back-icon-color="#000" title-color="#000">
<template v-slot:right>
<image class="collection" src="/static/icon/Bookmark.png" mode=""></image>
</template>
</u-navbar>
<view class="bottom-view" id="bottomId">
<!-- 麦克风/文本切换 -->
<view class="icon u-m-r-10" @click="handleChangeMic">
<image src="/static/icon/Mic.png" mode="" style="width: 100%;height: 100%;"></image>
</view>
<textarea v-if="!showMic" name="" v-model="text" class="text" maxlength="3000"></textarea>
<u-button v-else class="btn-mic" @click="handleSay">点击说话</u-button>
<!-- 表情包 -->
<view v-show="text==null || text==''" class="icon u-m-r-10 u-m-l-10">
<image src="/static/icon/expression.png" mode="" style="width: 100%;height: 100%;"></image>
</view>
<!-- 文本域展开 -->
<view v-show="text!=null && text!=''" class="icon u-m-r-10 u-m-l-10" @click="handleMagnify">
<image src="/static/icon/big.png" mode="" style="width: 100%;height: 100%;"></image>
</view>
<!-- 相机 -->
<view v-show="text==null || text==''" class="icon" @click="handleCamera">
<image src="/static/icon/Camera.png" mode="" style="width: 100%;height: 100%;"></image>
</view>
<!-- 文本发送 -->
<view v-show="text!=null && text!=''" class="icon u-m-r-10 u-m-l-10" @click="handleSend">
<image src="/static/icon/send.png" mode="" style="width: 100%;height: 100%;"></image>
</view>
</view>
<scroll-view :style="'height:'+scrollHeight+'px'" scroll-y="true" class="scroll-main">
<view v-for="(item, index) in dataList">
<view v-if="item.createId==userId" class="my-message">
<view class="msg-main">
<view class="msg-content" v-if="item.type=='text'">
<view class="text">
{{item.content}}
</view>
</view>
<view class="mp3-content" v-if="item.type=='mp3'">
<view class="mp3">
<view class="mp3-icon">
<image style="width: 100%;height: 100%;" src="/static/icon/listen.png" mode="">
</image>
</view>
点击播放
</view>
</view>
<view class="msg-avatar">
<image style="width: 100%;height: 100%;" :src="getImageUrl(item.userInfo.avatar)" mode="">
</image>
</view>
</view>
</view>
<view v-else class="other-message">
<view class="msg-main">
<view class="msg-avatar">
<image style="width: 100%;height: 100%;" :src="getImageUrl(item.userInfo.avatar)" mode="">
</image>
</view>
<view class="msg-content">
<view class="text">
{{item.content}}
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<u-popup v-model="showLongText" mode="bottom" height="80%">
<view class="long-text">
<u-input v-model="text" type="textarea" maxlength="3000" />
</view>
</u-popup>
<u-mask :show="showMask" @click="handleStopMic" blur="10">
<view class="mask-warp">
<view class="mask-text">
{{!showMaskBtn?'正在说话...点击结束': '点击播放录音'}}
</view>
<view v-if="showMaskBtn" class="mask-btn">
<u-button class="btn" @click.stop="handleCancel">取消</u-button>
<u-button class="btn" @click.stop="handleSendMp3">发送</u-button>
</view>
</view>
</u-mask>
<u-toast ref="msgToast" duration="6000" />
</view>
</template>
<script setup lang="ts">
import { onMounted, ref, getCurrentInstance, onBeforeMount } from 'vue';
import {
onLoad, onLaunch
} from "@dcloudio/uni-app";
import io from '@hyoga/uni-socket.io';
import { getImageUrl } from '../../util/common';
import { permissionUtil } from '@/uni_modules/colorful-uni-perm';
const text = ref('')
const socket = ref()
const navBarRef = ref()
const dataList = ref([])
const mockData = () => {
for (var i = 0; i < 3; i++) {
if (i % 2 == 0) {
dataList.value.push({
"createId": 3641,
"groupId": "1",
"content": "即使在没有空格的地方也 ",
"type": "text",
"messageId": 44,
"createTime": "2025-09-27T18:32:42.000Z",
"userInfo": {
"id": 9958,
"username": "15867461647",
"userType": "user",
"isSystemAccount": 0,
"avatar": "/uploads/documents/1753833656669_524106424.jpg"
}
})
} else {
dataList.value.push({
"createId": 9955,
"groupId": "1",
"content": "2222",
"type": "text",
"messageId": 44,
"createTime": "2025-09-27T18:32:42.000Z",
"userInfo": {
"id": 9958,
"username": "15867461647",
"userType": "user",
"isSystemAccount": 0,
"avatar": "/uploads/documents/1753833656669_524106424.jpg"
}
})
}
dataList.value.push(
{
"createId": 3641,
"groupId": "5",
"content": "http://114.55.111.44:9000/jurongquan/app_records/1759049412254.mp3",
"type": "mp3",
"messageId": 56,
"createTime": "2025-09-28T00:50:16.000Z",
"userInfo": {
"id": 3641,
"username": "15867461617",
"userType": "agent",
"isSystemAccount": 0,
"avatar": "/uploads/documents/1753833656669_524106424.jpg"
}
})
}
}
const handleSend = () => {
socket.value.emit('clientMsg', {
createId: userId.value,
groupId: groupId.value,
content: text.value,
type: "text"
})
}
const showLongText = ref(false)
const showMic = ref(false)
const showMask = ref(false)
const showMaskBtn = ref(false)
const msgToast = ref(null)
const voicePath = ref('')
const recorderManager = uni.getRecorderManager();
const innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = true;
// 选择麦克风
const handleChangeMic = () => {
showMic.value = !showMic.value
}
// 开始录音
const handleSay = () => {
permissionUtil.requestAndroidPermission('android.permission.READ_MEDIA_AUDIO').then((status) => {
// status 为 1 表示用户已授权0 表示用户已拒绝, -1 表示用户永久拒绝
console.log('权限申请结果:', status);
if (status == -1) {
// 弹窗提示开启权限
msgToast.value.show({
title: '您已关闭录音权限,请在设置开启应用录音权限',
type: 'warning'
})
} else if (status == 1) {
showMask.value = true
recorderManager.start();
recorderManager.onStart(() => {
console.log('录音开始');
});
recorderManager.onError((err) => {
console.error('录音错误', err);
});
}
});
}
// 停止录音
const handleStopMic = () => {
showMaskBtn.value = true
if (voicePath.value) {
console.log("播放录音");
innerAudioContext.src = voicePath.value
innerAudioContext.play();
} else {
recorderManager.stop()
// showMask.value = false
recorderManager.onStop(function (res) {
console.log('recorder stop' + JSON.stringify(res));
voicePath.value = res.tempFilePath;
});
}
}
// 取消发送录音
const handleCancel = () => {
showMask.value = false
showMaskBtn.value = false
voicePath.value = ''
}
// 发送录音文件
const handleSendMp3 = () => {
const token = uni.getStorageSync("token")
uni.uploadFile({
// url: 'http://192.168.0.4:3005/upload',
url: 'http://192.168.0.15:3007/upload/file',
filePath: voicePath.value,
header: {
"Authorization": "Bearer " + token
},
name: "file",
success: (uploadFileRes) => {
let result = JSON.parse(uploadFileRes.data)
if (result.data.fileUrl) {
// 上传文件成功,添加消息记录
socket.value.emit('clientMsg', {
createId: userId.value,
groupId: groupId.value,
content: result.data.fileUrl,
type: "mp3"
})
showMask.value = false
showMaskBtn.value = false
voicePath.value = ''
}
},
fail: (res) => {
console.log(JSON.stringify(res));
}
})
}
// 文本放大
const handleMagnify = () => {
showLongText.value = true
}
const handleCamera = () => {
permissionUtil.requestAndroidPermission('android.permission.READ_EXTERNAL_STORAGE').then((status) => {
console.log('相册权限申请结果:', status);
if (status == -1) {
// 弹窗提示开启权限
msgToast.value.show({
title: '您已关闭读取相册权限,请在设置开启应用相册权限',
type: 'warning'
})
} else if (status == 1) {
uni.chooseImage({
count: 10, //默认9
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: ['album'], //从相册选择
success: function (res) {
uploadImages(res.tempFilePaths)
}
});
}
})
}
const uploadImages = (value) => {
console.log(value);
const token = uni.getStorageSync("token")
uni.uploadFile({
// url: 'http://192.168.0.4:3005/upload/image',
url: 'http://192.168.0.15:3007/upload/files',
filePath: value,
header: {
"Authorization": "Bearer " + token
},
name: 'file',
success: (uploadFileRes) => {
console.log(uploadFileRes);
// console.log(JSON.stringify(uploadFileRes.data));
},
fail: (res) => {
console.log(JSON.stringify(res));
}
})
}
const groupId = ref()
const userId = ref()
onLoad((val) => {
groupId.value = val.groupId
});
const instance = getCurrentInstance();
const scrollHeight = ref(0)
const loadHeight = () => {
uni.getSystemInfo({
success(res) {
let screenHeight = res.screenHeight
uni.createSelectorQuery().in(instance.proxy).select("#navBarId").boundingClientRect((data : any) => {
scrollHeight.value = screenHeight - data.height
}).exec()
uni.createSelectorQuery().in(instance.proxy).select("#bottomId").boundingClientRect((data : any) => {
scrollHeight.value = scrollHeight.value - data.height
}).exec()
}
})
}
const connection = () => {
socket.value = io('http://192.168.0.15:3007', {
query: {},
transports: ['websocket', 'polling'],
timeout: 5000,
});
socket.value.on('connect', async () => {
console.log("连接成功");
})
socket.value.emit('addGroup', {
groupId: groupId.value,
})
socket.value.on('serverMsg', async (message) => {
dataList.value.push(message)
console.log(dataList.value);
})
}
const loadData = () => {
userId.value = uni.getStorageSync("user").id
}
onMounted(() => {
mockData()
loadData()
loadHeight()
connection()
})
</script>
<style scoped lang="scss">
.chat-container {
width: 100%;
height: 100vh;
background: linear-gradient(180deg, #E3E8FF 0%, #FFFFFF 100%);
background-blend-mode: lighten;
.collection {
width: 48rpx;
height: 48rpx;
margin-right: 24rpx;
}
.bottom-view {
width: 100%;
height: 100rpx;
// background-color: #aaffff;
position: absolute;
bottom: 0;
display: flex;
align-items: center;
padding: 10rpx;
.icon {
width: 60rpx;
height: 60rpx;
}
.text {
flex: 1;
height: 96%;
border: 2rpx solid #DEEFFF;
border-radius: 10rpx;
padding: 0 10rpx;
}
.btn-mic {
width: 100%;
height: 100%;
}
}
.scroll-main {
.my-message {
text-align: right;
margin-bottom: 20rpx;
.msg-main {
display: flex;
justify-content: flex-end;
padding: 0 10rpx;
.msg-content {
// background-color: #00ffff;
padding: 2rpx;
width: 70%;
margin-right: 10rpx;
// border: 1rpx solid #c7c7c7;
box-sizing: content-box;
.text {
overflow-wrap: break-word;
text-align: right;
}
}
.mp3-content {
padding: 2rpx;
// width: 70%;
margin-right: 10rpx;
box-sizing: content-box;
.mp3 {
display: flex;
// border: 1rpx solid #000;
padding: 15rpx 10rpx;
background-color: #55ff7f;
justify-content: flex-end;
align-items: center;
.mp3-icon {
margin-left: 10rpx;
width: 25rpx;
height: 25rpx;
}
}
}
.msg-avatar {
width: 80rpx;
height: 80rpx;
}
}
}
.other-message {
margin-bottom: 20rpx;
.msg-main {
display: flex;
justify-content: flex-start;
padding: 0 10rpx;
.msg-content {
// background-color: #00ffff;
padding: 2rpx;
width: 70%;
// border: 1rpx solid #c7c7c7;
box-sizing: content-box;
.text {
overflow-wrap: break-word;
text-align: left;
}
}
.msg-avatar {
width: 80rpx;
height: 80rpx;
margin-right: 10rpx;
}
}
}
}
.long-text {
padding: 40rpx 20rpx;
}
.mask-warp {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
.mask-text {
font-size: 36rpx;
color: #fff;
}
.mask-btn {
position: absolute;
bottom: 0;
width: 100%;
height: 80rpx;
display: flex;
.btn {
width: 50%;
}
}
}
}
</style>