232 lines
5.7 KiB
Vue
232 lines
5.7 KiB
Vue
<template>
|
||
<!-- 用强大的功能,开启盈利快车道 - Figma 1455:1490 -->
|
||
<view class="feature-section">
|
||
<text class="section-title">用强大的功能,开启盈利快车道</text>
|
||
|
||
<!-- 功能卡片横向滚动
|
||
:scroll-left 绑定 featScrollLeft,activeFeat 变化时尽量把选中项滚到左侧 -->
|
||
<scroll-view
|
||
scroll-x
|
||
class="feat-scroll"
|
||
show-scrollbar="false"
|
||
:scroll-left="featScrollLeft"
|
||
:scroll-with-animation="true"
|
||
>
|
||
<view class="feat-list">
|
||
<view
|
||
class="feat-card"
|
||
:class="{ active: activeFeat === index }"
|
||
v-for="(item, index) in features"
|
||
:key="index"
|
||
@tap="onFeatTap(index)"
|
||
>
|
||
<text class="feat-num">{{ item.step }}</text>
|
||
<view class="feat-text">
|
||
<text class="feat-title">{{ item.title }}</text>
|
||
<text class="feat-desc">{{ item.desc }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 集成平台大图 - 370x257 8px圆角
|
||
图片来源:features[activeFeat].img(4张卡片分别对应一张大图;占位时 4 张同图,设计师供图后换 mock.js 即可) -->
|
||
<view class="integration-wrap">
|
||
<image
|
||
class="integration-img"
|
||
:src="integrationImg"
|
||
mode="widthFix"
|
||
></image>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// 功能卡片尺寸(与 CSS 保持一致,用于计算「选中卡片滚到左边」的 scrollLeft)
|
||
// CSS: .feat-card { width:332rpx }, .feat-list gap:16rpx
|
||
// 1rpx = 0.5px,所以 1 张卡片+gap = (332 + 16) * 0.5 = 174px
|
||
const CARD_WIDTH_PX = 332 * 0.5 // 166
|
||
const CARD_GAP_PX = 16 * 0.5 // 8
|
||
const CARD_STEP_PX = CARD_WIDTH_PX + CARD_GAP_PX // 174
|
||
|
||
const AUTO_PLAY_INTERVAL = 3000 // 3 秒自动轮播一次
|
||
|
||
export default {
|
||
name: 'PlatformFeatures',
|
||
props: {
|
||
features: {
|
||
type: Array,
|
||
default: () => []
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
activeFeat: 0, // 默认从第一张开始轮播
|
||
featScrollLeft: 0, // scroll-view 的 scroll-left(px)
|
||
_timer: null
|
||
}
|
||
},
|
||
computed: {
|
||
integrationImg() {
|
||
const f = this.features[this.activeFeat]
|
||
return (f && f.img) || ''
|
||
}
|
||
},
|
||
watch: {
|
||
activeFeat(idx) {
|
||
// 选中项变化时:把选中卡片「尽量滚到左边」
|
||
// index * step = 选中卡片起始 x
|
||
this.scrollToCard(idx)
|
||
}
|
||
},
|
||
mounted() {
|
||
this.startAutoPlay()
|
||
},
|
||
beforeDestroy() {
|
||
this.stopAutoPlay()
|
||
},
|
||
methods: {
|
||
onFeatTap(index) {
|
||
// 用户手动点击:切换 + 先停轮播,再隔 3 秒重启(避免刚点完又被立即切走)
|
||
this.stopAutoPlay()
|
||
this.activeFeat = index
|
||
this.$emit('change', index)
|
||
this.startAutoPlay()
|
||
},
|
||
|
||
/**
|
||
* 让横滚容器把第 idx 张卡片「尽量」滚到左边贴边。
|
||
* scroll-view 的 scroll-left 有最大上限(内容总长 - 视口宽度),
|
||
* 当 idx 是最后几张时,scroll-left 超过最大值,scroll-view 会自动截断到可滚的最大值,
|
||
* 因此卡片虽无法真的贴左,但已经是「滚到最接近左侧」的位置了。
|
||
*/
|
||
scrollToCard(idx) {
|
||
const target = Math.max(0, idx) * CARD_STEP_PX
|
||
// 先赋值一个不同的值,保证 uni-app scroll-view 每次都能识别到属性变化
|
||
// (否则从 0 → 0 这种无变化赋值不触发滚动)
|
||
if (this.featScrollLeft === target) {
|
||
this.featScrollLeft = target + 0.001
|
||
this.$nextTick(() => {
|
||
this.featScrollLeft = target
|
||
})
|
||
} else {
|
||
this.featScrollLeft = target
|
||
}
|
||
},
|
||
|
||
startAutoPlay() {
|
||
this.stopAutoPlay()
|
||
if (!this.features || this.features.length <= 1) return
|
||
this._timer = setInterval(() => {
|
||
const total = this.features.length
|
||
const next = (this.activeFeat + 1) % total
|
||
this.activeFeat = next
|
||
this.$emit('change', next)
|
||
}, AUTO_PLAY_INTERVAL)
|
||
},
|
||
|
||
stopAutoPlay() {
|
||
if (this._timer) {
|
||
clearInterval(this._timer)
|
||
this._timer = null
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.feature-section {
|
||
width: 100%;
|
||
padding: 96rpx 0 0 0;
|
||
background: #ffffff;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 区块标题 - 20px Heavy #1A1A1A 居中 */
|
||
.section-title {
|
||
display: block;
|
||
text-align: center;
|
||
font-size: 40rpx;
|
||
font-weight: 900;
|
||
color: #1a1a1a;
|
||
line-height: 1.45;
|
||
margin-bottom: 64rpx;
|
||
}
|
||
|
||
/* 功能卡片横向滚动 */
|
||
.feat-scroll {
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
padding: 0 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
.feat-list {
|
||
display: inline-flex;
|
||
gap: 16rpx;
|
||
}
|
||
/* 卡片 - 166x80 圆角6px (332rpx × 160rpx) */
|
||
.feat-card {
|
||
flex-shrink: 0;
|
||
width: 332rpx;
|
||
height: 160rpx;
|
||
padding: 20rpx;
|
||
background: #f2f2f2;
|
||
border-radius: 12rpx;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
}
|
||
.feat-card.active {
|
||
background: #ff6800;
|
||
}
|
||
.feat-num {
|
||
font-size: 32rpx;
|
||
font-weight: 900;
|
||
color: #ff6800;
|
||
line-height: 52rpx;
|
||
}
|
||
.feat-card.active .feat-num {
|
||
color: #ffffff;
|
||
}
|
||
.feat-text {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.feat-title {
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
color: #000000;
|
||
line-height: 44rpx;
|
||
}
|
||
.feat-card.active .feat-title {
|
||
color: #ffffff;
|
||
}
|
||
.feat-desc {
|
||
margin-top: 6rpx;
|
||
font-size: 20rpx;
|
||
color: #000000;
|
||
opacity: 0.6;
|
||
line-height: 1.45;
|
||
white-space: normal;
|
||
}
|
||
.feat-card.active .feat-desc {
|
||
color: #ffffff;
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 集成平台大图 - 370x257 8px圆角 */
|
||
.integration-wrap {
|
||
margin-top: 20rpx;
|
||
padding: 0 18rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
.integration-img {
|
||
width: 100%;
|
||
border-radius: 16rpx;
|
||
display: block;
|
||
}
|
||
</style>
|