Merge branch 'main' of https://gitlab.guxuan.icu/Leo_Ding/JinShan_uniapp
This commit is contained in:
commit
cd8f2588e6
353
pages/chat/chatPage - 副本 (2).vue
Normal file
353
pages/chat/chatPage - 副本 (2).vue
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
<template>
|
||||||
|
<view class="chat-container">
|
||||||
|
<!-- 聊天消息区域 -->
|
||||||
|
<scroll-view scroll-y :scroll-top="scrollTop" class="chat-messages" :scroll-with-animation="true"
|
||||||
|
@scroll="onScroll">
|
||||||
|
<view v-for="(msg, index) in messages" :key="index"
|
||||||
|
:class="['message', msg.sender === 'me' ? 'sent' : 'received']">
|
||||||
|
<image v-if="msg.sender !== 'me'" :src="botAvatar" class="avatar"></image>
|
||||||
|
|
||||||
|
<view :class="['message-bubble', msg.sender === 'me' ? 'me' : '']">
|
||||||
|
<text class="message-text">{{ msg.content }}</text>
|
||||||
|
<view class="typing-indicator" v-if="msg.isTyping">
|
||||||
|
<view class="dot"></view>
|
||||||
|
<view class="dot"></view>
|
||||||
|
<view class="dot"></view>
|
||||||
|
<text class="typing-text">对方正在输入...</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<image v-if="msg.sender === 'me'" :src="userAvatar" class="avatar"></image>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 输入区域 -->
|
||||||
|
<view class="chat-input">
|
||||||
|
<input class="input-field" placeholder="输入消息..." v-model="inputMsg" @confirm="sendMessage"
|
||||||
|
confirm-type="send" />
|
||||||
|
<button class="send-btn" :disabled="!inputMsg.trim()" @click="sendMessage">
|
||||||
|
发送
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
BASE_URL
|
||||||
|
} from '@/utils/config';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inputMsg: '',
|
||||||
|
scrollTop: 0,
|
||||||
|
userAvatar: require('../../static/imgs/index/nav.png'),
|
||||||
|
botAvatar: require('../../static/imgs/ai/chuandaguwen.png'),
|
||||||
|
messages: [],
|
||||||
|
socketTask: null, // 微信小程序的 WebSocket 任务对象
|
||||||
|
isConnected: false,
|
||||||
|
reconnectAttempts: 0,
|
||||||
|
maxReconnectAttempts: 5,
|
||||||
|
reconnectDelay: 3000,
|
||||||
|
userId: null,
|
||||||
|
userName: '默认用户',
|
||||||
|
apiKey: '',
|
||||||
|
serviceUrl: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad(options) {
|
||||||
|
this.serviceUrl = options.serviceUrl || '';
|
||||||
|
this.apiKey = options.apiKey || '';
|
||||||
|
this.botAvatar = options.icon || this.botAvatar;
|
||||||
|
|
||||||
|
const userInfo = wx.getStorageSync('userInfo') || {};
|
||||||
|
this.userId = userInfo.id || Date.now().toString();
|
||||||
|
this.userName = userInfo.name || '默认用户';
|
||||||
|
this.userAvatar = userInfo.avatar || this.userAvatar;
|
||||||
|
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: options.name || 'AI助手'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.initWebSocket();
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnload() {
|
||||||
|
this.closeWebSocket();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// 初始化WebSocket连接
|
||||||
|
initWebSocket() {
|
||||||
|
if (this.isConnected) return;
|
||||||
|
|
||||||
|
// 微信小程序中使用 wx.connectSocket
|
||||||
|
this.socketTask = wx.connectSocket({
|
||||||
|
url: `ws://10.10.1.6:8071/api/v1/ws/ai?apiUrl=${encodeURIComponent(this.serviceUrl)}&apiToken=${this.apiKey}&userName=${this.userName}`,
|
||||||
|
success: () => {
|
||||||
|
console.log('WebSocket连接创建成功');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('WebSocket连接创建失败', err);
|
||||||
|
this.handleReconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听 WebSocket 事件
|
||||||
|
this.socketTask.onOpen(() => {
|
||||||
|
console.log('WebSocket连接已打开');
|
||||||
|
this.isConnected = true;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
this.addMessage('bot', '连接已建立,请问有什么可以帮您?');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socketTask.onMessage((res) => {
|
||||||
|
console.log('收到WebSocket消息:', res.data);
|
||||||
|
this.processBotMessage(res.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socketTask.onError((err) => {
|
||||||
|
console.error('WebSocket发生错误:', err);
|
||||||
|
this.isConnected = false;
|
||||||
|
this.addMessage('bot', '连接出错,请稍后再试');
|
||||||
|
this.handleReconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socketTask.onClose(() => {
|
||||||
|
console.log('WebSocket连接已关闭');
|
||||||
|
this.isConnected = false;
|
||||||
|
this.handleReconnect();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理机器人消息
|
||||||
|
processBotMessage(data) {
|
||||||
|
// 移除思考状态的消息
|
||||||
|
this.messages = this.messages.filter(msg => !msg.isTyping);
|
||||||
|
|
||||||
|
// 处理不同类型的消息
|
||||||
|
if (data.startsWith('<think>')) {
|
||||||
|
// 机器人正在思考
|
||||||
|
this.addMessage('bot', '', true);
|
||||||
|
} else if (data.startsWith('<answer>')) {
|
||||||
|
// 正式回答
|
||||||
|
const content = data.replace(/<answer>/g, '').replace(/<\/answer>/g, '');
|
||||||
|
this.addMessage('bot', content);
|
||||||
|
} else if (data.includes('</think>')) {
|
||||||
|
// 思考结束,不做特殊处理
|
||||||
|
} else {
|
||||||
|
// 普通消息
|
||||||
|
this.addMessage('bot', data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 添加消息到聊天列表
|
||||||
|
addMessage(sender, content, isTyping = false) {
|
||||||
|
const message = {
|
||||||
|
sender: sender === 'bot' ? 'other' : 'me',
|
||||||
|
content,
|
||||||
|
isTyping,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.messages.push(message);
|
||||||
|
this.scrollToBottom();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
sendMessage() {
|
||||||
|
const content = this.inputMsg.trim();
|
||||||
|
if (!content || !this.isConnected) return;
|
||||||
|
|
||||||
|
this.addMessage('me', content);
|
||||||
|
|
||||||
|
// 微信小程序中使用 socketTask.send
|
||||||
|
this.socketTask.send({
|
||||||
|
data: content,
|
||||||
|
success: () => {
|
||||||
|
console.log('消息发送成功');
|
||||||
|
// 添加机器人正在输入的提示
|
||||||
|
this.addMessage('bot', '', true);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('消息发送失败', err);
|
||||||
|
this.addMessage('bot', '消息发送失败,请重试');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.inputMsg = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理重连逻辑
|
||||||
|
handleReconnect() {
|
||||||
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||||
|
console.log('已达到最大重连次数');
|
||||||
|
this.addMessage('bot', '连接已断开,请刷新页面重试');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
console.log(`尝试重新连接,第${this.reconnectAttempts}次`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initWebSocket();
|
||||||
|
}, this.reconnectDelay);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 关闭WebSocket连接
|
||||||
|
closeWebSocket() {
|
||||||
|
if (this.socketTask) {
|
||||||
|
this.socketTask.close({
|
||||||
|
success: () => {
|
||||||
|
console.log('WebSocket已主动关闭');
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('关闭WebSocket失败', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.socketTask = null;
|
||||||
|
}
|
||||||
|
this.isConnected = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 滚动到底部
|
||||||
|
scrollToBottom() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollTop = 99999; // 足够大的值确保滚动到底部
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onScroll(e) {
|
||||||
|
// 可以在这里实现加载历史消息
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.chat-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
flex: 1;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #e5ddd5;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sent {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble {
|
||||||
|
max-width: 70%;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.me {
|
||||||
|
background-color: #dcf8c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input {
|
||||||
|
display: flex;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: white;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
padding: 0 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: #07C160;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对方正在输入提示 */
|
||||||
|
.typing-indicator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #7f8c8d;
|
||||||
|
margin: 0 3px;
|
||||||
|
animation: typing 1.4s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot:nth-child(1) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-text {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 8px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes typing {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
60%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -101,7 +101,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
get,
|
get,
|
||||||
post
|
post,
|
||||||
|
del
|
||||||
} from '@/utils/request';
|
} from '@/utils/request';
|
||||||
import {
|
import {
|
||||||
IMAGE_BASE_URL,
|
IMAGE_BASE_URL,
|
||||||
@ -236,16 +237,14 @@
|
|||||||
title: '处理中...',
|
title: '处理中...',
|
||||||
mask: true
|
mask: true
|
||||||
});
|
});
|
||||||
const res = await post('/api/v1/app_auth/metting-room/order/cancel', {
|
const res = await del(`/api/v1/app_auth/metting-room/order/${orderId}`);
|
||||||
id: orderId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res?.success) {
|
if (res?.success) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '取消成功',
|
title: '取消成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
});
|
});
|
||||||
this.getOrderList(); // 刷新列表
|
this.getOrderList();
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res?.message || '取消失败',
|
title: res?.message || '取消失败',
|
||||||
|
|||||||
@ -33,7 +33,8 @@
|
|||||||
:class="{'is-reply': comment.toUserName}">
|
:class="{'is-reply': comment.toUserName}">
|
||||||
<view class="comment-header">
|
<view class="comment-header">
|
||||||
<image class="avatar"
|
<image class="avatar"
|
||||||
:src="comment.pusherPortrait === '/static/imgs/index/nav.png' ? comment.pusherPortrait : `${IMAGE_BASE_URL}${comment.pusherPortrait}`" mode="aspectFill"></image>
|
:src="getAvatarSrc(comment.pusherPortrait)"
|
||||||
|
mode="aspectFill"></image>
|
||||||
<view class="comment-user-info">
|
<view class="comment-user-info">
|
||||||
<text class="username">{{ comment.pusherName }}</text>
|
<text class="username">{{ comment.pusherName }}</text>
|
||||||
<view class="meta-info">
|
<view class="meta-info">
|
||||||
@ -114,8 +115,27 @@
|
|||||||
this.error = '缺少内容ID';
|
this.error = '缺少内容ID';
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getAvatarSrc(portrait) {
|
||||||
|
const defaultAvatar = '/static/imgs/index/nav.png';
|
||||||
|
|
||||||
|
if (!portrait) {
|
||||||
|
return defaultAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultUrls = ['/static/imgs/index/nav.png', 'https://jinshan.nantong.info'];
|
||||||
|
|
||||||
|
if (defaultUrls.includes(portrait) || portrait.startsWith('http')) {
|
||||||
|
return portrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.IMAGE_BASE_URL}${portrait}`;
|
||||||
|
},
|
||||||
|
|
||||||
// 扁平化评论结构(核心修改)
|
// 扁平化评论结构(核心修改)
|
||||||
updateFlatComments() {
|
updateFlatComments() {
|
||||||
const flattenComments = (comments, parentUser = '') => {
|
const flattenComments = (comments, parentUser = '') => {
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
export const BASE_URL = 'https://jinshan.nantong.info';
|
|
||||||
export const IMAGE_BASE_URL = `https://jinshan.nantong.info`
|
|
||||||
export const WS_BASE_URL = `wss://jinshan.nantong.info`
|
|
||||||
// export const BASE_URL = 'http://10.10.1.6:8071';
|
// export const BASE_URL = 'http://10.10.1.6:8071';
|
||||||
// export const IMAGE_BASE_URL = `http://10.10.1.6:8071`;
|
// export const IMAGE_BASE_URL = `http://10.10.1.6:8071`;
|
||||||
|
export const BASE_URL = 'https://jinshan.nantong.info';
|
||||||
|
export const IMAGE_BASE_URL = `https://jinshan.nantong.info`;
|
||||||
|
export const WS_BASE_URL = `wss://jinshan.nantong.info`;
|
||||||
|
|
||||||
|
|
||||||
// http://36.212.197.253:8071
|
// http://36.212.197.253:8071
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user