YingXingAI/pages/home/index/index.vue

1540 lines
42 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="home-container">
<header-bar
title="源小新"
leftIcon="list"
@leftClick="handleLeftClick"
></header-bar>
<!-- 首页 -->
<view class="main-content" v-if="!isChat">
<view class="welcome-message">
<image
class="avatar"
src="/static/common/images/images_logo.png"
></image>
<text class="message-text"
>Hi~&nbsp;我是源小新你们的AI校园助手非常高兴认识您我可以为你答疑解惑</text
>
</view>
<view class="qa-section">
<view class="qa-header">
<text class="qa-title">大家都在问</text>
<view class="more-link" @click="hotQARefresh">
<image
class="icon-refresh"
src="/static/common/images/icon-refresh.png"
></image>
<text class="more-text">换一批</text>
</view>
</view>
<view class="qa-list">
<view
class="qa-item"
v-for="(item, index) in hotQuestions"
:key="index"
@click="handleQAClick(item)"
>
<text class="qa-question">{{ item.content }}</text>
</view>
</view>
<button class="chat-button" @click="handleStartChat">
<image
class="chat-icon"
src="/static/common/images/icon_chat.png"
></image>
<text class="chat-text">开启对话</text>
</button>
</view>
<view class="feature-grid">
<view
class="feature-item"
v-for="(item, index) in features"
:key="index"
:style="{
background: item.background,
}"
@click="handleFeatureClick(item)"
>
<image :src="item.icon" class="feature-icon"></image>
<text class="feature-text">{{ item.title }}</text>
</view>
</view>
</view>
<!-- 对话 -->
<view class="chat-container" v-if="isChat">
<scroll-view
class="chat-scroll"
scroll-y
:scroll-into-view="scrollToView"
scroll-with-animation
:scroll-top="scrollTop"
:show-scrollbar="true"
@scrolltolower="onScrollToLower"
@scrolltoupper="onScrollToUpper"
>
<!-- 头部卡片 -->
<!-- 后端让先注释 -->
<view class="chat-card">
<view class="chat-card-title-wrapper">
<view class="chat-card-title">源小新AI校园小助手</view>
<view class="more-link" @click="hotQARefresh">
<image
class="icon-refresh"
src="/static/common/images/icon-refresh.png"
></image>
<text class="more-text">换一批</text>
</view>
</view>
<view class="chat-card-desc"
>我是你们的AI校园助手我可以为您答疑解惑。</view
>
<view class="qa-list">
<view
class="qa-item"
v-for="(item, index) in hotQuestions"
:key="index"
@click="handleQAClick(item)"
>
<text class="qa-question">{{ item.content }}</text>
</view>
</view>
</view>
<!-- <view class="chat-card">
<view class="chat-card-title">源小新AI校园小助手</view>
<view class="chat-card-desc"
>我是你们的AI校园助手我可以为你答疑解惑。</view
>
</view> -->
<!-- 对话内容区域 -->
<view class="chat-content">
<!-- 上拉刷新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">
<text>已经到顶了</text>
</view>
<!-- 消息循环渲染 -->
<block
v-for="(message, index) in messageGroups"
:key="'msg-' + index"
>
<!-- 时间 -->
<view class="message-time" v-if="message.timeLabel !== 0">
{{ message.displayTime }}
</view>
<!-- 用户消息 -->
<view
class="message-right"
v-if="message.interactMode === 0"
:id="'msg-' + message.id"
>
<view class="message-content">
<text>{{ message.message }}</text>
</view>
<image
class="user-avatar"
src="/static/common/images/avatar.png"
mode="scaleToFill"
/>
</view>
<!-- AI消息 -->
<view
class="message-left"
v-if="message.interactMode === 1"
:id="'msg-' + message.id"
>
<image
class="ai-avatar"
src="/static/common/images/avatar_ai.png"
mode="scaleToFill"
/>
<view
class="message-content"
:class="{
'message-content-width': !message.isLoading,
}"
>
<!-- 加载动画 -->
<view v-if="message.isLoading" class="loading-dots">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
<!-- 正常消息内容 -->
<markdown-viewer v-else :content="message.message" />
</view>
<!-- 回答反馈:点赞/点踩 -->
<view v-if="!message.isLoading" class="feedback-container">
<view class="feedback-box">
<view
class="feedback-btn"
@click.stop="handleFeedback(message, true)"
>
<u-icon
:name="
message.isHelp === true ? 'thumb-up-fill' : 'thumb-up'
"
size="32"
:color="message.isHelp === true ? '#4370fe' : '#bfbfbf'"
></u-icon>
</view>
<view class="feedback-divider"></view>
<view
class="feedback-btn"
@click.stop="handleFeedback(message, false)"
>
<u-icon
:name="
message.isHelp === false
? 'thumb-down-fill'
: 'thumb-down'
"
size="32"
:color="message.isHelp === false ? '#4370fe' : '#bfbfbf'"
></u-icon>
</view>
</view>
</view>
</view>
</block>
</view>
</scroll-view>
<!-- 底部工具栏 -->
<view class="chat-footer">
<!-- 悬浮工具栏 -->
<view class="floating-tabs">
<view
class="tab-item"
v-for="(tab, index) in floatingTabs"
:key="index"
@click="handleFeatureClick({ title: tab.title })"
>
<image class="tab-icon" :src="tab.icon" mode="scaleToFill" />
<text>{{ tab.title }}</text>
</view>
</view>
<view class="input-container">
<view class="input-area">
<input
v-model="messageValue"
type="text"
class="chat-input"
:focus="true"
placeholder="请输入内容"
placeholder-style="color: #adadad;"
@confirm="sendMessageFn"
/>
<view class="send-btn" @click="sendMessageFn">
<image
class="send-icon"
src="/static/common/images/icon_send.png"
mode="scaleToFill"
/>
</view>
</view>
</view>
</view>
</view>
<!-- 对话弹出层 -->
<chat-history
:show.sync="popupShow"
:chat-history-list3="chatHistoryList3"
:user-name="vuex_user ? vuex_user.Name : ''"
@select-conversation="handleSelectConversation"
@create-conversation="handleCreateConversation"
@refresh-chat-history="getChatHistoryList"
></chat-history>
<!-- 完善信息弹出层 -->
<perfect-info :show.sync="perfectInfoShow"></perfect-info>
<!-- 咨询电话弹出层 -->
<advice-phone :show.sync="advicePhoneShow"></advice-phone>
<!-- 提示 -->
<u-toast ref="uToast" />
</view>
</template>
<script>
import MarkdownViewer from "@/components/markdown-viewer/markdown-viewer";
import CustomTabBar from "@/components/custom-tab-bar/custom-tab-bar.vue";
import AdvicePhone from "@/components/AdvicePhone.vue";
import PerfectInfo from "@/components/PerfectInfo.vue";
import ChatHistory from "@/components/ChatHistory.vue"; // 导入新组件
import HeaderBar from "@/components/HeaderBar.vue"; // 导入头部组件
export default {
components: {
CustomTabBar,
AdvicePhone,
MarkdownViewer,
PerfectInfo,
ChatHistory,
HeaderBar, // 注册头部组件
},
data() {
return {
isChat: false,
currentConversationId: "", // 当前对话的ID
currentDMid: "", // 当前对话的DMId
advicePhoneShow: false,
perfectInfoShow: false,
isLoading: false, // loadingMore
isLoadingMore: false, // 是否正在加载更多的标志位
noMoreData: false, // 是否已加载全部历史消息
isSwitchingConversation: false, // 是否正在切换对话的标志位
hotQuestions: [], // 热门问题
pageQuery: {
PageIndex: 1,
PageSize: 20,
},
questionList: [
"学习哪些专业比较好?",
"处于香港学校综合录取的城市招生?",
"学校有新华专业?",
"学校录取科目与有一些专业会有特殊...",
"我什么时候能推知道自己是否被录取?",
],
features: [
{
title: "在线咨询",
icon: "/static/common/images/icon_admissions.png",
path: "/pages/home/admissions/index",
background: "linear-gradient(0deg, #F4FBFE 0%, #F4FBFE 100%)",
},
{
title: "电话咨询",
icon: "/static/common/images/icon_phone.png",
background: "linear-gradient(0deg, #F4FBF9 0%, #F4FBF9 100%)",
},
],
floatingTabs: [
{
title: "首页",
icon: "/static/common/images/icon_home.png",
},
{
title: "招生在线",
icon: "/static/common/images/icon_admissions2.png",
},
{
title: "转人工",
icon: "/static/common/images/icon_conversation.png",
},
{
title: "电话咨询",
icon: "/static/common/images/icon_phone2.png",
},
],
popupShow: false,
chatHistoryList3: [
// {
// "id": "今天",
// "conversation": [
// {
// "title": "你好",
// "id": "0d03f23c-9c5b-45ac-ae95-be51e9383a62",
// "startTime": "2025-07-10T15:28:02.832316"
// },
// {
// "title": "你知道今年的录取分数线吗",
// "id": "771ab434-348c-4a35-bd03-adebc21436d8",
// "startTime": "2025-07-10T15:11:32.885001"
// }
// ]
// },
// {
// "id": "周三",
// "conversation": [
// {
// "title": "今天是周三",
// "id": "0d03f23c-9c5b-45ac-ae95-be51e9383a62",
// "startTime": "2025-07-09T15:28:02.832316"
// },
// {
// "title": "你知道今年的录取分数线吗",
// "id": "771ab434-348c-4a35-bd03-adebc21436d8",
// "startTime": "2025-07-09T15:11:32.885001"
// }
// ]
// }
],
activeIndex: 0,
commonQuestions: [
"新生报到流程",
"如何报考学校综合合录取评澳市评招生?",
"学校有新华专业?",
"学校录取科目与有一些专业会有特殊...",
"我什么时候能推知道自己是否被录取?",
],
scrollToView: "",
// 修改后的messageGroups数据结构
messageGroups: [
// {
// id: "9cac8661-bf09-4b63-ab15-1a0a36b921e0",
// message: "你知道今年的录取分数线吗",
// sendDate: "2025-07-10T15:11:32.886075",
// isSend: true,
// isRead: false,
// interactMode: 0,
// messageType: 0,
// timeLabel: 1,
// displayTime: "15:11",
// },
// {
// id: "02306fc3-c821-4a23-ad66-0bd77d154105",
// message:
// "目前2025年的录取分数线还没有正式公布因为学校需要等招生工作结束后才会公布最终的录取分数线。不过你可以参考2023-2024年的录取分数线作为大致的参考。\n\n如果你想知道具体专业的录取分数线可以告诉我你所在省份和科类历史类或物理类我可以根据往年数据给你一些参考建议。如果需要更准确的信息建议关注江西新能源科技职业学院的官网www.jxnee.edu.cn或者直接联系招生办电话0790-6764666/6765666。",
// sendDate: "2025-07-10T15:11:32.88644",
// isSend: true,
// isRead: false,
// interactMode: 1,
// messageType: 0,
// timeLabel: 0,
// displayTime: "",
// },
],
scrollTop: 0,
messageValue: "", // 输入框内容
};
},
watch: {
// 确保消息更新后自动滚动到底部
messageGroups: {
handler() {
// 只有在不是加载更多的情况下才滚动到底部
if (!this.isLoadingMore) {
this.scrollToBottom();
}
},
deep: true,
},
vuex_user: {
handler(newValue) {
// console.log('vuex_user',newValue);
if (newValue && newValue.Sex === 2) {
// this.perfectInfoShow = true;
}
},
immediate: true, // 这会使得组件创建时立即执行一次handler
deep: true,
},
},
onLoad() {
this.hotQARefresh(); // 获取热门聊天
},
methods: {
async handleLeftClick() {
await this.getChatHistoryList();
this.handlePopupShow();
},
// 重置对话状态
resetChatState({
conversationId = "",
dmid = "",
autoResetSwitching = true,
isChat = true,
} = {}) {
this.isChat = isChat;
this.isSwitchingConversation = true;
this.currentConversationId = conversationId;
this.currentDMid = dmid;
this.messageGroups = [];
this.pageQuery.PageIndex = 1;
this.isLoadingMore = false;
this.noMoreData = false;
if (autoResetSwitching) {
setTimeout(() => {
this.isSwitchingConversation = false;
}, 500);
}
},
async getChatHistoryList() {
this.$u.api.GetConversationPage().then((res) => {
this.chatHistoryList3 = res.data;
if (this.chatHistoryList3.length > 0) {
this.chatHistoryList3 = res.data.map((group) => {
// 对每个组的conversation数组进行倒序排序
return {
...group,
conversation: group.conversation.sort((a, b) => {
// 将日期字符串转换为时间戳并比较(倒序)
return (
new Date(b.startTime).getTime() -
new Date(a.startTime).getTime()
);
}),
};
});
}
console.log("this.chatHistoryList3", this.chatHistoryList3);
});
},
handlePopupShow() {
this.popupShow = true;
},
scrollToBottom() {
// 如果正在加载更多,不执行滚动
// 新建对话时不执行滚动
if (this.isLoadingMore) return;
// 使用nextTick确保DOM已更新
this.$nextTick(() => {
// setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query
.select(".chat-content")
.boundingClientRect((data) => {
if (data) {
console.log("chat-content高度:", data.height);
// 同时设置scrollTop和scrollToView
this.scrollTop = data.height + 200;
// if (this.messageGroups.length > 0) {
// const lastMessage =
// this.messageGroups[this.messageGroups.length - 1];
// this.scrollToView = "msg-" + lastMessage.id;
// console.log("设置scrollToView:", this.scrollToView);
// }
}
})
.exec();
// }, 300);
});
},
handleFeatureClick(item) {
if (item.title === "首页") {
this.resetChatState({ isChat: false });
return;
}
if (item.title === "电话咨询") {
this.advicePhoneShow = true;
return;
} else if (item.path) {
uni.navigateTo({
url: item.path,
});
return;
} else {
this.$refs.uToast.show({
title: "暂未开放",
type: "warning",
});
return;
}
},
// 修改发送消息的方法
sendMessageFn() {
if (!this.messageValue) {
return;
}
// 重置加载更多标志位
this.isLoadingMore = false;
const sendMessage = this.messageValue;
console.log("发送消息", sendMessage);
// 创建新的用户消息对象
const userMessage = {
id: Math.random().toString(36).substring(2, 15),
message: sendMessage,
sendDate: "",
isSend: true,
isRead: false,
interactMode: 0, // 用户消息
messageType: 0,
timeLabel: 0,
displayTime: "",
};
// 添加到消息列表
this.messageGroups.push(userMessage);
this.messageValue = "";
// 立即添加一个AI回复的加载状态消息
const loadingMessage = {
id: "loading_" + Math.random().toString(36).substring(2, 15),
message: "",
sendDate: "",
isSend: true,
isRead: false,
interactMode: 1, // AI消息
messageType: 0,
timeLabel: 0,
displayTime: "",
isLoading: true, // 标记为加载状态
};
// 添加加载状态消息到列表
this.messageGroups.push(loadingMessage);
const params = {
query: sendMessage,
conversationId: this.currentConversationId || "",
};
if (this.currentDMid) {
params.dmId = this.currentDMid;
}
this.$u.api
.SendMessageApi(params)
.then((res) => {
console.log("res.....", res);
const data = res.data;
this.currentConversationId = data.conversationId;
this.currentDMid = data.dmid;
// 从消息列表中移除加载状态消息
this.messageGroups = this.messageGroups.filter(
(msg) => !msg.isLoading
);
// 创建AI回复消息对象
const aiMessage = {
id:
data.conversationId ||
Math.random().toString(36).substring(2, 15),
message: data.content,
sendDate: "",
isSend: true,
isRead: false,
interactMode: 1, // AI消息
messageType: 0,
timeLabel: 0,
displayTime: "",
};
// 添加到消息列表
this.messageGroups.push(aiMessage);
})
.catch((error) => {
console.error("API请求失败:", error);
// 从消息列表中移除加载状态消息
this.messageGroups = this.messageGroups.filter(
(msg) => !msg.isLoading
);
// 添加错误消息
const errorMessage = {
id: "error_" + Math.random().toString(36).substring(2, 15),
message: "请求失败,请稍后重试",
sendDate: "",
isSend: true,
isRead: false,
interactMode: 1, // AI消息
messageType: 0,
timeLabel: 0,
displayTime: "",
};
this.messageGroups.push(errorMessage);
});
},
// 点击热门问题
handleQAClick(item) {
// 如果不在对话模式,切换到对话模式并初始化
if (!this.isChat) {
this.resetChatState();
}
const sendMessage = item.content;
console.log("点击热门问题", sendMessage);
// 重置加载更多标志位
this.isLoadingMore = false;
// 创建新的用户消息对象
const userMessage = {
id: Math.random().toString(36).substring(2, 15),
message: sendMessage,
sendDate: "",
isSend: true,
isRead: false,
interactMode: 0, // 用户消息
messageType: 0,
timeLabel: 0,
displayTime: "",
};
// 添加到消息列表
this.messageGroups.push(userMessage);
// 立即添加一个AI回复的加载状态消息
const loadingMessage = {
id: "loading_" + Math.random().toString(36).substring(2, 15),
message: "",
sendDate: "",
isSend: true,
isRead: false,
interactMode: 1, // AI消息
messageType: 0,
timeLabel: 0,
displayTime: "",
isLoading: true, // 标记为加载状态
};
// 添加加载状态消息到列表
this.messageGroups.push(loadingMessage);
const params = {
id: item.id,
};
if (this.currentDMid) {
params.dmId = this.currentDMid;
}
this.$u.api
.GetHotQuestionsFromId(params)
.then((res) => {
if (res.succeed) {
console.log("hotQuestionsDetail.....", res.data);
this.currentDMid = res.data.dmId;
const data = res.data.entityInfo;
// 从消息列表中移除加载状态消息
this.messageGroups = this.messageGroups.filter(
(msg) => !msg.isLoading
);
// 创建AI回复消息对象
const aiMessage = {
id:
data.conversationId ||
Math.random().toString(36).substring(2, 15),
message: data.detailedExplanation,
sendDate: "",
isSend: true,
isRead: false,
interactMode: 1, // AI消息
messageType: 0,
timeLabel: 0,
displayTime: "",
};
// 添加到消息列表
this.messageGroups.push(aiMessage);
} else {
// 从消息列表中移除加载状态消息
this.messageGroups = this.messageGroups.filter(
(msg) => !msg.isLoading
);
}
})
.catch((error) => {
console.error("API请求失败:", error);
// 从消息列表中移除加载状态消息
this.messageGroups = this.messageGroups.filter(
(msg) => !msg.isLoading
);
// 添加错误消息
const errorMessage = {
id: "error_" + Math.random().toString(36).substring(2, 15),
message: "请求失败,请稍后重试",
sendDate: "",
isSend: true,
isRead: false,
interactMode: 1, // AI消息
messageType: 0,
timeLabel: 0,
displayTime: "",
};
this.messageGroups.push(errorMessage);
});
},
// 格式化时间的辅助方法
formatTime(date) {
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
return `${hours}:${minutes}`;
},
// 处理选中的对话
handleSelectConversation(data) {
// 关闭弹窗
this.popupShow = false;
console.log("选中的对话111:", data);
// 根据conversationId加载对应的对话内容
this.currentConversationId = data.conversationId;
this.currentDMid = data.id;
this.isChat = true;
// 设置切换对话标志位,防止触发上拉刷新
this.isSwitchingConversation = true;
// 重置消息列表和分页相关状态
this.messageGroups = [];
this.pageQuery.PageIndex = 1;
this.isLoadingMore = false;
this.noMoreData = false;
// 刷新当前对话的消息详情
this.handleGetConversationDetail();
},
// 公共排序:按时间升序;时间相同,用户消息(interactMode=0)在前
sortMessages(list = []) {
return (list || []).sort((a, b) => {
const timeA = new Date(a.sendDate).getTime();
const timeB = new Date(b.sendDate).getTime();
if (timeA === timeB) return a.interactMode - b.interactMode;
return timeA - timeB;
});
},
// 刷新当前对话的消息详情
handleGetConversationDetail() {
this.$u.api
.GetConversationDetail({
"Item1.Id": this.currentDMid,
PageIndex: this.pageQuery.PageIndex,
PageSize: this.pageQuery.PageSize,
})
.then((res) => {
console.log("GetConversationDetail.....", res.item2);
const currentList = res.item2 || [];
// 将消息按sendDate升序排列时间相同时用户消息(interactMode=0)排在前面
this.messageGroups = this.sortMessages(currentList);
})
.finally(() => {
// 延迟重置切换对话标志位,确保滚动事件处理完成
setTimeout(() => {
this.isSwitchingConversation = false;
}, 500);
});
},
// 刷新当前页数据;若当前页为空且页码>1则自动回退上一页
refreshPageWithFallback() {
const currentIndex = this.pageQuery.PageIndex || 1;
const pageSize = this.pageQuery.PageSize;
return this.$u.api
.GetConversationDetail({
"Item1.Id": this.currentDMid,
PageIndex: currentIndex,
PageSize: pageSize,
})
.then((res2) => {
const currentList = res2?.item2 || [];
if ((!currentList || currentList.length === 0) && currentIndex > 1) {
const prevIndex = currentIndex - 1;
return this.$u.api
.GetConversationDetail({
"Item1.Id": this.currentDMid,
PageIndex: prevIndex,
PageSize: pageSize,
})
.then((res3) => {
const prevList = res3?.item2 || [];
this.messageGroups = this.sortMessages(prevList);
this.pageQuery.PageIndex = prevIndex;
});
} else {
this.messageGroups = this.sortMessages(currentList);
}
});
},
// 在开始新对话或选择对话时重置相关状态
handleCreateConversation() {
// 关闭弹窗
this.popupShow = false;
this.resetChatState();
},
// 开始新对话
handleStartChat() {
this.resetChatState();
},
// 滚动到底部事件处理
onScrollToLower() {
console.log("已滚动到底部");
},
// 滚动到顶部事件处理
onScrollToUpper() {
console.log("触发上拉刷新");
// 如果已经没有更多数据或正在切换对话或当前对话为空(新建对话),不再触发上拉刷新
if (this.noMoreData || this.isSwitchingConversation) {
return;
}
// 设置加载标志位为true防止触发滚动到底部
this.isLoadingMore = true;
this.isLoading = true;
this.pageQuery.PageIndex++;
this.$u.api
.GetConversationDetail({
"Item1.Id": this.currentDMid,
PageIndex: this.pageQuery.PageIndex,
PageSize: this.pageQuery.PageSize,
})
.then((res) => {
console.log("GetConversationDetail.....", res.item2);
// 如果返回的数据为空数组,说明没有更多历史消息了
if (!res.item2 || res.item2.length === 0) {
this.noMoreData = true; // 设置标记,不再触发上拉刷新
this.$refs.uToast.show({
title: "已经到顶了",
type: "warning",
duration: 1500,
});
return;
}
// 将消息按sendDate升序排列时间相同时用户消息(interactMode=0)排在前面
const nextPageMessageGroups = res.item2.sort((a, b) => {
const timeA = new Date(a.sendDate).getTime();
const timeB = new Date(b.sendDate).getTime();
// 如果时间相同按interactMode排序0排在前1排在后
if (timeA === timeB) {
return a.interactMode - b.interactMode;
}
// 否则按时间升序排列
return timeA - timeB;
});
this.messageGroups = [
...nextPageMessageGroups,
...this.messageGroups,
];
})
.finally(() => {
setTimeout(() => {
this.isLoading = false;
}, 300);
});
},
// 回答反馈:点赞/点踩(统一调用刷新&上一页回退逻辑)
handleFeedback(message, isHelp) {
this.$u.api.ModifyStatus({ id: message.id, isHelp }).then((res) => {
if (!res.succeed) return;
this.$u.toast("操作成功");
// 刷新当前页;若空则自动回退上一页并刷新
this.refreshPageWithFallback();
});
},
// 获取热门问题
hotQARefresh() {
console.log("刷新问题");
this.$u.api.GetHotQuestions({}).then((res) => {
if (res.succeed) {
console.log("GetHotQuestions.....", res.data); // enrollment 招生咨询 orientation 迎新咨询
this.hotQuestions = res.data.enrollment.concat(res.data.orientation);
}
});
},
},
};
</script>
<style lang="scss" scoped>
.home-container {
height: 100vh;
// background-color: #f5f7fc;
padding-bottom: calc(
112rpx + env(safe-area-inset-bottom)
); /* 为自定义tabBar预留空间 */
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;
/* Header样式移至HeaderBar组件 */
.main-content {
padding: 30rpx;
padding-top: 60rpx;
.welcome-message {
// display: inline-block;
// flex-wrap: wrap;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 32rpx;
// margin-bottom: 100rpx;
.avatar {
display: inline-block;
width: 50rpx;
height: 44rpx;
margin-right: 12rpx;
}
.message-text {
font-size: 28rpx;
color: #333333;
font-family: PingFang SC;
vertical-align: super;
}
}
.qa-section {
background-image: url("@/static/common/images/hot-question-bg11.png");
background-repeat: no-repeat;
background-size: 110% 130%;
background-position: 0 0;
// background-attachment: fixed;
// background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
padding-top: 0rpx;
margin-bottom: 32rpx;
.qa-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.qa-title {
font-family: DouyinSans;
font-weight: bold;
font-size: 36rpx;
color: #5255e6;
line-height: 36px;
background: linear-gradient(-56deg, #4d50dd 0%, #3e6aff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.more-link {
display: flex;
align-items: center;
.icon-refresh {
width: 36rpx;
height: 36rpx;
margin-right: 8rpx;
}
.more-text {
font-size: 26rpx;
color: #333;
}
}
}
.qa-list {
.qa-item {
margin-bottom: 16rpx;
.qa-question {
font-size: 28rpx;
color: #333333;
line-height: 1.5;
}
}
}
.chat-button {
display: flex;
align-items: center;
justify-content: center;
background-color: #4377fe;
border-radius: 16rpx;
height: 88rpx;
width: 100%;
margin-top: 30rpx;
border: none;
.chat-icon {
width: 38rpx;
height: 32rpx;
margin-right: 12rpx;
}
.chat-text {
color: #ffffff;
font-size: 32rpx;
}
}
}
.start-chat {
.chat-button {
display: flex;
align-items: center;
justify-content: center;
background-color: #4377fe;
border-radius: 16rpx;
height: 88rpx;
width: 100%;
margin-top: 30rpx;
border: none;
.chat-icon {
width: 38rpx;
height: 32rpx;
margin-right: 12rpx;
}
.chat-text {
color: #ffffff;
font-size: 32rpx;
}
}
}
.feature-grid {
background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
display: flex;
justify-content: space-between;
margin-top: 32rpx;
gap: 30rpx;
.feature-item {
height: 150rpx;
border-radius: 16rpx;
display: flex;
justify-content: flex-start;
align-items: center;
padding-left: 30rpx;
gap: 20rpx;
flex: 1;
.feature-icon {
width: 80rpx;
height: 80rpx;
margin-top: 16rpx;
}
.feature-text {
font-size: 26rpx;
color: #333333;
}
}
}
}
.chat-container {
display: flex;
flex-direction: column;
padding: 0 30rpx;
box-sizing: border-box;
height: calc(100vh - 88rpx - 180rpx);
position: relative;
overflow: hidden;
.chat-scroll {
flex: 1;
height: 100%;
overflow-y: scroll;
}
.chat-card {
background-image: url("@/static/common/images/hot-question-bg2.png");
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: 0 0;
// background-color: #ffffff;
// border-radius: 16rpx;
padding: 32rpx;
margin: 32rpx 0;
.chat-card-title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.chat-card-title {
font-family: DouyinSans;
font-weight: bold;
font-size: 36rpx;
color: #5255e6;
// line-height: 24rpx;
background: linear-gradient(-56deg, #4d50dd 0%, #3e6aff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.more-link {
display: flex;
align-items: center;
.icon-refresh {
width: 36rpx;
height: 36rpx;
margin-right: 8rpx;
}
.more-text {
font-size: 26rpx;
color: #333;
}
}
}
.chat-card-desc {
font-family: PingFang SC;
font-weight: 500;
font-size: 24rpx;
color: #9ba5c7;
line-height: 24rpx;
margin-bottom: 12rpx;
}
.qa-list {
margin-top: 32rpx;
.qa-item {
margin-top: 16rpx;
.qa-question {
font-size: 28rpx;
color: #333333;
line-height: 1.5;
}
}
}
}
.chat-content {
flex: 1;
padding: 20rpx 0;
margin-bottom: 150rpx;
.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;
}
.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;
// width: 70%;
padding: 20rpx 24rpx;
border-radius: 0 16rpx 16rpx 16rpx;
font-size: 28rpx;
line-height: 1.5;
/* 加载动画样式 */
.loading-dots {
display: flex;
align-items: center;
justify-content: flex-start;
.dot {
display: inline-block;
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: #cccccc;
margin: 0 6rpx;
opacity: 0.6;
animation: dot-flashing 1.5s infinite linear alternate;
&:nth-child(1) {
animation-delay: 0s;
}
&:nth-child(2) {
animation-delay: 0.5s;
}
&:nth-child(3) {
animation-delay: 1s;
}
}
@keyframes dot-flashing {
0% {
opacity: 0.6;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.2);
background-color: #4370fe;
}
100% {
opacity: 0.6;
transform: scale(1);
}
}
}
/* Markdown内容样式调整 */
/deep/ .markdown-container {
padding: 0;
/* 覆盖默认的padding避免额外空白 */
p {
margin: 0;
padding: 0;
}
/* 确保代码块不会超出气泡 */
pre {
max-width: 100%;
overflow-x: auto;
}
/* 调整图片大小 */
img {
max-width: 100%;
}
/* 调整表格样式 */
table {
font-size: 24rpx;
}
}
}
.message-content-width {
width: 70%;
}
/* 回答反馈容器,跟随左侧消息,右下角对齐 */
.feedback-container {
width: 70%;
margin-left: 80rpx; /* 头像宽度64rpx + 间距16rpx */
display: flex;
justify-content: flex-end;
margin-top: 16rpx;
}
.feedback-box {
display: flex;
align-items: center;
background-color: #ffffff;
padding: 4rpx 12rpx;
border-radius: 20rpx;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
}
.feedback-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.feedback-divider {
width: 2rpx;
height: 36rpx;
margin: 0 8rpx;
background-color: #e9edf7;
border-radius: 2rpx;
}
}
.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;
}
}
}
.chat-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
.floating-tabs {
display: flex;
justify-content: flex-start;
margin: 0 0 20rpx 20rpx;
.tab-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #ffffff;
padding: 8rpx 20rpx;
border-radius: 50rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
margin-right: 12rpx;
.tab-icon {
width: 24rpx;
height: 24rpx;
margin-right: 10rpx;
}
text {
font-size: 24rpx;
color: #333333;
}
}
}
.input-container {
padding: 32rpx;
background-color: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
.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;
}
}
}
}
}
}
}
/* 响应式布局 - PC端样式 */
@media screen and (min-width: 768px) {
.home-container {
.content {
max-width: 1200rpx;
margin: 0 auto;
.qa-section {
.qa-list {
display: flex;
flex-wrap: wrap;
.qa-item {
width: 48%;
margin-right: 2%;
}
}
}
.feature-grid {
max-width: 1200rpx;
margin: 40rpx auto 0;
}
}
}
}
</style>