|
|
|
@ -21,7 +21,7 @@ |
|
|
|
<!-- 主要内容区域 --> |
|
|
|
<view class="main-content"> |
|
|
|
<view class="banner-panel" v-if="messages.length === 0"> |
|
|
|
<image src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg" class="pray-banner" mode="aspectFill" ></image> |
|
|
|
<image src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg" class="pray-banner" ></image> |
|
|
|
<view class="contain"> |
|
|
|
<!-- 机器人头像和欢迎语 --> |
|
|
|
<view class="robot-container" v-if="messages.length === 0"> |
|
|
|
@ -33,11 +33,11 @@ |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 功能标签栏 --> |
|
|
|
<view class="function-tabs" v-if="messages.length === 0"> |
|
|
|
<!-- <view class="function-tabs" v-if="messages.length === 0"> |
|
|
|
<view ref="tabsTrack" class="tabs-track" :style="{ transform: 'translate3d(-' + marqueeOffset + 'px,0,0)' }"> |
|
|
|
<view class="tab-item" v-for="(tab, idx) in marqueeList" :key="idx">{{ tab }}</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> --> |
|
|
|
|
|
|
|
<!-- 特斯拉推荐卡片 --> |
|
|
|
<view class="recommend-card" v-if="messages.length === 0"> |
|
|
|
@ -55,7 +55,7 @@ |
|
|
|
|
|
|
|
</view> |
|
|
|
<!-- 可能感兴趣的话题 --> |
|
|
|
<view v-if="messages.length === 0" class="interest-section"> |
|
|
|
<!-- <view v-if="messages.length === 0" class="interest-section"> |
|
|
|
<text class="section-title">- 您可能感兴趣 -</text> |
|
|
|
<view class="topics-list"> |
|
|
|
<view class="topic-item" v-for="topic in hotTopics" :key="topic.id"> |
|
|
|
@ -66,7 +66,7 @@ |
|
|
|
<text class="topic-text" @click="sendMessageList(topic.text)">{{ topic.text }}</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> --> |
|
|
|
|
|
|
|
<!-- 聊天区域 --> |
|
|
|
<view class="chat-container" v-if="messages.length > 0"> |
|
|
|
@ -165,7 +165,6 @@ const hotTopics = ref([ |
|
|
|
|
|
|
|
// 新增:tabs 列表用于渲染与轮换 |
|
|
|
const tabsList = ref(['个股诊断', '市场情绪温度计', '买卖时机提示', '个股']); |
|
|
|
const marqueeList = computed(() => tabsList.value.concat(tabsList.value)); |
|
|
|
|
|
|
|
// 用于无缝滚动的引用与状态(兼容小程序/App) |
|
|
|
const tabsTrack = ref(null); |
|
|
|
@ -173,29 +172,12 @@ const marqueeOffset = ref(0); |
|
|
|
let tabsTickId = 0; |
|
|
|
const marqueeSpeed = 0.6; // 像素/帧 |
|
|
|
let currentThreshold = 0; // 当前第一个标签的宽度(含右外边距) |
|
|
|
let contentWidthHalf = 0; // 半宽:单份列表的总宽度 |
|
|
|
|
|
|
|
// 统一 raf/caf(小程序端可能没有 rAF) |
|
|
|
const hasRAF = typeof requestAnimationFrame === 'function'; |
|
|
|
const startFrame = (fn) => hasRAF ? requestAnimationFrame(fn) : setTimeout(fn, 16); |
|
|
|
const stopFrame = (id) => hasRAF ? cancelAnimationFrame(id) : clearTimeout(id); |
|
|
|
|
|
|
|
// 替换测量:测量双列表轨道总宽度的一半,避免频繁 DOM 重排 |
|
|
|
const measureContentWidth = (cb) => { |
|
|
|
const q = uni.createSelectorQuery(); |
|
|
|
q.select('.function-tabs .tabs-track').boundingClientRect(); |
|
|
|
q.exec(res => { |
|
|
|
const rect = res && res[0]; |
|
|
|
if (rect && rect.width) { |
|
|
|
contentWidthHalf = rect.width / 2; |
|
|
|
if (typeof cb === 'function') cb(contentWidthHalf); |
|
|
|
} else { |
|
|
|
setTimeout(() => measureContentWidth(cb), 16); |
|
|
|
} |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
// 原 measureFirstThreshold 保留,但不再用于轮播 |
|
|
|
const measureFirstThreshold = (cb) => { |
|
|
|
if (!tabsList.value.length) { currentThreshold = 0; if (cb) cb(0); return; } |
|
|
|
const q = uni.createSelectorQuery(); |
|
|
|
@ -217,17 +199,18 @@ const startTabsMarquee = () => { |
|
|
|
stopFrame(tabsTickId); |
|
|
|
if (tabsList.value.length <= 1) return; // 单个标签不滚动 |
|
|
|
marqueeOffset.value = 0; |
|
|
|
nextTick(() => { |
|
|
|
measureContentWidth(() => { |
|
|
|
const step = () => { |
|
|
|
marqueeOffset.value += marqueeSpeed; |
|
|
|
if (contentWidthHalf > 0 && marqueeOffset.value >= contentWidthHalf) { |
|
|
|
marqueeOffset.value = 0; // 重置为 0,实现无缝循环 |
|
|
|
} |
|
|
|
tabsTickId = startFrame(step); |
|
|
|
}; |
|
|
|
step(); |
|
|
|
}); |
|
|
|
measureFirstThreshold(() => { |
|
|
|
const step = () => { |
|
|
|
marqueeOffset.value += marqueeSpeed; |
|
|
|
if (currentThreshold > 0 && marqueeOffset.value >= currentThreshold) { |
|
|
|
const first = tabsList.value.shift(); |
|
|
|
if (first !== undefined) tabsList.value.push(first); |
|
|
|
marqueeOffset.value -= currentThreshold; |
|
|
|
measureFirstThreshold(); |
|
|
|
} |
|
|
|
tabsTickId = startFrame(step); |
|
|
|
}; |
|
|
|
nextTick(step); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
@ -457,14 +440,14 @@ const scrollToTop = () => { |
|
|
|
.robot-container { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
margin-bottom: 30rpx; |
|
|
|
margin-top: 30rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.robot-avatar { |
|
|
|
width: 130rpx; |
|
|
|
height: 130rpx; |
|
|
|
border-radius: 50%; |
|
|
|
margin-right: 10rpx; |
|
|
|
margin-left: 20rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.welcome-message { |
|
|
|
@ -521,9 +504,11 @@ const scrollToTop = () => { |
|
|
|
.recommend-card { |
|
|
|
background: url('https://d31zlh4on95l9h.cloudfront.net/images/4da1d629a55c307c3605ca15bf15189a.svg'); |
|
|
|
background-repeat: no-repeat; |
|
|
|
background-size: 100% 100%; |
|
|
|
/* border-radius: 20rpx; */ |
|
|
|
padding: 40rpx; |
|
|
|
margin-bottom: 30rpx; |
|
|
|
margin-top: 20rpx; |
|
|
|
margin-bottom: 10rpx; |
|
|
|
/* box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); */ |
|
|
|
} |
|
|
|
|
|
|
|
|