180 lines
4.4 KiB
Vue
180 lines
4.4 KiB
Vue
<template>
|
||
<scroll-view scroll-x="true" scroll-with-animation :scroll-left="scrollLeft" class="steps-scroll-container"
|
||
ref="scrollViewRef">
|
||
<view class="steps-content" ref="contentRef">
|
||
<view v-for="(item,index) in list" class="step-item" :key="index" :data-index="index" ref="stepItemRefs">
|
||
<view class="step-inner">
|
||
<view class="index" :class="{active:index<=cur}">
|
||
<text>{{index+1}}</text>
|
||
</view>
|
||
<view class="step-text color-999" :class="{'color-main':index<=cur}" @click="handleClick(index)">
|
||
{{item}}
|
||
</view>
|
||
</view>
|
||
<view class="step-arrow" v-if="index!=list.length-1">
|
||
<up-icon name="arrow-rightward" size="16" :color="isActive(index)?'#318AFE':'#999'"></up-icon>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
ref,
|
||
nextTick,
|
||
getCurrentInstance
|
||
} from 'vue';
|
||
|
||
const cur = defineModel({
|
||
default:0
|
||
})
|
||
const scrollLeft = ref(0)
|
||
const scrollViewRef = ref(null)
|
||
const contentRef = ref(null) // 内容容器ref
|
||
const stepItemRefs = ref([]) // 步骤项ref数组
|
||
const instance = getCurrentInstance()
|
||
|
||
function isActive(index) {
|
||
return cur.value === index
|
||
}
|
||
|
||
// 核心:精准居中计算(基于元素相对于内容容器的偏移)
|
||
const calcScrollCenter = (index) => {
|
||
nextTick(async () => {
|
||
try {
|
||
const query = uni.createSelectorQuery().in(instance)
|
||
|
||
// 1. 获取滚动容器宽度
|
||
const [scrollViewRect] = await new Promise(resolve => {
|
||
query.select('.steps-scroll-container').boundingClientRect(rect => resolve([
|
||
rect
|
||
])).exec()
|
||
})
|
||
const scrollViewWidth = scrollViewRect?.width || 0
|
||
|
||
// 2. 获取当前步骤项的布局信息
|
||
const [itemRect] = await new Promise(resolve => {
|
||
query.select(`.step-item[data-index="${index}"]`).boundingClientRect(rect =>
|
||
resolve([rect])).exec()
|
||
})
|
||
|
||
// 3. 获取内容容器的布局信息
|
||
const [contentRect] = await new Promise(resolve => {
|
||
query.select('.steps-content').boundingClientRect(rect => resolve([rect]))
|
||
.exec()
|
||
})
|
||
|
||
if (!itemRect || !contentRect) return
|
||
|
||
// 关键修正:元素相对于内容容器的左偏移(而非视口)
|
||
const itemOffsetLeft = itemRect.left - contentRect.left
|
||
// 居中公式:滚动距离 = 元素偏移 - (容器宽度/2) + (元素宽度/2)
|
||
let targetScrollLeft = itemOffsetLeft - (scrollViewWidth / 2) + (itemRect.width / 2)
|
||
|
||
// 4. 计算最大可滚动距离(边界限制)
|
||
const maxScrollLeft = Math.max(0, contentRect.width - scrollViewWidth)
|
||
// 限制滚动范围,避免超出边界
|
||
targetScrollLeft = Math.max(0, Math.min(targetScrollLeft, maxScrollLeft))
|
||
|
||
// 5. 设置滚动距离(强制整数,避免小数导致的偏移)
|
||
scrollLeft.value = Math.round(targetScrollLeft)
|
||
} catch (e) {
|
||
console.error('计算居中失败:', e)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 点击事件
|
||
const handleClick = (index) => {
|
||
if (cur.value === index) return
|
||
cur.value = index
|
||
calcScrollCenter(index)
|
||
}
|
||
|
||
// 初始化居中
|
||
nextTick(() => {
|
||
calcScrollCenter(cur.value)
|
||
})
|
||
|
||
// 步骤列表
|
||
const list = ref(['基础信息', '法人信息', '营业执照信息', '门店信息', '结算信息'])
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
// 滚动容器:固定宽度,隐藏滚动条
|
||
.steps-scroll-container {
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
box-sizing: border-box;
|
||
overflow-x: auto;
|
||
|
||
// 隐藏滚动条(三端兼容)
|
||
::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
-ms-overflow-style: none;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
// 内容容器:inline-flex,宽度自适应
|
||
.steps-content {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 10rpx 0;
|
||
}
|
||
|
||
// 单个步骤项:统一间距和布局
|
||
.step-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin: 0 10rpx; // 统一间距
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
// 步骤内部布局
|
||
.step-inner {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
|
||
// 数字索引
|
||
.index {
|
||
border: 1px solid #999;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border-radius: 50%;
|
||
margin-right: 10rpx;
|
||
color: #999;
|
||
|
||
&.active {
|
||
border-color: $my-main-color;
|
||
color: $my-main-color;
|
||
}
|
||
}
|
||
|
||
// 步骤文字
|
||
.step-text {
|
||
white-space: nowrap;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
// 箭头容器
|
||
.step-arrow {
|
||
padding: 0 10rpx;
|
||
margin-top: 2rpx;
|
||
}
|
||
|
||
// 样式补充
|
||
.color-main {
|
||
color: $my-main-color !important;
|
||
}
|
||
|
||
.color-999 {
|
||
color: #999;
|
||
}
|
||
</style> |