feat(store-center): 在门店中心添加扫码登录PC后台功能
- 门店中心右上角新增扫码登录按钮,使用base64 SVG图标兼容小程序Image组件渲染 - 扫码后通过解析二维码内容调用confirmWechatQRLogin接口完成PC端登录确认 - 支持扫码取消静默处理,扫码失败则弹窗提示错误信息 - 优化门店中心页面布局,调整扫码按钮绝对定位及样式 - 优化了用户管理页面的小型样式,包括按钮和文本间距调整 - 调整用户页面部分Badge显示逻辑,排除tabIndex为4的项不显示角标
This commit is contained in:
@@ -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')静默处理
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user