YingXingAI/pages/chat/index.vue

593 lines
15 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-page">
<!-- 顶部导航 -->
<header-bar
:title="vuex_msgUser.name"
leftIcon="arrow-left"
@leftClick="handleLeftClick"
></header-bar>
<!-- 消息列表 -->
<view class="chat-container">
<scroll-view
class="chat-scroll"
scroll-y
:scroll-into-view="scrollToView"
:scroll-top="scrollTop"
:scroll-with-animation="!isLoading"
:upper-threshold="20"
@scroll="handleScroll"
@scrolltoupper="handleScrollToUpper"
>
<view class="chat-content">
<!-- 教师信息 -->
<div class="teacher-info-card">
<image
class="teacher-avatar"
:src="receiverHeadSculptureUrl"
></image>
<div class="teacher-info">
<div class="teacher-name">{{ vuex_msgUser.name }}</div>
<div class="teacher-school">
<image
class="school-icon"
src="/static/common/images/icon_college.png"
></image>
<text class="school-text">{{ vuex_msgUser.collegeName }}</text>
</div>
<div class="teacher-college">
<image
class="college-icon"
src="/static/common/images/icon_major.png"
></image>
<text class="college-text">{{ vuex_msgUser.collegeName }}</text>
</div>
</div>
</div>
<!-- 上拉刷新loading -->
<view class="loading-more" v-if="isLoading">
<u-loading mode="circle" color="#4370fe"></u-loading>
</view>
<!-- 到顶部提示 -->
<view
class="no-more-data"
v-if="noMoreData && vuex_msgList && vuex_msgList.length"
>
<text>已经到顶了</text>
</view>
<view
v-for="(message, index) in vuex_msgList"
:key="message.id"
:id="'msg-' + message.id"
>
<!-- 时间 -->
<view class="message-time" v-if="isShowTime(index)">
{{ formatShowTime(message.sendDate) }}
</view>
<!-- 0 发送消息 -->
<view
class="message-right"
v-if="message.senderId === vuex_user.Id"
:id="'msg-' + message.id"
>
<view class="message-content">
<text>{{ message.message }}</text>
</view>
<image
class="user-avatar"
:src="headSculptureUrl"
mode="scaleToFill"
/>
</view>
<!-- 1 收到消息 -->
<view
class="message-left"
v-if="message.senderId !== vuex_user.Id"
:id="'msg-' + message.id"
>
<image
class="ai-avatar"
:src="receiverHeadSculptureUrl"
mode="scaleToFill"
/>
<view class="message-content">
<text>{{ message.message }}</text>
</view>
</view>
</view>
<!-- 底部间距 滚动锚点 -->
<view id="bottom-anchor" class="bottom-anchor"></view>
</view>
</scroll-view>
</view>
<!-- 输入栏 -->
<view class="chat-footer">
<view class="input-area">
<input
v-model="messageValue"
type="text"
class="chat-input"
:focus="true"
placeholder="请输入内容"
placeholder-style="color: #adadad;"
@confirm="handleSend"
/>
<view class="send-btn" @click="handleSend">
<image
class="send-icon"
src="/static/common/images/icon_send.png"
mode="scaleToFill"
/>
</view>
</view>
</view>
</view>
</template>
<script>
import HeaderBar from "@/components/HeaderBar.vue"; // 导入头部组件
import {
formatChatShowTime,
scrollToBottomByContentHeight,
shouldShowTime,
} from "@/utils/chat.js";
export default {
name: "ChatDetail",
components: {
HeaderBar, // 注册头部组件
},
data() {
return {
baseUrl: "",
// 头像
myAvatar: "/static/avatar/default-avatar.png",
otherAvatar: "/static/avatar/default-avatar.png",
// 消息列表
messageList: [
{
id: "9cac8661-bf09-4b63-ab15-1a0a36b91110",
message: "你知道今年的录取分数线吗",
sendDate: "2025-08-10T15:11:32.886075",
isSend: true,
isRead: false,
interactMode: 0,
messageType: 0,
},
{
id: "02306fc3-c821-4a23-ad66-0bd788854105",
message: "回答。",
sendDate: "2025-08-10T15:11:36.88644",
isSend: true,
isRead: false,
interactMode: 1,
messageType: 0,
},
],
// 输入框
messageValue: "",
// 滚动位置
scrollToView: "",
scrollTop: 0,
currentScrollTop: 0,
currentScrollHeight: 0,
PageIndex: 1,
PageSize: 20,
isLoading: false,
noMoreData: false,
};
},
onLoad(options) {
console.log(this.vuex_msgList);
console.log(this.vuex_msgUser, "this.vuex_msgUser");
this.baseUrl = this.$u.http.config.baseUrl;
// 加载历史消息
this.getMsgList();
},
computed: {
// 最后一条消息的ID
lastMsgId() {
const list = this.vuex_msgList || [];
if (!list.length) return "";
return list[list.length - 1]?.id || "";
},
receiverHeadSculptureUrl() {
if (this.vuex_msgUser.headSculptureUrl) {
return this.baseUrl + "/" + this.vuex_msgUser.headSculptureUrl;
}
return "/static/common/images/avatar_default2.png";
},
headSculptureUrl() {
if (this.vuex_user.HeadSculptureUrl) {
return this.baseUrl + "/" + this.vuex_user.HeadSculptureUrl;
}
return "/static/common/images/avatar_default2.png";
},
},
watch: {
// 监听最后一条消息的ID变化滚动到底部
lastMsgId(val) {
if (!val) return;
if (this.isLoading) return;
this.$nextTick(() => {
this.scrollToBottom();
});
},
},
methods: {
// 返回
handleLeftClick() {
uni.navigateBack();
},
// 点击发送
handleSend() {
if (!this.messageValue) {
return;
}
// 构建消息对象
const message = {
dialogueManagementId: this.vuex_msgUser.dialogueManagementId,
receiverId: this.vuex_msgUser.id,
message: this.messageValue,
messageType: 0,
filePath: "",
ip: "",
};
this.sendMsgFn(message);
},
// 发送消息
sendMsgFn(message) {
this.$u.api
.SendMessage_PrivateApi(message)
.then((res) => {
console.log(res, "发送消息成功");
if (res.succeed) {
// 添加到消息列表
const msgUserData = {
id: Math.random().toString(36).substring(2),
dialogueManagementId: this.vuex_msgUser.dialogueManagementId,
senderId: this.vuex_user.Id,
receiverId: this.vuex_msgUser.receiverId,
sendDate: new Date().toISOString(),
message: this.messageValue,
messageType: 0,
filePath: "",
// interactMode: 0,
};
this.$store.commit("push_Msg", msgUserData);
// 清空输入框
this.messageValue = "";
// // 滚动到底部
// this.$nextTick(() => {
// this.scrollToBottom();
// });
}
})
.catch((error) => {
return msg.warning("发送失败");
});
},
// 加载对话消息
getMsgList() {
return this.$store
.dispatch("fetchChatRecord", {
dialogueManagementId: this.vuex_msgUser.dialogueManagementId,
PageIndex: this.PageIndex,
PageSize: this.PageSize,
})
.then((list) => {
const len = Array.isArray(list) ? list.length : 0;
// 第一页无数据,设置为没有更多数据
if (len === 0 && this.PageIndex === 1) {
this.noMoreData = true;
}
return list;
});
},
handleScroll(e) {
const detail = (e && e.detail) || {};
this.currentScrollTop = Number(detail.scrollTop) || 0;
this.currentScrollHeight = Number(detail.scrollHeight) || 0;
},
// 滚动到顶部,加载下一页历史消息
handleScrollToUpper() {
if (this.isLoading || this.noMoreData) return;
this.isLoading = true;
const beforeTop = this.currentScrollTop || 0;
const beforeHeight = this.currentScrollHeight || 0;
const nextPageIndex = this.PageIndex + 1;
this.scrollToView = "";
this.$store
.dispatch("fetchChatRecordNextPage", {
dialogueManagementId: this.vuex_msgUser.dialogueManagementId,
PageIndex: nextPageIndex,
PageSize: this.PageSize,
})
.then((list) => {
if (!list || !list.length) {
this.noMoreData = true;
return;
}
this.PageIndex = nextPageIndex;
// this.$nextTick(() => {
// const query = uni.createSelectorQuery().in(this);
// query
// .select(".chat-content")
// .boundingClientRect((rect) => {
// const afterHeight = Number(rect && rect.height) || 0;
// const delta = afterHeight - beforeHeight;
// if (delta > 0) {
// this.scrollTop = beforeTop + delta;
// }
// })
// .exec();
// });
})
.finally(() => {
setTimeout(() => {
this.isLoading = false;
}, 100);
});
},
// 滚动到底部
scrollToBottom() {
if (this.isLoading) return;
scrollToBottomByContentHeight(this, {
selector: ".chat-content",
extraOffset: 200,
});
},
// 是否显示时间
isShowTime(index) {
return shouldShowTime(this.vuex_msgList, index);
},
// 格式化显示时间
formatShowTime(sendDate) {
return formatChatShowTime(sendDate);
},
},
};
</script>
<style lang="scss" scoped>
.chat-page {
height: 100vh;
padding-top: 88rpx;
background-image: url("@/static/common/images/images_bg.png");
width: 100%;
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: 0 88rpx;
background-attachment: fixed;
.chat-container {
display: flex;
flex-direction: column;
padding: 0 30rpx;
box-sizing: border-box;
height: calc(100vh - 88rpx - 146rpx);
position: relative;
overflow: hidden;
.chat-scroll {
flex: 1;
height: 100%;
overflow-y: scroll;
.loading-more {
text-align: center;
margin-bottom: 32rpx;
}
.no-more-data {
text-align: center;
font-size: 24rpx;
color: #999;
margin-bottom: 32rpx;
padding: 10rpx 0;
}
.teacher-info-card {
background-color: #ffffff;
padding: 32rpx;
border-radius: 16rpx;
margin: 30rpx 0;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
margin-bottom: 36rpx;
.teacher-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
margin-right: 36rpx;
object-fit: cover;
}
.teacher-info {
display: flex;
flex-direction: column;
justify-content: space-evenly;
gap: 16rpx;
flex: 1;
.teacher-name {
font-family: PingFang SC;
font-weight: bold;
font-size: 36rpx;
color: #333333;
}
.teacher-school,
.teacher-college {
display: flex;
align-items: center;
.school-icon,
.college-icon {
margin-right: 16rpx;
width: 24rpx;
height: 24rpx;
display: inline-block;
text-align: center;
}
.school-text,
.college-text {
font-size: 24rpx;
color: #666666;
}
}
}
}
.message-time {
text-align: center;
font-size: 24rpx;
color: #999999;
padding: 20rpx;
}
.message-left,
.message-right {
display: flex;
margin-bottom: 40rpx;
align-items: flex-start;
}
.message-left {
justify-content: flex-start;
flex-wrap: wrap; /* 允许反馈区换行到消息下方 */
.ai-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
margin-right: 16rpx;
background-color: #f0f0f0;
flex-shrink: 0;
}
.message-content {
background-color: #ffffff;
max-width: 70%;
padding: 20rpx 24rpx;
border-radius: 0 16rpx 16rpx 16rpx;
font-size: 28rpx;
line-height: 1.5;
}
}
.message-right {
justify-content: flex-end;
.user-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
margin-left: 16rpx;
background-color: #f0f0f0;
flex-shrink: 0;
}
.message-content {
background-color: #4370fe;
color: #ffffff;
max-width: 70%;
padding: 20rpx 24rpx;
border-radius: 16rpx 0 16rpx 16rpx;
font-size: 28rpx;
line-height: 1.5;
text-align: left;
}
}
.bottom-anchor {
height: 48rpx;
width: 100%;
}
}
}
.chat-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 32rpx;
box-sizing: border-box;
background-color: #ffffff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
.input-area {
display: flex;
align-items: center;
background-color: #f2f4f9;
border-radius: 20rpx;
padding: 16rpx 24rpx;
box-sizing: border-box;
.chat-input {
flex: 1;
height: 40rpx;
font-size: 28rpx;
color: #333;
background-color: transparent;
}
.send-btn {
width: 50rpx;
height: 50rpx;
display: flex;
align-items: center;
justify-content: center;
.send-icon {
width: 40rpx;
height: 46rpx;
}
}
}
}
}
</style>