/** * 引导状态管理模块 */ 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, // 刚刚完成的引导 key(用于 GuideOverlay 触发后端同步) completedGuideKey: null, } 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) // 标记为已完成 // 保存刚完成的引导 key(用于 GuideOverlay 触发后端同步) state.completedGuideKey = key // 重置步骤进度 setGuideCurrentStep(key, 0) // 清除子步骤完成记录 clearSubStepProgress(key) } state.currentGuide = null state.currentStep = 0 state.isNavigating = false // isActive 设为 false 放最后,让 GuideOverlay 的 watch 能读取 completedGuideKey state.isActive = 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 }