You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1081 lines
29 KiB
1081 lines
29 KiB
<script setup>
|
|
// 导入
|
|
import { ref, computed, onMounted, watch, nextTick, onUnmounted } from "vue";
|
|
import { setHeight } from "../utils/setHeight";
|
|
import { getUserCountAPI } from "../api/AIxiaocaishen";
|
|
import { ElMessage } from "element-plus";
|
|
import AIchat from "./AIchat.vue";
|
|
import AIfind from "./AIfind.vue";
|
|
import Feedback from "./Feedback.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 _ from "lodash";
|
|
|
|
import logo from "../assets/img/homePage/logo.png";
|
|
import madeInHL from "../assets/img/homePage/madeInHL.png";
|
|
import getCountAll from "../assets/img/homePage/get-count-all.png";
|
|
import announcementBtn from "../assets/img/homePage/announcement.png";
|
|
import thinkActive from "../assets/img/homePage/tail/think-active.png";
|
|
import thinkNoActive from "../assets/img/homePage/tail/think-no-active.png";
|
|
import languageBtn from "../assets/img/homePage/tail/language.png";
|
|
import dbqbButton01 from "../assets/img/AiEmotion/dbqb-button01.png";
|
|
import dbqbButton02 from "../assets/img/AiEmotion/dbqb-button02.png";
|
|
import emotionButton01 from "../assets/img/AiEmotion/emotion-button01.png";
|
|
import emotionButton02 from "../assets/img/AiEmotion/emotion-button02.png";
|
|
import voice from "../assets/img/homePage/tail/voice.png";
|
|
import voiceNoActive from "../assets/img/homePage/tail/voice-no-active.png";
|
|
import sendBtn from "../assets/img/homePage/tail/send.png";
|
|
import msgBtn from "../assets/img/homePage/tail/msg.png";
|
|
import feedbackBtn from "../assets/img/Feedback/feedbackBtn.png";
|
|
import AiEmotion from "./AiEmotion.vue";
|
|
|
|
// import VConsole from 'vconsole';
|
|
|
|
// const vConsole = new VConsole();
|
|
|
|
// 获取 AiEmotion 组件的 ref
|
|
const aiEmotionRef = ref(null);
|
|
// import { useUserStore } from "../store/userPessionCode.js";
|
|
const { getQueryVariable, setActiveTabIndex } = useDataStore();
|
|
const dataStore = useDataStore();
|
|
const chatStore = useChatStore();
|
|
// 变量
|
|
// 音频管理
|
|
const audioStore = useAudioStore();
|
|
const isVoice = computed(() => audioStore.isVoiceEnabled);
|
|
const toggleVoice = () => {
|
|
if (!audioStore.isVoiceEnabled) {
|
|
// 如果语音功能关闭,先开启语音功能
|
|
audioStore.toggleVoice();
|
|
} else {
|
|
// 如果语音功能开启,则切换播放/暂停状态
|
|
if (audioStore.currentAudioUrl || audioStore.ttsUrl) {
|
|
// 有音频时切换播放/暂停
|
|
audioStore.togglePlayPause();
|
|
} else {
|
|
// 没有音频时关闭语音功能
|
|
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: "夺宝奇兵大模型",
|
|
},
|
|
// {
|
|
// name: "AIfind",
|
|
// label: "发现",
|
|
// },
|
|
{
|
|
name: "AiEmotion",
|
|
label: "AI情绪大模型",
|
|
},
|
|
]);
|
|
|
|
// 修改 setActiveTab 方法,添加一个可选参数 forceAIchat
|
|
const setActiveTab = (tab, index, forceAIchat = false) => {
|
|
isScrolling.value = 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());
|
|
}
|
|
setActiveTabIndex(index);
|
|
console.log(tab, index, "tab, index");
|
|
setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
|
|
};
|
|
|
|
// 修改 activeComponent 的计算逻辑
|
|
const activeComponent = computed(() => {
|
|
if (isAnnouncementVisible.value) {
|
|
return Announcement;
|
|
}
|
|
if (activeTab.value === "AIchat") {
|
|
return AIchat;
|
|
} else if (activeTab.value === "AIfind") {
|
|
return AIfind;
|
|
} else if (activeTab.value === "AiEmotion") {
|
|
return AiEmotion; // 新增逻辑
|
|
}
|
|
});
|
|
|
|
// 新增一个方法,调用时先判断是否处于 AIchat,若不在则跳转到 AIchat
|
|
const ensureAIchat = () => {
|
|
setActiveTab("AIchat", 0, true);
|
|
};
|
|
|
|
// 获取次数
|
|
const UserCount = computed(() => chatStore.UserCount);
|
|
|
|
const getCount = () => {
|
|
console.log("点击了获取次数的按钮");
|
|
};
|
|
|
|
// 深度思考
|
|
const isThinking = ref(true);
|
|
const toggleThink = () => {
|
|
isThinking.value = !isThinking.value;
|
|
};
|
|
|
|
// 发送消息
|
|
const message = ref("");
|
|
// 传输对象
|
|
const messages = ref([]);
|
|
// 信息加载状态
|
|
const isLoading = computed(() => {
|
|
chatStore.isLoading;
|
|
});
|
|
|
|
// 添加用户消息
|
|
const updateMessage = (title) => {
|
|
message.value = title;
|
|
// console.log("updateMessage 的值:", title);
|
|
};
|
|
const sendMessage = async () => {
|
|
if (
|
|
localStorage.getItem("localToken") == null ||
|
|
localStorage.getItem("localToken") == ""
|
|
) {
|
|
ElMessage.error("请先登录");
|
|
return;
|
|
}
|
|
|
|
// 检查输入内容是否为空
|
|
if (!message.value || !message.value.trim()) {
|
|
ElMessage.warning("输入内容不能为空");
|
|
return;
|
|
}
|
|
|
|
isScrolling.value = false;
|
|
|
|
// 判断当前是否为 AiEmotion 组件
|
|
if (activeTab.value === "AiEmotion") {
|
|
// 调用 AiEmotion 组件的 handleSendMessage 方法
|
|
aiEmotionRef.value?.handleSendMessage(message.value);
|
|
message.value = ""; // 清空输入框
|
|
return;
|
|
}
|
|
|
|
// 调用 ensureAIchat 确保跳转到 AIchat 页面
|
|
ensureAIchat();
|
|
|
|
console.log(chatStore.isLoading, "isLoading.value1111");
|
|
if (chatStore.isLoading) return;
|
|
chatStore.setLoading(true);
|
|
console.log(chatStore.isLoading, "isLoading.value2222");
|
|
|
|
const messageContent = message.value;
|
|
// 重置消息输入框
|
|
message.value = "";
|
|
|
|
setTimeout(() => {
|
|
console.log("延时后添加消息", messageContent);
|
|
// 发送消息时,设置 isLoading 为 true
|
|
messages.value = [
|
|
...messages.value,
|
|
{
|
|
sender: "user",
|
|
content: messageContent,
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
];
|
|
console.log(messages.value, "messages.value");
|
|
}, 200);
|
|
};
|
|
|
|
// 公告
|
|
// 引入公告组件
|
|
import Announcement from "./Announcement.vue";
|
|
|
|
// 新增一个变量来控制是否显示公告页面
|
|
const isAnnouncementVisible = ref(false);
|
|
|
|
const showAnnouncement = async () => {
|
|
console.log("打开公告");
|
|
dataStore.isFeedback = false; // 显示用户反馈页面
|
|
isScrolling.value = false; //回复滚动到底部方法
|
|
setActiveTab("", -1); // 清空当前选中状态
|
|
|
|
isAnnouncementVisible.value = true; // 显示公告页面
|
|
};
|
|
|
|
// 跳转用户反馈
|
|
const showFeedback = () => {
|
|
console.log("打开用户反馈");
|
|
dataStore.isFeedback = true; // 显示用户反馈页面
|
|
};
|
|
|
|
// 点击剩余次数会弹出的弹窗
|
|
// 新增一个 ref 来控制弹窗的显示与隐藏
|
|
const dialogVisible = ref(false);
|
|
// 获取次数
|
|
const showCount = () => {
|
|
console.log("显示剩余次数");
|
|
// 显示弹窗
|
|
dialogVisible.value = true;
|
|
console.log("dialogVisible 的值:", dialogVisible.value); // 添加日志确认
|
|
};
|
|
|
|
// 保证发送消息时,滚动屏在底部
|
|
|
|
const tabContent = ref(null);
|
|
const isScrolling = ref(false); //判断用户是否在滚动
|
|
|
|
const smoothScrollToBottom = async () => {
|
|
console.log("调用滚动到底部的方法");
|
|
// await nextTick();
|
|
const container = tabContent.value;
|
|
// console.log(container, 'container')
|
|
// console.log(isScrolling.value, 'isScrolling.value')
|
|
if (!container) return;
|
|
|
|
await nextTick(); // 确保在DOM更新后执行
|
|
|
|
if (!isScrolling.value) {
|
|
container.scrollTop = container.scrollHeight - container.offsetHeight;
|
|
// container.scrollTop = container.scrollHeight;
|
|
// container.scrollTop = container.offsetHeight;
|
|
// container.scrollTop = container.scrollHeight + container.offsetHeight;
|
|
// console.log(container.scrollHeight, container.offsetHeight, container.scrollHeight - container.offsetHeight, container.scrollTop, "总长度", "可视长度", "位置")
|
|
}
|
|
};
|
|
|
|
const throttledSmoothScrollToBottom = _.throttle(smoothScrollToBottom, 300, {
|
|
trailing: false,
|
|
});
|
|
|
|
watch(
|
|
() => chatStore.messages,
|
|
() => {
|
|
// console.log('messages变化了')
|
|
throttledSmoothScrollToBottom();
|
|
// setTimeout(throttledSmoothScrollToBottom, 100);
|
|
},
|
|
{ deep: true, immediate: true }
|
|
);
|
|
|
|
watch(
|
|
activeTab,
|
|
async () => {
|
|
console.log("activeTab变化了", activeTab.value);
|
|
if (activeTab.value === "AIchat") {
|
|
isScrolling.value = false; //回复滚动到底部方法
|
|
setTimeout(() => {
|
|
throttledSmoothScrollToBottom();
|
|
}, 100);
|
|
}
|
|
// setTimeout(throttledSmoothScrollToBottom, 100);
|
|
},
|
|
{ deep: true, immediate: true }
|
|
);
|
|
|
|
// 获取token的核心函数
|
|
const fnGetToken = () => {
|
|
// console.log('进入fnGetToken')
|
|
window.JWready = (ress) => {
|
|
// console.log('进入JWready')
|
|
try {
|
|
ress = JSON.parse(ress);
|
|
// console.log(ress, 'ress')
|
|
} catch (error) {
|
|
console.log(error, "fnGetToken error");
|
|
} //platform为5是app端
|
|
// platform.value = ress.data.platform
|
|
// 处理平台判断
|
|
console.log(ress.data.platform, "ress.data.platform");
|
|
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",
|
|
}
|
|
);
|
|
}
|
|
};
|
|
// console.log('出来了')
|
|
// 触发App桥接
|
|
useAppBridge().packageFun("JWwebReady", () => {}, 5, {});
|
|
};
|
|
|
|
// 在setTimeout中延迟执行
|
|
setTimeout(() => {
|
|
fnGetToken();
|
|
}, 800);
|
|
|
|
const heightListener = () => {
|
|
const tabContainer = tabContent.value;
|
|
let befortop = 0;
|
|
|
|
const scrollHandler = () => {
|
|
const aftertop = tabContainer.scrollTop;
|
|
|
|
// 新增底部判断逻辑
|
|
const isBottom =
|
|
aftertop + tabContainer.offsetHeight + 70 >= tabContainer.scrollHeight;
|
|
|
|
if (activeTab.value === "AIchat") {
|
|
if (aftertop - befortop > 0) {
|
|
// console.log('向下滚动');
|
|
isScrolling.value = true;
|
|
} else {
|
|
// console.log('向上滚动');
|
|
isScrolling.value = true;
|
|
}
|
|
|
|
// 添加底部状态检测
|
|
if (isBottom) {
|
|
// console.log('滚动到底部');
|
|
isScrolling.value = false;
|
|
}
|
|
}
|
|
befortop = aftertop;
|
|
};
|
|
|
|
// console.log(isScrolling.value, 'isScrolling.value')
|
|
|
|
tabContainer.addEventListener("scroll", scrollHandler);
|
|
};
|
|
|
|
const throttledHeightListener = _.throttle(heightListener, 500, {
|
|
trailing: false,
|
|
});
|
|
|
|
const goToRecharge = () => {
|
|
console.log("点击充值");
|
|
// http://39.101.133.168:8919/payment/recharge/index?
|
|
// url=http%3A%2F%2Flocalhost%3A8080%2FLiveActivity%2Fpck
|
|
// &platform=1
|
|
// &token=+S4h5QEE1hTIb4CxphrnbZi0+fEeMx8pywnIlrmTmo4QO6IolWnVWu5r+J4rKXMwK41UPfKqyIp+RvWmtM8
|
|
|
|
const userAgent = navigator.userAgent.toLowerCase();
|
|
const mobileKeywords = ["mobile", "android", "iphone", "ipad", "ipod"];
|
|
const isMobile = mobileKeywords.some((keyword) =>
|
|
userAgent.includes(keyword)
|
|
);
|
|
|
|
console.log(isMobile ? "手机" : "电脑");
|
|
|
|
const url = encodeURI("http://39.101.133.168:8857/aixiaocaishen/homePage");
|
|
console.log(url, "url");
|
|
const platform = isMobile ? 2 : 1;
|
|
const token = encodeURIComponent(localStorage.getItem("localToken"));
|
|
console.log(token, "token");
|
|
const rechargeUrl =
|
|
"http://39.101.133.168:8919/payment/recharge/index?" +
|
|
"url=" +
|
|
url +
|
|
"&platform=" +
|
|
platform +
|
|
"&token=" +
|
|
token;
|
|
console.log(rechargeUrl, "rechargeUrl");
|
|
window.location.href = rechargeUrl;
|
|
// window.open(rechargeUrl)
|
|
};
|
|
|
|
const adjustFooterPosition = (height) => {
|
|
console.log("调整底部位置", height);
|
|
const footer = document.querySelector(".el-footer");
|
|
const main = document.querySelector(".el-main");
|
|
const homePage = document.querySelector(".homepage");
|
|
const app = document.getElementById("app");
|
|
// Footer 的默认高度(假设为 60px) // 动态推高 Footer
|
|
// footer.style.bottom = `${keyboardHeight}px`;
|
|
// 给 Main 区域留出 Footer + 键盘的空间
|
|
homePage.style.height = `${height}px`;
|
|
// app.style.height = `${height}px`;
|
|
|
|
void homePage.offsetHeight;
|
|
|
|
const html = document.querySelector("html");
|
|
const body = document.querySelector("body");
|
|
|
|
html.style.height = `${height}px`;
|
|
body.style.height = `${height}px`;
|
|
|
|
html.scrollTop = 0;
|
|
|
|
setTimeout(() => {
|
|
// 隐藏滚动条
|
|
html.style.overflow = "hidden";
|
|
body.style.overflow = "hidden";
|
|
}, 200);
|
|
|
|
// console.log(html.offsetHeight, 'html')
|
|
// console.log(html.clientHeight, 'html')
|
|
// console.log(html.scrollHeight, 'htmlScrollHeight')
|
|
// console.log(body.clientHeight, 'body')
|
|
// console.log(body.scrollHeight, 'bodyScrollHeight')
|
|
// console.log(homePage.offsetHeight, 'homePage')
|
|
// console.log(homePage.clientHeight, 'homePageClientHeight')
|
|
// console.log(homePage.scrollHeight, 'homePageScrollHeight')
|
|
// console.log(window.innerHeight, 'window.innerHeight')
|
|
// console.log(window.visualViewport.height, 'window.visualViewport.height')
|
|
// console.log(main.offsetHeight, 'main')
|
|
// console.log(main.clientHeight, 'mainClientHeight')
|
|
// console.log(main.scrollHeight, 'mainScrollHeight')
|
|
};
|
|
|
|
const onFocus = function () {
|
|
const visualViewport = window.visualViewport;
|
|
// 获取可视区域高度
|
|
setTimeout(() => {
|
|
console.log("输入框聚焦");
|
|
|
|
console.log(visualViewport.height, "visualViewport.height");
|
|
const keyboardHeight = window.innerHeight - visualViewport.height;
|
|
console.log(window.innerHeight, "window.innerHeight");
|
|
console.log(keyboardHeight, "keyboardHeight");
|
|
|
|
adjustFooterPosition(visualViewport.height);
|
|
}, 200);
|
|
};
|
|
|
|
const onBlur = function () {
|
|
const visualViewport = window.visualViewport;
|
|
setTimeout(() => {
|
|
console.log("输入框失焦");
|
|
|
|
const keyboardHeight = window.innerHeight - visualViewport.height;
|
|
console.log(window.innerHeight, "window.innerHeight");
|
|
console.log(visualViewport.height, "visualViewport.height");
|
|
console.log(keyboardHeight, "keyboardHeight");
|
|
adjustFooterPosition(visualViewport.height);
|
|
}, 200);
|
|
};
|
|
|
|
window.addEventListener("resize", () => {
|
|
// 检测是否为iOS设备
|
|
const isIOS = /iPhone|iPad|iPod|ios/i.test(navigator.userAgent);
|
|
console.log("是否为iOS设备:", isIOS);
|
|
if (!isIOS) {
|
|
console.log("窗口大小变化");
|
|
const homePage = document.querySelector(".homepage");
|
|
homePage.style.height = `${window.innerHeight}px`;
|
|
}
|
|
});
|
|
|
|
// 禁用全局触摸滚动
|
|
document.addEventListener(
|
|
"touchmove",
|
|
(e) => {
|
|
if (!dataStore.isFeedback) {
|
|
// 判断触摸目标是否在可滚动区域内
|
|
const isScrollableArea = e.target.closest(".tab-content");
|
|
|
|
// 如果不在可滚动区域,则阻止滚动
|
|
if (!isScrollableArea) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
},
|
|
{ passive: false }
|
|
);
|
|
|
|
onMounted(async () => {
|
|
// const isPhone =
|
|
// /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/i.test(
|
|
// navigator.userAgent
|
|
// );
|
|
// !isPhone &&
|
|
// localStorage.setItem(
|
|
// "localToken",
|
|
// decodeURIComponent(String(getQueryVariable("token")))
|
|
// );
|
|
setHeight(document.getElementById("testId")); // 给父组件发送窗口高度
|
|
// 获取次数
|
|
await chatStore.getUserCount();
|
|
// 滚动到底部
|
|
throttledSmoothScrollToBottom();
|
|
// 监听页面高度
|
|
throttledHeightListener();
|
|
|
|
// 添加输入框焦点处理
|
|
// handleInputFocus();
|
|
|
|
// 初始化视口高度变量
|
|
// updateAppHeight();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="homepage" id="testId">
|
|
<el-container v-if="!dataStore.isFeedback">
|
|
<!-- AI小财神头部: logo 次数 公告 -->
|
|
<el-header class="homepage-head">
|
|
<!-- logo -->
|
|
<div class="homepage-logo">
|
|
<img :src="logo" alt="图片加载失败" class="logo1" />
|
|
<!-- <img :src="madeInHL" alt="图片加载失败" class="logo2" /> -->
|
|
</div>
|
|
|
|
<div class="homepage-right-group">
|
|
<div class="count-badge" @click="showCount">
|
|
<img :src="getCountAll" class="action-btn" />
|
|
<div class="count-number">{{ UserCount }}次</div>
|
|
</div>
|
|
<img
|
|
:src="announcementBtn"
|
|
class="announcement-btn action-btn"
|
|
@click="showAnnouncement"
|
|
/>
|
|
<img
|
|
:src="feedbackBtn"
|
|
class="announcement-btn action-btn"
|
|
@click="showFeedback"
|
|
/>
|
|
</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 && !isAnnouncementVisible },
|
|
]"
|
|
>
|
|
<span>{{ tab.label }}</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<div class="tab-content" ref="tabContent">
|
|
<component
|
|
:is="activeComponent"
|
|
:messages="messages"
|
|
@updateMessage="updateMessage"
|
|
@sendMessage="sendMessage"
|
|
@ensureAIchat="ensureAIchat"
|
|
ref="aiEmotionRef"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</el-main>
|
|
<!-- 尾部: 问题输入框 深度思考 多语言 语音播报 -->
|
|
<el-footer class="homepage-footer" id="input">
|
|
<!-- 第一行按钮 -->
|
|
<div class="footer-first-line">
|
|
<div class="left-group">
|
|
<!-- <img v-if="isThinking" :src="thinkActive" @click="toggleThink" class="action-btn" />
|
|
<img v-else :src="thinkNoActive" @click="toggleThink" class="action-btn" />
|
|
<img :src="languageBtn" @click="changeLanguage" class="action-btn" /> -->
|
|
<!-- 夺宝奇兵大模型按钮 -->
|
|
<img
|
|
:src="activeTab === 'AIchat' ? dbqbButton01 : dbqbButton02"
|
|
@click="setActiveTab('AIchat', 0)"
|
|
class="action-btn model-btn"
|
|
alt="夺宝奇兵大模型"
|
|
/>
|
|
<!-- AI情绪大模型按钮 -->
|
|
<img :src="activeTab === 'AiEmotion' ? emotionButton01 : emotionButton02
|
|
" @click="setActiveTab('AiEmotion', 1)" class="action-btn model-btn" alt="AI情绪大模型" />
|
|
<img v-if="audioStore.isVoiceEnabled && !audioStore.isPlaying" :src="voice" @click="toggleVoice" class="action-btn" />
|
|
<img v-else-if="audioStore.isVoiceEnabled && audioStore.isPlaying" :src="voice" @click="toggleVoice" class="action-btn" style="opacity: 0.7; animation: pulse 1.5s infinite;" />
|
|
<img v-else :src="voiceNoActive" @click="toggleVoice" class="action-btn" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 第二行输入框 -->
|
|
<div class="footer-second-line">
|
|
<img :src="msgBtn" class="msg-icon" />
|
|
<el-input type="textarea" v-model="message" @focus="onFocus" @blur="onBlur"
|
|
:autosize="{ minRows: 1, maxRows: 4 }" placeholder="请输入股票名称或股票代码..." class="msg-input"
|
|
@keydown.enter.exact.prevent="isLoading ? null : sendMessage()" resize="none">
|
|
</el-input>
|
|
<img
|
|
v-if="!chatStore.isLoading"
|
|
:src="sendBtn"
|
|
@click="sendMessage"
|
|
class="action-btn send-btn"
|
|
/>
|
|
<!-- <div v-else @click="chatStore.setLoading(false)"> -->
|
|
<div v-else>
|
|
<el-icon class="is-loading">
|
|
<Loading />
|
|
</el-icon>
|
|
</div>
|
|
</div>
|
|
</el-footer>
|
|
</el-container>
|
|
<el-container v-else>
|
|
<el-header class="homepage-head">
|
|
<!-- logo -->
|
|
<div class="homepage-logo">
|
|
<img :src="logo" alt="图片加载失败" class="logo1" />
|
|
<img :src="madeInHL" alt="图片加载失败" class="logo2" />
|
|
</div>
|
|
|
|
<div class="homepage-right-group">
|
|
<div class="count-badge" @click="showCount">
|
|
<img :src="getCountAll" class="action-btn" />
|
|
<div class="count-number">{{ UserCount }}次</div>
|
|
</div>
|
|
<img
|
|
:src="announcementBtn"
|
|
class="announcement-btn action-btn"
|
|
@click="showAnnouncement"
|
|
/>
|
|
<img
|
|
:src="feedbackBtn"
|
|
class="announcement-btn action-btn"
|
|
@click="showFeedback"
|
|
/>
|
|
</div>
|
|
</el-header>
|
|
|
|
<!-- 主体部分:小人 问题轮询图 对话内容 -->
|
|
<el-main class="homepage-body">
|
|
<feedback :is="Feedback" />
|
|
</el-main>
|
|
</el-container>
|
|
<!-- 弹窗 -->
|
|
<!-- 新增弹窗组件 -->
|
|
<el-dialog v-model="dialogVisible" max-width="65%">
|
|
<!-- 自定义标题插槽,实现居中显示 -->
|
|
<template #header>
|
|
<div style="text-align: center">
|
|
<span>活动规则</span>
|
|
</div>
|
|
</template>
|
|
<!-- 中间内容部分 -->
|
|
<div class="ruleContent">
|
|
<p>试运行期间,AI小财神可以检索全市场数据</p>
|
|
<p>(每个市场20支股票,股票详情参见【公告】页面)</p>
|
|
<!-- <p>弘历会员每人每日拥有10次检索机会!</p> -->
|
|
</div>
|
|
<!-- <template #footer> -->
|
|
<!-- 添加一个div来包裹按钮,并设置样式使其居中 -->
|
|
<!-- <div style="text-align: center"> -->
|
|
<!-- <el-button style="background-color: orange; color: white; border: none" @click="goToRecharge"> -->
|
|
<!-- 去充值 -->
|
|
<!-- </el-button> -->
|
|
<!-- </div> -->
|
|
<!-- </template> -->
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* 标签栏 */
|
|
.tab-container {
|
|
display: flex;
|
|
gap: 30px;
|
|
margin-right: 40px;
|
|
margin-left: 40px;
|
|
margin-bottom: 10px;
|
|
padding: 0 20px;
|
|
justify-content: space-between;
|
|
height: 100%;
|
|
/* 新增右对齐 */
|
|
}
|
|
|
|
.tab-item {
|
|
cursor: pointer;
|
|
padding: 8px 12px;
|
|
font-size: clamp(18px, 3vw, 20px);
|
|
/* color: #999; */
|
|
color: #fff;
|
|
transition: all 0.3s;
|
|
border-bottom: 2px solid transparent;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.tab-item.active {
|
|
/* color: #000;
|
|
border-color: #000; */
|
|
background: linear-gradient(0deg, #ffffff, #fec13e);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
color: transparent;
|
|
border-color: #fec13e;
|
|
}
|
|
|
|
.tab-item:not(.active):hover {
|
|
color: #999999;
|
|
}
|
|
|
|
.tab-content {
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
scroll-behavior: smooth;
|
|
height: 100%;
|
|
/* 添加平滑滚动效果 */
|
|
}
|
|
|
|
@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>
|
|
html {
|
|
height: 100dvh;
|
|
overflow: hidden !important;
|
|
position: fixed;
|
|
margin: 0;
|
|
padding: 0;
|
|
-webkit-overflow-scrolling: auto;
|
|
/* 禁用 iOS 弹性滚动 */
|
|
}
|
|
|
|
body {
|
|
height: 100dvh;
|
|
overflow: clip;
|
|
margin: 0;
|
|
padding: 0;
|
|
-webkit-overflow-scrolling: auto;
|
|
/* 禁用 iOS 弹性滚动 */
|
|
position: fixed;
|
|
}
|
|
|
|
#app {
|
|
overflow: hidden;
|
|
height: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.homepage {
|
|
/* height: var(--app-height, 100vh); */
|
|
height: var(--app-height, 100vh);
|
|
margin: 0 auto;
|
|
background-image: url(/src/assets/img/homePage/bk01.jpg);
|
|
background-size: 100% 100%;
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
display: flex;
|
|
overflow: hidden;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
width: 100%;
|
|
/* -webkit-overflow-scrolling: touch; */
|
|
}
|
|
|
|
.homepage .el-container {
|
|
height: 100%;
|
|
flex-direction: column;
|
|
display: flex;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
/* 防止容器滚动 */
|
|
}
|
|
|
|
.el-container .el-header {
|
|
flex-shrink: 0;
|
|
/* 防止头部压缩 */
|
|
height: auto;
|
|
min-height: 60px;
|
|
padding: 5px 0;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
/* background-color: rgba(255, 255, 255, 0.9); */
|
|
}
|
|
|
|
.el-container .el-main {
|
|
flex: 1;
|
|
/* 自动占据剩余空间 */
|
|
overflow: hidden;
|
|
/* 主容器不滚动 */
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
/* 允许内容区域缩小 */
|
|
position: relative;
|
|
height: auto;
|
|
}
|
|
|
|
.el-container .el-footer {
|
|
flex-shrink: 0;
|
|
height: auto;
|
|
min-height: 70px;
|
|
position: sticky;
|
|
bottom: 0;
|
|
z-index: 20;
|
|
background-color: rgba(211, 24, 24, 0);
|
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
|
-webkit-transform: translateZ(0);
|
|
transform: translateZ(0);
|
|
padding-bottom: env(safe-area-inset-bottom, 0);
|
|
/* 适配iPhone X及以上的底部安全区域 */
|
|
}
|
|
|
|
.homepage-head {
|
|
padding: 0px;
|
|
display: flex;
|
|
position: relative;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
}
|
|
|
|
.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: 20px;
|
|
color: #573dfc;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.homepage-right-group .announcement-btn {
|
|
cursor: pointer;
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.homepage-right-group .announcement-btn:hover {
|
|
transform: scale(1.3);
|
|
}
|
|
|
|
.homepage-body {
|
|
padding: 0px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
min-height: 0;
|
|
/* 允许内容区域缩小 */
|
|
overflow: hidden;
|
|
}
|
|
|
|
.main-wrapper {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
min-height: 0;
|
|
/* 允许内容区域缩小 */
|
|
}
|
|
|
|
.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;
|
|
height: auto;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.logo2 {
|
|
width: 80px;
|
|
height: auto;
|
|
}
|
|
|
|
/* 尾部 */
|
|
.homepage-footer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
flex-shrink: 0;
|
|
width: 100%;
|
|
background-color: #fff;
|
|
}
|
|
|
|
.footer-first-line {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 5px 15px;
|
|
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);
|
|
}
|
|
|
|
.model-btn {
|
|
height: 32px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.model-btn:hover {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.send-btn {
|
|
margin-left: 10px;
|
|
height: 33px !important;
|
|
width: auto;
|
|
/* margin-right: 5px; */
|
|
}
|
|
|
|
/* 音频播放动画 */
|
|
@keyframes pulse {
|
|
0% {
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
transform: scale(1.1);
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
.footer-second-line {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 5px 15px 10px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.msg-icon {
|
|
position: absolute;
|
|
left: 25px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 24px;
|
|
z-index: 999;
|
|
}
|
|
|
|
.msg-input:deep(.el-textarea__inner) {
|
|
border: none !important;
|
|
box-shadow: none !important;
|
|
overflow-y: auto !important;
|
|
transition: all 0.2s ease-out;
|
|
padding: 8px 20px 8px 45px !important;
|
|
resize: none !important;
|
|
line-height: 1.5 !important;
|
|
max-height: 100px !important;
|
|
}
|
|
|
|
.msg-input {
|
|
min-height: 34px;
|
|
width: 100%;
|
|
border-radius: 20px;
|
|
font-size: 16px;
|
|
transition: all 0.3s ease-out;
|
|
overflow-y: hidden;
|
|
box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3);
|
|
background: #fff;
|
|
z-index: 5;
|
|
/* 添加iOS设备特殊处理 */
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
}
|
|
|
|
.msg-input:focus {
|
|
outline: none;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.action-btn {
|
|
height: 21px;
|
|
}
|
|
|
|
.footer-second-line {
|
|
padding: 5px 10px 10px;
|
|
}
|
|
|
|
.msg-input {
|
|
font-size: 16px;
|
|
}
|
|
}
|
|
|
|
.ruleContent {
|
|
text-align: center;
|
|
}
|
|
</style>
|