feat(store): 新增门店中心及VIP会员升级功能
- 个人信息页新增门店名称和门店地址展示,支持异步加载与只读显示 - 用户信息卡片整体改为绿色渐变背景,添加光晕和白色边框样式 - 新增升级VIP会员入口,突出展示并添加推荐标签 - 新建VIP会员升级页面,包含门店信息表单和VIP权益预览 - 提交升级申请调用新增api,支持状态检测表单只读 - 门店中心页面重构,简化订单管理,新增功能卡片及VIP审核入口 - 页面加载校验店员身份,非店员拒绝访问并提示 - 购物相关页面和组件全面支持VIP会员价格优先显示dealerPrice - 新增ShopDealerApply模型门店相关字段,丰富会员申请数据记录
This commit is contained in:
@@ -4,10 +4,11 @@ import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { useUser } from '@/hooks/useUser'
|
||||
import { useScrollHeight } from '@/hooks/useScrollHeight'
|
||||
import { useRequest } from '@/hooks/useRequest'
|
||||
import { getUserCardStats, getUserOrderStats, type UserCardStats, type UserOrderStats } from '@/api/shop/shopUserCard'
|
||||
import { getUserCardStats, getUserOrderStats } from '@/api/shop/shopUserCard'
|
||||
import { getMyClerk } from '@/api/shop/shopStoreUser'
|
||||
import { listUserRole } from '@/api/system/userRole'
|
||||
import type { UserRole } from '@/api/system/userRole/model'
|
||||
import { checkAndCacheVipStatus } from '@/utils/vip'
|
||||
import MemberBadge from '@/components/business/MemberBadge'
|
||||
|
||||
const UserPage: React.FC = () => {
|
||||
@@ -44,6 +45,8 @@ const UserPage: React.FC = () => {
|
||||
getMyClerk().then(data => setStoreInfo(data)).catch(() => setStoreInfo(null))
|
||||
// 查询用户角色
|
||||
listUserRole({ userId: user?.userId }).then(data => setUserRoles(data || [])).catch(() => setUserRoles([]))
|
||||
// 检查并缓存 VIP 状态
|
||||
checkAndCacheVipStatus((user as any)?.userId || (user as any)?.id)
|
||||
} else {
|
||||
setStoreInfo(null)
|
||||
setUserRoles([])
|
||||
@@ -64,11 +67,15 @@ const UserPage: React.FC = () => {
|
||||
const userId = Taro.getStorageSync('UserId')
|
||||
if (userId) {
|
||||
listUserRole({ userId }).then(data => setUserRoles(data || [])).catch(() => {})
|
||||
// 刷新 VIP 状态缓存
|
||||
checkAndCacheVipStatus(userId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const menuItems = [
|
||||
// 升级VIP会员 - 醒目样式
|
||||
{ icon: '👑', label: '升级VIP会员', url: '/pages/user/vip-upgrade/index', highlight: true },
|
||||
// 门店中心:仅门店店员/店长显示(通过 /shop/shop-store-user/my 判断)
|
||||
...(storeInfo ? [{ icon: '🏪', label: '门店中心', url: '/pages/store/center/index' }] : []),
|
||||
{ icon: '💰', label: '我的钱包', url: '/pages/user/wallet' },
|
||||
@@ -110,6 +117,8 @@ const UserPage: React.FC = () => {
|
||||
const uid = user?.userId
|
||||
if (uid) {
|
||||
listUserRole({ userId: uid }).then(data => setUserRoles(data || [])).catch(() => {})
|
||||
// 刷新 VIP 状态
|
||||
checkAndCacheVipStatus(uid)
|
||||
}
|
||||
} finally {
|
||||
setRefreshing(false)
|
||||
@@ -120,69 +129,81 @@ const UserPage: React.FC = () => {
|
||||
<View className='h-full bg-gray-50'>
|
||||
<ScrollView scrollY refresherEnabled={!!isLoggedIn} refresherTriggered={refreshing} onRefresherRefresh={onRefresh} style={{ height: scrollHeight }}>
|
||||
{/* 用户信息卡片 */}
|
||||
<View className='mx-3 mt-3 p-4 bg-white rounded-xl'>
|
||||
<View className='flex items-center gap-3' onClick={handleAvatarClick}>
|
||||
{isLoggedIn && user?.avatar ? (
|
||||
<Image className='w-14 h-14 rounded-full' src={user.avatar} mode='aspectFill' />
|
||||
) : (
|
||||
<View className='w-14 h-14 rounded-full bg-gray-100 flex items-center justify-center'>
|
||||
<Text className='text-2xl text-gray-300'>👤</Text>
|
||||
<View className='mx-3 mt-3 p-4 rounded-xl relative overflow-hidden' style={{ background: 'linear-gradient(135deg, #15803d 0%, #22c55e 60%, #4ade80 100%)' }}>
|
||||
{/* 装饰性光晕 */}
|
||||
<View className='absolute -top-8 -right-8 w-32 h-32 rounded-full opacity-20' style={{ background: 'radial-gradient(circle, #ffffff, transparent)' }} />
|
||||
<View className='absolute -bottom-4 -left-4 w-20 h-20 rounded-full opacity-10' style={{ background: 'radial-gradient(circle, #ffffff, transparent)' }} />
|
||||
<View className='relative z-10'>
|
||||
<View className='flex items-center gap-3' onClick={handleAvatarClick}>
|
||||
{isLoggedIn && user?.avatar ? (
|
||||
<Image className='w-14 h-14 rounded-full border-2 border-white shadow-sm' src={user.avatar} mode='aspectFill' />
|
||||
) : (
|
||||
<View className='w-14 h-14 rounded-full bg-white bg-opacity-20 flex items-center justify-center border-2 border-white border-opacity-30'>
|
||||
<Text className='text-2xl'>👤</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className='flex-1'>
|
||||
<Text className='text-lg font-medium text-white block'>
|
||||
{isLoggedIn ? (user?.nickname || user?.phone || '用户') : '点击登录'}
|
||||
</Text>
|
||||
<View className='flex items-center gap-2 mt-1 flex-wrap'>
|
||||
{/*{isLoggedIn && (*/}
|
||||
{/* <View*/}
|
||||
{/* className='inline-flex items-center rounded-full text-xs px-2 py-1'*/}
|
||||
{/* style={{ background: 'rgba(255,255,255,0.2)', color: '#ffffff', border: '1px solid rgba(255,255,255,0.3)' }}*/}
|
||||
{/* >*/}
|
||||
{/* <Text>{(user as any)?.memberLevelName || '普通用户'}</Text>*/}
|
||||
{/* </View>*/}
|
||||
{/*)}*/}
|
||||
{userRoles.map(role => (
|
||||
<View
|
||||
key={role.roleId}
|
||||
className='inline-flex items-center rounded-full text-xs px-2 py-0.5'
|
||||
style={{ background: 'rgba(255,255,255,0.2)', color: '#ffffff', border: '1px solid rgba(255,255,255,0.3)' }}
|
||||
>
|
||||
<Text>{role.roleName}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
{isLoggedIn && <Text className='text-white text-opacity-60 text-sm'>{'>'}</Text>}
|
||||
</View>
|
||||
{/* 数据概览 */}
|
||||
{isLoggedIn && (
|
||||
<View className='grid grid-cols-3 gap-2 mt-4 pt-4 border-t border-white border-opacity-15'>
|
||||
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/wallet' })}>
|
||||
{!hasCardData ? (
|
||||
<View className='h-7 w-16 mx-auto rounded animate-pulse' style={{ background: 'rgba(255,255,255,0.25)' }} />
|
||||
) : (
|
||||
<Text className='text-lg font-bold text-white block'>
|
||||
{cardStats?.balance || '0.00'}
|
||||
</Text>
|
||||
)}
|
||||
<Text className='text-xs text-white text-opacity-70'>余额</Text>
|
||||
</View>
|
||||
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/points-record' })}>
|
||||
{!hasCardData ? (
|
||||
<View className='h-7 w-10 mx-auto rounded animate-pulse' style={{ background: 'rgba(255,255,255,0.25)' }} />
|
||||
) : (
|
||||
<Text className='text-lg font-bold text-white block'>
|
||||
{cardStats?.points || (user as any)?.points || 0}
|
||||
</Text>
|
||||
)}
|
||||
<Text className='text-xs text-white text-opacity-70'>积分</Text>
|
||||
</View>
|
||||
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/coupon-list' })}>
|
||||
{!hasCardData ? (
|
||||
<View className='h-7 w-10 mx-auto rounded animate-pulse' style={{ background: 'rgba(255,255,255,0.25)' }} />
|
||||
) : (
|
||||
<Text className='text-lg font-bold text-white block'>
|
||||
{cardStats?.coupons || 0}
|
||||
</Text>
|
||||
)}
|
||||
<Text className='text-xs text-white text-opacity-70'>优惠券</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<View className='flex-1'>
|
||||
<Text className='text-lg font-medium text-gray-800 block'>
|
||||
{isLoggedIn ? (user?.nickname || user?.phone || '用户') : '点击登录'}
|
||||
</Text>
|
||||
<View className='flex items-center gap-2 mt-1 flex-wrap'>
|
||||
{isLoggedIn && <MemberBadge levelName={(user as any)?.memberLevelName} />}
|
||||
{userRoles.map(role => (
|
||||
<View
|
||||
key={role.roleId}
|
||||
className='inline-flex items-center rounded-full text-xs px-2 py-0.5'
|
||||
style={{ background: '#eff6ff', color: '#1d4ed8', border: '1px solid #bfdbfe' }}
|
||||
>
|
||||
<Text>{role.roleName}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
{isLoggedIn && <Text className='text-gray-300 text-sm'>{'>'}</Text>}
|
||||
</View>
|
||||
{/* 数据概览 */}
|
||||
{isLoggedIn && (
|
||||
<View className='grid grid-cols-3 gap-2 mt-4 pt-4 border-t border-gray-50'>
|
||||
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/wallet' })}>
|
||||
{!hasCardData ? (
|
||||
<View className='h-7 w-16 mx-auto bg-gray-200 rounded animate-pulse' />
|
||||
) : (
|
||||
<Text className='text-lg font-bold text-gray-800 block'>
|
||||
{cardStats?.balance || '0.00'}
|
||||
</Text>
|
||||
)}
|
||||
<Text className='text-xs text-gray-400'>余额</Text>
|
||||
</View>
|
||||
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/points-record' })}>
|
||||
{!hasCardData ? (
|
||||
<View className='h-7 w-10 mx-auto bg-gray-200 rounded animate-pulse' />
|
||||
) : (
|
||||
<Text className='text-lg font-bold text-gray-800 block'>
|
||||
{cardStats?.points || (user as any)?.points || 0}
|
||||
</Text>
|
||||
)}
|
||||
<Text className='text-xs text-gray-400'>积分</Text>
|
||||
</View>
|
||||
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/coupon-list' })}>
|
||||
{!hasCardData ? (
|
||||
<View className='h-7 w-10 mx-auto bg-gray-200 rounded animate-pulse' />
|
||||
) : (
|
||||
<Text className='text-lg font-bold text-gray-800 block'>
|
||||
{cardStats?.coupons || 0}
|
||||
</Text>
|
||||
)}
|
||||
<Text className='text-xs text-gray-400'>优惠券</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{/* 我的订单快捷入口 */}
|
||||
<View className='bg-white rounded-xl mx-3 mt-3 p-4'>
|
||||
@@ -223,14 +244,26 @@ const UserPage: React.FC = () => {
|
||||
key={item.label}
|
||||
className={`flex items-center justify-between px-4 py-3 ${
|
||||
idx < menuItems.length - 1 ? 'border-b border-gray-50' : ''
|
||||
}`}
|
||||
} ${(item as any).highlight ? '' : ''}`}
|
||||
style={(item as any).highlight ? {
|
||||
background: 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)',
|
||||
} : {}}
|
||||
onClick={() => Taro.navigateTo({ url: item.url })}
|
||||
>
|
||||
<View className='flex items-center gap-3'>
|
||||
<Text className='text-base'>{item.icon}</Text>
|
||||
<Text className='text-sm text-gray-700'>{item.label}</Text>
|
||||
<View className={`${(item as any).highlight ? 'w-8 h-8 rounded-lg flex items-center justify-center text-lg' : ''}`} style={(item as any).highlight ? { background: 'linear-gradient(135deg, #f59e0b, #d97706)' } : {}}>
|
||||
<Text className='text-base'>{(item as any).highlight ? '' : item.icon}{(item as any).highlight ? item.icon : ''}</Text>
|
||||
</View>
|
||||
<View className='flex items-center gap-2'>
|
||||
<Text className={`text-sm ${(item as any).highlight ? 'text-amber-900 font-semibold' : 'text-gray-700'}`}>{item.label}</Text>
|
||||
{(item as any).highlight && (
|
||||
<View className='px-1.5 py-0.5 rounded-full' style={{ background: 'linear-gradient(135deg, #dc2626, #b91c1c)' }}>
|
||||
<Text className='text-white text-xs'>推荐</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<Text className='text-gray-300 text-sm'>{'>'}</Text>
|
||||
<Text className={`text-sm ${(item as any).highlight ? 'text-amber-700' : 'text-gray-300'}`}>{'>'}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user