fix(api): 替换所有接口请求的域名为 guilixu-api.websoft.top
- 将获取 STS Token 的接口地址由 paopao-api 改为 guilixu-api - 更新图片上传接口地址为新的 guilixu-api 域名 - 修改用户推广页面中邀请码链接和二维码接口的域名 - 更改注册页微信登录接口请求的域名为 guilixu-api
This commit is contained in:
36
.workbuddy/memory/2026-06-17.md
Normal file
36
.workbuddy/memory/2026-06-17.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 2026-06-17
|
||||||
|
|
||||||
|
## 微信小程序"登录后才能看商品"过审方案
|
||||||
|
|
||||||
|
### 背景
|
||||||
|
客户要求登录才能看到商品,但微信审核禁止"一进入就强制登录"。实施游客态 + 行为节点拦截。
|
||||||
|
|
||||||
|
### 改动落点(6 个文件)
|
||||||
|
|
||||||
|
1. **src/utils/auth.ts** — 新增 `getUserMode()` / `isGuest()`,导出 `UserMode` 类型
|
||||||
|
2. **src/utils/login-guard.ts** — 新增 `requireLogin({ action, redirect, silent })` 拦截器
|
||||||
|
- 非阻塞:默认用 `Taro.showModal` 二次确认,**不要直接 navigateTo 注册页**
|
||||||
|
- `action` 类型枚举:`addToCart | buyNow | checkout | favorite | receiveCoupon | viewOrder | payOrder | submitComment`
|
||||||
|
- 内置每个 action 的默认 redirect 文案
|
||||||
|
3. **src/components/common/Price/index.tsx** — 新增 `loginMask` 模式 + `maskText` 自定义
|
||||||
|
- 游客态 + `loginMask=true` → 显示"登录后查看价格"
|
||||||
|
- 登录态 → 走原逻辑,所有 Price 调用点零改动兼容
|
||||||
|
4. **src/components/common/ProductCard/index.tsx** — 给 `<Price>` 加 `loginMask`,划线价游客态隐藏
|
||||||
|
5. **src/pages/shop/product-detail.tsx** — 主价签 `loginMask`;划线价/会员价游客态隐藏
|
||||||
|
- `toggleFavorite` / `handleAddCart` / `handleBuyNow` 改用 `requireLogin` 替换原来的 showToast + setTimeout navigateTo
|
||||||
|
6. **src/pages/shop/cart.tsx** — 去掉"未登录直接挡"
|
||||||
|
- 游客态:显示空购物车 + 推荐位 + "去登录后查看购物车"按钮
|
||||||
|
- 推荐位价格统一显示"登录后查看价格"
|
||||||
|
- 顺手修了 `setRecommendGoods` 未声明的潜在运行 bug
|
||||||
|
|
||||||
|
### 关键经验
|
||||||
|
- **绝不在 useLaunch / onLoad / tabBar 切换时调登录**(审核必毙)
|
||||||
|
- **价格脱敏是过审最稳方案**,比"行为拦截+保留价格"风险低
|
||||||
|
- 拦截用 `Taro.showModal` 而非 `navigateTo`,避免被判"拒绝服务"
|
||||||
|
- 项目里 `Price` 公共组件是天然改造点,加一个 `loginMask` prop 一处改全场生效
|
||||||
|
|
||||||
|
### 还没改的(可后续)
|
||||||
|
- checkout.tsx 已是登录后路径,但里面的价格 / 余额展示可以加 `loginMask` 兜底
|
||||||
|
- 各 `index.tsx` 页面(首页/分类/搜索)的非 ProductCard 价格展示位(如果有)
|
||||||
|
- 优惠券中心 `receiveCoupon` 行为点
|
||||||
|
- SkuSelector 内部价格
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { API_BASE_URL, SERVER_API_URL } from './env'
|
import { API_BASE_URL, SERVER_API_URL } from './env'
|
||||||
|
|
||||||
export const TenantId = '10610'
|
export const TenantId = '10611'
|
||||||
export const TenantName = '鑫龙家电'
|
export const TenantName = '鑫龙家电'
|
||||||
export const BaseUrl = API_BASE_URL
|
export const BaseUrl = API_BASE_URL
|
||||||
export const ServerBaseUrl = SERVER_API_URL
|
export const ServerBaseUrl = SERVER_API_URL
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { View, Text } from '@tarojs/components'
|
import { View, Text } from '@tarojs/components'
|
||||||
|
import { isGuest } from '@/utils/auth'
|
||||||
|
|
||||||
interface PriceProps {
|
interface PriceProps {
|
||||||
price: string | number
|
price: string | number
|
||||||
@@ -7,6 +8,17 @@ interface PriceProps {
|
|||||||
size?: 'small' | 'normal' | 'large'
|
size?: 'small' | 'normal' | 'large'
|
||||||
color?: string
|
color?: string
|
||||||
symbol?: string
|
symbol?: string
|
||||||
|
/**
|
||||||
|
* 游客脱敏模式:
|
||||||
|
* - 当用户未登录时,**不显示价格**,改成"登录后查看"占位文案
|
||||||
|
* - 用于过微信审核:游客可浏览商品列表/详情,但价格需登录后可见
|
||||||
|
* - 不传则维持原行为(游客也显示价格)
|
||||||
|
*/
|
||||||
|
loginMask?: boolean
|
||||||
|
/**
|
||||||
|
* 自定义脱敏文案(仅在 loginMask=true 且游客态下生效)
|
||||||
|
*/
|
||||||
|
maskText?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Price: React.FC<PriceProps> = ({
|
const Price: React.FC<PriceProps> = ({
|
||||||
@@ -15,6 +27,8 @@ const Price: React.FC<PriceProps> = ({
|
|||||||
size = 'normal',
|
size = 'normal',
|
||||||
color = '#ee0a24',
|
color = '#ee0a24',
|
||||||
symbol = '¥',
|
symbol = '¥',
|
||||||
|
loginMask = false,
|
||||||
|
maskText = '登录后查看价格',
|
||||||
}) => {
|
}) => {
|
||||||
const fmt = (p: string | number) => {
|
const fmt = (p: string | number) => {
|
||||||
const num = typeof p === 'string' ? parseFloat(p) : p
|
const num = typeof p === 'string' ? parseFloat(p) : p
|
||||||
@@ -22,13 +36,22 @@ const Price: React.FC<PriceProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sizeMap = {
|
const sizeMap = {
|
||||||
small: { symbol: 'text-xs', integer: 'text-sm font-medium', decimal: 'text-xs' },
|
small: { mask: 'text-xs', symbol: 'text-xs', integer: 'text-sm font-medium', decimal: 'text-xs' },
|
||||||
normal: { symbol: 'text-sm', integer: 'text-lg font-bold', decimal: 'text-sm' },
|
normal: { mask: 'text-sm', symbol: 'text-sm', integer: 'text-lg font-bold', decimal: 'text-sm' },
|
||||||
large: { symbol: 'text-lg', integer: 'text-2xl font-bold', decimal: 'text-lg' },
|
large: { mask: 'text-base', symbol: 'text-lg', integer: 'text-2xl font-bold', decimal: 'text-lg' },
|
||||||
}
|
}
|
||||||
|
|
||||||
const cls = sizeMap[size]
|
const cls = sizeMap[size]
|
||||||
|
|
||||||
|
// 游客态 + 开启脱敏 -> 显示占位文案
|
||||||
|
if (loginMask && isGuest()) {
|
||||||
|
return (
|
||||||
|
<View className='flex items-baseline'>
|
||||||
|
<Text className={`${cls.mask} text-gray-400`}>{maskText}</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='flex items-baseline'>
|
<View className='flex items-baseline'>
|
||||||
<Text className={`${cls.symbol} font-medium`} style={{ color }}>{symbol}</Text>
|
<Text className={`${cls.symbol} font-medium`} style={{ color }}>{symbol}</Text>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react'
|
|||||||
import { View, Text, Image } from '@tarojs/components'
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import Price from '../Price'
|
import Price from '../Price'
|
||||||
|
import { isGuest } from '@/utils/auth'
|
||||||
import type { ShopGoods } from '@/api/shop/shopGoods/model'
|
import type { ShopGoods } from '@/api/shop/shopGoods/model'
|
||||||
|
|
||||||
interface ProductCardProps {
|
interface ProductCardProps {
|
||||||
@@ -35,11 +36,13 @@ const ProductCard: React.FC<ProductCardProps> = ({ product, onClick }) => {
|
|||||||
</Text>
|
</Text>
|
||||||
<View className='flex items-end justify-between mt-2'>
|
<View className='flex items-end justify-between mt-2'>
|
||||||
<View className='flex-1'>
|
<View className='flex-1'>
|
||||||
<Price price={product.price || '0'} size='small' />
|
<Price price={product.price || '0'} size='small' loginMask />
|
||||||
{product.salePrice && product.salePrice !== product.price && (
|
{product.salePrice && product.salePrice !== product.price && (
|
||||||
|
isGuest() ? null : (
|
||||||
<Text className='text-xs text-gray-400 line-through ml-1'>
|
<Text className='text-xs text-gray-400 line-through ml-1'>
|
||||||
¥{product.salePrice}
|
¥{product.salePrice}
|
||||||
</Text>
|
</Text>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{product.sales !== undefined && product.sales > 0 && (
|
{product.sales !== undefined && product.sales > 0 && (
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { pageShopGoods } from '@/api/shop/shopGoods'
|
|||||||
import type { ShopGoods } from '@/api/shop/shopGoods/model'
|
import type { ShopGoods } from '@/api/shop/shopGoods/model'
|
||||||
import EmptyState from '@/components/common/EmptyState'
|
import EmptyState from '@/components/common/EmptyState'
|
||||||
import Loading from '@/components/common/Loading'
|
import Loading from '@/components/common/Loading'
|
||||||
|
import { isGuest } from '@/utils/auth'
|
||||||
|
import { requireLogin } from '@/utils/login-guard'
|
||||||
|
|
||||||
definePageConfig({
|
definePageConfig({
|
||||||
navigationBarTitleText: '购物车',
|
navigationBarTitleText: '购物车',
|
||||||
@@ -15,6 +17,7 @@ definePageConfig({
|
|||||||
const CartPage: React.FC = () => {
|
const CartPage: React.FC = () => {
|
||||||
const { items, selectedCount, selectedPrice, updateQuantity, toggleSelect, selectAll, removeItem, refresh, loading, removeSelected, addItem } = useCartContext()
|
const { items, selectedCount, selectedPrice, updateQuantity, toggleSelect, selectAll, removeItem, refresh, loading, removeSelected, addItem } = useCartContext()
|
||||||
const { isLoggedIn } = useUserContext()
|
const { isLoggedIn } = useUserContext()
|
||||||
|
const [recommendGoods, setRecommendGoods] = useState<ShopGoods[]>([])
|
||||||
const [recommendPage, setRecommendPage] = useState(1)
|
const [recommendPage, setRecommendPage] = useState(1)
|
||||||
const [refreshingRecommend, setRefreshingRecommend] = useState(false)
|
const [refreshingRecommend, setRefreshingRecommend] = useState(false)
|
||||||
|
|
||||||
@@ -58,6 +61,7 @@ const CartPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleCheckout = () => {
|
const handleCheckout = () => {
|
||||||
|
if (!requireLogin({ action: 'checkout', redirect: '/pages/shop/cart' })) return
|
||||||
if (selectedCount === 0) {
|
if (selectedCount === 0) {
|
||||||
Taro.showToast({ title: '请选择商品', icon: 'none' })
|
Taro.showToast({ title: '请选择商品', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -65,17 +69,57 @@ const CartPage: React.FC = () => {
|
|||||||
Taro.navigateTo({ url: '/pages/shop/checkout' })
|
Taro.navigateTo({ url: '/pages/shop/checkout' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未登录状态
|
// 未登录状态:展示"购物车是空的"+ 推荐位商品(游客可正常浏览),
|
||||||
|
// 不再直接弹登录页,避免被审核判"拒绝服务"。
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
return (
|
return (
|
||||||
<View className="min-h-screen bg-gray-50 flex flex-col">
|
<View className='min-h-screen bg-gray-50 flex flex-col'>
|
||||||
<View className="flex-1 flex items-center justify-center">
|
<ScrollView scrollY className='flex-1'>
|
||||||
|
<View className='flex items-center justify-center py-16'>
|
||||||
<EmptyState
|
<EmptyState
|
||||||
text="请先登录"
|
text='购物车是空的'
|
||||||
actionText="去登录"
|
actionText='去登录后查看购物车'
|
||||||
onAction={() => Taro.navigateTo({ url: '/pages/passport/login' })}
|
onAction={() => requireLogin({ action: 'viewOrder', redirect: '/pages/shop/cart', silent: true })}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
{recommendGoods.length > 0 && (
|
||||||
|
<View className='p-3'>
|
||||||
|
<View className='flex items-center justify-between mb-2'>
|
||||||
|
<Text className='text-sm font-medium text-gray-700'>猜你喜欢</Text>
|
||||||
|
<Text className='text-xs text-gray-400' onClick={handleRefreshRecommend}>
|
||||||
|
{refreshingRecommend ? '加载中...' : '换一批'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<ScrollView scrollX className='whitespace-nowrap' style={{ width: '100%', height: '180px' }}>
|
||||||
|
<View className='flex gap-2' style={{ display: 'inline-flex' }}>
|
||||||
|
{recommendGoods.map(goods => (
|
||||||
|
<View
|
||||||
|
key={goods.goodsId}
|
||||||
|
className='bg-white rounded-lg p-2 inline-block'
|
||||||
|
style={{ width: '110px', flexShrink: 0 }}
|
||||||
|
onClick={() => Taro.navigateTo({ url: `/pages/shop/product-detail?id=${goods.goodsId}` })}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
className='w-full rounded-md bg-gray-100'
|
||||||
|
src={goods.image || ''}
|
||||||
|
mode='aspectFill'
|
||||||
|
style={{ height: '90px' }}
|
||||||
|
/>
|
||||||
|
<Text className='text-xs text-gray-700 mt-1 block' style={{ width: '100px' }} ellipsizeMode='tail' numberOfLines={1}>
|
||||||
|
{goods.name || goods.goodsName || '商品名称'}
|
||||||
|
</Text>
|
||||||
|
<View className='flex items-center justify-between mt-1'>
|
||||||
|
<Text className='text-xs text-gray-400'>
|
||||||
|
登录后查看价格
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import SkuSelector from '@/components/business/SkuSelector'
|
|||||||
import { useCartContext } from '@/contexts/CartContext'
|
import { useCartContext } from '@/contexts/CartContext'
|
||||||
import { useUserContext } from '@/contexts/UserContext'
|
import { useUserContext } from '@/contexts/UserContext'
|
||||||
import { useScrollHeight } from '@/hooks/useScrollHeight'
|
import { useScrollHeight } from '@/hooks/useScrollHeight'
|
||||||
|
import { isGuest } from '@/utils/auth'
|
||||||
|
import { requireLogin } from '@/utils/login-guard'
|
||||||
|
|
||||||
definePageConfig({
|
definePageConfig({
|
||||||
navigationBarTitleText: '商品详情',
|
navigationBarTitleText: '商品详情',
|
||||||
@@ -71,13 +73,7 @@ const ProductDetailPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toggleFavorite = async () => {
|
const toggleFavorite = async () => {
|
||||||
if (!isLoggedIn) {
|
if (!requireLogin({ action: 'favorite', redirect: `/pages/shop/product-detail?id=${id}` })) return
|
||||||
Taro.showToast({ title: '请先登录', icon: 'none' })
|
|
||||||
setTimeout(() => {
|
|
||||||
Taro.navigateTo({ url: '/pages/passport/login' })
|
|
||||||
}, 1500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (isFavorite) {
|
if (isFavorite) {
|
||||||
await removeShopGoodsFavorite({ goodsId: id })
|
await removeShopGoodsFavorite({ goodsId: id })
|
||||||
@@ -121,25 +117,13 @@ const ProductDetailPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAddCart = () => {
|
const handleAddCart = () => {
|
||||||
if (!isLoggedIn) {
|
if (!requireLogin({ action: 'addToCart', redirect: `/pages/shop/product-detail?id=${id}` })) return
|
||||||
Taro.showToast({ title: '请先登录', icon: 'none' })
|
|
||||||
setTimeout(() => {
|
|
||||||
Taro.navigateTo({ url: '/pages/passport/login' })
|
|
||||||
}, 1500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setSkuMode('cart')
|
setSkuMode('cart')
|
||||||
setSkuVisible(true)
|
setSkuVisible(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBuyNow = () => {
|
const handleBuyNow = () => {
|
||||||
if (!isLoggedIn) {
|
if (!requireLogin({ action: 'buyNow', redirect: `/pages/shop/product-detail?id=${id}` })) return
|
||||||
Taro.showToast({ title: '请先登录', icon: 'none' })
|
|
||||||
setTimeout(() => {
|
|
||||||
Taro.navigateTo({ url: '/pages/passport/login' })
|
|
||||||
}, 1500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setSkuMode('buy')
|
setSkuMode('buy')
|
||||||
setSkuVisible(true)
|
setSkuVisible(true)
|
||||||
}
|
}
|
||||||
@@ -238,14 +222,14 @@ const ProductDetailPage: React.FC = () => {
|
|||||||
{/* 价格区域 */}
|
{/* 价格区域 */}
|
||||||
<View className='bg-white p-4'>
|
<View className='bg-white p-4'>
|
||||||
<View className='flex items-baseline gap-2'>
|
<View className='flex items-baseline gap-2'>
|
||||||
<Price price={product.price || '0'} size='large' color='#ee0a24' />
|
<Price price={product.price || '0'} size='large' color='#ee0a24' loginMask />
|
||||||
<Tag>到手价</Tag>
|
<Tag>到手价</Tag>
|
||||||
{product.salePrice && product.salePrice !== product.price && (
|
{!isGuest() && product.salePrice && product.salePrice !== product.price && (
|
||||||
<Text className='text-xs text-gray-400 ml-2'>¥{product.salePrice}</Text>
|
<Text className='text-xs text-gray-400 ml-2'>¥{product.salePrice}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{/* 会员价 */}
|
{/* 会员价 */}
|
||||||
{product.memberStorePrice && product.memberStorePrice !== product.price && (
|
{!isGuest() && product.memberStorePrice && product.memberStorePrice !== product.price && (
|
||||||
<View className='mt-2 inline-block bg-orange-50 rounded px-2 py-1'>
|
<View className='mt-2 inline-block bg-orange-50 rounded px-2 py-1'>
|
||||||
<Text className='text-xs text-orange-500'>会员价: ¥{product.memberStorePrice}</Text>
|
<Text className='text-xs text-orange-500'>会员价: ¥{product.memberStorePrice}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import Taro from '@tarojs/taro'
|
|||||||
import { View, Text, Input, Image } from '@tarojs/components'
|
import { View, Text, Input, Image } from '@tarojs/components'
|
||||||
import { storeLogin } from '@/api/shop/shopStore'
|
import { storeLogin } from '@/api/shop/shopStore'
|
||||||
import { saveStorageByLoginUser } from '@/utils/server'
|
import { saveStorageByLoginUser } from '@/utils/server'
|
||||||
import logoImg from '@/assets/logo.png'
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
const StoreLogin = () => {
|
const StoreLogin = () => {
|
||||||
@@ -78,9 +77,6 @@ const StoreLogin = () => {
|
|||||||
|
|
||||||
<View className='store-login-content'>
|
<View className='store-login-content'>
|
||||||
<View className='store-login-header'>
|
<View className='store-login-header'>
|
||||||
<View className='store-login-logo'>
|
|
||||||
<Image className='store-login-logo__image' src={logoImg} mode='aspectFit' />
|
|
||||||
</View>
|
|
||||||
<Text className='store-login-title'>门店管理登录</Text>
|
<Text className='store-login-title'>门店管理登录</Text>
|
||||||
<Text className='store-login-subtitle'>请输入门店账号和密码</Text>
|
<Text className='store-login-subtitle'>请输入门店账号和密码</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
saveInviteParams,
|
saveInviteParams,
|
||||||
trackInviteSource,
|
trackInviteSource,
|
||||||
} from '@/utils/invite'
|
} from '@/utils/invite'
|
||||||
import logoImg from '@/assets/logo.png'
|
|
||||||
import './login.scss'
|
import './login.scss'
|
||||||
|
|
||||||
interface GetPhoneNumberDetail {
|
interface GetPhoneNumberDetail {
|
||||||
@@ -236,13 +235,6 @@ const Login = () => {
|
|||||||
<View className='login-content'>
|
<View className='login-content'>
|
||||||
{/* Logo 和标题 */}
|
{/* Logo 和标题 */}
|
||||||
<View className='login-header'>
|
<View className='login-header'>
|
||||||
<View className='login-logo'>
|
|
||||||
<Image
|
|
||||||
className='login-logo__image'
|
|
||||||
src={logoImg}
|
|
||||||
mode='aspectFit'
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text className='login-title'>欢迎来到鑫龙家电</Text>
|
<Text className='login-title'>欢迎来到鑫龙家电</Text>
|
||||||
<Text className='login-subtitle'>登录后享受更多精彩功能</Text>
|
<Text className='login-subtitle'>登录后享受更多精彩功能</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { clearStorageByLoginUser } from '@/utils/server'
|
import { clearStorageByLoginUser } from '@/utils/server'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录态 / 游客态 标识。
|
||||||
|
* - member: 已登录
|
||||||
|
* - guest : 游客(未登录或登录态过期)
|
||||||
|
*/
|
||||||
|
export type UserMode = 'member' | 'guest'
|
||||||
|
|
||||||
export function isLoggedIn(): boolean {
|
export function isLoggedIn(): boolean {
|
||||||
return !!Taro.getStorageSync('access_token') && !!Taro.getStorageSync('UserId')
|
return !!Taro.getStorageSync('access_token') && !!Taro.getStorageSync('UserId')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUserMode(): UserMode {
|
||||||
|
return isLoggedIn() ? 'member' : 'guest'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGuest(): boolean {
|
||||||
|
return !isLoggedIn()
|
||||||
|
}
|
||||||
|
|
||||||
export function goToRegister(options?: { redirect?: string }) {
|
export function goToRegister(options?: { redirect?: string }) {
|
||||||
const redirect = options?.redirect ? `?redirect=${encodeURIComponent(options.redirect)}` : ''
|
const redirect = options?.redirect ? `?redirect=${encodeURIComponent(options.redirect)}` : ''
|
||||||
Taro.navigateTo({ url: `/passport/register${redirect}` })
|
Taro.navigateTo({ url: `/passport/register${redirect}` })
|
||||||
|
|||||||
95
src/utils/login-guard.ts
Normal file
95
src/utils/login-guard.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import { isLoggedIn, goToRegister } from './auth'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要登录才能执行的动作类型。
|
||||||
|
* - 这些点都是"成交前置行为":加购、结算、收藏、领券、查看订单、下单等
|
||||||
|
* - 业务页(首页/分类/搜索/详情)始终对游客开放,价格字段做脱敏即可
|
||||||
|
*/
|
||||||
|
export type LoginRequiredAction =
|
||||||
|
| 'addToCart'
|
||||||
|
| 'buyNow'
|
||||||
|
| 'checkout'
|
||||||
|
| 'favorite'
|
||||||
|
| 'receiveCoupon'
|
||||||
|
| 'viewOrder'
|
||||||
|
| 'payOrder'
|
||||||
|
| 'submitComment'
|
||||||
|
|
||||||
|
const ACTION_LABEL: Record<LoginRequiredAction, string> = {
|
||||||
|
addToCart: '加入购物车',
|
||||||
|
buyNow: '立即购买',
|
||||||
|
checkout: '结算',
|
||||||
|
favorite: '收藏',
|
||||||
|
receiveCoupon: '领取优惠券',
|
||||||
|
viewOrder: '查看订单',
|
||||||
|
payOrder: '支付订单',
|
||||||
|
submitComment: '提交评价',
|
||||||
|
}
|
||||||
|
|
||||||
|
const ACTION_REDIRECT_FALLBACK: Record<LoginRequiredAction, string> = {
|
||||||
|
addToCart: '/pages/shop/cart',
|
||||||
|
buyNow: '/pages/shop/cart',
|
||||||
|
checkout: '/pages/shop/cart',
|
||||||
|
favorite: '/pages/user/favorite-list',
|
||||||
|
receiveCoupon: '/pages/index/coupon-center',
|
||||||
|
viewOrder: '/pages/order/order',
|
||||||
|
payOrder: '/pages/order/order',
|
||||||
|
submitComment: '/pages/order/order',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequireLoginOptions {
|
||||||
|
/** 动作类型,用于生成提示文案 */
|
||||||
|
action: LoginRequiredAction
|
||||||
|
/** 登录后跳回的目标 URL(相对于本小程序),不传则用动作默认值 */
|
||||||
|
redirect?: string
|
||||||
|
/** 自定义弹窗标题 */
|
||||||
|
title?: string
|
||||||
|
/** 自定义弹窗内容(不传则按 action 自动生成) */
|
||||||
|
content?: string
|
||||||
|
/**
|
||||||
|
* true: 不弹窗,直接跳登录页(仅用于"非常确定用户就要登录"的场景,比如优惠券领取)
|
||||||
|
* 默认 false: 弹一个非阻塞的二次确认
|
||||||
|
*/
|
||||||
|
silent?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行为级登录守卫:游客态触发时弹出一个非阻塞的二次确认。
|
||||||
|
*
|
||||||
|
* 返回:
|
||||||
|
* - true : 已登录,可继续执行原动作
|
||||||
|
* - false : 游客态,已拦截(弹窗点了取消 / 弹窗点了去登录但还在当前页停留)
|
||||||
|
*
|
||||||
|
* 微信审核要点:
|
||||||
|
* 1. 永远不要在 useLaunch / onLoad 里主动调;
|
||||||
|
* 2. 永远不要在 tabBar 切换时调(tabBar 三个页面要 100% 游客可浏览);
|
||||||
|
* 3. 不要在 onShow 里无脑调,否则审核员每次切回都要点确认,体验差。
|
||||||
|
*/
|
||||||
|
export function requireLogin(options: RequireLoginOptions): boolean {
|
||||||
|
if (isLoggedIn()) return true
|
||||||
|
|
||||||
|
const actionLabel = ACTION_LABEL[options.action]
|
||||||
|
const redirect = options.redirect || ACTION_REDIRECT_FALLBACK[options.action]
|
||||||
|
const title = options.title || '需要登录'
|
||||||
|
const content = options.content || `登录后才能${actionLabel},是否前往登录?`
|
||||||
|
|
||||||
|
if (options.silent) {
|
||||||
|
goToRegister({ redirect })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.showModal({
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
confirmText: '去登录',
|
||||||
|
cancelText: '再逛逛',
|
||||||
|
confirmColor: '#0e932e',
|
||||||
|
success: ({ confirm }) => {
|
||||||
|
if (confirm) {
|
||||||
|
goToRegister({ redirect })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user