feat(store-center): 在门店中心添加扫码登录PC后台功能

- 门店中心右上角新增扫码登录按钮,使用base64 SVG图标兼容小程序Image组件渲染
- 扫码后通过解析二维码内容调用confirmWechatQRLogin接口完成PC端登录确认
- 支持扫码取消静默处理,扫码失败则弹窗提示错误信息
- 优化门店中心页面布局,调整扫码按钮绝对定位及样式
- 优化了用户管理页面的小型样式,包括按钮和文本间距调整
- 调整用户页面部分Badge显示逻辑,排除tabIndex为4的项不显示角标
This commit is contained in:
2026-07-17 12:38:27 +08:00
parent 21b8403fab
commit bd3589af61
5 changed files with 1322 additions and 1230 deletions

View File

@@ -94,3 +94,13 @@
### 验证 ### 验证
- `npx taro build --type weapp` 构建成功 - `npx taro build --type weapp` 构建成功
## 门店中心右上角扫码登录 PC 后台src/pages/store/center/index.tsx
- **需求**:门店中心顶部绿色渐变区右上角加一个二维码扫码图标,点击后调起微信扫码,扫码结果解析出 token 后调 `confirmWechatQRLogin` 确认 PC 端登录
- **参考**`/Users/gxwebsoft/VUE/template-10584/src/pages/user/components/UserCard.tsx` 中的 `UnifiedQRButton` 组件
- **实现**
- 复用已有 API`parseQRContent` + `confirmWechatQRLogin``@/api/passport/qr-login`),与 `src/components/QRLoginScanner.tsx` 同款
- 新增 `handleScanLogin` callback`Taro.scanCode``parseQRContent` → 校验 token/userId → `confirmWechatQRLogin` → 成功弹窗
- 右上角按钮用 `absolute top-3 right-4 z-20` 定位,半透明白色圆形背景 + base64 SVG 图标
- 小程序不支持 `<svg>` 标签,用 `Image` 组件 + `data:image/svg+xml;base64,...` data URI 渲染图标
- 用户取消扫码errMsg 含 'cancel')静默处理

View File

