完成一键登录和支付功能
This commit is contained in:
@@ -31,7 +31,9 @@ export const PAGE = {
|
||||
REFERRAL: '/pages/memberInfo/referral',
|
||||
MY_COURSES: '/pages/memberInfo/myCourses',
|
||||
CHECK_IN_HISTORY: '/pages/memberInfo/checkInHistory',
|
||||
PURCHASE_CARD: '/pages/memberInfo/purchaseCard'
|
||||
PURCHASE_CARD: '/pages/memberInfo/purchaseCard',
|
||||
SET_PAY_PASSWORD: '/pages/memberInfo/setPayPassword',
|
||||
RECHARGE_STORED_CARD: '/pages/memberInfo/rechargeStoredCard'
|
||||
}
|
||||
|
||||
/** 底部 TabBar 页面路径,顺序与 TabBar.vue 一致 */
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* 登录拦截Hook
|
||||
* 用于在需要登录的操作前检查用户登录状态,未登录则弹出登录框
|
||||
*
|
||||
* 使用方式:
|
||||
* import { useLoginGuard } from '@/common/hooks/useLoginGuard.js'
|
||||
*
|
||||
* const { checkLogin, requireLogin } = useLoginGuard()
|
||||
*
|
||||
* // 方式1:检查登录状态(返回布尔值)
|
||||
* if (!checkLogin()) return
|
||||
*
|
||||
* // 方式2:需要登录才执行(推荐)
|
||||
* await requireLogin()
|
||||
* // 之后的代码只有在登录后才执行
|
||||
*
|
||||
* // 方式3:在点击事件中使用
|
||||
* async function handleClick() {
|
||||
* await requireLogin()
|
||||
* // 执行需要登录的操作
|
||||
* }
|
||||
*/
|
||||
|
||||
import { getToken } from '@/utils/request.js'
|
||||
import { loadMemberStore } from '@/common/memberInfo/store.js'
|
||||
|
||||
// 保存当前页面的登录弹窗引用
|
||||
let loginModalRef = null
|
||||
|
||||
/**
|
||||
* 注册登录弹窗组件
|
||||
* 在App.vue或页面onMounted中调用
|
||||
* @param {Object} modalRef - loginModal组件的ref
|
||||
*/
|
||||
export function registerLoginModal(modalRef) {
|
||||
loginModalRef = modalRef
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录状态
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isLoggedIn() {
|
||||
const token = getToken()
|
||||
const isLoginStorage = uni.getStorageSync('isLogin')
|
||||
return !!(token || isLoginStorage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录用户信息
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
export function getLoginUser() {
|
||||
const store = loadMemberStore()
|
||||
return store.profile || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用登录拦截Hook
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function useLoginGuard() {
|
||||
|
||||
/**
|
||||
* 检查登录状态(同步)
|
||||
* @returns {Boolean} 是否已登录
|
||||
*/
|
||||
function checkLogin() {
|
||||
return isLoggedIn()
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要登录才继续执行(异步)
|
||||
* 如果未登录,弹出登录框,等待用户登录或取消
|
||||
* @param {Object} options - 配置选项
|
||||
* @param {String} options.title - 弹窗标题
|
||||
* @param {String} options.subtitle - 弹窗副标题
|
||||
* @param {Function} options.onSuccess - 登录成功回调
|
||||
* @param {Function} options.onCancel - 取消登录回调
|
||||
* @returns {Promise<Boolean>} 是否已登录(用户登录成功返回true,取消返回false)
|
||||
*/
|
||||
async function requireLogin(options = {}) {
|
||||
// 如果已经登录,直接返回true
|
||||
if (isLoggedIn()) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果传入了onSuccess回调,先检查
|
||||
if (options.onSuccess) {
|
||||
const store = loadMemberStore()
|
||||
if (store.profile?.id) {
|
||||
options.onSuccess(store.profile)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 显示登录弹窗
|
||||
return new Promise((resolve) => {
|
||||
if (!loginModalRef) {
|
||||
console.warn('[useLoginGuard] 登录弹窗未注册,请先调用 registerLoginModal')
|
||||
// 尝试跳转到登录页
|
||||
uni.navigateTo({
|
||||
url: '/pages/memberInfo/login',
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
|
||||
// 保存resolve函数
|
||||
let resolvePromise = resolve
|
||||
|
||||
// 显示登录弹窗
|
||||
loginModalRef.show()
|
||||
|
||||
// 监听登录成功
|
||||
const loginSuccessHandler = (result) => {
|
||||
if (options.onSuccess) {
|
||||
options.onSuccess(result)
|
||||
}
|
||||
resolvePromise(true)
|
||||
}
|
||||
|
||||
// 监听登录失败/取消
|
||||
const closeHandler = () => {
|
||||
if (options.onCancel) {
|
||||
options.onCancel()
|
||||
}
|
||||
resolvePromise(false)
|
||||
}
|
||||
|
||||
// 注册事件监听
|
||||
uni.$once('loginModal:success', loginSuccessHandler)
|
||||
uni.$once('loginModal:close', closeHandler)
|
||||
|
||||
// 设置超时
|
||||
setTimeout(() => {
|
||||
uni.$off('loginModal:success', loginSuccessHandler)
|
||||
uni.$off('loginModal:close', closeHandler)
|
||||
}, 60000) // 60秒超时
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全执行函数(仅登录后执行)
|
||||
* @param {Function} fn - 要执行的函数
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {Promise<any>} 函数返回值或null
|
||||
*/
|
||||
async function safeExecute(fn, options = {}) {
|
||||
const loggedIn = await requireLogin(options)
|
||||
if (loggedIn && typeof fn === 'function') {
|
||||
return await fn()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装需要登录的API调用
|
||||
* @param {Function} apiFn - API函数
|
||||
* @param {Array} args - API参数
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
async function callLoggedInApi(apiFn, args = [], options = {}) {
|
||||
const loggedIn = await requireLogin(options)
|
||||
if (loggedIn && typeof apiFn === 'function') {
|
||||
return await apiFn(...args)
|
||||
}
|
||||
throw new Error('用户未登录')
|
||||
}
|
||||
|
||||
return {
|
||||
checkLogin,
|
||||
requireLogin,
|
||||
safeExecute,
|
||||
callLoggedInApi,
|
||||
isLoggedIn,
|
||||
getLoginUser
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建登录拦截Mixin(用于选项式API)
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function loginGuardMixin() {
|
||||
return {
|
||||
data() {
|
||||
return {
|
||||
isLogin: false
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.checkLoginStatus()
|
||||
},
|
||||
onShow() {
|
||||
this.checkLoginStatus()
|
||||
},
|
||||
methods: {
|
||||
checkLoginStatus() {
|
||||
this.isLogin = isLoggedIn()
|
||||
},
|
||||
|
||||
async ensureLogin() {
|
||||
if (!this.isLogin) {
|
||||
if (!loginModalRef) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/memberInfo/login'
|
||||
})
|
||||
return false
|
||||
}
|
||||
loginModalRef.show()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由守卫配置
|
||||
* 配置需要登录的页面路径
|
||||
*/
|
||||
export const protectedRoutes = [
|
||||
'/pages/memberInfo/memberInfo',
|
||||
'/pages/memberInfo/purchaseCard',
|
||||
'/pages/memberInfo/memberCard',
|
||||
'/pages/memberInfo/checkIn',
|
||||
'/pages/memberInfo/booking',
|
||||
'/pages/memberInfo/coupons',
|
||||
'/pages/memberInfo/points',
|
||||
'/pages/memberInfo/userInfo'
|
||||
]
|
||||
|
||||
/**
|
||||
* 检查当前页面是否需要登录
|
||||
* @param {String} currentPage - 当前页面路径
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isProtectedRoute(currentPage) {
|
||||
return protectedRoutes.some(route => currentPage.includes(route))
|
||||
}
|
||||
@@ -12,7 +12,10 @@ export {
|
||||
cancelOngoingBooking,
|
||||
renewMemberCard,
|
||||
parseLocalDate,
|
||||
saveUserProfile
|
||||
saveUserProfile,
|
||||
getCurrentMemberId,
|
||||
getLoginMemberInfo,
|
||||
getToken
|
||||
} from './store.js'
|
||||
export {
|
||||
getLatestBodyTestRecord,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { formatMemberCenterPhone, normalizePhoneForStore } from './format.js'
|
||||
import {
|
||||
getDefaultBodyTestState,
|
||||
mergeBodyTestState,
|
||||
@@ -10,14 +9,36 @@ import {
|
||||
mergeModuleState,
|
||||
finalizeModules
|
||||
} from './moduleStore.js'
|
||||
import {
|
||||
getDefaultCourseCatalog,
|
||||
import { getDefaultCourseCatalog,
|
||||
mergeCourseCatalog,
|
||||
canCancelBooking
|
||||
} from './bookingStore.js'
|
||||
import { getMemberId as requestGetMemberId } from '@/utils/request.js'
|
||||
|
||||
const STORAGE_KEY = 'gym_member_info_v1'
|
||||
|
||||
/**
|
||||
* 获取当前登录会员ID
|
||||
* @deprecated 请使用 @/utils/request.js 中的 getMemberId
|
||||
*/
|
||||
export function getCurrentMemberId() {
|
||||
return requestGetMemberId()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录信息
|
||||
*/
|
||||
export function getLoginMemberInfo() {
|
||||
return uni.getStorageSync('loginMemberInfo') || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
*/
|
||||
export function getToken() {
|
||||
return uni.getStorageSync('token') || null
|
||||
}
|
||||
|
||||
export function buildCardTip(remainingDays) {
|
||||
return `距离下次到期还有${remainingDays}天,请及时续费`
|
||||
}
|
||||
@@ -41,12 +62,6 @@ export function syncStats(store) {
|
||||
function finalizeStore(store) {
|
||||
syncStats(store)
|
||||
applyCardInfo(store)
|
||||
if (store.profile?.avatar) {
|
||||
store.memberProfile.avatar = store.profile.avatar
|
||||
}
|
||||
if (store.profile?.phone) {
|
||||
store.memberProfile.phone = formatMemberCenterPhone(store.profile.phone)
|
||||
}
|
||||
const latestBodyTest = getLatestBodyTestRecord(store)
|
||||
if (latestBodyTest) {
|
||||
const previous = store.bodyTest.records[1]
|
||||
@@ -60,11 +75,10 @@ function finalizeStore(store) {
|
||||
|
||||
function getDefaultStore() {
|
||||
return finalizeStore({
|
||||
profile: {},
|
||||
memberProfile: {},
|
||||
stats: {},
|
||||
cardInfo: {},
|
||||
card: {},
|
||||
cards: [],
|
||||
records: [],
|
||||
ongoingBookings: [],
|
||||
historyBookings: [],
|
||||
@@ -74,7 +88,11 @@ function getDefaultStore() {
|
||||
modules: getDefaultModuleState(),
|
||||
courseCatalog: getDefaultCourseCatalog(),
|
||||
couponPoints: {},
|
||||
referral: {}
|
||||
referral: {},
|
||||
storedCard: {
|
||||
balance: 0,
|
||||
zeroVerified: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -83,11 +101,10 @@ export { getDefaultStore }
|
||||
function mergeDefaults(saved) {
|
||||
const defaults = getDefaultStore()
|
||||
return finalizeStore({
|
||||
profile: { ...defaults.profile, ...(saved.profile || {}) },
|
||||
memberProfile: { ...defaults.memberProfile, ...(saved.memberProfile || {}) },
|
||||
stats: { ...defaults.stats, ...(saved.stats || {}) },
|
||||
cardInfo: { ...defaults.cardInfo, ...(saved.cardInfo || {}) },
|
||||
card: { ...defaults.card, ...(saved.card || {}) },
|
||||
cards: saved.cards?.length ? saved.cards : defaults.cards,
|
||||
records: saved.records?.length ? saved.records : defaults.records,
|
||||
ongoingBookings: saved.ongoingBookings ?? defaults.ongoingBookings,
|
||||
historyBookings: saved.historyBookings ?? defaults.historyBookings,
|
||||
@@ -97,7 +114,8 @@ function mergeDefaults(saved) {
|
||||
modules: mergeModuleState(saved.modules),
|
||||
courseCatalog: mergeCourseCatalog(saved.courseCatalog),
|
||||
couponPoints: { ...defaults.couponPoints, ...(saved.couponPoints || {}) },
|
||||
referral: { ...defaults.referral, ...(saved.referral || {}) }
|
||||
referral: { ...defaults.referral, ...(saved.referral || {}) },
|
||||
storedCard: { ...defaults.storedCard, ...(saved.storedCard || {}) }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -193,21 +211,33 @@ export function getBookingPreview(store, limit = 2) {
|
||||
return store.ongoingBookings.slice(0, limit).map(toBookingPreviewItem)
|
||||
}
|
||||
|
||||
export function getExpiringCards(store, daysThreshold = 30) {
|
||||
if (!store.cards || store.cards.length === 0) {
|
||||
return []
|
||||
}
|
||||
return store.cards.filter(card => {
|
||||
const remainingDays = computeRemainingDays(card.validityEnd || card.expireTime)
|
||||
return remainingDays > 0 && remainingDays <= daysThreshold
|
||||
}).sort((a, b) => {
|
||||
const daysA = computeRemainingDays(a.validityEnd || a.expireTime)
|
||||
const daysB = computeRemainingDays(b.validityEnd || b.expireTime)
|
||||
return daysA - daysB
|
||||
})
|
||||
}
|
||||
|
||||
export function getCenterPageData(store) {
|
||||
const expiringCards = getExpiringCards(store, 30)
|
||||
return {
|
||||
userInfo: { ...store.memberProfile },
|
||||
stats: {
|
||||
checkInCount: store.stats?.checkInCount ?? 0,
|
||||
trainingHours: store.stats?.trainingHours ?? 0,
|
||||
pointsBalance: store.stats?.pointsBalance ?? 0
|
||||
},
|
||||
cardInfo: { ...store.cardInfo },
|
||||
expiringCards: expiringCards,
|
||||
bookingPreview: getBookingPreview(store),
|
||||
checkIns: store.checkIns.map((item) => ({ ...item })),
|
||||
bodyReport: {
|
||||
...store.bodyReport,
|
||||
weight: store.profile.weight || store.bodyReport.weight
|
||||
},
|
||||
bodyReport: { ...store.bodyReport },
|
||||
couponPoints: {
|
||||
...store.couponPoints,
|
||||
points: store.stats.pointsBalance
|
||||
@@ -290,16 +320,9 @@ export function renewMemberCard(store, addDays = 90) {
|
||||
}
|
||||
|
||||
export function saveUserProfile(store, profile) {
|
||||
const phone = normalizePhoneForStore(profile.phone ?? store.profile.phone)
|
||||
store.profile = { ...store.profile, ...profile, phone }
|
||||
store.memberProfile = {
|
||||
...store.memberProfile,
|
||||
name: store.profile.name,
|
||||
phone: formatMemberCenterPhone(store.profile.phone),
|
||||
avatar: store.profile.avatar || store.memberProfile.avatar
|
||||
}
|
||||
if (store.profile.weight) {
|
||||
store.bodyReport.weight = store.profile.weight
|
||||
// 不再保存profile到store,用户信息从loginMemberInfo获取
|
||||
if (profile.weight) {
|
||||
store.bodyReport.weight = profile.weight
|
||||
}
|
||||
finalizeStore(store)
|
||||
saveMemberStore(store)
|
||||
@@ -311,3 +334,17 @@ export function persistMemberStore(store) {
|
||||
saveMemberStore(store)
|
||||
return store
|
||||
}
|
||||
|
||||
export function getStoredCardBalance() {
|
||||
console.log('[storedCard] 余额不使用缓存,每次从后端读取')
|
||||
return null
|
||||
}
|
||||
|
||||
export function saveStoredCardBalance(balance) {
|
||||
console.log('[storedCard] 余额不使用缓存,跳过保存')
|
||||
return Number(balance) || 0
|
||||
}
|
||||
|
||||
export function clearStoredCardCache() {
|
||||
console.log('[storedCard] 余额不使用缓存,无需清除')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
* 路由权限拦截器
|
||||
* 自动拦截配置好的路径,未登录时自动弹出登录弹窗
|
||||
*
|
||||
* 使用方式:在 App.vue 中 import 并调用 init() 即可
|
||||
* 无需在每个页面单独配置
|
||||
*/
|
||||
|
||||
import { getToken } from '@/utils/request.js'
|
||||
|
||||
/**
|
||||
* 白名单 - 不需要登录即可访问的页面
|
||||
*/
|
||||
const WHITE_LIST = [
|
||||
'/pages/index/index',
|
||||
'/pages/course/course',
|
||||
'/pages/memberInfo/login',
|
||||
'/pages/memberInfo/register',
|
||||
'/pages/memberInfo/forgetPassword',
|
||||
'/pages/about/about',
|
||||
'/pages/help/help',
|
||||
'/pages/notice/notice',
|
||||
'/pages/product/product',
|
||||
'/pages/store/store',
|
||||
'/pages/article/article',
|
||||
'/pages/search/search'
|
||||
]
|
||||
|
||||
/**
|
||||
* 需要登录的页面路径前缀
|
||||
* 匹配以下前缀的页面都需要登录
|
||||
*/
|
||||
const LOGIN_REQUIRED_PREFIXES = [
|
||||
'/pages/memberInfo/memberCard',
|
||||
'/pages/memberInfo/purchaseCard',
|
||||
'/pages/memberInfo/checkIn',
|
||||
'/pages/memberInfo/booking',
|
||||
'/pages/memberInfo/coupons',
|
||||
'/pages/memberInfo/points',
|
||||
'/pages/memberInfo/userInfo',
|
||||
'/pages/memberInfo/order',
|
||||
'/pages/memberInfo/settings',
|
||||
'/pages/memberInfo/feedback',
|
||||
'/pages/memberInfo/invite',
|
||||
'/pages/memberInfo/recharge'
|
||||
]
|
||||
|
||||
/**
|
||||
* 需要登录的具体页面路径
|
||||
*/
|
||||
const LOGIN_REQUIRED_PATHS = [
|
||||
'/pages/memberInfo/memberInfo'
|
||||
]
|
||||
|
||||
/**
|
||||
* 检查当前路径是否需要登录
|
||||
* @param {String} url - 页面路径(可能包含query参数)
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isNeedLogin(url) {
|
||||
// 去掉query参数
|
||||
const path = url.split('?')[0]
|
||||
|
||||
// 检查白名单
|
||||
if (WHITE_LIST.includes(path)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查具体路径
|
||||
if (LOGIN_REQUIRED_PATHS.includes(path)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查路径前缀
|
||||
if (LOGIN_REQUIRED_PREFIXES.some(prefix => path.startsWith(prefix))) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已登录
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isLoggedIn() {
|
||||
const token = getToken()
|
||||
const isLoginStorage = uni.getStorageSync('isLogin')
|
||||
return !!(token || isLoginStorage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录弹窗组件引用(需要在App.vue中注册)
|
||||
*/
|
||||
let loginModalRef = null
|
||||
|
||||
/**
|
||||
* 注册登录弹窗组件
|
||||
* @param {Object} modalRef - LoginModal组件的ref
|
||||
*/
|
||||
export function registerLoginModal(modalRef) {
|
||||
loginModalRef = modalRef
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示登录弹窗
|
||||
*/
|
||||
function showLoginModal() {
|
||||
if (loginModalRef) {
|
||||
loginModalRef.show()
|
||||
} else {
|
||||
// 如果没有注册弹窗,跳转到登录页
|
||||
uni.navigateTo({
|
||||
url: '/pages/memberInfo/login'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录用户原本要访问的页面
|
||||
* @param {String} url - 页面路径
|
||||
*/
|
||||
function saveRedirectUrl(url) {
|
||||
uni.setStorageSync('redirectUrl', url)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户原本要访问的页面
|
||||
* @returns {String|null}
|
||||
*/
|
||||
export function getRedirectUrl() {
|
||||
const url = uni.getStorageSync('redirectUrl')
|
||||
uni.removeStorageSync('redirectUrl')
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到用户原本要访问的页面
|
||||
*/
|
||||
export function redirectToSavedUrl() {
|
||||
const url = getRedirectUrl()
|
||||
if (url) {
|
||||
uni.navigateTo({
|
||||
url: url,
|
||||
fail: () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化路由拦截器
|
||||
*/
|
||||
export function initPermissionInterceptor() {
|
||||
console.log('[PermissionInterceptor] 初始化路由拦截器')
|
||||
|
||||
// 拦截 navigateTo
|
||||
uni.addInterceptor('navigateTo', {
|
||||
invoke(args) {
|
||||
const url = args.url
|
||||
if (isNeedLogin(url) && !isLoggedIn()) {
|
||||
console.log('[PermissionInterceptor] 拦截 navigateTo:', url)
|
||||
saveRedirectUrl(url)
|
||||
showLoginModal()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
success() {
|
||||
// 跳转成功
|
||||
},
|
||||
fail(err) {
|
||||
console.error('[PermissionInterceptor] navigateTo 失败:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// 拦截 redirectTo
|
||||
uni.addInterceptor('redirectTo', {
|
||||
invoke(args) {
|
||||
const url = args.url
|
||||
if (isNeedLogin(url) && !isLoggedIn()) {
|
||||
console.log('[PermissionInterceptor] 拦截 redirectTo:', url)
|
||||
saveRedirectUrl(url)
|
||||
showLoginModal()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
success() {},
|
||||
fail(err) {
|
||||
console.error('[PermissionInterceptor] redirectTo 失败:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// 拦截 reLaunch
|
||||
uni.addInterceptor('reLaunch', {
|
||||
invoke(args) {
|
||||
const url = args.url
|
||||
if (isNeedLogin(url) && !isLoggedIn()) {
|
||||
console.log('[PermissionInterceptor] 拦截 reLaunch:', url)
|
||||
saveRedirectUrl(url)
|
||||
showLoginModal()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
success() {},
|
||||
fail(err) {
|
||||
console.error('[PermissionInterceptor] reLaunch 失败:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// 拦截 switchTab
|
||||
uni.addInterceptor('switchTab', {
|
||||
invoke(args) {
|
||||
const url = args.url
|
||||
if (isNeedLogin(url) && !isLoggedIn()) {
|
||||
console.log('[PermissionInterceptor] 拦截 switchTab:', url)
|
||||
saveRedirectUrl(url)
|
||||
showLoginModal()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
success() {},
|
||||
fail(err) {
|
||||
console.error('[PermissionInterceptor] switchTab 失败:', err)
|
||||
}
|
||||
})
|
||||
|
||||
// 拦截 navigateBack
|
||||
uni.addInterceptor('navigateBack', {
|
||||
invoke() {
|
||||
return true
|
||||
},
|
||||
success() {},
|
||||
fail() {}
|
||||
})
|
||||
|
||||
console.log('[PermissionInterceptor] 路由拦截器初始化完成')
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加需要登录的页面路径
|
||||
* @param {String|Array} paths - 页面路径或路径数组
|
||||
*/
|
||||
export function addLoginRequiredPaths(paths) {
|
||||
if (Array.isArray(paths)) {
|
||||
LOGIN_REQUIRED_PATHS.push(...paths)
|
||||
} else {
|
||||
LOGIN_REQUIRED_PATHS.push(paths)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加需要登录的页面路径前缀
|
||||
* @param {String|Array} prefixes - 路径前缀或前缀数组
|
||||
*/
|
||||
export function addLoginRequiredPrefixes(prefixes) {
|
||||
if (Array.isArray(prefixes)) {
|
||||
LOGIN_REQUIRED_PREFIXES.push(...prefixes)
|
||||
} else {
|
||||
LOGIN_REQUIRED_PREFIXES.push(prefixes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到白名单
|
||||
* @param {String|Array} paths - 页面路径或路径数组
|
||||
*/
|
||||
export function addWhiteList(paths) {
|
||||
if (Array.isArray(paths)) {
|
||||
WHITE_LIST.push(...paths)
|
||||
} else {
|
||||
WHITE_LIST.push(paths)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前配置的白名单
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function getWhiteList() {
|
||||
return [...WHITE_LIST]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前配置的需要登录的路径
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function getLoginRequiredPaths() {
|
||||
return [...LOGIN_REQUIRED_PATHS]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前配置的需要登录的路径前缀
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function getLoginRequiredPrefixes() {
|
||||
return [...LOGIN_REQUIRED_PREFIXES]
|
||||
}
|
||||
@@ -360,3 +360,82 @@
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.member-card-section__count {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.member-card-section__count-num {
|
||||
font-size: var(--font-size-md);
|
||||
font-family: var(--font-family);
|
||||
font-weight: 700;
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.member-card-section__count-text {
|
||||
font-size: var(--font-size-xs);
|
||||
font-family: var(--font-family);
|
||||
font-weight: 400;
|
||||
color: rgba(255, 212, 184, 1);
|
||||
}
|
||||
|
||||
.member-card-preview__tag--expiring {
|
||||
background-color: #FFE4E4;
|
||||
}
|
||||
|
||||
.member-card-preview__tag--expiring .member-card-preview__tag-text {
|
||||
color: #E74C3C;
|
||||
}
|
||||
|
||||
.member-card-preview__tag--expired {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.member-card-preview__tag--expired .member-card-preview__tag-text {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.member-card-preview__tag--active {
|
||||
background-color: rgba(255, 255, 255, 0.19);
|
||||
}
|
||||
|
||||
.member-card-preview__tag--active .member-card-preview__tag-text {
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.member-card-preview__days-num--expiring {
|
||||
color: #FFE4E4;
|
||||
}
|
||||
|
||||
.member-card-preview__days-num--expired {
|
||||
color: #FF6B6B;
|
||||
}
|
||||
|
||||
.member-card-preview__purchase {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
background-color: var(--bg-white);
|
||||
}
|
||||
|
||||
.member-card-preview__purchase-text {
|
||||
font-size: var(--font-size-base);
|
||||
font-family: var(--font-family);
|
||||
font-weight: 600;
|
||||
color: var(--accent-orange);
|
||||
width: auto;
|
||||
height: auto;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user