447 lines
12 KiB
JavaScript
447 lines
12 KiB
JavaScript
/**
|
||
* 引导状态管理模块
|
||
*/
|
||
import { getGuideConfig, shouldShowGuide, markGuideAsShown, markGuideDone, getGuideCurrentStep, setGuideCurrentStep, completeSubStep, clearSubStepProgress, getNextIncompleteStep } from '@/utils/guideConfig'
|
||
|
||
// 设置页面实例的辅助函数(保留接口兼容)
|
||
// eslint-disable-next-line no-unused-vars
|
||
export function setGuidePageInstance(_instance) {
|
||
// uni.createSelectorQuery() 不需要传入页面实例
|
||
}
|
||
|
||
const state = {
|
||
// 当前激活的引导配置
|
||
currentGuide: null,
|
||
// 当前步骤索引
|
||
currentStep: 0,
|
||
// 是否显示引导
|
||
isActive: false,
|
||
// 目标元素位置信息
|
||
targetRect: {
|
||
top: 0,
|
||
left: 0,
|
||
width: 0,
|
||
height: 0
|
||
},
|
||
// 是否正在页面跳转中(保持遮罩显示)
|
||
isNavigating: false,
|
||
// 待执行的行动(高亮区域点击触发)
|
||
pendingAction: null,
|
||
// 是否处于组件打开模式(遮罩隐藏)
|
||
componentMode: false,
|
||
}
|
||
|
||
const mutations = {
|
||
/**
|
||
* 开始引导
|
||
* @param {Object} guideConfig 引导配置
|
||
* @param {number} guideConfig.guideConfig 引导配置对象
|
||
* @param {number} [guideConfig.startFromStep] 从指定步骤开始(可选,默认从保存的进度继续)
|
||
*/
|
||
START_GUIDE(state, { guideConfig, startFromStep }) {
|
||
console.log('[Guide] START_GUIDE mutation called, key:', guideConfig.key, 'startFromStep:', startFromStep)
|
||
state.currentGuide = guideConfig
|
||
// 如果指定了起始步骤,从指定步骤开始;否则恢复之前保存的进度
|
||
state.currentStep = startFromStep !== undefined ? startFromStep : (getGuideCurrentStep(guideConfig.key) || 0)
|
||
console.log('[Guide] START_GUIDE, currentStep set to:', state.currentStep)
|
||
state.isActive = true
|
||
state.isNavigating = false
|
||
console.log('[Guide] START_GUIDE, isActive set to:', state.isActive)
|
||
},
|
||
|
||
/**
|
||
* 下一步
|
||
*/
|
||
NEXT_STEP(state) {
|
||
if (state.currentGuide && state.currentStep < state.currentGuide.steps.length - 1) {
|
||
state.currentStep++
|
||
// 存储当前步骤
|
||
setGuideCurrentStep(state.currentGuide.key, state.currentStep)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 上一步
|
||
*/
|
||
PREV_STEP(state) {
|
||
if (state.currentStep > 0) {
|
||
state.currentStep--
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 结束引导
|
||
*/
|
||
END_GUIDE(state) {
|
||
// 标记引导已显示
|
||
if (state.currentGuide) {
|
||
const key = state.currentGuide.key
|
||
markGuideAsShown(key)
|
||
markGuideDone(key) // 标记为已完成
|
||
// 重置步骤进度
|
||
setGuideCurrentStep(key, 0)
|
||
// 清除子步骤完成记录
|
||
clearSubStepProgress(key)
|
||
}
|
||
state.currentGuide = null
|
||
state.currentStep = 0
|
||
state.isActive = false
|
||
state.isNavigating = false
|
||
},
|
||
|
||
/**
|
||
* 标记当前子步骤为已完成
|
||
*/
|
||
COMPLETE_SUBSTEP(state) {
|
||
if (state.currentGuide) {
|
||
const key = state.currentGuide.key
|
||
const stepIndex = state.currentStep
|
||
// 标记子步骤完成
|
||
completeSubStep(key, stepIndex)
|
||
// 更新存储的当前步骤
|
||
setGuideCurrentStep(key, stepIndex)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 设置目标元素位置
|
||
*/
|
||
SET_TARGET_RECT(state, rect) {
|
||
state.targetRect = rect
|
||
},
|
||
|
||
/**
|
||
* 设置跳转状态
|
||
*/
|
||
SET_NAVIGATING(state, isNavigating) {
|
||
state.isNavigating = isNavigating
|
||
},
|
||
|
||
/**
|
||
* 更新当前步骤(用于页面跳转后重新定位)
|
||
*/
|
||
UPDATE_STEP(state, step) {
|
||
state.currentStep = step
|
||
},
|
||
|
||
/**
|
||
* 打开组件
|
||
*/
|
||
OPEN_COMPONENT(state, componentName) {
|
||
state.pendingAction = { type: 'component', name: componentName }
|
||
},
|
||
|
||
/**
|
||
* 执行函数
|
||
*/
|
||
EXECUTE_FUNCTION(state, handler) {
|
||
state.pendingAction = { type: 'function', handler }
|
||
},
|
||
|
||
/**
|
||
* 清除待执行行动
|
||
*/
|
||
CLEAR_PENDING_ACTION(state) {
|
||
state.pendingAction = null
|
||
},
|
||
|
||
/**
|
||
* 关闭遮罩进入组件模式
|
||
*/
|
||
SET_COMPONENT_MODE(state) {
|
||
state.componentMode = true
|
||
},
|
||
|
||
/**
|
||
* 重置组件模式
|
||
*/
|
||
RESET_COMPONENT_MODE(state) {
|
||
state.componentMode = false
|
||
}
|
||
}
|
||
|
||
const getters = {
|
||
// 获取当前步骤配置
|
||
currentStepConfig: (state) => {
|
||
if (!state.currentGuide || !state.currentGuide.steps) return null
|
||
return state.currentGuide.steps[state.currentStep]
|
||
},
|
||
|
||
// 是否还有下一步
|
||
hasNext: (state) => {
|
||
if (!state.currentGuide) return false
|
||
return state.currentStep < state.currentGuide.steps.length - 1
|
||
},
|
||
|
||
// 是否是第一步
|
||
isFirst: (state) => {
|
||
return state.currentStep === 0
|
||
},
|
||
|
||
// 是否是最后一步
|
||
isLast: (state) => {
|
||
if (!state.currentGuide) return false
|
||
return state.currentStep === state.currentGuide.steps.length - 1
|
||
}
|
||
}
|
||
|
||
const actions = {
|
||
/**
|
||
* 初始化引导
|
||
* @param {string} guideKey 引导key
|
||
*/
|
||
initGuide({ commit }, guideKey) {
|
||
console.log(`[Guide] 开始初始化引导: ${guideKey}, shouldShowGuide: ${shouldShowGuide(guideKey)}`)
|
||
|
||
// 检查是否需要显示引导
|
||
if (!shouldShowGuide(guideKey)) {
|
||
console.log(`[Guide] 引导已显示过,跳过: ${guideKey}`)
|
||
return false
|
||
}
|
||
|
||
const guide = getGuideConfig(guideKey)
|
||
if (!guide) {
|
||
console.warn(`[Guide] 未找到引导配置: ${guideKey}`)
|
||
return false
|
||
}
|
||
|
||
console.log(`[Guide] 开始显示引导: ${guideKey}, 步骤数: ${guide.steps.length}`)
|
||
commit('START_GUIDE', { guideConfig: guide })
|
||
return true
|
||
},
|
||
|
||
/**
|
||
* 从头开始引导(清除所有进度)
|
||
* @param {string} guideKey 引导key
|
||
*/
|
||
startGuideFromBeginning({ commit }, guideKey) {
|
||
const guide = getGuideConfig(guideKey)
|
||
if (!guide) {
|
||
console.warn(`[Guide] 未找到引导配置: ${guideKey}`)
|
||
return false
|
||
}
|
||
// 清除子步骤进度
|
||
clearSubStepProgress(guideKey)
|
||
// 从步骤0开始
|
||
commit('START_GUIDE', { guideConfig: guide, startFromStep: 0 })
|
||
return true
|
||
},
|
||
|
||
/**
|
||
* 从断点继续引导
|
||
* @param {string} guideKey 引导key
|
||
*/
|
||
resumeGuide({ commit }, guideKey) {
|
||
const guide = getGuideConfig(guideKey)
|
||
if (!guide) {
|
||
console.warn(`[Guide] 未找到引导配置: ${guideKey}`)
|
||
return false
|
||
}
|
||
// 获取第一个未完成的步骤
|
||
const resumeStep = getNextIncompleteStep(guideKey)
|
||
console.log('[Guide] resumeGuide:', guideKey, 'resumeStep:', resumeStep, 'guide:', guide)
|
||
commit('START_GUIDE', { guideConfig: guide, startFromStep: resumeStep })
|
||
return true
|
||
},
|
||
|
||
/**
|
||
* 计算目标元素位置
|
||
* @param {string} selector 元素选择器
|
||
*/
|
||
calculateTargetPosition({ commit }, selector) {
|
||
return new Promise((resolve, reject) => {
|
||
if (!selector) {
|
||
console.log(`[Guide] selector 为空`)
|
||
resolve(null)
|
||
return
|
||
}
|
||
|
||
console.log(`[Guide] 开始查询元素: ${selector}`)
|
||
|
||
// 获取系统信息用于 px 转 rpx
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
const rpxRatio = 750 / systemInfo.windowWidth
|
||
|
||
// 不传 in() 参数,使用默认页面实例
|
||
const query = uni.createSelectorQuery()
|
||
query.select(selector).boundingClientRect((rect) => {
|
||
console.log(`[Guide] 元素查询结果:`, rect)
|
||
if (rect) {
|
||
// 将 px 转换为 rpx
|
||
commit('SET_TARGET_RECT', {
|
||
top: rect.top * rpxRatio,
|
||
left: rect.left * rpxRatio,
|
||
width: rect.width * rpxRatio,
|
||
height: rect.height * rpxRatio
|
||
})
|
||
console.log(`[Guide] 转换后的 rpx 坐标:`, {
|
||
top: rect.top * rpxRatio,
|
||
left: rect.left * rpxRatio,
|
||
width: rect.width * rpxRatio,
|
||
height: rect.height * rpxRatio
|
||
})
|
||
resolve(rect)
|
||
} else {
|
||
console.warn(`[Guide] 未找到目标元素: ${selector}`)
|
||
resolve(null)
|
||
}
|
||
}).exec()
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 下一步
|
||
*/
|
||
nextStep({ commit, getters, state }) {
|
||
const stepConfig = getters.currentStepConfig
|
||
|
||
// 检查 action 配置
|
||
if (stepConfig && stepConfig.action) {
|
||
const { action } = stepConfig
|
||
if (action.type === 'navigate' && action.url) {
|
||
// 如果 isNavigating 已为 true,说明是由 handleHighlightClick 发起的导航
|
||
// 只需要完成步骤即可,不需要再次发起导航
|
||
if (state.isNavigating) {
|
||
commit('COMPLETE_SUBSTEP')
|
||
if (getters.hasNext) {
|
||
commit('NEXT_STEP')
|
||
} else {
|
||
commit('END_GUIDE')
|
||
}
|
||
return
|
||
}
|
||
commit('SET_NAVIGATING', true)
|
||
uni.navigateTo({
|
||
url: action.url,
|
||
success: () => {
|
||
commit('COMPLETE_SUBSTEP')
|
||
commit('SET_NAVIGATING', false)
|
||
},
|
||
fail: (err) => {
|
||
console.error('[Guide] 页面跳转失败:', err)
|
||
commit('SET_NAVIGATING', false)
|
||
}
|
||
})
|
||
return
|
||
} else if (action.type === 'component' && action.name) {
|
||
store.dispatch('guide/openComponent', action.name)
|
||
commit('COMPLETE_SUBSTEP')
|
||
if (getters.hasNext) {
|
||
commit('NEXT_STEP')
|
||
} else {
|
||
commit('END_GUIDE')
|
||
}
|
||
return
|
||
} else if (action.type === 'function' && action.handler) {
|
||
store.dispatch('guide/executeFunction', action.handler)
|
||
commit('COMPLETE_SUBSTEP')
|
||
if (getters.hasNext) {
|
||
commit('NEXT_STEP')
|
||
} else {
|
||
commit('END_GUIDE')
|
||
}
|
||
return
|
||
}
|
||
}
|
||
|
||
// 检查是否正在跳转中(由 handleHighlightClick 设置)
|
||
// 只有在非 action 流程中才检查此标志
|
||
if (state.isNavigating) {
|
||
commit('COMPLETE_SUBSTEP')
|
||
if (getters.hasNext) {
|
||
commit('NEXT_STEP')
|
||
} else {
|
||
commit('END_GUIDE')
|
||
}
|
||
return
|
||
}
|
||
|
||
if (stepConfig && stepConfig.nextPage) {
|
||
// 需要跳转页面
|
||
commit('SET_NAVIGATING', true)
|
||
uni.navigateTo({
|
||
url: stepConfig.nextPage,
|
||
success: () => {
|
||
// 跳转成功后不立即关闭遮罩,等待目标页面 onReady 后重新定位
|
||
},
|
||
fail: (err) => {
|
||
console.error('[Guide] 页面跳转失败:', err)
|
||
commit('SET_NAVIGATING', false)
|
||
}
|
||
})
|
||
} else if (getters.hasNext) {
|
||
// 先标记当前步骤完成,再前进
|
||
commit('COMPLETE_SUBSTEP')
|
||
commit('NEXT_STEP')
|
||
} else {
|
||
// 最后一步:标记完成并结束
|
||
commit('COMPLETE_SUBSTEP')
|
||
commit('END_GUIDE')
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 上一步
|
||
*/
|
||
prevStep({ commit, getters }) {
|
||
if (!getters.isFirst) {
|
||
commit('PREV_STEP')
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 跳过引导
|
||
*/
|
||
skipGuide({ commit }) {
|
||
commit('END_GUIDE')
|
||
},
|
||
|
||
/**
|
||
* 页面跳转后重新定位引导
|
||
*/
|
||
relocateOnPageReady({ commit, state, dispatch }) {
|
||
if (!state.isNavigating) return
|
||
|
||
// 延迟执行,确保 DOM 已渲染
|
||
setTimeout(() => {
|
||
const stepConfig = state.currentGuide?.steps[state.currentStep]
|
||
if (stepConfig && stepConfig.target) {
|
||
dispatch('calculateTargetPosition', stepConfig.target).then(() => {
|
||
commit('SET_NAVIGATING', false)
|
||
})
|
||
}
|
||
}, 100)
|
||
},
|
||
|
||
/**
|
||
* 打开组件
|
||
*/
|
||
openComponent({ commit }, componentName) {
|
||
console.log(`[Guide] 打开组件: ${componentName}`)
|
||
uni.$emit('guide:openComponent', componentName)
|
||
commit('OPEN_COMPONENT', componentName)
|
||
},
|
||
|
||
/**
|
||
* 执行函数
|
||
*/
|
||
executeFunction({ commit }, handler) {
|
||
console.log(`[Guide] 执行函数: ${handler}`)
|
||
commit('EXECUTE_FUNCTION', handler)
|
||
},
|
||
|
||
/**
|
||
* 清除待执行行动
|
||
*/
|
||
clearPendingAction({ commit }) {
|
||
commit('CLEAR_PENDING_ACTION')
|
||
}
|
||
}
|
||
|
||
export default {
|
||
namespaced: true,
|
||
state,
|
||
mutations,
|
||
getters,
|
||
actions
|
||
} |