hookehuyr

feat(首页): 添加流程步骤底部箭头跟随效果和轮播触摸滑动功能

- 将底部箭头改为全局组件并实现跟随当前选中步骤移动
- 为新闻轮播添加触摸滑动切换功能,支持左右滑动切换
- 移除路由中未使用的滚动行为配置
- 优化轮播容器触摸事件处理,避免与垂直滚动冲突
/*
* @Date: 2025-10-30 10:29:15
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-11-04 10:53:52
* @LastEditTime: 2025-11-04 20:35:35
* @FilePath: /stdj_h5/src/router/index.js
* @Description: 文件描述
*/
......@@ -53,13 +53,6 @@ const routes = [
const router = createRouter({
history: createWebHashHistory('/index.html'),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0 }
}
}
})
// 路由守卫
......
......@@ -37,7 +37,7 @@
<div class="rope-background"></div>
<!-- 流程步骤 -->
<div class="process-steps">
<div class="process-steps" ref="processStepsRef">
<div
v-for="(step, index) in processSteps"
:key="index"
......@@ -52,11 +52,10 @@
<div class="step-content">
<span class="step-text">{{ step.name }}</span>
</div>
<!-- 底部箭头(选中状态) -->
<div v-if="step.active" class="bottom-arrow">
<img src="https://cdn.ipadbiz.cn/stdj/images/home/%E7%82%B9@2x.png" alt="选中" />
</div>
<!-- 全局底部箭头:跟随选中项移动 -->
<div v-show="processSteps.length" class="bottom-arrow moving-arrow" :style="arrowStyle">
<img src="https://cdn.ipadbiz.cn/stdj/images/home/%E7%82%B9@2x.png" alt="选中" />
</div>
</div>
</div>
......@@ -116,7 +115,13 @@
</div>
<!-- 轮播容器 -->
<div class="news-carousel-container">
<div
class="news-carousel-container"
ref="newsCarouselContainer"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
>
<div class="news-carousel" ref="newsCarousel">
<div class="news-item" v-for="(item, index) in displayNewsItems" :key="index" @click="handleNewsClick(item)">
<div class="news-image">
......@@ -180,6 +185,10 @@ const videoOptions = ref({
// 法会流程步骤数据
const processSteps = ref([])
// 流程步骤容器引用
const processStepsRef = ref(null)
// 底部箭头的动态样式
const arrowStyle = ref({ left: '50%', bottom: '-1.25rem', transform: 'translateX(-50%)' })
// 获取当前选中的步骤
const currentStep = computed(() => {
......@@ -228,13 +237,18 @@ const calculateLinePosition = async () => {
}
}
// 点击切换步骤状态
/**
* 点击切换步骤状态并更新底部箭头位置
* @param {number} index 选中步骤索引
*/
const selectStep = (index) => {
processSteps.value.forEach((step, i) => {
step.active = i === index
})
// 切换步骤后重新计算装饰线位置
calculateLinePosition()
// 切换步骤后更新底部箭头位置
updateArrowPosition()
}
// 三师七证数据列表
......@@ -277,6 +291,8 @@ onMounted(async () => {
// 初始化轮播位置
nextTick(() => {
updateCarouselPosition(false)
// 初始化底部箭头位置
updateArrowPosition()
})
}
})
......@@ -284,6 +300,7 @@ onMounted(async () => {
// 监听当前步骤变化,重新计算装饰线位置
watch(currentStep, () => {
calculateLinePosition()
updateArrowPosition()
}, { deep: true })
// 查看更多按钮点击事件
......@@ -329,6 +346,15 @@ const viewMore = (type, item) => {
const newsCarousel = ref(null)
const currentSlide = ref(1) // 从1开始,因为0是克隆的最后一项
const isTransitioning = ref(false)
// 轮播容器引用(用于触摸滑动)
const newsCarouselContainer = ref(null)
// 触摸滑动相关状态
const touchStartX = ref(0)
const touchStartY = ref(0)
const touchDeltaX = ref(0)
const touchDeltaY = ref(0)
const isSwiping = ref(false)
const swipeThreshold = 30 // 滑动触发阈值,单位px
// 新闻数据
const newsItems = ref([])
......@@ -400,6 +426,64 @@ const updateCarouselPosition = (animated = true) => {
carouselEl.style.transform = `translateX(-${distance}px)`
}
/**
* 触摸开始事件:记录初始触摸点
* @param {TouchEvent} e 触摸事件对象
*/
const onTouchStart = (e) => {
const t = e.touches && e.touches[0]
if (!t) return
touchStartX.value = t.clientX
touchStartY.value = t.clientY
touchDeltaX.value = 0
touchDeltaY.value = 0
isSwiping.value = false
}
/**
* 触摸移动事件:计算水平与垂直位移,判断是否为水平滑动
* @param {TouchEvent} e 触摸事件对象
*/
const onTouchMove = (e) => {
const t = e.touches && e.touches[0]
if (!t) return
touchDeltaX.value = t.clientX - touchStartX.value
touchDeltaY.value = t.clientY - touchStartY.value
// 以水平位移为主且超过轻微阈值时,认定为滑动
if (Math.abs(touchDeltaX.value) > Math.abs(touchDeltaY.value) && Math.abs(touchDeltaX.value) > 10) {
isSwiping.value = true
// 阻止默认滚动,避免与页面垂直滚动冲突
e.preventDefault()
} else {
isSwiping.value = false
}
}
/**
* 触摸结束事件:根据滑动距离与方向触发上一张/下一张
*/
const onTouchEnd = () => {
if (!isSwiping.value) return
if (isTransitioning.value) return
if (Math.abs(touchDeltaX.value) >= swipeThreshold) {
if (touchDeltaX.value < 0) {
// 左滑,下一张
nextSlide()
} else {
// 右滑,上一张
prevSlide()
}
}
// 重置状态
touchStartX.value = 0
touchStartY.value = 0
touchDeltaX.value = 0
touchDeltaY.value = 0
isSwiping.value = false
}
// 处理新闻点击事件
const handleNewsClick = (item) => {
// 这里可以添加跳转到新闻详情页面的逻辑
......@@ -409,6 +493,32 @@ const handleNewsClick = (item) => {
router.push({ name: 'NewsDetail', params: { id: item.id } })
}
}
/**
* 更新底部箭头的位置,使其移动到当前选中步骤下方
*/
const updateArrowPosition = () => {
const containerEl = processStepsRef.value
if (!containerEl) return
const stepsEls = containerEl.querySelectorAll('.process-step')
const activeIndex = processSteps.value.findIndex(s => s.active)
if (activeIndex < 0 || !stepsEls[activeIndex]) return
const activeEl = stepsEls[activeIndex]
const containerRect = containerEl.getBoundingClientRect()
const activeRect = activeEl.getBoundingClientRect()
// 计算选中项中心点相对容器的left位置
const centerX = activeRect.left + activeRect.width / 2 - containerRect.left
// 使用px进行精确定位(移动端rem布局下,定位需要像素计算)
arrowStyle.value = {
left: `${centerX}px`,
bottom: '-1.25rem',
transform: 'translateX(-50%)'
}
}
</script>
<style scoped>
......@@ -895,6 +1005,15 @@ const handleNewsClick = (item) => {
.bottom-arrow img {
width: 1rem;
height: 1rem;
/* 底部箭头左右位移动画 */
animation: arrowHSlide 1.8s ease-in-out infinite;
will-change: transform;
}
/* 可移动箭头样式:启用left过渡,实现平滑跟随 */
.moving-arrow {
transition: left 0.3s ease;
pointer-events: none;
}
/* 响应式调整 */
......@@ -1228,6 +1347,8 @@ const handleNewsClick = (item) => {
overflow: hidden;
margin-top: 2rem;
z-index: 2; /* 确保轮播容器在背景图片之上 */
/* 仅允许垂直滚动,优化左右滑动体验 */
touch-action: pan-y;
}
.news-carousel {
......