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.

1540 lines
37 KiB

4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks 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. >
  10. </image>
  11. </view>
  12. <view class="header-center">
  13. <text class="title" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"
  14. >DeepMate</text
  15. >
  16. </view>
  17. <view class="header-right">
  18. <image
  19. src="https://d31zlh4on95l9h.cloudfront.net/images/d7c4e74201213a25dd9574e908233928.svg"
  20. class="icon"
  21. >
  22. </image>
  23. <image
  24. style="margin-left: 10px"
  25. src="https://d31zlh4on95l9h.cloudfront.net/images/099903c4aabf5713488b5cb60815e3f7.svg"
  26. class="icon"
  27. @click="openHistoryDrawer"
  28. ></image>
  29. <!-- 新增新会话按钮
  30. <button class="new-chat-button" @click="newChat">
  31. <text class="new-chat-text">新会话</text>
  32. </button> -->
  33. </view>
  34. </view>
  35. <!-- 主要内容区域 -->
  36. <view class="main-content">
  37. <view class="banner-panel" v-if="messages.length === 0">
  38. <image
  39. src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg"
  40. class="pray-banner"
  41. ></image>
  42. <view class="contain">
  43. <!-- 机器人头像和欢迎语 -->
  44. <view class="robot-container" v-if="messages.length === 0">
  45. <image
  46. src="https://d31zlh4on95l9h.cloudfront.net/images/61fa384381c88ad80be28f41827fe0e5.svg"
  47. class="robot-avatar"
  48. ></image>
  49. <view class="welcome-message">
  50. <text class="greeting">Hi, 我是您的股市随身顾问~</text>
  51. <text class="description"
  52. >个股诊断市场情绪解读都可以找我</text
  53. >
  54. </view>
  55. </view>
  56. <!-- 功能标签栏 -->
  57. <!-- <view class="function-tabs" v-if="messages.length === 0">
  58. <view ref="tabsTrack" class="tabs-track" :style="{ transform: 'translate3d(-' + marqueeOffset + 'px,0,0)' }">
  59. <view class="tab-item" v-for="(tab, idx) in marqueeList" :key="idx">{{ tab }}</view>
  60. </view>
  61. </view> -->
  62. <!-- 特斯拉推荐卡片 -->
  63. <view class="recommend-card" v-if="messages.length === 0" @click="goBlank">
  64. <view class="arrow" v-if="messages.length === 0"></view>
  65. <view class="card-content">
  66. <image
  67. src="../../static/images/tesla-logo.png"
  68. class="logo"
  69. ></image>
  70. <view class="card-text">
  71. <text class="main-question">当前特斯拉该如何布局</text>
  72. <text class="stock-code">TSLA</text>
  73. </view>
  74. <image
  75. src="https://d31zlh4on95l9h.cloudfront.net/images/40d94054644f6e3f1c366751f07f0010.svg"
  76. class="arrow-icon"
  77. @click="goBlank"
  78. ></image>
  79. </view>
  80. </view>
  81. </view>
  82. </view>
  83. <!-- 可能感兴趣的话题 -->
  84. <!-- <view v-if="messages.length === 0" class="interest-section">
  85. <text class="section-title">- 您可能感兴趣 -</text>
  86. <view class="topics-list">
  87. <view class="topic-item" v-for="topic in hotTopics" :key="topic.id">
  88. <image :src="topic.icon" class="tag-icon"></image>
  89. <text class="topic-text" @click="sendMessageList(topic.text)">{{
  90. topic.text
  91. }}</text>
  92. </view>
  93. </view>
  94. </view> -->
  95. <view v-if="messages.length === 0" class="welcome-section">
  96. </view>
  97. <!-- 聊天区域 -->
  98. <!-- 顶部粘性欢迎块始终保留在聊天上方 -->
  99. <view class="chat-header" v-if="messages.length > 0">
  100. <view class="robot-container">
  101. <image src="https://d31zlh4on95l9h.cloudfront.net/images/61fa384381c88ad80be28f41827fe0e5.svg"
  102. class="robot-avatar"></image>
  103. <view class="welcome-message"> <text class="greeting">Hi, 我是您的股市随身顾问~</text>
  104. </view>
  105. </view>
  106. </view>
  107. <scroll-view
  108. class="chat-container"
  109. scroll-y="true"
  110. :scroll-top="chatScrollTop"
  111. :scroll-with-animation="!isSending"
  112. @scroll="onChatScroll"
  113. v-if="messages.length > 0"
  114. >
  115. <view class="message-list" id="messageList">
  116. <view
  117. v-for="(message, index) in messages"
  118. :key="index"
  119. :class="
  120. message.isUser ? 'message user-message' : 'message bot-message'
  121. "
  122. >
  123. <!-- 会话图标 -->
  124. <text
  125. :class="
  126. message.isUser
  127. ? 'fa-solid fa-user message-icon'
  128. : 'fa-solid fa-robot message-icon'
  129. "
  130. ></text>
  131. <!-- 会话内容 -->
  132. <view class="message-content">
  133. <!-- <text class="message-text">{{ message.content }}</text> -->
  134. <!-- loading -->
  135. <view
  136. class="loading-dots"
  137. v-if="message.isThinking || !message.isUser"
  138. >
  139. <view class="thinking-process">
  140. <view class="thinking-header">
  141. <view class="thinking-icon"></view>
  142. <view class="thinking-title">{{
  143. message.isTyping ? "正在思考" : "思考完成"
  144. }}</view>
  145. <view class="thinking-count"> </view>
  146. <view
  147. class="thinking-toggle"
  148. @click="message.isThinking = !message.isThinking"
  149. >
  150. <span v-if="message.isThinking"></span>
  151. <span v-else></span>
  152. </view>
  153. </view>
  154. <view v-show="message.isThinking" class="thinking-content">
  155. <view class="thinking-item">
  156. <view class="item-status">
  157. <span class="checkmark"></span>
  158. </view>
  159. <view class="item-text">问题分析完成</view>
  160. </view>
  161. <view class="thinking-item">
  162. <view class="item-status">
  163. <span class="checkmark"></span>
  164. </view>
  165. <view class="item-text">收集相关信息</view>
  166. </view>
  167. </view>
  168. </view>
  169. </view>
  170. <!-- 使用 rich-text 渲染 Markdown 内容 -->
  171. <rich-text
  172. v-if="!message.isUser"
  173. class="message-text"
  174. :nodes="renderMarkdown(message.content)"
  175. ></rich-text>
  176. <text v-else class="message-text">{{ message.content }}</text>
  177. </view>
  178. </view>
  179. </view>
  180. </scroll-view>
  181. </view>
  182. <!-- 输入框区域 -->
  183. <view class="input-area">
  184. <view class="input-wrapper">
  185. <input
  186. type="text"
  187. placeholder="请输入股票代码/名称,获取AI洞察"
  188. placeholder-style="color:#fff;opacity:1"
  189. class="input-field"
  190. v-model="inputMessage"
  191. @confirm="sendMessage"
  192. />
  193. <image class="send-button" @click="sendMessage" :disabled="isSending">
  194. <!-- <image
  195. src="https://d31zlh4on95l9h.cloudfront.net/images/95f1ea2262e9157db13c93c0dc1c5d96.svg"
  196. class="send-icon"
  197. ></image> -->
  198. </image>
  199. </view>
  200. <text class="disclaimer"
  201. >以上数据由AI生成不作为最终投资建议决策需独立</text
  202. >
  203. </view>
  204. <image
  205. class="back-to-top"
  206. src="https://d31zlh4on95l9h.cloudfront.net/images/ba357635d2bb480241952bb1cabacd73.svg"
  207. :style="{
  208. transform: 'translate3d(' + backTopX + 'px,' + backTopY + 'px,0)',
  209. }"
  210. @touchstart="onBackTopTouchStart"
  211. @touchmove="onBackTopTouchMove"
  212. @touchend="onBackTopTouchEnd"
  213. @click="onBackTopClick"
  214. ></image>
  215. <!-- 搜索历史侧拉框 -->
  216. <view class="drawer-overlay" v-show="showHistoryDrawer"></view>
  217. <view class="drawer-panel" v-show="showHistoryDrawer" @click.stop @touchmove.stop.prevent :style="{ transform: 'translateY(' + drawerOffsetY + 'px)' }">
  218. <view class="drawer-header">
  219. <text class="drawer-title">历史对话</text>
  220. <view class="drawer-actions">
  221. <view class="delete-all-container">
  222. <image class="delete-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/a62a41f8513bf00b040fd8e79cfe04f4.svg"></image>
  223. <text class="delete-all" @click="clearAllHistory">删除全部</text>
  224. </view>
  225. <view class="drawer-close" @click="onDrawerBackClick"><text class="drawer-close-icon"></text></view>
  226. </view>
  227. </view>
  228. <scroll-view scroll-y="true" class="drawer-content">
  229. <view class="drawer-inner">
  230. <view v-if="groupedHistory.length === 0" class="empty-history">
  231. <text>暂无历史记录</text>
  232. </view>
  233. <view v-for="(section, sIdx) in groupedHistory" :key="sIdx" class="history-section">
  234. <text class="section-title">{{ section.title }}</text>
  235. <view v-for="(item, idx) in section.items" :key="idx" class="history-item">
  236. <view class="history-left">
  237. <view class="flag-circle"><text class="flag-emoji">🇺🇸</text></view>
  238. </view>
  239. <view class="history-main">
  240. <text class="history-query">{{ item.query }}</text>
  241. </view>
  242. <text class="history-time">{{ formatTime(item.time) }}</text>
  243. </view>
  244. </view>
  245. </view>
  246. </scroll-view>
  247. </view>
  248. <footerBar class="static-footer" :type="type"></footerBar>
  249. </view>
  250. </template>
  251. <script setup>
  252. const { safeAreaInsets } = uni.getSystemInfoSync();
  253. import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue";
  254. import footerBar from "../../components/footerBar-cn";
  255. import marked from "marked"; // 引入 marked 库
  256. import { onPageScroll } from "@dcloudio/uni-app";
  257. import { postStock, postIntent } from "../../api/deepMate/deepMate";
  258. // 设置 marked 选项
  259. marked.setOptions({
  260. renderer: new marked.Renderer(),
  261. highlight: null, // 如果需要代码高亮,可以设置适当的函数
  262. langPrefix: "language-",
  263. pedantic: false,
  264. gfm: true,
  265. breaks: false,
  266. sanitize: false,
  267. smartLists: true,
  268. smartypants: false,
  269. xhtml: false,
  270. });
  271. // 创建一个用于渲染 Markdown 的函数
  272. const renderMarkdown = (content) => {
  273. if (!content) return "";
  274. return marked.parse(content);
  275. };
  276. const type = ref("member");
  277. const inputMessage = ref("");
  278. const showThinking = ref(true);
  279. const isSending = ref(false);
  280. const chatScrollTop = ref(0);
  281. const chatContainerHeight = ref(0);
  282. const uuid = ref("");
  283. const messages = ref([]);
  284. const showHistoryDrawer = ref(false);
  285. const drawerOffsetY = ref(0);
  286. const searchHistory = ref([]);
  287. const hotTopics = ref([
  288. {
  289. id: 1,
  290. text: "英伟达(NVDA)股票情绪温度?",
  291. icon: "https://d31zlh4on95l9h.cloudfront.net/images/7ed58be0f4b81aeb398d9ba2534a624b.svg",
  292. },
  293. {
  294. id: 2,
  295. text: "博通(AVGO)明天还能涨吗?",
  296. icon: "https://d31zlh4on95l9h.cloudfront.net/images/7ed58be0f4b81aeb398d9ba2534a624b.svg",
  297. },
  298. {
  299. id: 3,
  300. text: "为什么Fluence Energy(FLNC)会暴涨?",
  301. icon: "https://d31zlh4on95l9h.cloudfront.net/images/7ed58be0f4b81aeb398d9ba2534a624b.svg",
  302. },
  303. {
  304. id: 4,
  305. text: "为什么Fluence Energy(FLNC)会暴涨?",
  306. icon: "https://d31zlh4on95l9h.cloudfront.net/images/7ed58be0f4b81aeb398d9ba2534a624b.svg",
  307. },
  308. ]);
  309. // 初始化
  310. onMounted(() => {
  311. const sys = uni.getSystemInfoSync();
  312. const iconSize = uni.upx2px(100); // 和样式保持一致
  313. const reserveBottom = uni.upx2px(260); // 预留底部输入区域空间
  314. const initX = Math.max(0, sys.windowWidth - iconSize - uni.upx2px(30));
  315. const initY = Math.max(0, Math.floor(sys.windowHeight * 0.65) - iconSize);
  316. backTopTargetX.value = initX;
  317. backTopTargetY.value = initY;
  318. backTopX.value = initX;
  319. backTopY.value = initY;
  320. initUUID();
  321. if (messages.value.length === 0) {
  322. // nextTick(startTabsMarquee);
  323. }
  324. if (messages.value.length > 0) {
  325. nextTick(() => {
  326. measureChatContainer();
  327. scrollToBottom();
  328. });
  329. }
  330. // 载入历史
  331. const hist = uni.getStorageSync("search_history") || [];
  332. searchHistory.value = Array.isArray(hist) ? hist : [];
  333. // 缓存今天日期(YYYY-MM-DD)
  334. const todayStr = new Date().toISOString().slice(0, 10);
  335. uni.setStorageSync('today_date', todayStr);
  336. });
  337. // 初始化 UUID
  338. const initUUID = () => {
  339. let storedUUID = uni.getStorageSync("user_uuid");
  340. if (!storedUUID) {
  341. storedUUID = generateUUID();
  342. uni.setStorageSync("user_uuid", storedUUID);
  343. }
  344. uuid.value = storedUUID;
  345. };
  346. // 生成简单UUID
  347. const generateUUID = () => {
  348. return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
  349. var r = (Math.random() * 16) | 0,
  350. v = c == "x" ? r : (r & 0x3) | 0x8;
  351. return v.toString(16);
  352. });
  353. };
  354. // 计算聊天容器可视高度
  355. const measureChatContainer = () => {
  356. const q = uni.createSelectorQuery();
  357. q.select(".chat-container").boundingClientRect();
  358. q.exec((res) => {
  359. chatContainerHeight.value = res[0]?.height || 0;
  360. });
  361. };
  362. // 新会话
  363. const newChat = () => {
  364. messages.value = [];
  365. uni.removeStorageSync("user_uuid");
  366. initUUID();
  367. };
  368. // 跳转到空白页
  369. const goBlank = () => {
  370. uni.navigateTo({
  371. url: "/pages/blank/blank",
  372. });
  373. };
  374. // 历史抽屉控制
  375. const openHistoryDrawer = () => {
  376. const hideDistance = uni.upx2px(900);
  377. drawerOffsetY.value = hideDistance;
  378. showHistoryDrawer.value = true;
  379. setTimeout(() => { drawerOffsetY.value = 0; }, 10);
  380. };
  381. const closeHistoryDrawer = () => { showHistoryDrawer.value = false; };
  382. const onDrawerBackClick = () => {
  383. const hideDistance = uni.upx2px(900);
  384. drawerOffsetY.value = hideDistance;
  385. setTimeout(() => {
  386. closeHistoryDrawer();
  387. drawerOffsetY.value = 0;
  388. }, 180);
  389. };
  390. // 时间格式化:YYYY-MM-DD HH:mm
  391. const pad2 = (n) => (n < 10 ? "0" + n : "" + n);
  392. const formatTime = (t) => {
  393. const d = new Date(t);
  394. const y = d.getFullYear();
  395. const m = pad2(d.getMonth() + 1);
  396. const day = pad2(d.getDate());
  397. const hh = pad2(d.getHours());
  398. const mm = pad2(d.getMinutes());
  399. return `${y}-${m}-${day} ${hh}:${mm}`;
  400. };
  401. // 历史分组(今天/昨天/近一周/按月)
  402. const groupedHistory = computed(() => {
  403. const sections = [];
  404. // 从缓存获取今天日期,如果没有则使用当前日期
  405. const cachedTodayStr = uni.getStorageSync('today_date');
  406. const now = cachedTodayStr ? new Date(cachedTodayStr + 'T00:00:00') : new Date();
  407. const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
  408. const isSameDay = (a, b) => startOfDay(a).getTime() === startOfDay(b).getTime();
  409. const isYesterday = (d) => {
  410. const y = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
  411. return isSameDay(d, y);
  412. };
  413. const isToday = (d) => isSameDay(d, now);
  414. const withinLast7Days = (d) => {
  415. const seven = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
  416. return d >= seven && !isToday(d) && !isYesterday(d);
  417. };
  418. const monthLabel = (d) => `${d.getMonth() + 1}`;
  419. const today = [];
  420. const yesterday = [];
  421. const last7 = [];
  422. const byMonth = new Map();
  423. searchHistory.value.forEach((item) => {
  424. const dt = new Date(item.time);
  425. if (isToday(dt)) {
  426. today.push(item);
  427. } else if (isYesterday(dt)) {
  428. yesterday.push(item);
  429. } else if (withinLast7Days(dt)) {
  430. last7.push(item);
  431. } else {
  432. const year = dt.getFullYear();
  433. const month = dt.getMonth() + 1;
  434. const key = `${year}-${month}`;
  435. if (!byMonth.has(key)) byMonth.set(key, { title: `${month}`, year, month, items: [] });
  436. byMonth.get(key).items.push(item);
  437. }
  438. });
  439. if (today.length) sections.push({ title: '今天', items: today });
  440. if (yesterday.length) sections.push({ title: '昨天', items: yesterday });
  441. if (last7.length) sections.push({ title: '近一周', items: last7 });
  442. const monthSections = Array.from(byMonth.values()).sort((a, b) => {
  443. if (a.year !== b.year) return b.year - a.year;
  444. return b.month - a.month; // 月份倒序,如 10月 在 9月 之前
  445. });
  446. sections.push(...monthSections);
  447. return sections;
  448. });
  449. const clearAllHistory = () => {
  450. searchHistory.value = [];
  451. uni.setStorageSync('search_history', []);
  452. };
  453. // 发送消息
  454. const sendMessage = () => {
  455. if (inputMessage.value.trim() === "" || isSending.value) return;
  456. const userMessage = {
  457. content: inputMessage.value,
  458. isUser: true,
  459. isThinking: false,
  460. isTyping: false,
  461. };
  462. messages.value.push(userMessage);
  463. inputMessage.value = "";
  464. // 记录搜索历史
  465. const entry = { query: userMessage.content, time: Date.now() };
  466. searchHistory.value.unshift(entry);
  467. uni.setStorageSync("search_history", searchHistory.value);
  468. // 发送后强制恢复并滚到底部
  469. shouldAutoScroll.value = true;
  470. nextTick(() => {
  471. scrollToBottom();
  472. });
  473. // 模拟机器人回复
  474. simulateBotResponse(userMessage.content);
  475. };
  476. // 模拟机器人回复
  477. const simulateBotResponse = async (userMessage) => {
  478. // 添加机器人加载消息
  479. const botMsg = {
  480. content: "",
  481. isUser: false,
  482. isTyping: true,
  483. isThinking: true,
  484. };
  485. messages.value.push(botMsg);
  486. await new Promise((resolve) => setTimeout(resolve, 2000));
  487. isSending.value = true;
  488. // 首先进行意图识别
  489. const res = await postIntent({
  490. content: "森那美",
  491. language: "cn",
  492. marketlist: "hk,cn,usa,my,sg,vi,in,gb",
  493. token:
  494. "9ior41AF0xTIbIG2pRnnbZi0+fEeMx8pywnilrmTwo5FbqJ91WrSWOxp9MkpKiNtedtUafqvzIwpFKrwuMs",
  495. model: "1",
  496. });
  497. console.log("res" + res);
  498. // 意图识别不通过
  499. if (res.code !== 200) {
  500. return;
  501. }
  502. // 获取意图识别结果
  503. const recordId = res.data.recordId;
  504. const parentId = res.data.parentId;
  505. const stockId = res.data.stockId;
  506. await new Promise((resolve) => setTimeout(resolve, 2000));
  507. // 获取股票信息
  508. const StockInfo = await postStock({
  509. recordId,
  510. parentId,
  511. stockId,
  512. token:
  513. "9ior41AF0xTIbIG2pRnnbZi0+fEeMx8pywnilrmTwo5FbqJ91WrSWOxp9MkpKiNtedtUafqvzIwpFKrwuMs",
  514. language: "cn",
  515. });
  516. console.log("StockInfo", StockInfo);
  517. // if (StockInfo.code !== 200) {
  518. // return ;
  519. // }
  520. const markdown = StockInfo.markdown;
  521. console.log("StockInfo", StockInfo);
  522. // 添加请求延迟
  523. // const toDataInfo = await getData();
  524. // console.log(toDataInfo);
  525. // dataInfo.value = toDataInfo.data;
  526. // console.log(dataInfo.value);
  527. messages.value[messages.value.length - 1].isThinking = false;
  528. // 滚动到底部
  529. nextTick(() => {
  530. scrollToBottom();
  531. });
  532. // 模拟流式响应
  533. let responseText = `我已经收到您的消息: "${userMessage}"。+"${markdown}" `;
  534. let index = 0;
  535. const botIndex = messages.value.length - 1;
  536. const baseDelay = 5; // 普通字符基础延迟(毫秒)
  537. const slowPunct = /[。!?!?;;]/; // 句号、感叹号、分号等较长停顿
  538. const midPunct = /[,、,::]/; // 逗号、顿号、冒号等中等停顿
  539. const typeWriter = () => {
  540. if (index < responseText.length) {
  541. const ch = responseText.charAt(index);
  542. const current = messages.value[botIndex];
  543. // 通过数组替换触发渲染,避免部分平台对子项属性变更不响应
  544. messages.value.splice(botIndex, 1, {
  545. ...current,
  546. content: current.content + ch,
  547. isTyping: true,
  548. });
  549. index++;
  550. scrollToBottom();
  551. const delay = slowPunct.test(ch)
  552. ? 220
  553. : midPunct.test(ch)
  554. ? 120
  555. : baseDelay;
  556. setTimeout(typeWriter, delay);
  557. } else {
  558. const current = messages.value[botIndex];
  559. messages.value.splice(botIndex, 1, { ...current, isTyping: false });
  560. isSending.value = false;
  561. nextTick(() => {
  562. scrollToBottom();
  563. });
  564. }
  565. };
  566. // 启动前稍作停顿,避免过快开始
  567. setTimeout(typeWriter, 500);
  568. console.log("messages", messages);
  569. };
  570. // 当消息出现或变化时,测量容器并滚到底部
  571. watch(messages, (arr) => {
  572. if (arr.length > 0) {
  573. nextTick(() => {
  574. measureChatContainer();
  575. scrollToBottom();
  576. });
  577. }
  578. });
  579. // 滚动到底部(仅聊天区域滚动)
  580. const scrollToBottom = () => {
  581. if (!shouldAutoScroll.value) return;
  582. const query = uni.createSelectorQuery();
  583. query.select("#messageList").boundingClientRect();
  584. query.exec((res) => {
  585. if (res[0]) {
  586. latestContentHeight.value = res[0].height;
  587. chatScrollTop.value = res[0].height; // scroll-view 会自动夹紧到最大位置
  588. }
  589. });
  590. };
  591. const scrollToTop = () => {
  592. chatScrollTop.value = 0;
  593. };
  594. // 自动滚动控制:用户向上滚动时暂停自动滚到底部
  595. const shouldAutoScroll = ref(true);
  596. const latestContentHeight = ref(0);
  597. const lastScrollTop = ref(0);
  598. const windowHeight = uni.getSystemInfoSync().windowHeight;
  599. const AUTO_SCROLL_REENABLE_THRESHOLD = 40000; // px,接近底部时恢复自动滚动
  600. const onChatScroll = (e) => {
  601. const st = e.detail?.scrollTop || 0;
  602. const delta = st - lastScrollTop.value;
  603. lastScrollTop.value = st;
  604. if (delta < 0) {
  605. shouldAutoScroll.value = false;
  606. return;
  607. }
  608. const distanceToBottom =
  609. latestContentHeight.value - st - chatContainerHeight.value;
  610. if (distanceToBottom <= AUTO_SCROLL_REENABLE_THRESHOLD) {
  611. shouldAutoScroll.value = true;
  612. }
  613. };
  614. // 回到顶部图标拖拽状态
  615. const backTopX = ref(0);
  616. const backTopY = ref(0);
  617. const backTopDragging = ref(false);
  618. const backTopDragOffset = ref({ x: 0, y: 0 });
  619. const backTopTargetX = ref(0);
  620. const backTopTargetY = ref(0);
  621. let backTopRAF = 0;
  622. const backTopSmoothing = 0.15; // 越大越跟手,越小越顺滑
  623. const backTopEpsilon = 0.5; // 收敛阈值
  624. const raf =
  625. typeof requestAnimationFrame === "function"
  626. ? requestAnimationFrame
  627. : (fn) => setTimeout(fn, 16);
  628. const caf =
  629. typeof cancelAnimationFrame === "function"
  630. ? cancelAnimationFrame
  631. : (id) => clearTimeout(id);
  632. function stepBackTop() {
  633. const dx = backTopTargetX.value - backTopX.value;
  634. const dy = backTopTargetY.value - backTopY.value;
  635. // 插值缓动,避免每帧重排
  636. backTopX.value += dx * backTopSmoothing;
  637. backTopY.value += dy * backTopSmoothing;
  638. if (Math.abs(dx) > backTopEpsilon || Math.abs(dy) > backTopEpsilon) {
  639. backTopRAF = raf(stepBackTop);
  640. } else {
  641. backTopX.value = backTopTargetX.value;
  642. backTopY.value = backTopTargetY.value;
  643. backTopRAF = 0;
  644. }
  645. }
  646. function ensureBackTopRAF() {
  647. if (!backTopRAF) {
  648. backTopRAF = raf(stepBackTop);
  649. }
  650. }
  651. const clamp = (val, min, max) => Math.max(min, Math.min(val, max));
  652. const onBackTopTouchStart = (e) => {
  653. const t = e.touches && e.touches[0];
  654. if (!t) return;
  655. backTopDragging.value = false;
  656. backTopDragOffset.value = {
  657. x: t.pageX - backTopX.value,
  658. y: t.pageY - backTopY.value,
  659. };
  660. };
  661. const onBackTopTouchMove = (e) => {
  662. const t = e.touches && e.touches[0];
  663. if (!t) return;
  664. const sys = uni.getSystemInfoSync();
  665. const iconSize = uni.upx2px(100);
  666. const reserveBottom = uni.upx2px(260);
  667. const nx = t.pageX - backTopDragOffset.value.x;
  668. const ny = t.pageY - backTopDragOffset.value.y;
  669. const clampedX = clamp(nx, 0, sys.windowWidth - iconSize);
  670. const clampedY = clamp(ny, 0, sys.windowHeight - reserveBottom - iconSize);
  671. if (
  672. Math.abs(clampedX - backTopX.value) + Math.abs(clampedY - backTopY.value) >
  673. 3
  674. ) {
  675. backTopDragging.value = true;
  676. }
  677. backTopTargetX.value = clampedX;
  678. backTopTargetY.value = clampedY;
  679. ensureBackTopRAF();
  680. };
  681. const onBackTopTouchEnd = () => {
  682. // 结束拖拽即可
  683. };
  684. const onBackTopClick = () => {
  685. if (backTopDragging.value) return; // 拖拽时不触发点击回到顶部
  686. scrollToTop();
  687. };
  688. </script>
  689. <style scoped>
  690. .deepMate-page {
  691. display: flex;
  692. flex-direction: column;
  693. position: fixed;
  694. /* 充满视口,彻底禁用页面滚动 */
  695. top: 0;
  696. left: 0;
  697. right: 0;
  698. bottom: 0;
  699. height: 100vh;
  700. overflow: hidden;
  701. /* 锁定页面滚动 */
  702. background-color: #ffffff;
  703. padding: 20rpx 0rpx;
  704. }
  705. .header {
  706. display: flex;
  707. justify-content: space-between;
  708. align-items: center;
  709. padding: 20rpx 30rpx;
  710. background-color: #ffffff;
  711. box-shadow: 0 2rpx rgba(0, 0, 0, 0.1);
  712. }
  713. .header-left,
  714. .header-right {
  715. display: flex;
  716. align-items: center;
  717. }
  718. .header-left .icon,
  719. .header-right .icon {
  720. width: 40rpx;
  721. height: 40rpx;
  722. /* margin-right: 20rpx; */
  723. }
  724. .header-center .title {
  725. position: fixed;
  726. top: 10rpx;
  727. left: 50%;
  728. transform: translateX(-50%);
  729. font-size: 36rpx;
  730. font-weight: bold;
  731. color: #333333;
  732. }
  733. .new-chat-button {
  734. background-color: #ff6600;
  735. border: none;
  736. border-radius: 8rpx;
  737. padding: 10rpx 20rpx;
  738. }
  739. .new-chat-text {
  740. color: white;
  741. font-size: 24rpx;
  742. }
  743. .main-content {
  744. background-color: #fff;
  745. flex: 1;
  746. display: flex;
  747. flex-direction: column;
  748. overflow: hidden;
  749. /* 内部滚动交给聊天容器 */
  750. padding: 20rpx;
  751. margin-top: 1rpx;
  752. margin-bottom: 280rpx;
  753. }
  754. /* 聊天顶部粘性欢迎块样式 */
  755. .chat-header {
  756. position: sticky;
  757. top: 0;
  758. /* 在页面滚动时始终贴顶 */
  759. z-index: 50;
  760. background-color: #ffffff;
  761. padding: 3rpx 20rpx;
  762. }
  763. .robot-container {
  764. display: flex;
  765. align-items: center;
  766. margin-top: 30rpx;
  767. }
  768. .robot-avatar {
  769. width: 130rpx;
  770. height: 130rpx;
  771. border-radius: 50%;
  772. margin-left: 20rpx;
  773. }
  774. .welcome-message {
  775. flex: 1;
  776. }
  777. .greeting {
  778. font-size: 32rpx;
  779. margin-left: 50rpx;
  780. top: 40rpx;
  781. font-weight: bold;
  782. color: #333333;
  783. line-height: 48rpx;
  784. }
  785. .description {
  786. display: block;
  787. font-size: 24rpx;
  788. color: #666666;
  789. line-height: 36rpx;
  790. margin-top: 10rpx;
  791. margin-left: 45rpx;
  792. }
  793. .function-tabs {
  794. display: block;
  795. overflow: hidden;
  796. margin-bottom: 30rpx;
  797. }
  798. .tabs-track {
  799. display: inline-flex;
  800. white-space: nowrap;
  801. will-change: transform;
  802. }
  803. .tab-item {
  804. padding: 5rpx 20rpx;
  805. border-radius: 20rpx;
  806. font-size: 20rpx;
  807. font-weight: 700;
  808. color: #666666;
  809. background-color: #fffefe;
  810. margin-right: 20rpx;
  811. transition: all 0.3s;
  812. }
  813. .tab-item.active {
  814. color: #ff6600;
  815. background-color: #fff;
  816. border: 1rpx solid #ff6600;
  817. }
  818. .recommend-card {
  819. background: url("https://d31zlh4on95l9h.cloudfront.net/images/4da1d629a55c307c3605ca15bf15189a.svg");
  820. background-repeat: no-repeat;
  821. background-position: center bottom;
  822. background-size: contain;
  823. /* min-height: 20rpx; */
  824. /* border-radius: 20rpx; */
  825. padding: 40rpx;
  826. margin-top: 20rpx;
  827. margin-bottom: 10rpx;
  828. /* box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); */
  829. }
  830. .welcome-section{
  831. /* 灰色卡片(recommend-card)之后展示背景图 */
  832. margin-top: 10rpx;
  833. display: flex;
  834. align-items: center;
  835. justify-content: center;
  836. background: url('https://d31zlh4on95l9h.cloudfront.net/images/eca84d9fb54712cb3bc6c6174773b83b.svg');
  837. background-repeat: no-repeat;
  838. background-position: center top; /* 放在容器顶部,正好在灰色卡片下方 */
  839. background-size: 80% auto; /* 缩放以适配宽度 */
  840. height: 460rpx; /* 提供可视高度,让背景图可见 */
  841. }
  842. .card-content {
  843. display: flex;
  844. align-items: center;
  845. justify-content: space-between;
  846. margin-left: 40rpx;
  847. }
  848. .logo {
  849. width: 80rpx;
  850. height: 80rpx;
  851. background-color: #ff0000;
  852. border-radius: 10rpx;
  853. display: flex;
  854. align-items: center;
  855. justify-content: center;
  856. margin-right: 20rpx;
  857. }
  858. .card-text {
  859. flex: 1;
  860. margin-left: 20rpx;
  861. }
  862. .main-question {
  863. font-size: 32rpx;
  864. color: #333333;
  865. line-height: 48rpx;
  866. }
  867. .stock-code {
  868. display: block;
  869. font-size: 24rpx;
  870. color: #ff3b30;
  871. background-color: #ffffff;
  872. padding: 2rpx 15rpx;
  873. border-radius: 12rpx;
  874. margin-top: 8rpx;
  875. width: fit-content;
  876. border: 1rpx solid #ff3b30;
  877. }
  878. .arrow-icon {
  879. background: url("https://d31zlh4on95l9h.cloudfront.net/images/40d94054644f6e3f1c366751f07f0010.svg");
  880. background-repeat: no-repeat;
  881. left: 0.5rem;
  882. top: 1.8rem;
  883. background-size: 100% 100%;
  884. width: 60rpx;
  885. height: 60rpx;
  886. }
  887. .interest-section {
  888. margin-bottom: 30rpx;
  889. }
  890. .section-title {
  891. display: block;
  892. text-align: left;
  893. font-size: 26rpx;
  894. color: #666666;
  895. margin-bottom: 20rpx;
  896. }
  897. .topics-list {
  898. display: flex;
  899. flex-direction: column;
  900. gap: 15rpx;
  901. }
  902. .topic-item {
  903. display: flex;
  904. align-items: center;
  905. padding: 15rpx 20rpx;
  906. background-color: #f0f0f0;
  907. border-radius: 15rpx;
  908. width: fit-content;
  909. }
  910. .tag-icon {
  911. width: 24rpx;
  912. height: 24rpx;
  913. margin-right: 10rpx;
  914. }
  915. .topic-text {
  916. font-size: 28rpx;
  917. color: #333333;
  918. flex: 1;
  919. }
  920. /* 聊天区域样式 */
  921. .chat-container {
  922. margin-top: 30rpx;
  923. border-radius: 10rpx;
  924. height: calc(100vh - 50rpx);
  925. /* 缩短滚动区域,避免被输入框覆盖 */
  926. overflow-y: auto;
  927. -webkit-overflow-scrolling: touch;
  928. }
  929. .message-list {
  930. background-color: #fff;
  931. margin-bottom: 400rpx;
  932. /* padding: 20rpx; */
  933. }
  934. .message {
  935. display: flex;
  936. align-items: flex-start;
  937. margin-bottom: 30rpx;
  938. }
  939. .user-message {
  940. flex-direction: row-reverse;
  941. justify-content: flex-start;
  942. }
  943. .message-icon {
  944. font-size: 24rpx;
  945. margin: 0 10rpx;
  946. padding: 10rpx;
  947. border-radius: 50%;
  948. background-color: #ddd;
  949. width: 40rpx;
  950. height: 40rpx;
  951. display: flex;
  952. align-items: center;
  953. justify-content: center;
  954. }
  955. .user-message .message-icon {
  956. background-color: #007aff;
  957. border-radius: 50%;
  958. color: #fff;
  959. /* box-shadow: 0 0 12rpx rgba(0, 122, 255, 0.4); */
  960. }
  961. .bot-message .message-icon {
  962. background: url('https://d31zlh4on95l9h.cloudfront.net/images/e723171126bd31c52137709ebbd1a7ea.svg');
  963. color: white;
  964. }
  965. .message-content {
  966. max-width: 70%;
  967. position: relative;
  968. }
  969. .user-message .message-content {
  970. /* background-color: #007aff; */
  971. border: 2rpx solid #f3908f;
  972. border-radius: 20rpx;
  973. padding: 10rpx;
  974. }
  975. .bot-message .message-content {
  976. background-color: #f0f0f0;
  977. border-radius: 10rpx;
  978. padding: 15rpx;
  979. }
  980. .message-text {
  981. font-size: 28rpx;
  982. line-height: 40rpx;
  983. }
  984. .user-message .message-text {
  985. color:black;
  986. }
  987. .bot-message .message-text {
  988. color: #333;
  989. }
  990. .loading-dots {
  991. display: flex;
  992. align-items: center;
  993. /* padding-top: 10rpx; */
  994. }
  995. .dot {
  996. width: 10rpx;
  997. height: 10rpx;
  998. background-color: #666;
  999. border-radius: 50%;
  1000. margin: 0 4rpx;
  1001. animation: loading 1.4s infinite ease-in-out both;
  1002. }
  1003. .user-message .dot {
  1004. background-color: white;
  1005. }
  1006. .dot:nth-child(1) {
  1007. animation-delay: -0.32s;
  1008. }
  1009. .dot:nth-child(2) {
  1010. animation-delay: -0.16s;
  1011. }
  1012. @keyframes loading {
  1013. 0%,
  1014. 80%,
  1015. 100% {
  1016. transform: scale(0);
  1017. }
  1018. 40% {
  1019. transform: scale(1);
  1020. }
  1021. }
  1022. .input-area {
  1023. position: fixed;
  1024. margin-top: 20rpx;
  1025. bottom: 70rpx;
  1026. left: 0;
  1027. right: 0;
  1028. padding: 0 40rpx 80rpx 40rpx;
  1029. background-color: #ffffff;
  1030. }
  1031. .input-wrapper {
  1032. position: relative;
  1033. display: flex;
  1034. align-items: center;
  1035. padding: 15rpx 20rpx;
  1036. background-color: rgb(220, 31, 29);
  1037. border-radius: 100rpx;
  1038. display: flex;
  1039. align-items: center;
  1040. justify-content: center;
  1041. height: 50rpx;
  1042. }
  1043. .mic-icon {
  1044. width: 36rpx;
  1045. height: 36rpx;
  1046. margin-right: 20rpx;
  1047. }
  1048. .input-field {
  1049. flex: 1;
  1050. font-size: 28rpx;
  1051. color: #fff;
  1052. display: flex;
  1053. align-items: center;
  1054. justify-content: center;
  1055. margin-left: 60rpx;
  1056. background: none;
  1057. border: none;
  1058. outline: none;
  1059. }
  1060. .input-field::placeholder {
  1061. color: #ffffff !important;
  1062. opacity: 1;
  1063. }
  1064. /* .uni-scroll-view{
  1065. height: 92%;
  1066. } */
  1067. .send-button {
  1068. background: url("https://d31zlh4on95l9h.cloudfront.net/images/95f1ea2262e9157db13c93c0dc1c5d96.svg");
  1069. background-repeat: no-repeat;
  1070. background-size: 100% 100%;
  1071. height: 50rpx;
  1072. width: 50rpx;
  1073. padding: 0;
  1074. border: 1rpx solid transparent;
  1075. margin-left: 20rpx;
  1076. }
  1077. .send-icon {
  1078. width: 36rpx;
  1079. height: 36rpx;
  1080. }
  1081. .disclaimer {
  1082. font-size: 15rpx;
  1083. color: #4d4c4c;
  1084. display: flex;
  1085. align-items: center;
  1086. justify-content: center;
  1087. margin-top: 15rpx;
  1088. }
  1089. .banner-panel {
  1090. position: relative;
  1091. height: 480rpx;
  1092. /* 拉长容器,灰色背景跟随变高 */
  1093. overflow: hidden;
  1094. /* 让圆角和内部层剪裁一致 */
  1095. border-radius: 15rpx;
  1096. }
  1097. .panelShow {
  1098. height: 12%;
  1099. }
  1100. .pray-banner {
  1101. position: absolute;
  1102. /* background-size: 100% 100%; */
  1103. inset: 0;
  1104. /* 顶部、底部、左、右都贴合容器 */
  1105. width: 100%;
  1106. height: 81%;
  1107. border-radius: 15rpx;
  1108. z-index: 1;
  1109. /* 在灰底之上、内容之下 */
  1110. }
  1111. .contain {
  1112. margin: 0 20rpx;
  1113. gap: 5rpx;
  1114. }
  1115. .banner-panel .robot-container,
  1116. .banner-panel .function-tabs,
  1117. .banner-panel .recommend-card {
  1118. position: relative;
  1119. z-index: 2;
  1120. }
  1121. .back-to-top {
  1122. position: fixed;
  1123. left: 0;
  1124. top: 0;
  1125. width: 100rpx;
  1126. height: 100rpx;
  1127. z-index: 1000;
  1128. }
  1129. .back-to-top:active {
  1130. transform: scale(0.96);
  1131. }
  1132. .static-footer {
  1133. position: fixed;
  1134. bottom: 0;
  1135. }
  1136. /* 搜索历史侧拉框样式 */
  1137. .drawer-overlay {
  1138. position: fixed;
  1139. left: 0;
  1140. top: 0;
  1141. right: 0;
  1142. bottom: 0;
  1143. background-color: rgba(0, 0, 0, 0.35);
  1144. z-index: 900;
  1145. }
  1146. .drawer-panel {
  1147. position: fixed;
  1148. left: 0;
  1149. right: 0;
  1150. bottom: 0;
  1151. height: 75vh;
  1152. max-height: 80vh;
  1153. width: 100%;
  1154. background: #ffffff;
  1155. box-shadow: 0 -8rpx 20rpx rgba(0, 0, 0, 0.06);
  1156. z-index: 1000;
  1157. display: flex;
  1158. flex-direction: column;
  1159. border-top-left-radius: 20rpx;
  1160. border-top-right-radius: 20rpx;
  1161. transition: transform 0.22s ease;
  1162. }
  1163. .drawer-back {
  1164. position: absolute;
  1165. left: 50%;
  1166. top: -14px;
  1167. transform: translateX(-50%);
  1168. width: 28px;
  1169. height: 48px;
  1170. border-radius: 12px;
  1171. background: #fff;
  1172. box-shadow: 0 2px 10px rgba(0,0,0,0.08);
  1173. display: flex;
  1174. align-items: center;
  1175. justify-content: center;
  1176. }
  1177. .drawer-back-icon {
  1178. font-size: 16px;
  1179. color: #8a8a8a;
  1180. }
  1181. .drawer-actions {
  1182. display: flex;
  1183. align-items: center;
  1184. gap: 40rpx;
  1185. }
  1186. .delete-all-container {
  1187. display: flex;
  1188. align-items: center;
  1189. gap: 14rpx;
  1190. }
  1191. .delete-icon {
  1192. width: 45rpx;
  1193. height: 40rpx;
  1194. }
  1195. .delete-all {
  1196. font-size: 28rpx;
  1197. }
  1198. .drawer-close {
  1199. background: url('https://d31zlh4on95l9h.cloudfront.net/images/444fbe3218954f3f907664c91025c4e4.svg');
  1200. width: 48rpx;
  1201. height: 48rpx;
  1202. border-radius: 24rpx;
  1203. /* background: #f5f5f5; */
  1204. /* box-shadow: 0 2px 8px rgba(0,0,0,0.08); */
  1205. display: flex;
  1206. align-items: center;
  1207. justify-content: center;
  1208. }
  1209. .drawer-close-icon {
  1210. font-size: 24rpx;
  1211. color: #8a8a8a;
  1212. }
  1213. .drawer-header {
  1214. display: flex;
  1215. align-items: center;
  1216. justify-content: space-between;
  1217. padding: 52rpx 28rpx 0rpx 28rpx;
  1218. /* border-bottom: 2rpx solid #f0f0f0; */
  1219. }
  1220. .drawer-title {
  1221. font-size: 32rpx;
  1222. font-weight: 600;
  1223. color: #333333;
  1224. }
  1225. /* .drawer-close {
  1226. font-size: 42rpx;
  1227. color: #999999;
  1228. } */
  1229. .drawer-content {
  1230. flex: 1;
  1231. min-height: 0; /* 让 flex 子元素可在容器内收缩以启用滚动 */
  1232. height: 100%;
  1233. overscroll-behavior-y: contain; /* 防止滚动串联到页面 */
  1234. -webkit-overflow-scrolling: touch; /* iOS 惯性滚动 */
  1235. touch-action: pan-y; /* 优化触控滚动,仅垂直 */
  1236. }
  1237. .drawer-inner {
  1238. padding: 20rpx 24rpx 20rpx 24rpx;
  1239. }
  1240. .history-section {
  1241. margin-bottom: 20rpx;
  1242. /* margin: 0 24rpx; */
  1243. }
  1244. .section-title {
  1245. font-size: 26rpx;
  1246. color: #888;
  1247. margin: 10rpx 6rpx 16rpx;
  1248. }
  1249. .history-item {
  1250. display: flex;
  1251. align-items: center;
  1252. padding: 19rpx 18rpx;
  1253. background-color: #f6f7f9;
  1254. border-radius: 12rpx;
  1255. margin-bottom: 12rpx;
  1256. }
  1257. .history-left { margin-right: 12rpx; }
  1258. .flag-circle {
  1259. width: 36rpx;
  1260. height: 36rpx;
  1261. border-radius: 50%;
  1262. background: #fff;
  1263. border: 2rpx solid #eee;
  1264. display: flex;
  1265. align-items: center;
  1266. justify-content: center;
  1267. }
  1268. .flag-emoji { font-size: 24rpx; }
  1269. .history-main { flex: 1; }
  1270. .history-query {
  1271. font-size: 28rpx;
  1272. color: #333;
  1273. }
  1274. .history-time {
  1275. font-size: 22rpx;
  1276. color: #999;
  1277. }
  1278. .history-card {
  1279. background-color: #fff;
  1280. border: 2rpx solid #f2f2f2;
  1281. border-left: 8rpx solid rgb(220, 31, 29);
  1282. border-radius: 12rpx;
  1283. padding: 18rpx 20rpx;
  1284. margin-bottom: 16rpx;
  1285. box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.03);
  1286. }
  1287. .history-query {
  1288. font-size: 28rpx;
  1289. color: #333333;
  1290. font-weight: 500;
  1291. }
  1292. .history-time {
  1293. margin-top: 8rpx;
  1294. font-size: 22rpx;
  1295. color: #888888;
  1296. }
  1297. .empty-history {
  1298. padding: 40rpx;
  1299. color: #999999;
  1300. text-align: center;
  1301. }
  1302. .thinking-process {
  1303. margin: 10rpx 0;
  1304. border: 2rpx solid #e5e5e5;
  1305. border-radius: 20rpx;
  1306. background-color: #f9f9f9;
  1307. }
  1308. .thinking-header {
  1309. display: flex;
  1310. align-items: center;
  1311. padding: 20rpx 30rpx;
  1312. cursor: pointer;
  1313. background-color: #fff;
  1314. border-radius: 20rpx;
  1315. border-bottom: 2rpx solid #e5e5e5;
  1316. }
  1317. .thinking-icon {
  1318. font-size: 32rpx;
  1319. margin-right: 16rpx;
  1320. color: #d47c45;
  1321. }
  1322. .thinking-title {
  1323. font-size: 28rpx;
  1324. font-weight: 500;
  1325. color: #d47c45;
  1326. margin-right: 16rpx;
  1327. }
  1328. .thinking-count {
  1329. font-size: 24rpx;
  1330. color: #666;
  1331. margin-right: 16rpx;
  1332. }
  1333. .thinking-toggle {
  1334. font-size: 24rpx;
  1335. color: #999;
  1336. }
  1337. .thinking-content {
  1338. padding: 20rpx 30rpx;
  1339. }
  1340. .thinking-item {
  1341. display: flex;
  1342. align-items: center;
  1343. margin-bottom: 16rpx;
  1344. padding: 8rpx 0;
  1345. }
  1346. .item-status {
  1347. width: 32rpx;
  1348. height: 32rpx;
  1349. border-radius: 50%;
  1350. background-color: #f0f0f0;
  1351. display: flex;
  1352. justify-content: center;
  1353. align-items: center;
  1354. margin-right: 16rpx;
  1355. }
  1356. .checkmark {
  1357. font-size: 20rpx;
  1358. color: #ff0000;
  1359. }
  1360. .item-text {
  1361. font-size: 24rpx;
  1362. color: #333;
  1363. }
  1364. </style>