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.

916 lines
25 KiB

1 month ago
  1. <template>
  2. <view class="deepMate-page">
  3. <!-- 顶部导航栏 -->
  4. <view class="header" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
  5. <view class="header-left">
  6. <image
  7. src="https://d31zlh4on95l9h.cloudfront.net/images/f91e09b5987802185e7679055dafd272.svg"
  8. class="icon"
  9. ></image>
  10. </view>
  11. <view class="header-center">
  12. <text class="title">DeepMate</text>
  13. </view>
  14. <view class="header-right">
  15. <image
  16. src="https://d31zlh4on95l9h.cloudfront.net/images/d7c4e74201213a25dd9574e908233928.svg"
  17. class="icon"
  18. ></image>
  19. <image
  20. src="https://d31zlh4on95l9h.cloudfront.net/images/099903c4aabf5713488b5cb60815e3f7.svg"
  21. class="icon"
  22. ></image>
  23. <!-- 新增新会话按钮
  24. <button class="new-chat-button" @click="newChat">
  25. <text class="new-chat-text">新会话</text>
  26. </button> -->
  27. </view>
  28. </view>
  29. <!-- 主要内容区域 -->
  30. <view class="main-content">
  31. <view class="banner-panel" v-if="messages.length === 0">
  32. <image src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg" class="pray-banner" ></image>
  33. <view class="contain">
  34. <!-- 机器人头像和欢迎语 -->
  35. <view class="robot-container" v-if="messages.length === 0">
  36. <image src="https://d31zlh4on95l9h.cloudfront.net/images/61fa384381c88ad80be28f41827fe0e5.svg" class="robot-avatar"></image>
  37. <view class="welcome-message">
  38. <text class="greeting">Hi, 我是您的股市随身顾问~</text>
  39. <text class="description">个股诊断市场情绪解读都可以找我</text>
  40. </view>
  41. </view>
  42. <!-- 功能标签栏 -->
  43. <!-- <view class="function-tabs" v-if="messages.length === 0">
  44. <view ref="tabsTrack" class="tabs-track" :style="{ transform: 'translate3d(-' + marqueeOffset + 'px,0,0)' }">
  45. <view class="tab-item" v-for="(tab, idx) in marqueeList" :key="idx">{{ tab }}</view>
  46. </view>
  47. </view> -->
  48. <!-- 特斯拉推荐卡片 -->
  49. <view class="recommend-card" v-if="messages.length === 0">
  50. <view class="arrow" v-if="messages.length === 0"></view>
  51. <view class="card-content">
  52. <image src="../../static/images/tesla-logo.png" class="logo"></image>
  53. <view class="card-text">
  54. <text class="main-question">当前特斯拉该如何布局</text>
  55. <text class="stock-code">TSLA</text>
  56. </view>
  57. <image src="https://d31zlh4on95l9h.cloudfront.net/images/40d94054644f6e3f1c366751f07f0010.svg" class="arrow-icon" @click="goBlank"></image>
  58. </view>
  59. </view>
  60. </view>
  61. </view>
  62. <!-- 可能感兴趣的话题 -->
  63. <!-- <view v-if="messages.length === 0" class="interest-section">
  64. <text class="section-title">- 您可能感兴趣 -</text>
  65. <view class="topics-list">
  66. <view class="topic-item" v-for="topic in hotTopics" :key="topic.id">
  67. <image :src="topic.icon" class="tag-icon"></image>
  68. <text class="topic-text" @click="sendMessageList(topic.text)">{{
  69. topic.text
  70. }}</text>
  71. </view>
  72. </view>
  73. </view> -->
  74. <!-- 聊天区域 -->
  75. <!-- 顶部粘性欢迎块始终保留在聊天上方 -->
  76. <view class="chat-header" v-if="messages.length > 0"> <view class="robot-container"> <image src="https://d31zlh4on95l9h.cloudfront.net/images/61fa384381c88ad80be28f41827fe0e5.svg" class="robot-avatar"></image>
  77. <view class="welcome-message"> <text class="greeting">Hi, 我是您的股市随身顾问~</text> <text class="description">个股诊断市场情绪解读都可以找我</text>
  78. </view> </view>
  79. </view>
  80. <view class="chat-container" v-if="messages.length > 0">
  81. <view class="message-list" id="messageList">
  82. <view
  83. v-for="(message, index) in messages"
  84. :key="index"
  85. :class="
  86. message.isUser ? 'message user-message' : 'message bot-message'
  87. "
  88. >
  89. <!-- 会话图标 -->
  90. <text
  91. :class="
  92. message.isUser
  93. ? 'fa-solid fa-user message-icon'
  94. : 'fa-solid fa-robot message-icon'
  95. "
  96. ></text>
  97. <!-- 会话内容 -->
  98. <view class="message-content">
  99. <text class="message-text">{{ message.content }}</text>
  100. <!-- loading -->
  101. <view
  102. class="loading-dots"
  103. v-if="message.isThinking || message.isTyping"
  104. >
  105. <text class="dot"></text>
  106. <text class="dot"></text>
  107. <text class="dot"></text>
  108. </view>
  109. </view>
  110. </view>
  111. </view>
  112. </view>
  113. </view>
  114. <!-- 输入框区域 -->
  115. <view class="input-area">
  116. <view class="input-wrapper">
  117. <input
  118. type="text"
  119. placeholder="请输入股票代码/名称,获取AI洞察"
  120. placeholder-style="color:#fff;opacity:1"
  121. class="input-field"
  122. v-model="inputMessage"
  123. @confirm="sendMessage"
  124. />
  125. <image class="send-button" @click="sendMessage" :disabled="isSending">
  126. <!-- <image
  127. src="https://d31zlh4on95l9h.cloudfront.net/images/95f1ea2262e9157db13c93c0dc1c5d96.svg"
  128. class="send-icon"
  129. ></image> -->
  130. </image>
  131. </view>
  132. <text class="disclaimer"
  133. >以上数据由AI生成不作为最终投资建议决策需独立</text
  134. >
  135. </view>
  136. <image
  137. class="back-to-top"
  138. src="https://d31zlh4on95l9h.cloudfront.net/images/ba357635d2bb480241952bb1cabacd73.svg"
  139. :style="{ transform: 'translate3d(' + backTopX + 'px,' + backTopY + 'px,0)' }"
  140. @touchstart="onBackTopTouchStart"
  141. @touchmove="onBackTopTouchMove"
  142. @touchend="onBackTopTouchEnd"
  143. @click="onBackTopClick"
  144. ></image>
  145. <footerBar class="static-footer" :type="type"></footerBar>
  146. </view>
  147. </template>
  148. <script setup>
  149. const { safeAreaInsets } = uni.getSystemInfoSync();
  150. import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue";
  151. import footerBar from '../../components/footerBar-cn'
  152. import { onPageScroll } from '@dcloudio/uni-app'
  153. const type = ref('member')
  154. const inputMessage = ref("");
  155. const isSending = ref(false);
  156. const uuid = ref("");
  157. const messages = ref([]);
  158. const hotTopics = ref([
  159. {
  160. id: 1,
  161. text: "英伟达(NVDA)股票情绪温度?",
  162. icon: "https://d31zlh4on95l9h.cloudfront.net/images/7ed58be0f4b81aeb398d9ba2534a624b.svg",
  163. },
  164. {
  165. id: 2,
  166. text: "博通(AVGO)明天还能涨吗?",
  167. icon: "https://d31zlh4on95l9h.cloudfront.net/images/7ed58be0f4b81aeb398d9ba2534a624b.svg",
  168. },
  169. {
  170. id: 3,
  171. text: "为什么Fluence Energy(FLNC)会暴涨?",
  172. icon: "https://d31zlh4on95l9h.cloudfront.net/images/7ed58be0f4b81aeb398d9ba2534a624b.svg",
  173. },
  174. {
  175. id: 4,
  176. text: "为什么Fluence Energy(FLNC)会暴涨?",
  177. icon: "https://d31zlh4on95l9h.cloudfront.net/images/7ed58be0f4b81aeb398d9ba2534a624b.svg",
  178. },
  179. ]);
  180. // 统一 raf/caf(小程序端可能没有 rAF)
  181. // const hasRAF = typeof requestAnimationFrame === 'function';
  182. // const startFrame = (fn) => hasRAF ? requestAnimationFrame(fn) : setTimeout(fn, 16);
  183. // const stopFrame = (id) => hasRAF ? cancelAnimationFrame(id) : clearTimeout(id);
  184. // 初始化
  185. onMounted(() => {
  186. const sys = uni.getSystemInfoSync();
  187. const iconSize = uni.upx2px(100); // 和样式保持一致
  188. const reserveBottom = uni.upx2px(260); // 预留底部输入区域空间
  189. const initX = Math.max(0, sys.windowWidth - iconSize - uni.upx2px(30));
  190. const initY = Math.max(0, Math.floor(sys.windowHeight * 0.65) - iconSize);
  191. backTopTargetX.value = initX;
  192. backTopTargetY.value = initY;
  193. backTopX.value = initX;
  194. backTopY.value = initY;
  195. initUUID();
  196. if (messages.value.length === 0) {
  197. nextTick(startTabsMarquee);
  198. }
  199. if (messages.value.length > 0) {
  200. nextTick(() => { scrollToBottom(); });
  201. }
  202. });
  203. // 初始化 UUID
  204. const initUUID = () => {
  205. let storedUUID = uni.getStorageSync("user_uuid");
  206. if (!storedUUID) {
  207. storedUUID = generateUUID();
  208. uni.setStorageSync("user_uuid", storedUUID);
  209. }
  210. uuid.value = storedUUID;
  211. };
  212. // 生成简单UUID
  213. const generateUUID = () => {
  214. return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
  215. var r = (Math.random() * 16) | 0,
  216. v = c == "x" ? r : (r & 0x3) | 0x8;
  217. return v.toString(16);
  218. });
  219. };
  220. // 新会话
  221. const newChat = () => {
  222. messages.value = [];
  223. uni.removeStorageSync("user_uuid");
  224. initUUID();
  225. };
  226. // 跳转到空白页
  227. const goBlank = () => {
  228. uni.navigateTo({
  229. url: "/pages/blank/blank",
  230. });
  231. };
  232. // 发送消息
  233. const sendMessage = () => {
  234. if (inputMessage.value.trim() === "" || isSending.value) return;
  235. const userMessage = {
  236. content: inputMessage.value,
  237. isUser: true,
  238. isThinking: false,
  239. isTyping: false,
  240. };
  241. messages.value.push(userMessage);
  242. inputMessage.value = "";
  243. // 发送后强制恢复并滚到底部
  244. shouldAutoScroll.value = true;
  245. nextTick(() => { forceScrollToBottom(); });
  246. // 模拟机器人回复
  247. simulateBotResponse(userMessage.content);
  248. };
  249. // 模拟机器人回复
  250. const simulateBotResponse = (userMessage) => {
  251. isSending.value = true;
  252. // 添加机器人加载消息
  253. const botMsg = {
  254. content: "",
  255. isUser: false,
  256. isTyping: true,
  257. isThinking: false,
  258. };
  259. messages.value.push(botMsg);
  260. // 滚动到底部
  261. nextTick(() => {
  262. scrollToBottom();
  263. });
  264. // 模拟流式响应
  265. let responseText = `我已经收到您的消息: "${userMessage}"。作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?`;
  266. let index = 0;
  267. const botIndex = messages.value.length - 1;
  268. const baseDelay = 165; // 普通字符基础延迟(毫秒)
  269. const slowPunct = /[。!?!?;;]/; // 句号、感叹号、分号等较长停顿
  270. const midPunct = /[,、,::]/; // 逗号、顿号、冒号等中等停顿
  271. const typeWriter = () => {
  272. if (index < responseText.length) {
  273. const ch = responseText.charAt(index);
  274. const current = messages.value[botIndex];
  275. // 通过数组替换触发渲染,避免部分平台对子项属性变更不响应
  276. messages.value.splice(
  277. botIndex,
  278. 1,
  279. { ...current, content: current.content + ch, isTyping: true }
  280. );
  281. index++;
  282. scrollToBottom();
  283. const delay = slowPunct.test(ch) ? 220 : midPunct.test(ch) ? 120 : baseDelay;
  284. setTimeout(typeWriter, delay);
  285. } else {
  286. const current = messages.value[botIndex];
  287. messages.value.splice(
  288. botIndex,
  289. 1,
  290. { ...current, isTyping: false }
  291. );
  292. isSending.value = false;
  293. nextTick(() => { scrollToBottom(); });
  294. }
  295. };
  296. // 启动前稍作停顿,避免过快开始
  297. setTimeout(typeWriter, 500);
  298. };
  299. // 滚动到底部
  300. const scrollToBottom = () => {
  301. if (!shouldAutoScroll.value) return; // 暂停自动滚动
  302. const query = uni.createSelectorQuery();
  303. query.select("#messageList").boundingClientRect();
  304. query.selectViewport().scrollOffset();
  305. query.exec((res) => {
  306. if (res[0] && res[1]) {
  307. latestContentHeight.value = res[0].height;
  308. uni.pageScrollTo({ scrollTop: res[0].height, duration: 100 });
  309. }
  310. });
  311. };
  312. const scrollToTop = () => {
  313. uni.pageScrollTo({ scrollTop: 0, duration: 200 });
  314. };
  315. // 自动滚动控制:用户向上滚动时暂停自动滚到底部
  316. const shouldAutoScroll = ref(true);
  317. const latestContentHeight = ref(0);
  318. const lastScrollTop = ref(0);
  319. const windowHeight = uni.getSystemInfoSync().windowHeight;
  320. const AUTO_SCROLL_REENABLE_THRESHOLD = 1; // px,接近底部时恢复自动滚动
  321. onPageScroll((e) => {
  322. const st = e.scrollTop;
  323. const delta = st - lastScrollTop.value;
  324. lastScrollTop.value = st;
  325. if (delta < 0) {
  326. shouldAutoScroll.value = false;
  327. return;
  328. }
  329. const distanceToBottom = latestContentHeight.value - st - windowHeight;
  330. if (distanceToBottom <= AUTO_SCROLL_REENABLE_THRESHOLD) {
  331. shouldAutoScroll.value = true;
  332. }
  333. });
  334. // 回到顶部图标拖拽状态
  335. const backTopX = ref(0);
  336. const backTopY = ref(0);
  337. const backTopDragging = ref(false);
  338. const backTopDragOffset = ref({ x: 0, y: 0 });
  339. const backTopTargetX = ref(0);
  340. const backTopTargetY = ref(0);
  341. let backTopRAF = 0;
  342. const backTopSmoothing = 0.15; // 越大越跟手,越小越顺滑
  343. const backTopEpsilon = 0.5; // 收敛阈值
  344. const raf = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : (fn) => setTimeout(fn, 16);
  345. const caf = typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame : (id) => clearTimeout(id);
  346. function stepBackTop() {
  347. const dx = backTopTargetX.value - backTopX.value;
  348. const dy = backTopTargetY.value - backTopY.value;
  349. // 插值缓动,避免每帧重排
  350. backTopX.value += dx * backTopSmoothing;
  351. backTopY.value += dy * backTopSmoothing;
  352. if (Math.abs(dx) > backTopEpsilon || Math.abs(dy) > backTopEpsilon) {
  353. backTopRAF = raf(stepBackTop);
  354. } else {
  355. backTopX.value = backTopTargetX.value;
  356. backTopY.value = backTopTargetY.value;
  357. backTopRAF = 0;
  358. }
  359. }
  360. function ensureBackTopRAF() {
  361. if (!backTopRAF) {
  362. backTopRAF = raf(stepBackTop);
  363. }
  364. }
  365. const clamp = (val, min, max) => Math.max(min, Math.min(val, max));
  366. const onBackTopTouchStart = (e) => {
  367. const t = e.touches && e.touches[0];
  368. if (!t) return;
  369. backTopDragging.value = false;
  370. backTopDragOffset.value = {
  371. x: t.pageX - backTopX.value,
  372. y: t.pageY - backTopY.value,
  373. };
  374. };
  375. const onBackTopTouchMove = (e) => {
  376. const t = e.touches && e.touches[0];
  377. if (!t) return;
  378. const sys = uni.getSystemInfoSync();
  379. const iconSize = uni.upx2px(100);
  380. const reserveBottom = uni.upx2px(260);
  381. const nx = t.pageX - backTopDragOffset.value.x;
  382. const ny = t.pageY - backTopDragOffset.value.y;
  383. const clampedX = clamp(nx, 0, sys.windowWidth - iconSize);
  384. const clampedY = clamp(ny, 0, sys.windowHeight - reserveBottom - iconSize);
  385. if (Math.abs(clampedX - backTopX.value) + Math.abs(clampedY - backTopY.value) > 3) {
  386. backTopDragging.value = true;
  387. }
  388. backTopTargetX.value = clampedX;
  389. backTopTargetY.value = clampedY;
  390. ensureBackTopRAF();
  391. };
  392. const onBackTopTouchEnd = () => {
  393. // 结束拖拽即可
  394. };
  395. const onBackTopClick = () => {
  396. if (backTopDragging.value) return; // 拖拽时不触发点击回到顶部
  397. scrollToTop();
  398. };
  399. </script>
  400. <style scoped>
  401. .deepMate-page {
  402. display: flex;
  403. flex-direction: column;
  404. height: 100vh;
  405. background-color: #ffffff;
  406. padding: 20rpx 0rpx;
  407. }
  408. .header {
  409. display: flex;
  410. justify-content: space-between;
  411. align-items: center;
  412. padding: 20rpx 30rpx;
  413. background-color: #ffffff;
  414. }
  415. .header-left,
  416. .header-right {
  417. display: flex;
  418. align-items: center;
  419. }
  420. .header-left .icon,
  421. .header-right .icon {
  422. width: 40rpx;
  423. height: 40rpx;
  424. margin-right: 20rpx;
  425. }
  426. .header-center .title {
  427. font-size: 36rpx;
  428. font-weight: bold;
  429. color: #333333;
  430. }
  431. .new-chat-button {
  432. background-color: #ff6600;
  433. border: none;
  434. border-radius: 8rpx;
  435. padding: 10rpx 20rpx;
  436. }
  437. .new-chat-text {
  438. color: white;
  439. font-size: 24rpx;
  440. }
  441. .main-content {
  442. background-color: #fff;
  443. flex: 1;
  444. padding: 20rpx;
  445. /* overflow-y: auto; 取消独立滚动,使用页面滚动以便自动到达底部 */
  446. margin-top: 20rpx;
  447. margin-bottom: 120rpx;
  448. }
  449. /* 聊天顶部粘性欢迎块样式 */
  450. .chat-header {
  451. position: sticky;
  452. top: 0; /* 在页面滚动时始终贴顶 */
  453. z-index: 50;
  454. background-color: #ffffff;
  455. padding: 3rpx 20rpx;
  456. }
  457. .robot-container {
  458. display: flex;
  459. align-items: center;
  460. margin-top: 30rpx;
  461. }
  462. .robot-avatar {
  463. width: 130rpx;
  464. height: 130rpx;
  465. border-radius: 50%;
  466. margin-left: 20rpx;
  467. }
  468. .welcome-message {
  469. flex: 1;
  470. }
  471. .greeting {
  472. font-size: 32rpx;
  473. margin-left: 50rpx;
  474. top: 40rpx;
  475. font-weight: bold;
  476. color: #333333;
  477. line-height: 48rpx;
  478. }
  479. .description {
  480. display: block;
  481. font-size: 24rpx;
  482. color: #666666;
  483. line-height: 36rpx;
  484. margin-top: 10rpx;
  485. margin-left: 45rpx;
  486. }
  487. .function-tabs {
  488. display: block;
  489. overflow: hidden;
  490. margin-bottom: 30rpx;
  491. }
  492. .tabs-track {
  493. display: inline-flex;
  494. white-space: nowrap;
  495. will-change: transform;
  496. }
  497. .tab-item {
  498. padding: 5rpx 20rpx;
  499. border-radius: 20rpx;
  500. font-size: 20rpx;
  501. font-weight: 700;
  502. color: #666666;
  503. background-color: #fffefe;
  504. margin-right: 20rpx;
  505. transition: all 0.3s;
  506. }
  507. .tab-item.active {
  508. color: #ff6600;
  509. background-color: #fff;
  510. border: 1rpx solid #ff6600;
  511. }
  512. .recommend-card {
  513. background: url("https://d31zlh4on95l9h.cloudfront.net/images/4da1d629a55c307c3605ca15bf15189a.svg");
  514. background-repeat: no-repeat;
  515. background-position: center bottom;
  516. background-size: contain;
  517. /* min-height: 20rpx; */
  518. /* border-radius: 20rpx; */
  519. padding: 40rpx;
  520. margin-top: 20rpx;
  521. margin-bottom: 10rpx;
  522. /* box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); */
  523. }
  524. .card-content {
  525. display: flex;
  526. align-items: center;
  527. justify-content: space-between;
  528. margin-left: 40rpx;
  529. }
  530. .logo {
  531. width: 80rpx;
  532. height: 80rpx;
  533. background-color: #ff0000;
  534. border-radius: 10rpx;
  535. display: flex;
  536. align-items: center;
  537. justify-content: center;
  538. margin-right: 20rpx;
  539. }
  540. .card-text {
  541. flex: 1;
  542. margin-left: 20rpx;
  543. }
  544. .main-question {
  545. font-size: 32rpx;
  546. color: #333333;
  547. line-height: 48rpx;
  548. }
  549. .stock-code {
  550. display: block;
  551. font-size: 24rpx;
  552. color: #ff3b30;
  553. background-color: #ffffff;
  554. padding: 2rpx 15rpx;
  555. border-radius: 12rpx;
  556. margin-top: 8rpx;
  557. width: fit-content;
  558. border: 1rpx solid #ff3b30;
  559. }
  560. .arrow-icon {
  561. background: url("https://d31zlh4on95l9h.cloudfront.net/images/40d94054644f6e3f1c366751f07f0010.svg");
  562. background-repeat: no-repeat;
  563. left: 0.5rem;
  564. top: 1.8rem;
  565. background-size: 100% 100%;
  566. width: 60rpx;
  567. height: 60rpx;
  568. }
  569. .interest-section {
  570. margin-bottom: 30rpx;
  571. }
  572. .section-title {
  573. display: block;
  574. text-align: center;
  575. font-size: 26rpx;
  576. color: #666666;
  577. margin-bottom: 20rpx;
  578. }
  579. .topics-list {
  580. display: flex;
  581. flex-direction: column;
  582. gap: 15rpx;
  583. }
  584. .topic-item {
  585. display: flex;
  586. align-items: center;
  587. padding: 15rpx 20rpx;
  588. background-color: #f0f0f0;
  589. border-radius: 15rpx;
  590. width: fit-content;
  591. }
  592. .tag-icon {
  593. width: 24rpx;
  594. height: 24rpx;
  595. margin-right: 10rpx;
  596. }
  597. .topic-text {
  598. font-size: 28rpx;
  599. color: #333333;
  600. flex: 1;
  601. }
  602. /* 聊天区域样式 */
  603. .chat-container {
  604. margin-top: 30rpx;
  605. border-radius: 10rpx;
  606. height: fit-content;
  607. /* overflow-y: auto; */
  608. }
  609. .message-list {
  610. background-color: #fff;
  611. margin-bottom: 400rpx;
  612. /* padding: 20rpx; */
  613. }
  614. .message {
  615. display: flex;
  616. align-items: flex-start;
  617. margin-bottom: 30rpx;
  618. }
  619. /* .user-message {
  620. flex-direction: row-reverse;
  621. } */
  622. .message-icon {
  623. font-size: 24rpx;
  624. margin: 0 10rpx;
  625. padding: 10rpx;
  626. border-radius: 50%;
  627. background-color: #ddd;
  628. width: 40rpx;
  629. height: 40rpx;
  630. display: flex;
  631. align-items: center;
  632. justify-content: center;
  633. }
  634. .user-message .message-icon {
  635. background-color: transparent;
  636. border-radius: 0;
  637. /* padding: 0;
  638. width: auto;
  639. height: auto; */
  640. color: #333;
  641. }
  642. .bot-message .message-icon {
  643. background: url('https://d31zlh4on95l9h.cloudfront.net/images/61fa384381c88ad80be28f41827fe0e5.svg');
  644. color: white;
  645. }
  646. .message-content {
  647. max-width: 70%;
  648. position: relative;
  649. }
  650. .user-message .message-content {
  651. background-color: #007aff;
  652. border-radius: 10rpx;
  653. padding: 15rpx;
  654. }
  655. .bot-message .message-content {
  656. background-color: #f0f0f0;
  657. border-radius: 10rpx;
  658. padding: 15rpx;
  659. }
  660. .message-text {
  661. font-size: 28rpx;
  662. line-height: 40rpx;
  663. }
  664. .user-message .message-text {
  665. color: white;
  666. }
  667. .bot-message .message-text {
  668. color: #333;
  669. }
  670. .loading-dots {
  671. display: flex;
  672. align-items: center;
  673. padding-top: 10rpx;
  674. }
  675. .dot {
  676. width: 10rpx;
  677. height: 10rpx;
  678. background-color: #666;
  679. border-radius: 50%;
  680. margin: 0 4rpx;
  681. animation: loading 1.4s infinite ease-in-out both;
  682. }
  683. .user-message .dot {
  684. background-color: white;
  685. }
  686. .dot:nth-child(1) {
  687. animation-delay: -0.32s;
  688. }
  689. .dot:nth-child(2) {
  690. animation-delay: -0.16s;
  691. }
  692. @keyframes loading {
  693. 0%,
  694. 80%,
  695. 100% {
  696. transform: scale(0);
  697. }
  698. 40% {
  699. transform: scale(1);
  700. }
  701. }
  702. .input-area {
  703. position: fixed;
  704. margin-top: 20rpx;
  705. bottom: 70rpx;
  706. left: 0;
  707. right: 0;
  708. padding: 0 40rpx 80rpx 40rpx;
  709. background-color: #ffffff;
  710. }
  711. .input-wrapper {
  712. position: relative;
  713. display: flex;
  714. align-items: center;
  715. padding: 15rpx 20rpx;
  716. background-color: rgb(220, 31, 29);
  717. border-radius: 100rpx;
  718. display: flex;
  719. align-items: center;
  720. justify-content: center;
  721. height: 50rpx;
  722. }
  723. .mic-icon {
  724. width: 36rpx;
  725. height: 36rpx;
  726. margin-right: 20rpx;
  727. }
  728. .input-field {
  729. flex: 1;
  730. font-size: 28rpx;
  731. color: #fff;
  732. display: flex;
  733. align-items: center;
  734. justify-content: center;
  735. margin-left: 60rpx;
  736. background: none;
  737. border: none;
  738. outline: none;
  739. }
  740. .input-field::placeholder {
  741. color: #ffffff !important;
  742. opacity: 1;
  743. }
  744. .send-button {
  745. background: url("https://d31zlh4on95l9h.cloudfront.net/images/95f1ea2262e9157db13c93c0dc1c5d96.svg");
  746. background-repeat: no-repeat;
  747. background-size: 100% 100%;
  748. height: 50rpx;
  749. width: 50rpx;
  750. padding: 0;
  751. border: 1rpx solid transparent;
  752. margin-left: 20rpx;
  753. }
  754. .send-icon {
  755. width: 36rpx;
  756. height: 36rpx;
  757. }
  758. .disclaimer {
  759. font-size: 15rpx;
  760. color: #4d4c4c;
  761. display: flex;
  762. align-items: center;
  763. justify-content: center;
  764. margin-top: 15rpx;
  765. }
  766. .banner-panel {
  767. position: relative;
  768. height: 480rpx; /* 拉长容器,灰色背景跟随变高 */
  769. overflow: hidden; /* 让圆角和内部层剪裁一致 */
  770. border-radius: 15rpx;
  771. }
  772. .panelShow{
  773. height: 12%;
  774. }
  775. .pray-banner {
  776. position: absolute;
  777. /* background-size: 100% 100%; */
  778. inset: 0; /* 顶部、底部、左、右都贴合容器 */
  779. width: 100%;
  780. height: 81%;
  781. border-radius: 15rpx;
  782. z-index: 1; /* 在灰底之上、内容之下 */
  783. }
  784. .contain {
  785. margin: 0 20rpx;
  786. gap: 5rpx;
  787. }
  788. .banner-panel .robot-container,
  789. .banner-panel .function-tabs,
  790. .banner-panel .recommend-card {
  791. position: relative;
  792. z-index: 2;
  793. }
  794. .back-to-top {
  795. position: fixed;
  796. left: 0;
  797. top: 0;
  798. width: 100rpx;
  799. height: 100rpx;
  800. z-index: 1000;
  801. }
  802. .back-to-top:active {
  803. transform: scale(0.96);
  804. }
  805. .static-footer {
  806. position: fixed;
  807. bottom: 0;
  808. }
  809. </style>