209 lines
5.5 KiB
JavaScript
209 lines
5.5 KiB
JavaScript
// 新用户引导组件
|
||
Component({
|
||
properties: {
|
||
show: {
|
||
type: Boolean,
|
||
value: false
|
||
},
|
||
steps: {
|
||
type: Array,
|
||
value: []
|
||
}
|
||
},
|
||
|
||
data: {
|
||
currentStep: 0,
|
||
totalSteps: 0,
|
||
title: '',
|
||
description: '',
|
||
spotlightStyle: {},
|
||
contentStyle: '',
|
||
arrowDirection: 'top',
|
||
showArrow: true
|
||
},
|
||
|
||
observers: {
|
||
'show': function(show) {
|
||
if (show && this.data.steps.length > 0) {
|
||
this.setData({
|
||
currentStep: 0,
|
||
totalSteps: this.data.steps.length
|
||
})
|
||
this.showStep(0)
|
||
}
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
// 显示指定步骤
|
||
showStep(stepIndex) {
|
||
if (stepIndex < 0 || stepIndex >= this.data.steps.length) {
|
||
return
|
||
}
|
||
|
||
const step = this.data.steps[stepIndex]
|
||
this.setData({
|
||
currentStep: stepIndex,
|
||
title: step.title,
|
||
description: step.description,
|
||
showArrow: step.showArrow !== false
|
||
})
|
||
|
||
// 延迟获取元素位置,确保页面已渲染
|
||
setTimeout(() => {
|
||
this.updateSpotlight(step)
|
||
}, 50)
|
||
},
|
||
|
||
// 更新高亮区域
|
||
updateSpotlight(step) {
|
||
const query = wx.createSelectorQuery().in(this)
|
||
|
||
// 查询目标元素(在页面中)
|
||
const pageQuery = wx.createSelectorQuery()
|
||
pageQuery.select(step.selector).boundingClientRect()
|
||
pageQuery.selectViewport().scrollOffset()
|
||
|
||
pageQuery.exec((res) => {
|
||
if (!res || !res[0]) {
|
||
console.warn('未找到目标元素:', step.selector)
|
||
return
|
||
}
|
||
|
||
const rect = res[0]
|
||
const scroll = res[1]
|
||
|
||
// 计算高亮区域样式(添加padding扩展区域)
|
||
const padding = step.padding || 8
|
||
const spotlightStyle = {
|
||
top: rect.top - padding,
|
||
left: rect.left - padding,
|
||
width: rect.width + padding * 2,
|
||
height: rect.height + padding * 2,
|
||
borderRadius: step.borderRadius || 16
|
||
}
|
||
|
||
// 计算提示内容位置
|
||
const contentStyle = this.calculateContentPosition(spotlightStyle, step.position || 'bottom')
|
||
|
||
this.setData({
|
||
spotlightStyle,
|
||
contentStyle: contentStyle.style,
|
||
arrowDirection: contentStyle.arrowDirection
|
||
})
|
||
})
|
||
},
|
||
|
||
// 计算提示内容位置
|
||
calculateContentPosition(spotlightStyle, preferredPosition) {
|
||
const windowInfo = wx.getWindowInfo()
|
||
const screenHeight = windowInfo.windowHeight
|
||
const screenWidth = windowInfo.windowWidth
|
||
const contentWidth = 600 // rpx转换为px约300
|
||
const contentMaxHeight = 400 // 估算最大高度
|
||
|
||
let position = preferredPosition
|
||
let top = 0
|
||
let left = 0
|
||
let arrowDirection = 'top'
|
||
|
||
// 计算中心点
|
||
const spotlightCenterX = spotlightStyle.left + spotlightStyle.width / 2
|
||
const spotlightCenterY = spotlightStyle.top + spotlightStyle.height / 2
|
||
|
||
// 根据位置偏好计算
|
||
switch (position) {
|
||
case 'top':
|
||
top = spotlightStyle.top - contentMaxHeight - 40
|
||
left = Math.max(20, Math.min(spotlightCenterX - contentWidth / 2, screenWidth - contentWidth - 20))
|
||
arrowDirection = 'bottom'
|
||
|
||
// 如果顶部空间不足,改为底部
|
||
if (top < 20) {
|
||
position = 'bottom'
|
||
}
|
||
break
|
||
|
||
case 'bottom':
|
||
top = spotlightStyle.top + spotlightStyle.height + 20
|
||
left = Math.max(20, Math.min(spotlightCenterX - contentWidth / 2, screenWidth - contentWidth - 20))
|
||
arrowDirection = 'top'
|
||
|
||
// 如果底部空间不足,改为顶部
|
||
if (top + contentMaxHeight > screenHeight - 20) {
|
||
position = 'top'
|
||
top = spotlightStyle.top - contentMaxHeight - 40
|
||
arrowDirection = 'bottom'
|
||
}
|
||
break
|
||
|
||
case 'left':
|
||
left = spotlightStyle.left - contentWidth - 20
|
||
top = Math.max(20, Math.min(spotlightCenterY - 100, screenHeight - contentMaxHeight - 20))
|
||
arrowDirection = 'right'
|
||
|
||
// 如果左侧空间不足,改为右侧
|
||
if (left < 20) {
|
||
position = 'right'
|
||
}
|
||
break
|
||
|
||
case 'right':
|
||
left = spotlightStyle.left + spotlightStyle.width + 20
|
||
top = Math.max(20, Math.min(spotlightCenterY - 100, screenHeight - contentMaxHeight - 20))
|
||
arrowDirection = 'left'
|
||
|
||
// 如果右侧空间不足,改为左侧
|
||
if (left + contentWidth > screenWidth - 20) {
|
||
position = 'left'
|
||
left = spotlightStyle.left - contentWidth - 20
|
||
arrowDirection = 'right'
|
||
}
|
||
break
|
||
}
|
||
|
||
// 如果重新计算了位置,递归调用
|
||
if (position !== preferredPosition) {
|
||
return this.calculateContentPosition(spotlightStyle, position)
|
||
}
|
||
|
||
return {
|
||
style: `top: ${top}px; left: ${left}px;`,
|
||
arrowDirection
|
||
}
|
||
},
|
||
|
||
// 下一步
|
||
handleNext() {
|
||
const nextStep = this.data.currentStep + 1
|
||
|
||
if (nextStep >= this.data.steps.length) {
|
||
// 引导完成
|
||
this.handleComplete()
|
||
} else {
|
||
// 显示下一步
|
||
this.showStep(nextStep)
|
||
}
|
||
},
|
||
|
||
// 跳过引导
|
||
handleSkip() {
|
||
this.handleComplete()
|
||
},
|
||
|
||
// 完成引导
|
||
handleComplete() {
|
||
this.triggerEvent('complete')
|
||
},
|
||
|
||
// 点击遮罩(不做任何操作,防止穿透)
|
||
handleMaskTap() {
|
||
// 可选:点击遮罩也进入下一步
|
||
// this.handleNext()
|
||
},
|
||
|
||
// 阻止滚动穿透
|
||
preventMove() {}
|
||
}
|
||
})
|