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() {}
|
|||
|
|
}
|
|||
|
|
})
|