|
|
<script setup> // 导入
import { ref, computed, onMounted, watch, nextTick } from "vue"; import { setHeight } from "../utils/setHeight"; import { getUserCountAPI } from "../api/AIxiaocaishen"; import AIchat from "./AIchat.vue"; import AIfind from "./AIfind.vue"; import { useAppBridge } from '../assets/js/useAppBridge.js' import { useDataStore } from '@/store/dataList.js' import { useChatStore } from '../store/chat' import { useAudioStore } from '../store/audio' // import { useUserStore } from "../store/userPessionCode.js";
const { getQueryVariable } = useDataStore() // 变量
// 音频管理
const audioStore = useAudioStore() const isVoice = computed(() => audioStore.isVoiceEnabled) const toggleVoice = () => { audioStore.toggleVoice() } // 将默认值改为从 sessionStorage 中获取,如果没有则使用默认值 'aifindCow'为第一个默认tab
const activeTab = ref(sessionStorage.getItem("activeTabAI") || "AIchat"); const activeIndex = ref( parseInt(sessionStorage.getItem("activeIndexAI") || "0") );
const tabs = computed(() => [ { name: "AIchat", label: "AI对话", }, { name: "AIfind", label: "发现", }, ]);
// 修改 setActiveTab 方法,添加一个可选参数 forceAIchat
const setActiveTab = (tab, index, forceAIchat = false) => { isAnnouncementVisible.value = false; if (forceAIchat && activeTab.value !== "AIchat") { activeTab.value = "AIchat"; activeIndex.value = 0; sessionStorage.setItem("activeTabAI", "AIchat"); sessionStorage.setItem("activeIndexAI", "0"); } else { activeTab.value = tab; activeIndex.value = index; sessionStorage.setItem("activeTabAI", tab); sessionStorage.setItem("activeIndexAI", index.toString()); } setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
};
// 修改 activeComponent 的计算逻辑
const activeComponent = computed(() => { if (isAnnouncementVisible.value) { return Announcement; } return activeTab.value === "AIchat" ? AIchat : AIfind; });
// 新增一个方法,调用时先判断是否处于 AIchat,若不在则跳转到 AIchat
const ensureAIchat = () => { setActiveTab("AIchat", 0, true); };
// 获取次数
const UserCount = ref(0); const getUserCount = async () => { const result = await getUserCountAPI({ token: localStorage.getItem('localToken') }); UserCount.value = result.data.hasCount;
};
const getCount = () => { console.log('点击了获取次数的按钮') }
// 深度思考
const isThinking = ref(true); const toggleThink = () => { isThinking.value = !isThinking.value; };
// 发送消息
const message = ref(""); // 传输对象
const messages = ref([]); // 信息加载状态
const isLoading = ref(false);
// 添加用户消息
const updateMessage = (title) => { message.value = title; // console.log("updateMessage 的值:", title);
}; const sendMessage = async () => { // 调用 ensureAIchat 确保跳转到 AIchat 页面
ensureAIchat();
if (!message.value) return; if (isLoading.value) return;
// 发送消息时,设置 isLoading 为 true
messages.value = [ ...messages.value, { sender: "user", content: message.value, timestamp: new Date().toISOString(), } ];
// 重置消息输入框
message.value = ""; };
// 公告
// 引入公告组件
import Announcement from "./Announcement.vue";
// 新增一个变量来控制是否显示公告页面
const isAnnouncementVisible = ref(false);
const showAnnouncement = () => { console.log("打开公告"); isAnnouncementVisible.value = true; // 显示公告页面
activeTab.value = 'Announcement`' };
// 点击剩余次数会弹出的弹窗
// 新增一个 ref 来控制弹窗的显示与隐藏
const dialogVisible = ref(false); // 获取次数
const showCount = () => { console.log("显示剩余次数"); // 显示弹窗
dialogVisible.value = true; console.log("dialogVisible 的值:", dialogVisible.value); // 添加日志确认
};
// 保证发送消息时,滚动屏在底部
const chatStore = useChatStore() const tabContent = ref(null); const isScrolling = ref(false); //判断用户是否在滚动
const smoothScrollToBottom = async () => { await nextTick(); const container = tabContent.value; if (!container) return; if (!isScrolling.value) container.scrollTop = container.scrollHeight - container.offsetHeight;
requestAnimationFrame(() => { container.scrollTop = container.scrollHeight - container.offsetHeight; });
}
const handleScroll = function () { const scrollContainer = tabContent.value const scrollTop = scrollContainer.scrollTop const scrollHeight = scrollContainer.scrollHeight const offsetHeight = scrollContainer.offsetHeight console.log(scrollTop, scrollHeight, offsetHeight, "scrollTop, scrollHeight, offsetHeight"); if (scrollTop + offsetHeight < scrollHeight) { // 用户开始滚动并在最底部之上,取消保持在最底部的效果
isScrolling.value = true } else { // 用户停止滚动并滚动到最底部,开启保持到最底部的效果
isScrolling.value = false } console.log(isScrolling.value)
}
watch( () => chatStore.messages, () => { smoothScrollToBottom(); }, { deep: true, immediate: true } );
watch( activeTab, async (newVal) => { smoothScrollToBottom(); });
// 在setTimeout中延迟执行
setTimeout(() => { fnGetToken() }, 800)
// 获取token的核心函数
const fnGetToken = () => { window.JWready = (ress) => { // 处理平台判断
if (!ress.data.platform) { // 非App环境通过URL参数获取
localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token')))) // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
} else { // App环境通过桥接获取
useAppBridge().packageFun( 'JWgetStorage', (response) => { const res = JSON.parse(response) // 解析返回的结果
localStorage.setItem('localToken', res.data) // localStorage.setItem('localToken', "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w")
}, 5, { key: 'token' } ) } } // 触发App桥接
useAppBridge().packageFun('JWwebReady', () => { }, 5, {}) }
onMounted(async () => { setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
getUserCount(); smoothScrollToBottom(); // 监听滚动事件,判断用户滚动状态
tabContent.value.addEventListener('scroll', handleScroll)
}) </script>
<template> <div class="homepage" id="testId"> <el-container> <!-- AI小财神头部: logo 次数 公告 --> <el-header class="homepage-head"> <!-- logo --> <div class="homepage-logo"> <img src="src\assets\img\homePage\logo.png" alt="图片加载失败" class="logo1" /> <img src="src\assets\img\homePage\madeInHL.png" alt="图片加载失败" class="logo2" /> </div>
<div class="homepage-right-group"> <div class="count-badge" @click="showCount"> <img src="src\assets\img\homePage\get-count-all.png" class="action-btn" /> <div class="count-number">{{ UserCount }}次</div> </div> <img src="src\assets\img\homePage\announcement.png" class="announcement-btn action-btn" @click="showAnnouncement" /> </div> </el-header> <!-- 主体部分:小人 问题轮询图 对话内容 --> <el-main class="homepage-body"> <div class="main-wrapper"> <section class="tab-section"> <div class="tab-container"> <div v-for="(tab, index) in tabs" :key="tab.name" @click="setActiveTab(tab.name, index)" :class="['tab-item', { active: activeIndex === index }]"> <span>{{ tab.label }}</span> </div> </div> </section> <div class="tab-content" ref="tabContent"> <component :is="activeComponent" :messages="messages" @updateMessage="updateMessage" @sendMessage="sendMessage" /> </div> </div> </el-main> <!-- 尾部: 问题输入框 深度思考 多语言 语音播报 --> <el-footer class="homepage-footer"> <!-- 第一行按钮 --> <div class="footer-first-line"> <div class="left-group"> <img v-if="isThinking" src="src\assets\img\homePage\tail\think-active.png" @click="toggleThink" class="action-btn" /> <img v-else src="src\assets\img\homePage\tail\think-no-active.png" @click="toggleThink" class="action-btn" /> <img src="src\assets\img\homePage\tail\language.png" @click="changeLanguage" class="action-btn" /> <img v-if="isVoice" src="src\assets\img\homePage\tail\voice.png" @click="toggleVoice" class="action-btn" /> <img v-else src="src\assets\img\homePage\tail\voice-no-active.png" @click="toggleVoice" class="action-btn" /> </div> <img src="src\assets\img\homePage\tail\send.png" @click="sendMessage" class="action-btn send-btn" /> </div>
<!-- 第二行输入框 --> <div class="footer-second-line"> <img src="src\assets\img\homePage\tail\msg.png" class="msg-icon" /> <el-input type="textarea" v-model="message" :autosize="{ minRows: 1, maxRows: 4 }" placeholder="给AI小财神发消息..." class="msg-input" @keydown.enter.exact.prevent="isLoading ? null : sendMessage()" resize="none"> </el-input> </div> </el-footer> </el-container> <!-- 弹窗 --> <!-- 新增弹窗组件 --> <el-dialog v-model="dialogVisible" width="65%"> <!-- 自定义标题插槽,实现居中显示 --> <template #header> <div style="text-align: center"> <span>活动规则</span> </div> </template> <!-- 中间内容部分 --> <p> 活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则活动规则 </p> <template #footer> <!-- 添加一个div来包裹按钮,并设置样式使其居中 --> <div style="text-align: center"> <el-button style="background-color: orange; color: white; border: none" @click="dialogVisible = false"> 去充值 </el-button> </div> </template> </el-dialog> </div> </template>
<style scoped> /* 标签栏 */ .tab-container { display: flex; gap: 30px; margin-bottom: 10px; padding: 0 20px; justify-content: flex-end; /* 新增右对齐 */ }
.tab-item { cursor: pointer; padding: 8px 12px; font-size: clamp(18px, 3vw, 20px); color: #999; transition: all 0.3s; border-bottom: 2px solid transparent; font-weight: bold; }
.tab-item.active { color: #000; border-color: #000; }
.tab-item:not(.active):hover { color: #666; }
.tab-content { /* height: 100%; */ overflow-y: auto; overflow-x: hidden; scroll-behavior: smooth; /* 添加平滑滚动效果 */ }
@media (max-width: 768px) { .tab-container { gap: 15px; padding: 0 10px; }
.tab-item { font-size: clamp(14px, 3vw, 16px); padding: 6px 10px; } } </style>
<style scoped> .homepage { height: 100vh; margin: 0 auto; background-image: url(src/assets/img/homePage/bk.png); background-size: 100% 100%; background-repeat: no-repeat; background-position: center; display: flex; }
.homepage .el-container { height: 100%; flex-direction: column; /* 明确纵向排列 */ display: flex; overflow: hidden; }
.el-container .el-header { height: 10%; /* 设置头部高度 */ margin-top: 5px; }
.el-container .el-main { overflow-y: hidden; overflow-x: hidden; /* 新增滚动条 */ flex: 1 1 0%; min-height: 0; scrollbar-width: thin; scrollbar-color: #888 transparent; }
.el-container .el-footer { /* height: 11%; */ height: auto; min-height: 70px; gap: 5px; margin-top: 0; }
.homepage-head { padding: 0px; /* 启用 flex 布局 */ display: flex; position: relative; /* 左右分开布局 */ justify-content: space-between; }
.homepage-right-group { display: flex; gap: 8px; align-items: center; margin-left: auto; margin-right: 20px; }
.homepage-right-group .action-btn { height: 40px; }
.homepage-right-group .count-badge { position: relative; cursor: pointer; }
.homepage-right-group .count-badge .count-number { position: absolute; top: 6px; right: 29px; color: #573dfc; font-size: 12px; font-weight: bold; }
.homepage-right-group .announcement-btn { cursor: pointer; transition: transform 0.2s; }
.homepage-right-group .announcement-btn:hover { transform: scale(1.05); }
.homepage-body { padding: 0px; height: calc(100% - 70px); /* 根据底部高度调整 */ }
.main-wrapper { height: 100%; display: flex; flex-direction: column; }
.tab-section { flex-shrink: 0; /* 禁止伸缩 */ }
.tab-content { flex: 1; overflow-y: auto; min-height: 0; /* 关键:允许内容收缩 */ }
.homepage-logo { height: 100%; /* 改为根据内容自适应 */ width: fit-content; display: flex; flex-direction: column; align-items: center; justify-content: center; /* 固定左间距 */ margin-left: 20px; margin-right: auto; /* 新增相对定位 */ position: relative; }
/* 新增媒体查询适配小屏幕 */ @media (max-width: 768px) { .homepage-logo { margin-left: 10px; left: 0; } }
.logo1 { width: 120px; /* 固定 logo1 尺寸 */ height: auto; margin-bottom: 8px; /* 添加间距 */ }
.logo2 { width: 80px; /* 缩小 logo2 尺寸 */ height: auto; }
/* 尾部 */ .homepage-footer { display: flex; flex-direction: column; gap: 5px; /* margin-top: auto; */ margin-bottom: 20px; flex-shrink: 0; height: auto; }
.footer-first-line { display: flex; justify-content: space-between; align-items: center; margin-bottom: auto; flex-shrink: 0; }
.left-group { display: flex; gap: 15px; }
.action-btn { cursor: pointer; transition: transform 0.2s; height: 28px; }
.action-btn:hover { transform: scale(1.05); }
.send-btn { margin-left: auto; margin-right: 5px; }
.footer-second-line { position: relative; display: flex; height: auto; align-items: flex-end; flex: 1; margin-top: auto; min-height: 34px; }
.msg-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); width: 24px; z-index: 1; }
.msg-input:deep(.el-textarea__inner) { border: none !important; box-shadow: none !important; overflow-y: auto !important; /* 强制显示滚动条 */ transition: all 0.2s ease-out; /* 添加过渡效果 */ }
/* .msg-input:deep(.el-textarea__inner:focus) { border: none !important; box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3) !important; } */
.msg-input { min-height: 34px; max-height: 120px; width: calc(100% - 65px);
border-radius: 20px;
padding: 0px 20px 0 45px; font-size: 16px; transition: height 0.2s ease-out; /* 添加高度过渡效果 */ overflow-y: hidden; /* 隐藏垂直滚动条 */ box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3); background: #fff; }
.msg-input:focus { outline: none; }
@media (max-width: 768px) { .action-btn { height: 28px; }
.footer-second-line { height: 50px; }
.msg-input { font-size: 16px; } } </style>
|