guards.js 4.58 KB
/*
 * @Date: 2025-03-20 20:36:36
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2026-01-20 10:18:33
 * @FilePath: /mlaj/src/router/guards.js
 * @Description: 路由守卫逻辑
 */
import { getAuthInfoAPI } from '@/api/auth'
import { buildOAuthAuthorizeUrl } from '@/utils/oauthHashRestore'
import { wxInfo } from '@/utils/tools'

// TAG: 需要登录才能访问的路由
export const authRequiredRoutes = [
  {
    path: '/profile',
    exact: false,
  },
  {
    path: '/checkout',
    exact: true,
  },
  {
    path: '/activities/[^/]+/signup',
    regex: true,
  },
  {
    path: '/checkin',
    exact: false,
  },
  {
    path: '/teacher',
    exact: false,
  },
  {
    path: '/studyDetail',
    exact: false,
  },
]

/**
 * @description 兼容旧逻辑的微信授权检查(不再自动跳转)
 * @returns {boolean} 始终返回true,不在路由守卫内自动触发授权
 */
export const checkWxAuth = async () => {
  // 说明:根据新业务需求,微信授权不应在路由守卫中自动触发
  // 此函数保留以兼容旧代码调用,但不再进行重定向
  try {
    if (!import.meta.env.DEV && wxInfo().isWeiXin) {
      // 仅做一次授权状态探测,避免无意义请求
      await getAuthInfoAPI()
    }
  } catch (error) {
    // 忽略授权探测错误,不影响后续流程
  }
  return true
}

/**
 * @description 手动发起微信授权登录(仅在用户点击微信图标时触发)
 * @returns {void}
 */
/**
 * @function startWxAuth
 * @description 手动发起微信授权登录;使用不含 hash 的完整 URL 作为回跳参数,避免微信浏览器对 hash 的处理差异导致回跳异常。
 * @returns {void}
 */
export const startWxAuth = async () => {
  // 开发环境不触发微信授权
  if (import.meta.env.DEV) {
    // 开发环境下不触发微信授权登录
    return
  }

  const info = wxInfo()
  // 非微信环境不进行授权跳转
  if (!info.isWeiXin) {
    return
  }

  // 如果已授权则不跳转;否则进入授权页
  try {
    const { code, data } = await getAuthInfoAPI()
    if (code && data.openid_has) {
      return
    }
  } catch (e) {
    // 探测失败不影响授权流程,继续跳转
  }

  // 跳转到微信授权地址。
  // OAuth 回跳链路对 hash 路由并不稳定,这里显式拆分 base_url 与 ret_hash,
  // 由前端在 main.js 安装路由前进行一次 hash 复原。
  const short_url = buildOAuthAuthorizeUrl(window.location.href)
  if (!short_url) {
    return
  }
  location.href = short_url
}

// 首次访问标志
const HAS_VISITED_WELCOME = 'has_visited_welcome'
const WELCOME_VISITED_AT = 'welcome_visited_at'

/**
 * @description 检查用户是否已访问过欢迎页
 * @returns {boolean}
 */
export const hasVisitedWelcome = () => localStorage.getItem(HAS_VISITED_WELCOME) === 'true'

/**
 * @description 标记用户已访问欢迎页
 * @returns {void}
 */
export const markWelcomeVisited = () => {
  localStorage.setItem(HAS_VISITED_WELCOME, 'true')
  localStorage.setItem(WELCOME_VISITED_AT, Date.now().toString())
}

/**
 * @description 重置欢迎页标志(用于调试)
 * @returns {void}
 */
export const resetWelcomeFlag = () => {
  localStorage.removeItem(HAS_VISITED_WELCOME)
  localStorage.removeItem(WELCOME_VISITED_AT)
}

// 检查用户是否已登录

/**
 * @description 登录权限检查,未登录时重定向到登录页
 * @param {*} to 目标路由对象
 * @returns {true|Object} 允许通过或返回重定向对象
 */
export const checkAuth = to => {
  const currentUser = JSON.parse(localStorage.getItem('currentUser'))

  // 检查当前路由是否需要认证
  // 方式一:白名单匹配(兼容旧逻辑)
  const needAuthByList = authRequiredRoutes.some(route => {
    // 如果是正则匹配模式
    if (route.regex) {
      return new RegExp(`^${route.path}$`).test(to.path)
    }
    // 如果是精确匹配模式
    if (route.exact) {
      return to.path === route.path
    }
    // 默认前缀匹配模式
    return to.path.startsWith(route.path)
  })
  // 方式二:读取路由元信息 requiresAuth(推荐)
  // 注意:axios 拦截器中调用时,to 可能不是完整的 Route 对象,可能缺少 matched 属性
  // 如果没有 matched 属性,则回退到仅依赖 path 的白名单匹配
  const needAuthByMeta = to.matched
    ? to.matched.some(record => record.meta && record.meta.requiresAuth === true)
    : false

  const needAuth = needAuthByList || needAuthByMeta

  if (needAuth && !currentUser) {
    // 未登录时重定向到登录页面
    return { path: '/login', query: { redirect: to.fullPath } }
  }

  return true
}