@@ -4,8 +4,12 @@ import Taro from '@tarojs/taro'
import {getMyClerk} from '@/api/shop/shopStoreUser' import {getMyClerk} from '@/api/shop/shopStoreUser'
import {listShopDealerApply} from '@/api/shop/shopDealerApply' import {listShopDealerApply} from '@/api/shop/shopDealerApply'
import {pageShopOrder} from '@/api/shop/shopOrder' import {pageShopOrder} from '@/api/shop/shopOrder'
import {confirmWechatQRLogin, parseQRContent} from '@/api/passport/qr-login'
import Badge from '@/components/common/Badge' import Badge from '@/components/common/Badge'
// 二维码扫码图标base64 SVG data URI兼容小程序 Image 组件)
const QR_SCAN_ICON = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMyA3VjVhMiAyIDAgMCAxIDItMmgyTTE3IDNoMmEyIDIgMCAwIDEgMiAydjJNMjEgMTd2MmEyIDIgMCAwIDEtMiAyaC0yTTcgMjFINWEyIDIgMCAwIDEtMi0ydi0yIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiAvPjxyZWN0IHg9IjciIHk9IjciIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiAvPjxyZWN0IHg9IjEzIiB5PSI3IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgLz48cmVjdCB4PSI3IiB5PSIxMyIgd2lkdGg9IjQiIGhlaWdodD0iNCIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIC8+PHBhdGggZD0iTTEzIDEzaDJ2Mk0xNyAxM3YyTTE3IDE3djJNMTMgMTd2Mk0xMyAxOWgyIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiAvPjwvc3ZnPg=="
definePageConfig({ definePageConfig({
navigationBarTitleText: '门店中心', navigationBarTitleText: '门店中心',
}) })
@@ -92,13 +96,15 @@ export default function StoreCenterPage() {
// VIP 待审核 // VIP 待审核
const vipData = await listShopDealerApply({applyStatus: 10}) const vipData = await listShopDealerApply({applyStatus: 10})
setPendingVipCount((vipData || []).length) setPendingVipCount((vipData || []).length)
} catch { /* ignore */ } } catch { /* ignore */
}
try { try {
// 待处理订单:未付款/待发货/待收货 // 待处理订单:未付款/待发货/待收货
const orderData = await pageShopOrder({statusFilter: 1, page: 1, limit: 1}) const orderData = await pageShopOrder({statusFilter: 1, page: 1, limit: 1})
setPendingOrderCount(orderData?.total || 0) setPendingOrderCount(orderData?.total || 0)
} catch { /* ignore */ } } catch { /* ignore */
}
} }
/** 获取卡片角标数字 */ /** 获取卡片角标数字 */
@@ -108,6 +114,39 @@ export default function StoreCenterPage() {
return 0 return 0
} }
/** 扫码登录 PC 后台 */
const handleScanLogin = useCallback(async () => {
try {
Taro.showLoading({title: '请扫码...', mask: true})
const scanRes = await Taro.scanCode({onlyFromCamera: false, scanType: ['qrCode']})
Taro.hideLoading()
const rawContent = scanRes.result || ''
const token = parseQRContent(rawContent)
const userId = Number(Taro.getStorageSync('UserId'))
if (!token) {
Taro.showModal({title: '提示', content: '未识别到有效的登录二维码', showCancel: false})
return
}
if (!userId) {
Taro.showModal({title: '提示', content: '当前用户未登录', showCancel: false})
return
}
Taro.showLoading({title: '正在确认登录...', mask: true})
await confirmWechatQRLogin(token, userId)
Taro.hideLoading()
Taro.showModal({title: '登录成功', content: 'PC 端后台登录已确认,请返回网页端查看', showCancel: false})
} catch (err: any) {
Taro.hideLoading()
if (err?.errMsg?.includes('cancel')) {
return // 用户取消扫码,静默处理
}
Taro.showModal({title: '登录失败', content: err?.message || '扫码登录失败,请重试', showCancel: false})
}
}, [])
/** 请求订阅消息授权 */ /** 请求订阅消息授权 */
const handleSubscribe = useCallback(() => { const handleSubscribe = useCallback(() => {
if (SUBSCRIBE_TMPL_IDS.length === 0) { if (SUBSCRIBE_TMPL_IDS.length === 0) {
@@ -162,9 +201,19 @@ export default function StoreCenterPage() {
style={{background: 'radial-gradient(circle, #ffffff, transparent)'}}/> style={{background: 'radial-gradient(circle, #ffffff, transparent)'}}/>
<View className='absolute -bottom-6 -left-6 w-24 h-24 rounded-full opacity-15' <View className='absolute -bottom-6 -left-6 w-24 h-24 rounded-full opacity-15'
style={{background: 'radial-gradient(circle, #ffffff, transparent)'}}/> style={{background: 'radial-gradient(circle, #ffffff, transparent)'}}/>
{/* 右上角扫码登录按钮 */}
<View
className='absolute top-3 right-4 z-20 w-9 h-9 rounded-full bg-white/20 border border-white/30 flex items-center justify-center active:opacity-70'
onClick={handleScanLogin}
>
<Image src={QR_SCAN_ICON} className='w-5 h-5' mode='aspectFit'/>
</View>
<View className='relative z-10 flex items-center gap-4'> <View className='relative z-10 flex items-center gap-4'>
{/* 店员头像 */} {/* 店员头像 */}
<View className='w-16 h-16 rounded-full bg-white/20 border-2 border-white/40 overflow-hidden flex-shrink-0'> <View
className='w-16 h-16 rounded-full bg-white/20 border-2 border-white/40 overflow-hidden flex-shrink-0'>
{storeInfo.avatar ? ( {storeInfo.avatar ? (
<Image <Image
className='w-full h-full' className='w-full h-full'
@@ -216,7 +265,8 @@ export default function StoreCenterPage() {
{/* 待处理角标 */} {/* 待处理角标 */}
{card.showBadge && ( {card.showBadge && (
<Badge count={getBadgeCount(card.key)} className='absolute -top-1 -right-1' size={20} fontSize={11} fontWeight='bold' /> <Badge count={getBadgeCount(card.key)} className='absolute -top-1 -right-1' size={20}
fontSize={11} fontWeight='bold'/>
)} )}
{/* 箭头 */} {/* 箭头 */}
@@ -227,13 +277,14 @@ export default function StoreCenterPage() {
</View> </View>
{/* 底部提示 */} {/* 底部提示 */}
<View className='mx-3 mt-6 mb-6'> <View className='mx-3 mt-3 mb-6'>
{/* 订阅消息提醒 */} {/* 订阅消息提醒 */}
<View <View
className='bg-white rounded-xl p-4 flex items-center gap-3 active:opacity-80 mb-3' className='bg-white rounded-xl p-4 flex items-center gap-3 active:opacity-80 mb-3'
onClick={handleSubscribe} onClick={handleSubscribe}
> >
<View className='w-10 h-10 rounded-full bg-orange-50 flex items-center justify-center flex-shrink-0'> <View
className='w-10 h-10 rounded-full bg-orange-50 flex items-center justify-center flex-shrink-0'>
<Text className='text-xl'>🔔</Text> <Text className='text-xl'>🔔</Text>
</View> </View>
<View className='flex-1 min-w-0'> <View className='flex-1 min-w-0'>
@@ -245,9 +296,10 @@ export default function StoreCenterPage() {
<Text className='text-orange-500 text-sm flex-shrink-0'></Text> <Text className='text-orange-500 text-sm flex-shrink-0'></Text>
</View> </View>
{/*<Text className='text-gray-300 text-xs text-center block'>*/} <Text className='text-gray-300 text-xs text-center block'>
{/* 未来更多功能将持续上线*/} 线
{/*</Text>*/} </Text>
<View className={'h-4'}></View>
</View> </View>
</View> </View>
) )

View File

@@ -133,7 +133,8 @@ export default function StoreGoodsPage() {
useEffect(() => { useEffect(() => {
listShopGoodsCategory({}) listShopGoodsCategory({})
.then(data => setCategories(data || [])) .then(data => setCategories(data || []))
.catch(() => {}) .catch(() => {
})
}, []) }, [])
// 获取当前登录店员的门店ID // 获取当前登录店员的门店ID
@@ -142,7 +143,8 @@ export default function StoreGoodsPage() {
.then(data => { .then(data => {
if (data?.storeId) setStoreId(data.storeId) if (data?.storeId) setStoreId(data.storeId)
}) })
.catch(() => {}) .catch(() => {
})
}, []) }, [])
// 切换 tab 时重新加载 // 切换 tab 时重新加载
@@ -480,7 +482,8 @@ export default function StoreGoodsPage() {
onClick={() => Taro.previewImage({urls: [goods.image!]})} onClick={() => Taro.previewImage({urls: [goods.image!]})}
/> />
) : ( ) : (
<View className='w-20 h-20 rounded-lg bg-gray-100 flex items-center justify-center flex-shrink-0'> <View
className='w-20 h-20 rounded-lg bg-gray-100 flex items-center justify-center flex-shrink-0'>
<Text className='text-2xl text-gray-300'>📦</Text> <Text className='text-2xl text-gray-300'>📦</Text>
</View> </View>
)} )}
@@ -488,7 +491,10 @@ export default function StoreGoodsPage() {
<View className='flex items-center gap-2 mb-1'> <View className='flex items-center gap-2 mb-1'>
<Text <Text
className='text-xs px-1.5 py-0.5 rounded' className='text-xs px-1.5 py-0.5 rounded'
style={{ color: getStatusColor(goods.status), background: getStatusColor(goods.status) + '15' }} style={{
color: getStatusColor(goods.status),
background: getStatusColor(goods.status) + '15'
}}
> >
{getStatusText(goods.status)} {getStatusText(goods.status)}
</Text> </Text>
@@ -499,7 +505,8 @@ export default function StoreGoodsPage() {
<Text className='text-xs px-1.5 py-0.5 rounded bg-amber-50 text-amber-600'></Text> <Text className='text-xs px-1.5 py-0.5 rounded bg-amber-50 text-amber-600'></Text>
)} )}
</View> </View>
<Text className='text-sm text-gray-800 block' style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <Text className='text-sm text-gray-800 block'
style={{overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>
{goods.name} {goods.name}
</Text> </Text>
{goods.categoryName && ( {goods.categoryName && (
@@ -520,19 +527,18 @@ export default function StoreGoodsPage() {
<Text className='text-sm text-gray-400 line-through'>¥{goods.salePrice}</Text> <Text className='text-sm text-gray-400 line-through'>¥{goods.salePrice}</Text>
</View> </View>
)} )}
{goods.dealerPrice && (
<View className='flex-1'> <View className='flex-1'>
<Text className='text-xs text-gray-400 block'>VIP价</Text> <Text className='text-xs text-gray-400 block'>VIP价</Text>
<Text className='text-sm text-purple-500'>¥{goods.dealerPrice}</Text> <Text className='text-sm text-purple-500'>¥{goods.dealerPrice}</Text>
</View> </View>
)}
<View className='flex-1'> <View className='flex-1'>
<Text className='text-xs text-gray-400 block'></Text> <Text className='text-xs text-gray-400 block'></Text>
<Text className='text-sm text-gray-700'>{goods.sales || 0}</Text> <Text className='text-sm text-gray-700'>{goods.sales || 0}</Text>
</View> </View>
<View className='flex-1'> <View className='flex-1'>
<Text className='text-xs text-gray-400 block'></Text> <Text className='text-xs text-gray-400 block'></Text>
<Text className={`text-sm ${isSoldOut ? 'text-red-500' : 'text-gray-700'}`}>{goods.stock ?? 0}</Text> <Text
className={`text-sm ${isSoldOut ? 'text-red-500' : 'text-gray-700'}`}>{goods.stock ?? 0}</Text>
</View> </View>
</View> </View>
@@ -586,7 +592,11 @@ export default function StoreGoodsPage() {
{searchText ? ( {searchText ? (
<Text <Text
className='text-gray-400 text-lg px-1' className='text-gray-400 text-lg px-1'
onClick={() => { setSearchText(''); setKeyword(''); loadGoods(activeTab, 1) }} onClick={() => {
setSearchText('');
setKeyword('');
loadGoods(activeTab, 1)
}}
>×</Text> >×</Text>
) : null} ) : null}
</View> </View>
@@ -670,9 +680,11 @@ export default function StoreGoodsPage() {
className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl max-h-[60vh] overflow-y-auto' className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl max-h-[60vh] overflow-y-auto'
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<View className='sticky top-0 bg-white border-b border-gray-50 px-5 py-4 flex justify-between items-center'> <View
className='sticky top-0 bg-white border-b border-gray-50 px-5 py-4 flex justify-between items-center'>
<Text className='text-base font-medium text-gray-800'></Text> <Text className='text-base font-medium text-gray-800'></Text>
<Text className='text-gray-400 text-lg' onClick={() => setShowCategoryPicker(false)}>×</Text> <Text className='text-gray-400 text-lg'
onClick={() => setShowCategoryPicker(false)}>×</Text>
</View> </View>
<View <View
className='px-5 py-3.5 border-b border-gray-50' className='px-5 py-3.5 border-b border-gray-50'
@@ -701,27 +713,31 @@ export default function StoreGoodsPage() {
{showEditModal && editGoods && ( {showEditModal && editGoods && (
<View className='fixed inset-0 z-50 flex items-end justify-center'> <View className='fixed inset-0 z-50 flex items-end justify-center'>
<View className='absolute inset-0 bg-black/50' onClick={closeEditModal}/> <View className='absolute inset-0 bg-black/50' onClick={closeEditModal}/>
<View className='relative bg-white rounded-t-2xl w-full px-5 pt-6 pb-10 max-h-[80vh] overflow-y-auto'> <View
className='relative bg-white rounded-t-2xl w-full px-5 pt-6 pb-10 max-h-[80vh] overflow-y-auto'>
<Text className='text-lg font-medium text-gray-800 text-center mb-5 block'></Text> <Text className='text-lg font-medium text-gray-800 text-center mb-5 block'></Text>
{/* 商品信息 */} {/* 商品信息 */}
<View className='bg-gray-50 rounded-xl p-4 mb-5 flex items-center gap-3'> <View className='bg-gray-50 rounded-xl p-4 mb-5 flex items-center gap-3'>
<View className='relative' onClick={handleUploadImage}> <View className='relative' onClick={handleUploadImage}>
{editGoods.image ? ( {editGoods.image ? (
<Image className='w-16 h-16 rounded-lg' src={getCompressedImageUrl(editGoods.image)} mode='aspectFill' /> <Image className='w-16 h-16 rounded-lg' src={getCompressedImageUrl(editGoods.image)}
mode='aspectFill'/>
) : ( ) : (
<View className='w-16 h-16 rounded-lg bg-gray-200 flex items-center justify-center'> <View className='w-16 h-16 rounded-lg bg-gray-200 flex items-center justify-center'>
<Text className='text-2xl text-gray-300'>📷</Text> <Text className='text-2xl text-gray-300'>📷</Text>
</View> </View>
)} )}
<View className='absolute inset-0 rounded-lg bg-black/30 flex items-center justify-center'> <View
className='absolute inset-0 rounded-lg bg-black/30 flex items-center justify-center'>
<Text className='text-white text-xs'> <Text className='text-white text-xs'>
{uploadingImage ? '上传中...' : '更换'} {uploadingImage ? '上传中...' : '更换'}
</Text> </Text>
</View> </View>
</View> </View>
<View className='flex-1 min-w-0'> <View className='flex-1 min-w-0'>
<Text className='text-sm text-gray-800 block' style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> <Text className='text-sm text-gray-800 block'
style={{overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>
{editGoods.name} {editGoods.name}
</Text> </Text>
<Text className='text-xs text-gray-400 mt-1 block'>ID: {editGoods.goodsId}</Text> <Text className='text-xs text-gray-400 mt-1 block'>ID: {editGoods.goodsId}</Text>
@@ -734,7 +750,8 @@ export default function StoreGoodsPage() {
<View className='flex flex-wrap gap-3'> <View className='flex flex-wrap gap-3'>
{editForm.files.map((file, index) => ( {editForm.files.map((file, index) => (
<View key={file.url + index} className='relative w-20 h-20'> <View key={file.url + index} className='relative w-20 h-20'>
<Image className='w-20 h-20 rounded-lg bg-gray-100' src={getCompressedImageUrl(file.url)} mode='aspectFill' /> <Image className='w-20 h-20 rounded-lg bg-gray-100'
src={getCompressedImageUrl(file.url)} mode='aspectFill'/>
<View <View
className='absolute -top-1.5 -right-1.5 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center' className='absolute -top-1.5 -right-1.5 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center'
onClick={() => handleRemoveBanner(index)} onClick={() => handleRemoveBanner(index)}
@@ -747,7 +764,8 @@ export default function StoreGoodsPage() {
className='w-20 h-20 rounded-lg border border-dashed border-gray-300 flex flex-col items-center justify-center' className='w-20 h-20 rounded-lg border border-dashed border-gray-300 flex flex-col items-center justify-center'
onClick={handleAddBanner} onClick={handleAddBanner}
> >
<Text className='text-2xl text-gray-300 mb-0.5'>{uploadingBanner ? '...' : '+'}</Text> <Text
className='text-2xl text-gray-300 mb-0.5'>{uploadingBanner ? '...' : '+'}</Text>
<Text className='text-xs text-gray-400'>{uploadingBanner ? '上传中' : '上传'}</Text> <Text className='text-xs text-gray-400'>{uploadingBanner ? '上传中' : '上传'}</Text>
</View> </View>
</View> </View>
@@ -843,21 +861,25 @@ export default function StoreGoodsPage() {
{showAddModal && ( {showAddModal && (
<View className='fixed inset-0 z-50 flex items-end justify-center'> <View className='fixed inset-0 z-50 flex items-end justify-center'>
<View className='absolute inset-0 bg-black/50' onClick={closeAddModal}/> <View className='absolute inset-0 bg-black/50' onClick={closeAddModal}/>
<View className='relative bg-white rounded-t-2xl w-full px-5 pt-6 pb-10 max-h-[85vh] overflow-y-auto'> <View
className='relative bg-white rounded-t-2xl w-full px-5 pt-6 pb-10 max-h-[85vh] overflow-y-auto'>
<Text className='text-lg font-medium text-gray-800 text-center mb-5 block'></Text> <Text className='text-lg font-medium text-gray-800 text-center mb-5 block'></Text>
{/* 商品图片 */} {/* 商品图片 */}
<View className='mb-4'> <View className='mb-4'>
<Text className='text-sm text-gray-600 mb-2 block'> <Text className='text-red-500'>*</Text></Text> <Text className='text-sm text-gray-600 mb-2 block'> <Text
className='text-red-500'>*</Text></Text>
<View className='relative w-20 h-20' onClick={handleAddUploadImage}> <View className='relative w-20 h-20' onClick={handleAddUploadImage}>
{addForm.image ? ( {addForm.image ? (
<Image className='w-20 h-20 rounded-lg' src={getCompressedImageUrl(addForm.image)} mode='aspectFill' /> <Image className='w-20 h-20 rounded-lg' src={getCompressedImageUrl(addForm.image)}
mode='aspectFill'/>
) : ( ) : (
<View className='w-20 h-20 rounded-lg bg-gray-100 flex items-center justify-center'> <View className='w-20 h-20 rounded-lg bg-gray-100 flex items-center justify-center'>
<Text className='text-2xl text-gray-300'>📷</Text> <Text className='text-2xl text-gray-300'>📷</Text>
</View> </View>
)} )}
<View className='absolute inset-0 rounded-lg bg-black/30 flex items-center justify-center'> <View
className='absolute inset-0 rounded-lg bg-black/30 flex items-center justify-center'>
<Text className='text-white text-xs'> <Text className='text-white text-xs'>
{addUploadingImage ? '上传中...' : '上传'} {addUploadingImage ? '上传中...' : '上传'}
</Text> </Text>
@@ -867,7 +889,8 @@ export default function StoreGoodsPage() {
{/* 商品名称 */} {/* 商品名称 */}
<View className='mb-4'> <View className='mb-4'>
<Text className='text-sm text-gray-600 mb-2 block'> <Text className='text-red-500'>*</Text></Text> <Text className='text-sm text-gray-600 mb-2 block'> <Text
className='text-red-500'>*</Text></Text>
<Input <Input
className='bg-gray-50 rounded-lg px-4 py-3 text-sm' className='bg-gray-50 rounded-lg px-4 py-3 text-sm'
placeholder='请输入商品名称' placeholder='请输入商品名称'
@@ -899,7 +922,8 @@ export default function StoreGoodsPage() {
<View className='flex flex-wrap gap-3'> <View className='flex flex-wrap gap-3'>
{addForm.files.map((file, index) => ( {addForm.files.map((file, index) => (
<View key={file.url + index} className='relative w-20 h-20'> <View key={file.url + index} className='relative w-20 h-20'>
<Image className='w-20 h-20 rounded-lg bg-gray-100' src={getCompressedImageUrl(file.url)} mode='aspectFill' /> <Image className='w-20 h-20 rounded-lg bg-gray-100'
src={getCompressedImageUrl(file.url)} mode='aspectFill'/>
<View <View
className='absolute -top-1.5 -right-1.5 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center' className='absolute -top-1.5 -right-1.5 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center'
onClick={() => handleAddRemoveBanner(index)} onClick={() => handleAddRemoveBanner(index)}
@@ -912,15 +936,18 @@ export default function StoreGoodsPage() {
className='w-20 h-20 rounded-lg border border-dashed border-gray-300 flex flex-col items-center justify-center' className='w-20 h-20 rounded-lg border border-dashed border-gray-300 flex flex-col items-center justify-center'
onClick={handleAddUploadBanner} onClick={handleAddUploadBanner}
> >
<Text className='text-2xl text-gray-300 mb-0.5'>{addUploadingBanner ? '...' : '+'}</Text> <Text
<Text className='text-xs text-gray-400'>{addUploadingBanner ? '上传中' : '上传'}</Text> className='text-2xl text-gray-300 mb-0.5'>{addUploadingBanner ? '...' : '+'}</Text>
<Text
className='text-xs text-gray-400'>{addUploadingBanner ? '上传中' : '上传'}</Text>
</View> </View>
</View> </View>
</View> </View>
{/* 到手价 */} {/* 到手价 */}
<View className='mb-4'> <View className='mb-4'>
<Text className='text-sm text-gray-600 mb-2 block'> (¥) <Text className='text-red-500'>*</Text></Text> <Text className='text-sm text-gray-600 mb-2 block'> (¥) <Text
className='text-red-500'>*</Text></Text>
<Input <Input
className='bg-gray-50 rounded-lg px-4 py-3 text-sm' className='bg-gray-50 rounded-lg px-4 py-3 text-sm'
type='digit' type='digit'
@@ -956,7 +983,8 @@ export default function StoreGoodsPage() {
{/* 库存 */} {/* 库存 */}
<View className='mb-4'> <View className='mb-4'>
<Text className='text-sm text-gray-600 mb-2 block'> <Text className='text-red-500'>*</Text></Text> <Text className='text-sm text-gray-600 mb-2 block'> <Text
className='text-red-500'>*</Text></Text>
<Input <Input
className='bg-gray-50 rounded-lg px-4 py-3 text-sm' className='bg-gray-50 rounded-lg px-4 py-3 text-sm'
type='number' type='number'
@@ -1017,9 +1045,11 @@ export default function StoreGoodsPage() {
className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl max-h-[60vh] overflow-y-auto' className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl max-h-[60vh] overflow-y-auto'
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<View className='sticky top-0 bg-white border-b border-gray-50 px-5 py-4 flex justify-between items-center'> <View
className='sticky top-0 bg-white border-b border-gray-50 px-5 py-4 flex justify-between items-center'>
<Text className='text-base font-medium text-gray-800'></Text> <Text className='text-base font-medium text-gray-800'></Text>
<Text className='text-gray-400 text-lg' onClick={() => setShowAddCategoryPicker(false)}>×</Text> <Text className='text-gray-400 text-lg'
onClick={() => setShowAddCategoryPicker(false)}>×</Text>
</View> </View>
<View <View
className='px-5 py-3.5 border-b border-gray-50' className='px-5 py-3.5 border-b border-gray-50'

View File

@@ -183,7 +183,7 @@ export default function StoreUsersPage() {
{displayName} {displayName}
</Text> </Text>
<View <View
className='px-1.5 py-0.5 rounded text-[10px]' className='px-1 py-1 rounded text-xs'
style={{ style={{
color: isDisabled ? '#dc2626' : '#16a34a', color: isDisabled ? '#dc2626' : '#16a34a',
background: isDisabled ? '#fef2f2' : '#f0fdf4', background: isDisabled ? '#fef2f2' : '#f0fdf4',
@@ -192,14 +192,14 @@ export default function StoreUsersPage() {
{isDisabled ? '已禁用' : '正常'} {isDisabled ? '已禁用' : '正常'}
</View> </View>
</View> </View>
<Text className='text-xs text-gray-400 mt-0.5 block'> <Text className='text-xs text-gray-400 mt-1 block'>
{user.phone || user.mobile || '未绑定手机'} {user.phone || user.mobile || '未绑定手机'}
</Text> </Text>
</View> </View>
{/* 禁用/启用按钮 */} {/* 禁用/启用按钮 */}
<View <View
className={`px-3 py-1.5 rounded-lg flex-shrink-0 ${isDisabled ? 'bg-green-50' : 'bg-red-50'}`} className={`px-3 py-1 rounded-lg flex-shrink-0 ${isDisabled ? 'bg-green-50' : 'bg-red-50'}`}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
handleToggleStatus(user) handleToggleStatus(user)
@@ -215,17 +215,17 @@ export default function StoreUsersPage() {
<View className='flex items-center gap-4 mt-3 pt-3 border-t border-gray-50'> <View className='flex items-center gap-4 mt-3 pt-3 border-t border-gray-50'>
{user.memberLevelName && ( {user.memberLevelName && (
<View className='flex items-center gap-1'> <View className='flex items-center gap-1'>
<Text className='text-[10px] text-gray-400'>:</Text> <Text className='text-xs text-gray-400'>:</Text>
<Text className='text-xs text-purple-500'>{user.memberLevelName}</Text> <Text className='text-xs text-purple-500'>{user.memberLevelName}</Text>
</View> </View>
)} )}
<View className='flex items-center gap-1'> <View className='flex items-center gap-1'>
<Text className='text-[10px] text-gray-400'>:</Text> <Text className='text-xs text-gray-400'>:</Text>
<Text className='text-xs text-gray-500'>{formatTime(user.createTime)}</Text> <Text className='text-xs text-gray-500'>{formatTime(user.createTime)}</Text>
</View> </View>
{user.balance !== undefined && Number(user.balance) > 0 && ( {user.balance !== undefined && Number(user.balance) > 0 && (
<View className='flex items-center gap-1'> <View className='flex items-center gap-1'>
<Text className='text-[10px] text-gray-400'>:</Text> <Text className='text-xs text-gray-400'>:</Text>
<Text className='text-xs text-orange-500'>¥{user.balance}</Text> <Text className='text-xs text-orange-500'>¥{user.balance}</Text>
</View> </View>
)} )}
@@ -238,7 +238,7 @@ export default function StoreUsersPage() {
<View className='min-h-full bg-gray-50'> <View className='min-h-full bg-gray-50'>
{/* 搜索栏 */} {/* 搜索栏 */}
<View className='bg-white px-3 py-2 flex items-center gap-2'> <View className='bg-white px-3 py-2 flex items-center gap-2'>
<View className='flex-1 flex items-center bg-gray-100 rounded-lg px-3 py-1.5'> <View className='flex-1 flex items-center bg-gray-100 rounded-lg px-3 py-1'>
<Input <Input
className='flex-1 text-sm' className='flex-1 text-sm'
placeholder='搜索昵称 / 手机号' placeholder='搜索昵称 / 手机号'
@@ -255,7 +255,7 @@ export default function StoreUsersPage() {
) : null} ) : null}
</View> </View>
<View <View
className='px-3 py-1.5 rounded-lg bg-blue-500' className='px-3 py-1 rounded-lg bg-blue-500'
onClick={handleSearch} onClick={handleSearch}
> >
<Text className='text-sm text-white'></Text> <Text className='text-sm text-white'></Text>
@@ -342,7 +342,7 @@ export default function StoreUsersPage() {
{detailUser.nickname || detailUser.realName || '未设置昵称'} {detailUser.nickname || detailUser.realName || '未设置昵称'}
</Text> </Text>
<View <View
className='px-2 py-0.5 rounded text-xs' className='px-2 py-1 rounded text-xs'
style={{ style={{
color: detailUser.status === 1 ? '#dc2626' : '#16a34a', color: detailUser.status === 1 ? '#dc2626' : '#16a34a',
background: detailUser.status === 1 ? '#fef2f2' : '#f0fdf4', background: detailUser.status === 1 ? '#fef2f2' : '#f0fdf4',

View File

@@ -281,7 +281,7 @@ const UserPage: React.FC = () => {
<Text className='text-xl mb-1'>{item.icon}</Text> <Text className='text-xl mb-1'>{item.icon}</Text>
<Text className='text-xs text-gray-600'>{item.label}</Text> <Text className='text-xs text-gray-600'>{item.label}</Text>
{/* 数量角标count 为 0 时组件内部不渲染 */} {/* 数量角标count 为 0 时组件内部不渲染 */}
<Badge count={item.count} className='absolute -top-1 -right-1' /> {item.tabIndex != 4 && <Badge count={item.count} className='absolute -top-1 -right-1' />}
</View> </View>
))} ))}
</View> </View>