|
|
@@ -2,10 +2,10 @@
|
|
|
<view class="chat-container">
|
|
|
<view class="header">
|
|
|
<text class="title">{{name || '在线客服'}}</text>
|
|
|
- <view class="status">
|
|
|
+ <!-- <view class="status">
|
|
|
<text class="status-dot" :class="{ 'online': isConnected }"></text>
|
|
|
- <text class="status-text">{{ isConnected ? '已连接' : '连接中...' }}</text>
|
|
|
- </view>
|
|
|
+ <text class="status-text">{{ isConnected ? '已连接' : '连接中...' }}</text>
|
|
|
+ </view> -->
|
|
|
</view>
|
|
|
|
|
|
<scroll-view
|
|
|
@@ -15,33 +15,36 @@
|
|
|
:scroll-with-animation="true"
|
|
|
>
|
|
|
<view class="message-item" v-for="(msg, index) in messages" :key="index" :id="'msg-' + index">
|
|
|
- <view v-if="msg.type === 'text'" class="message-bubble" :class="msg.from === 'me' ? 'right' : 'left'">
|
|
|
+ <view v-if="msg.type === 6" class="system-message">
|
|
|
+ <text>{{ showTime(msg.create_time)}}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="msg.type === 1" class="message-bubble" :class="msg.is_mine ? 'right' : 'left'">
|
|
|
<view class="avatar">
|
|
|
- <image v-if="msg.from === 'me' && userInfo.avatar" :src="userInfo.avatar" mode="aspectFill"></image>
|
|
|
- <view v-else :class="msg.from === 'me' ? 'my-avatar' : 'service-avatar'">
|
|
|
- <text>{{ msg.from === 'me' ? '我' : '客' }}</text>
|
|
|
- </view>
|
|
|
+ <image v-if="msg.is_mine && userInfo.avatar" :src="userInfo.avatar" mode="aspectFill"></image>
|
|
|
+ <image v-if="!msg.is_mine && avatar" :src="avatar" mode="scaleToFill"></image>
|
|
|
+ <!-- <view v-else :class="msg.is_mine ? 'my-avatar' : 'service-avatar'">
|
|
|
+ <text>{{ msg.is_mine ? '我' : '客' }}</text>
|
|
|
+ </view> -->
|
|
|
</view>
|
|
|
<view class="bubble-content">
|
|
|
<text>{{ msg.content }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <view v-else-if="msg.type === 'image'" class="message-bubble" :class="msg.from === 'me' ? 'right' : 'left'">
|
|
|
+ <view v-else-if="msg.type === 3" class="message-bubble" :class="msg.is_mine ? 'right' : 'left'">
|
|
|
<view class="avatar">
|
|
|
- <image v-if="msg.from === 'me' && userInfo.avatar" :src="userInfo.avatar" mode="aspectFill"></image>
|
|
|
- <view v-else :class="msg.from === 'me' ? 'my-avatar' : 'service-avatar'">
|
|
|
- <text>{{ msg.from === 'me' ? '我' : '客' }}</text>
|
|
|
- </view>
|
|
|
+ <image v-if="msg.is_mine && userInfo.avatar" :src="userInfo.avatar" mode="scaleToFill"></image>
|
|
|
+ <image v-if="!msg.is_mine && avatar" :src="avatar" mode=""></image>
|
|
|
+ <!-- <view v-else :class="msg.is_mine ? 'my-avatar' : 'service-avatar'">
|
|
|
+ <text>{{ msg.is_mine ? '我' : '客' }}</text>
|
|
|
+ </view> -->
|
|
|
</view>
|
|
|
<view class="bubble-image" @click="previewImage(msg.content)">
|
|
|
<image :src="msg.content" mode="widthFix" @tap="previewImage(msg.content)"></image>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <view v-else-if="msg.type === 'system'" class="system-message">
|
|
|
- <text>{{ msg.content }}</text>
|
|
|
- </view>
|
|
|
+
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
|
|
|
@@ -54,6 +57,7 @@
|
|
|
placeholder="请输入消息..."
|
|
|
placeholder-class="placeholder"
|
|
|
@confirm="sendText"
|
|
|
+ maxlength="1000"
|
|
|
/>
|
|
|
</view>
|
|
|
<view class="action-buttons">
|
|
|
@@ -75,10 +79,12 @@
|
|
|
import {
|
|
|
upload
|
|
|
} from '@/api/set.js';
|
|
|
+ import { send, getRecordList } from '@/api/kf.js'
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
+ to_uid: '',
|
|
|
name: '',
|
|
|
socketTask: null,
|
|
|
isConnected: false,
|
|
|
@@ -87,79 +93,89 @@
|
|
|
messages: [],
|
|
|
inputText: '',
|
|
|
scrollToView: '',
|
|
|
- wsUrl: 'ws://api.myje.cn:2345'
|
|
|
+ wsUrl: 'wss://api.myje.cn',
|
|
|
+ page: 1,
|
|
|
+ limit: 1000,
|
|
|
+ pollingTimer: null,
|
|
|
+ pollingInterval: 3000,
|
|
|
+ lastMessageId: 0,
|
|
|
+ avatar: ''
|
|
|
}
|
|
|
},
|
|
|
onLoad(opt) {
|
|
|
- this.addSystemMessage('正在连接服务器...');
|
|
|
- this.name = opt.name
|
|
|
- this.connectWebSocket(opt.to_uid);
|
|
|
+ this.name = decodeURI(opt.name)
|
|
|
+ this.to_uid = opt.to_uid
|
|
|
+ this.avatar = uni.getStorageSync('avt')
|
|
|
+ console.log(this.avatar);
|
|
|
+
|
|
|
+ this.getRecordList()
|
|
|
+ this.startPolling()
|
|
|
+ // this.connectWebSocket();
|
|
|
},
|
|
|
onUnload() {
|
|
|
- this.closeWebSocket();
|
|
|
+ // this.closeWebSocket();
|
|
|
+ this.stopPolling();
|
|
|
+ },
|
|
|
+ onHide() {
|
|
|
+ this.stopPolling();
|
|
|
},
|
|
|
computed: {
|
|
|
...mapState('user', ['userInfo'])
|
|
|
},
|
|
|
methods: {
|
|
|
- connectWebSocket(to_uid) {
|
|
|
- const token = uni.getStorageSync('token') || '';
|
|
|
- const wsUrl =`${this.wsUrl}?token=${token}` + '&type=user&from=3&to_uid=' + to_uid;
|
|
|
+ showTime(time) {
|
|
|
+ const date = new Date(time * 1000);
|
|
|
+ const today = new Date();
|
|
|
+ const isToday = date.toDateString() === today.toDateString();
|
|
|
|
|
|
- this.socketTask = uni.connectSocket({
|
|
|
- url: wsUrl,
|
|
|
- success: () => {
|
|
|
- console.log('WebSocket连接请求成功');
|
|
|
- },
|
|
|
- fail: (err) => {
|
|
|
- console.log('WebSocket连接失败', err);
|
|
|
- this.handleReconnect();
|
|
|
- }
|
|
|
- });
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(date.getDate()).padStart(2, '0');
|
|
|
+ const hours = String(date.getHours()).padStart(2, '0');
|
|
|
+ const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
|
|
|
|
- this.socketTask.onOpen(() => {
|
|
|
- console.log('WebSocket连接成功');
|
|
|
- this.isConnected = true;
|
|
|
- this.reconnectCount = 0;
|
|
|
- this.addSystemMessage('已连接到服务器');
|
|
|
- });
|
|
|
-
|
|
|
- this.socketTask.onMessage((res) => {
|
|
|
- this.handleMessage(res.data);
|
|
|
- });
|
|
|
-
|
|
|
- this.socketTask.onClose(() => {
|
|
|
- console.log('WebSocket连接关闭');
|
|
|
- this.isConnected = false;
|
|
|
- this.handleReconnect();
|
|
|
- });
|
|
|
-
|
|
|
- this.socketTask.onError((err) => {
|
|
|
- console.log('WebSocket错误', err);
|
|
|
- this.isConnected = false;
|
|
|
- this.addSystemMessage('连接发生错误');
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- handleReconnect() {
|
|
|
- if (this.reconnectCount < this.maxReconnect) {
|
|
|
- this.reconnectCount++;
|
|
|
- this.addSystemMessage(`正在重连... (${this.reconnectCount}/${this.maxReconnect})`);
|
|
|
- setTimeout(() => {
|
|
|
- this.connectWebSocket();
|
|
|
- }, 2000);
|
|
|
+ if (isToday) {
|
|
|
+ return `${hours}:${minutes}`;
|
|
|
} else {
|
|
|
- this.addSystemMessage('连接失败,请稍后重试');
|
|
|
+ return `${month}-${day} ${hours}:${minutes}`;
|
|
|
}
|
|
|
},
|
|
|
-
|
|
|
- closeWebSocket() {
|
|
|
- if (this.socketTask) {
|
|
|
- this.socketTask.close();
|
|
|
- this.socketTask = null;
|
|
|
- }
|
|
|
+ getRecordList() {
|
|
|
+ let that = this
|
|
|
+ getRecordList({
|
|
|
+ to_user_id: that.to_uid,
|
|
|
+ page: that.page,
|
|
|
+ limit: that.limit
|
|
|
+ }).then(res => {
|
|
|
+ const originalList = res.data.list;
|
|
|
+
|
|
|
+ if (originalList.length > 0) {
|
|
|
+ that.lastMessageId = originalList[originalList.length - 1].create_time;
|
|
|
+ const processedList = [{type: 6, create_time: originalList[0].create_time}];
|
|
|
+ for (let i = 0; i < originalList.length; i++) {
|
|
|
+ processedList.push(originalList[i]);
|
|
|
+
|
|
|
+ if (i < originalList.length - 1) {
|
|
|
+ const currentTime = originalList[i].create_time;
|
|
|
+ const nextTime = originalList[i + 1].create_time;
|
|
|
+ const diff = Math.abs(nextTime - currentTime);
|
|
|
+
|
|
|
+ if (diff >= 3600) {
|
|
|
+ processedList.push({
|
|
|
+ type: 6,
|
|
|
+ create_time: nextTime
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ that.messages = processedList;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ that.scrollToBottom()
|
|
|
+ })
|
|
|
},
|
|
|
-
|
|
|
handleMessage(data) {
|
|
|
try {
|
|
|
const message = typeof data === 'string' ? JSON.parse(data) : data;
|
|
|
@@ -172,100 +188,107 @@
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
-
|
|
|
addMessage(msg) {
|
|
|
this.messages.push({
|
|
|
- from: msg.from || 'service',
|
|
|
- type: msg.type || 'text',
|
|
|
+ is_mine: msg.is_mine || true,
|
|
|
+ type: msg.type || 1,
|
|
|
content: msg.content,
|
|
|
- time: new Date().getTime()
|
|
|
+ create_time: msg.create_time
|
|
|
});
|
|
|
+ this.lastMessageId = msg.create_time
|
|
|
+ // console.log(this.messages,'this.messages');
|
|
|
this.scrollToBottom();
|
|
|
},
|
|
|
-
|
|
|
- addSystemMessage(content) {
|
|
|
- this.messages.push({
|
|
|
- type: 'system',
|
|
|
- content: content,
|
|
|
- time: new Date().getTime()
|
|
|
+ startPolling() {
|
|
|
+ this.stopPolling();
|
|
|
+ this.pollingTimer = setInterval(() => {
|
|
|
+ this.pollNewMessages();
|
|
|
+ }, this.pollingInterval);
|
|
|
+ },
|
|
|
+ stopPolling() {
|
|
|
+ if (this.pollingTimer) {
|
|
|
+ clearInterval(this.pollingTimer);
|
|
|
+ this.pollingTimer = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ pollNewMessages() {
|
|
|
+ let that = this;
|
|
|
+ getRecordList({
|
|
|
+ to_user_id: that.to_uid,
|
|
|
+ page: 1,
|
|
|
+ limit: that.limit
|
|
|
+ }).then(res => {
|
|
|
+ const newMessages = res.data.list;
|
|
|
+ console.log(newMessages,'newMessages');
|
|
|
+ if (newMessages.length > 0) {
|
|
|
+ const latestId = newMessages[newMessages.length - 1].create_time;
|
|
|
+ console.log(latestId,'latestId','that.lastMessageId',that.lastMessageId,newMessages[0]);
|
|
|
+ if (latestId > that.lastMessageId) {
|
|
|
+ for (let i = 0; i < newMessages.length; i++) {
|
|
|
+ const msgId = newMessages[i].create_time;
|
|
|
+ console.log(msgId,that.lastMessageId);
|
|
|
+ if (msgId > that.lastMessageId) {
|
|
|
+ that.messages.push(newMessages[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ that.lastMessageId = latestId;
|
|
|
+ console.log(that.lastMessageId);
|
|
|
+ that.scrollToBottom();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }).catch(err => {
|
|
|
+ console.log('轮询获取消息失败', err);
|
|
|
});
|
|
|
- this.scrollToBottom();
|
|
|
},
|
|
|
-
|
|
|
sendText() {
|
|
|
+ let that = this
|
|
|
if (!this.inputText.trim()) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
const text = this.inputText.trim();
|
|
|
-
|
|
|
- if (!this.isConnected) {
|
|
|
- uni.showToast({
|
|
|
- title: '连接未就绪',
|
|
|
- icon: 'none'
|
|
|
+ send({
|
|
|
+ to_user_id: that.to_uid,
|
|
|
+ content: text,
|
|
|
+ type: 1,
|
|
|
+ form_type: 2
|
|
|
+ }).then(res => {
|
|
|
+ let time = new Date(res.data.create_time).getTime() / 1000
|
|
|
+ this.addMessage({
|
|
|
+ is_mine: true,
|
|
|
+ type: 1,
|
|
|
+ content: text,
|
|
|
+ create_time:time
|
|
|
});
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const message = {
|
|
|
- type: 'text',
|
|
|
- content: text
|
|
|
- };
|
|
|
+ this.inputText = '';
|
|
|
+ })
|
|
|
|
|
|
- this.socketTask.send({
|
|
|
- data: JSON.stringify(message),
|
|
|
- success: () => {
|
|
|
- this.addMessage({
|
|
|
- from: 'me',
|
|
|
- type: 'text',
|
|
|
- content: text
|
|
|
- });
|
|
|
- this.inputText = '';
|
|
|
- },
|
|
|
- fail: () => {
|
|
|
- uni.showToast({
|
|
|
- title: '发送失败',
|
|
|
- icon: 'none'
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
},
|
|
|
|
|
|
sendImage() {
|
|
|
- if (!this.isConnected) {
|
|
|
- uni.showToast({
|
|
|
- title: '连接未就绪',
|
|
|
- icon: 'none'
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
+ let that = this
|
|
|
upload({
|
|
|
filename: ''
|
|
|
}).then(data => {
|
|
|
const imageUrl = data[0].img;
|
|
|
-
|
|
|
const message = {
|
|
|
type: 'image',
|
|
|
content: imageUrl
|
|
|
};
|
|
|
+ send({
|
|
|
+ to_user_id: that.to_uid,
|
|
|
+ content: imageUrl,
|
|
|
+ type: 3,
|
|
|
+ form_type: 2
|
|
|
+ }).then(res => {
|
|
|
+ let time = new Date(res.data.create_time).getTime() / 1000
|
|
|
+ this.addMessage({
|
|
|
+ is_mine: true,
|
|
|
+ type: 3,
|
|
|
+ content: imageUrl,
|
|
|
+ create_time: time
|
|
|
+ });
|
|
|
+ })
|
|
|
|
|
|
- this.socketTask.send({
|
|
|
- data: JSON.stringify(message),
|
|
|
- success: () => {
|
|
|
- this.addMessage({
|
|
|
- from: 'me',
|
|
|
- type: 'image',
|
|
|
- content: imageUrl
|
|
|
- });
|
|
|
- },
|
|
|
- fail: () => {
|
|
|
- uni.showToast({
|
|
|
- title: '发送失败',
|
|
|
- icon: 'none'
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
}).catch(err => {
|
|
|
console.log('上传图片失败', err);
|
|
|
});
|
|
|
@@ -300,7 +323,7 @@
|
|
|
|
|
|
.header {
|
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
+ justify-content: center;
|
|
|
align-items: center;
|
|
|
padding: 20rpx 30rpx;
|
|
|
background-color: #FFFFFF;
|
|
|
@@ -436,7 +459,7 @@
|
|
|
text {
|
|
|
font-size: $font-sm;
|
|
|
color: $font-color-light;
|
|
|
- background-color: rgba(0, 0, 0, 0.06);
|
|
|
+ // background-color: rgba(0, 0, 0, 0.06);
|
|
|
padding: 8rpx 20rpx;
|
|
|
border-radius: 20rpx;
|
|
|
}
|