feat(user): 新增收货地址管理及售后申请页面

- 新增地址类型定义,增强前端地址数据结构
- 新增地址编辑页面,支持地址智能识别和定位选点功能
- 地址编辑支持省市区选择及默认地址设置
- 新增地址列表页面,支持地址展示、删除、编辑和选择功能
- 实现售后申请页面,支持选择售后类型和退款原因
- 售后申请支持商品选择、退款金额计算和凭证上传
- 新增售后详情页面,支持售后状态展示及申请取消
- 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
2026-07-01 12:11:56 +08:00
parent bf6ed504cc
commit 1fa58040f3
636 changed files with 58878 additions and 716 deletions

View File

@@ -36,7 +36,7 @@ const helpData: HelpCategory[] = [
title: '支付与退款',
icon: '💰',
questions: [
{ id: 201, title: '支持哪些支付方式?', content: '我们支持微信支付、支付宝支付(如有)、货到付款、积分抵扣等多种支付方式。' },
{ id: 201, title: '支持哪些支付方式?', content: '我们支持微信支付、支付宝支付(如有)、余额支付、积分抵扣等多种支付方式。' },
{ id: 202, title: '如何申请退款?', content: '在订单详情页点击"申请退款"按钮,填写退款原因和说明,提交后等待商家审核。' },
{ id: 203, title: '退款多久到账?', content: '退款审核通过后原路退回您的支付账户。微信支付一般1-3个工作日到账余额支付即时到账。' },
]

View File

@@ -29,10 +29,10 @@ const IndexPage: React.FC = () => {
// 功能入口8个
const featureEntries = [
{ icon: '🎁', label: '全部商品', url: '/pages/shop/index' },
{ icon: '🎯', label: '限时秒杀', url: '/pages/shop/seckill-list/index' },
{ icon: '🤝', label: '收货地址', url: '/pages/user/address-list' },
{ icon: '🎫', label: '领券中心', url: '/pages/index/coupon-center/index' },
{ icon: '⭐', label: '我的收藏', url: '/pages/user/favorite-list/index' },
{ icon: '👑', label: '会员中心', url: '/pages/user/member/index' },
{ icon: '👑', label: '关于我们', url: '/pages/user/about/index' },
{ icon: '💰', label: '积分商城', url: '/pages/points/index' },
{ icon: '🏪', label: '门店信息', url: '/pages/store/list/index' },
{ icon: '❓', label: '帮助中心', url: '/pages/user/help-center/index' },
@@ -86,7 +86,7 @@ const IndexPage: React.FC = () => {
}
// tabBar 页面列表
const tabBarPages = ['/pages/index/index', '/pages/shop/index', '/pages/user/user']
const tabBarPages = ['/pages/index/index', '/pages/shop/index', '/pages/order/list', '/pages/user/user']
const handleFeatureClick = (url: string) => {
if (tabBarPages.includes(url)) {
@@ -231,7 +231,7 @@ const IndexPage: React.FC = () => {
<Image
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }}
src={item.image}
mode='aspectFit'
mode='aspectFill'
/>
) : (
<View style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} className='bg-gray-100 flex items-center justify-center'>
@@ -239,20 +239,20 @@ const IndexPage: React.FC = () => {
</View>
)}
</View>
<View className='p-2'>
<Text className='text-sm text-gray-700 truncate block'>{item.goodsName || item.name}</Text>
<View className='flex items-center mt-1'>
<Price
price={item.price || '0'}
original={item.salePrice && item.salePrice !== item.price ? item.salePrice : undefined}
size='small'
loginMask
/>
<View className='p-2'>
<Text className='text-sm text-gray-700 truncate block'>{item.goodsName || item.name}</Text>
<View className='flex items-center mt-1'>
<Price
price={item.price || '0'}
original={item.salePrice && item.salePrice !== item.price ? item.salePrice : undefined}
size='small'
loginMask
/>
</View>
{item.sales !== undefined && item.sales > 0 && (
<Text className='text-xs text-gray-400 mt-1'> {item.sales}</Text>
)}
</View>
{item.sales !== undefined && item.sales > 0 && (
<Text className='text-xs text-gray-400 mt-1'> {item.sales}</Text>
)}
</View>
</View>
))
) : (

View File

@@ -31,7 +31,7 @@ const SearchPage: React.FC = () => {
if (!q.trim()) return
setLoading(true)
try {
const params: ShopGoodsParam = { keywords: q, status: 0, page: 1, limit: 20 }
const params: ShopGoodsParam = { keywords: q, isShow: 1, page: 1, limit: 20 }
const res = await pageShopGoods(params)
setList(res?.list || [])
// 保存搜索历史

View File

@@ -112,12 +112,12 @@ const StoreListPage: React.FC = () => {
>
<Text className='text-xs text-green-500'>📍 </Text>
</View>
{/*<View*/}
{/* className='flex-1 py-2 bg-orange-50 rounded-lg text-center'*/}
{/* onClick={() => handleBooking(store.id!)}*/}
{/*>*/}
{/* <Text className='text-xs text-orange-500'>📅 立即预约</Text>*/}
{/*</View>*/}
<View
className='flex-1 py-2 bg-orange-50 rounded-lg text-center'
onClick={() => handleBooking(store.id!)}
>
<Text className='text-xs text-orange-500'>📅 </Text>
</View>
</View>
</View>
))}

View File

@@ -30,7 +30,12 @@ interface TabState {
}
const OrderListPage: React.FC = () => {
const { tab: initTab } = Taro.getCurrentInstance().router?.params || {}
const initTab = Taro.getCurrentInstance().router?.params?.tab
?? Taro.getStorageSync('order_tab')
// 读取后清除,避免影响下次进入
if (Taro.getStorageSync('order_tab') !== '') {
Taro.removeStorageSync('order_tab')
}
const [tabIndex, setTabIndex] = useState(Number(initTab) || 0)
// 每个 tab 独立维护状态

View File

@@ -1,3 +1,3 @@
export default {
navigationBarTitleText: '分享好友',
navigationBarTitleText: '分销推广',
}

View File

@@ -91,26 +91,14 @@ const SeckillDetailPage: React.FC = () => {
setCountdown(`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`)
}
const handleBuyClick = () => {
const handleBuyNow = () => {
if (!seckill) return
if (seckill.status === 0) {
Taro.showToast({ title: '活动尚未开始,请耐心等待', icon: 'none' })
return
}
if (seckill.status === 2 || new Date(seckill.endTime).getTime() <= Date.now()) {
Taro.showToast({ title: '活动已结束,下次再来', icon: 'none' })
return
}
if (seckill.stock <= 0) {
Taro.showToast({ title: '已抢光,下次再来', icon: 'none' })
Taro.showToast({ title: '已抢光', icon: 'none' })
return
}
if (submitting) return
// 先拉取商品规格(如果没有的话)
if (!product && seckill.goodsId) {
Taro.showLoading({ title: '加载规格...' })
@@ -267,11 +255,8 @@ const SeckillDetailPage: React.FC = () => {
<View className="bg-white border-t border-gray-100 px-4 py-3">
<View
className="w-full py-3 rounded-full text-white text-center text-base font-medium"
style={{
backgroundColor: isActive && !submitting ? '#ef4444' : '#d1d5db',
opacity: submitting ? 0.7 : 1,
}}
onClick={handleBuyClick}
style={{ backgroundColor: isActive ? '#ef4444' : '#d1d5db' }}
onClick={isActive && !submitting ? handleBuyNow : undefined}
>
<Text>
{submitting ? '抢购中...' : isActive ? (seckill.stock > 0 ? '立即抢购' : '已抢光') : seckill.status === 0 ? '即将开始' : '已结束'}

View File

@@ -8,7 +8,7 @@ import { useScrollHeight } from '@/hooks/useScrollHeight'
import { getMyAvailableCoupons } from '@/api/shop/shopUserCoupon'
import AddressCard from '@/components/business/AddressCard'
import CouponCard from '@/components/business/CouponCard'
import { createOrder, type WxPayResult } from '@/api/shop/shopOrder'
import { createOrder } from '@/api/shop/shopOrder'
import type { ShopOrder } from '@/api/shop/shopOrder/model'
import { listShopGoods } from '@/api/shop/shopGoods'
import type { ShopGoods } from '@/api/shop/shopGoods/model'
@@ -124,8 +124,7 @@ const CheckoutPage: React.FC = () => {
const [rushBuyProducts, setRushBuyProducts] = useState<ShopGoods[]>([])
// 支付方式相关状态
// payType: 1=微信支付, 0=货到付款(默认)
const [payType, setPayType] = useState<number>(0)
const [payType, setPayType] = useState<number>(0) // 0: 货到付款
// 从本地存储获取立即购买的数据
const buyNowItems = useMemo(() => {
@@ -217,7 +216,7 @@ const CheckoutPage: React.FC = () => {
}
}
// 加载凑单推荐商品
// 选择地址
const handleSelectAddress = () => {
Taro.navigateTo({
url: '/pages/user/address-list?from=checkout',
@@ -271,21 +270,18 @@ const CheckoutPage: React.FC = () => {
const res = await createOrder(orderParams)
// 清理购物车数据(无论哪种支付方式都需清理)
// 货到付款 - 只需创建订单,无需调用支付接口
console.log('订单创建成功', res)
// 清理数据
if (fromBuyNow) {
Taro.removeStorageSync('buy_now')
} else {
await removeSelected()
}
// 根据支付方式处理
if (payType === 1) {
// 微信支付 - res 是 WxPayResult
await handleWxPay(res as WxPayResult)
}
// 货到付款(payType=0)无需在线支付,直接下单成功
Taro.showToast({ title: payType === 0 ? '下单成功' : '支付成功', icon: 'success' })
// 显示成功提示
Taro.showToast({ title: '订单已提交', icon: 'success' })
setTimeout(() => {
Taro.switchTab({ url: '/pages/order/list' })
}, 1500)
@@ -296,25 +292,6 @@ const CheckoutPage: React.FC = () => {
}
}
// 微信支付
const handleWxPay = async (payData: WxPayResult & { paid?: string }) => {
// 后端返回已支付(回调丢失自动修复场景)
if (payData.paid === 'true') {
return // 直接视为支付成功
}
return new Promise<void>((resolve, reject) => {
Taro.requestPayment({
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.paySign,
success: () => resolve(),
fail: (err) => reject(new Error('支付取消'))
})
})
}
// 安全的数字格式化
const formatPrice = (price: number | string): string => {
const num = typeof price === 'string' ? parseFloat(price) : price
@@ -462,49 +439,23 @@ const CheckoutPage: React.FC = () => {
<View className='bg-white rounded-lg mt-3 p-3'>
<Text className='text-sm font-medium text-gray-800 mb-3 block'></Text>
{/* 微信支付 */}
{/*<View*/}
{/* className='flex items-center py-3 px-2 rounded-lg mb-2'*/}
{/* style={{ backgroundColor: payType === 1 ? '#f0fdf4' : '#f9fafb' }}*/}
{/* onClick={() => setPayType(1)}*/}
{/*>*/}
{/* <View className='w-8 h-8 rounded-full bg-green-50 flex items-center justify-center mr-3'>*/}
{/* <Text className='text-sm'>微</Text>*/}
{/* </View>*/}
{/* <View className='flex-1'>*/}
{/* <Text className='text-sm text-gray-800 block'>微信支付</Text>*/}
{/* <Text className='text-xs text-gray-400 block'>推荐使用</Text>*/}
{/* </View>*/}
{/* <View*/}
{/* className='w-5 h-5 rounded-full border-2 flex items-center justify-center'*/}
{/* style={{ borderColor: payType === 1 ? '#22c55e' : '#d1d5db' }}*/}
{/* >*/}
{/* {payType === 1 && (*/}
{/* <View className='w-2 h-2 rounded-full' style={{ backgroundColor: '#22c55e' }} />*/}
{/* )}*/}
{/* </View>*/}
{/*</View>*/}
{/* 货到付款 */}
<View
className='flex items-center py-3 px-2 rounded-lg'
style={{ backgroundColor: payType === 0 ? '#f0fdf4' : '#f9fafb' }}
onClick={() => setPayType(0)}
style={{ backgroundColor: '#f0fdf4' }}
>
<View className='w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center mr-3'>
<Text className='text-sm'></Text>
</View>
<View className='flex-1'>
<Text className='text-sm text-gray-800 block'></Text>
<Text className='text-xs text-gray-400 block'></Text>
<Text className='text-xs text-gray-400 block'></Text>
</View>
<View
className='w-5 h-5 rounded-full border-2 flex items-center justify-center'
style={{ borderColor: payType === 0 ? '#22c55e' : '#d1d5db' }}
style={{ borderColor: '#22c55e' }}
>
{payType === 0 && (
<View className='w-2 h-2 rounded-full' style={{ backgroundColor: '#22c55e' }} />
)}
<View className='w-2 h-2 rounded-full' style={{ backgroundColor: '#22c55e' }} />
</View>
</View>
</View>

View File

@@ -7,7 +7,6 @@ import type { ShopGroupBuy, ShopGroupBuyRecord } from '@/api/shop/shopGroupBuy/m
import type { ShopGoods } from '@/api/shop/shopGoods/model'
import type { ShopGoodsSku } from '@/api/shop/shopGoodsSku/model'
import SkuSelector from '@/components/business/SkuSelector'
import Price from '@/components/common/Price'
definePageConfig({
navigationBarTitleText: '拼团详情',
@@ -193,12 +192,12 @@ const GroupBuyDetailPage: React.FC = () => {
{groupBuy.goodsName || groupBuy.product?.name}
</Text>
<View className="flex items-baseline gap-2">
<Price
price={groupBuy.groupPrice}
original={groupBuy.product?.price || 0}
size='large'
loginMask
/>
<Text className="text-2xl font-bold text-red-500">
¥{groupBuy.groupPrice}
</Text>
<Text className="text-xs text-gray-400 line-through">
¥{groupBuy.product?.price || 0}
</Text>
</View>
<View className="flex items-center gap-2 mt-1">
{isActive && remaining > 0 && (

View File

@@ -4,7 +4,6 @@ import Taro from '@tarojs/taro'
import { pageShopGroupBuy } from '@/api/shop/shopGroupBuy'
import type { ShopGroupBuy } from '@/api/shop/shopGroupBuy/model'
import EmptyState from '@/components/common/EmptyState'
import Price from '@/components/common/Price'
definePageConfig({
navigationBarTitleText: '拼团活动',
@@ -92,12 +91,12 @@ const GroupBuyListPage: React.FC = () => {
<View className='flex items-center justify-between mt-1'>
<View className='flex items-baseline gap-1'>
<Price
price={item.groupPrice}
original={item.product?.price || 0}
size='small'
loginMask
/>
<Text className='text-lg font-bold text-red-500'>
{'\u00A5'}{item.groupPrice}
</Text>
<Text className='text-xs text-gray-400 line-through'>
{'\u00A5'}{item.product?.price || 0}
</Text>
</View>
<View
className='px-3 py-1 rounded-full text-white text-xs'

View File

@@ -85,15 +85,8 @@ const ProductDetailPage: React.FC = () => {
setIsFavorite(true)
Taro.showToast({ title: '收藏成功', icon: 'success' })
}
} catch (err: any) {
// 接口报错时仍更新UI状态避免状态不一致
console.error('[ProductDetail] 收藏操作失败:', err?.message || err)
if (isFavorite) {
setIsFavorite(false)
} else {
setIsFavorite(true)
}
// 不再额外弹toasthandleError已经弹了后端返回的错误信息
} catch (e: any) {
Taro.showToast({ title: e?.message || '操作失败', icon: 'none' })
}
}
@@ -165,12 +158,10 @@ const ProductDetailPage: React.FC = () => {
}
const handleGoCart = () => {
// 购物车不是 tabBar 页面,使用 navigateTo路径与 app.config.ts 中注册的 'pages/shop/cart' 对应
Taro.navigateTo({ url: '/pages/shop/cart' })
}
const handleContactService = () => {
// 直接跳转到在线客服页面(已实现完整功能:微信客服按钮 + 历史消息 + 热线 + 微信留言)
Taro.navigateTo({ url: '/pages/user/customer-service/index' })
}
@@ -214,7 +205,6 @@ const ProductDetailPage: React.FC = () => {
'<img src="$2" mode="widthFix" style="max-width:100%;display:block;" />'
)
// 兜底:如果内容里直接包含 http(s) 图片链接(非 img 标签包裹的),也尝试转成图片
// 匹配独立的 https://xxx.jpg/png/gif 链接
if (html.includes('http') && !html.includes('<img')) {
html = html.replace(
/(https?:\/\/[^\s\)]+\.(jpg|jpeg|png|gif|webp)(\?[^\s\)]*)?)/gi,
@@ -229,44 +219,44 @@ const ProductDetailPage: React.FC = () => {
<ScrollView scrollY style={{ height: scrollHeight }}>
{/* 图片轮播 */}
<Swiper
className='w-full'
style={{ height: `${swiperHeight}px` }}
indicatorDots
indicatorColor='#e5e7eb'
indicatorActiveColor='#0e932e'
autoplay
circular
>
{images.map((img, idx) => (
<SwiperItem key={idx}>
<Image
className='w-full'
style={{ display: 'block' }}
src={img}
mode='widthFix'
onLoad={(e: any) => {
// 根据第一张图片的实际宽高比动态设置 Swiper 高度
if (idx === 0) {
const { width, height } = e.detail
if (width > 0) {
const screenWidth = Taro.getSystemInfoSync().windowWidth
setSwiperHeight(Math.round((height / width) * screenWidth))
}
}
}}
onClick={() => Taro.previewImage({ current: img, urls: images })}
/>
</SwiperItem>
))}
{images.length === 0 && (
<SwiperItem>
<View className='w-full h-full bg-gray-100 flex items-center justify-center'>
<Text className='text-gray-300 text-sm'></Text>
</View>
</SwiperItem>
)}
</Swiper>
<Swiper
className='w-full'
style={{ height: `${swiperHeight}px` }}
indicatorDots
indicatorColor='#e5e7eb'
indicatorActiveColor='#0e932e'
autoplay
circular
>
{images.map((img, idx) => (
<SwiperItem key={idx}>
<Image
className='w-full'
style={{ display: 'block' }}
src={img}
mode='widthFix'
onLoad={(e: any) => {
// 根据第一张图片的实际宽高比动态设置 Swiper 高度
if (idx === 0) {
const { width, height } = e.detail
if (width > 0) {
const screenWidth = Taro.getSystemInfoSync().windowWidth
setSwiperHeight(Math.round((height / width) * screenWidth))
}
}
}}
onClick={() => Taro.previewImage({ current: img, urls: images })}
/>
</SwiperItem>
))}
{images.length === 0 && (
<SwiperItem>
<View className='w-full h-full bg-gray-100 flex items-center justify-center'>
<Text className='text-gray-300 text-sm'></Text>
</View>
</SwiperItem>
)}
</Swiper>
{/* 价格区域 */}
<View className='bg-white p-4'>

View File

@@ -7,7 +7,6 @@ import type { ShopSeckill } from '@/api/shop/shopSeckill/model'
import type { ShopGoods } from '@/api/shop/shopGoods/model'
import type { ShopGoodsSku } from '@/api/shop/shopGoodsSku/model'
import SkuSelector from '@/components/business/SkuSelector'
import Price from '@/components/common/Price'
definePageConfig({
navigationBarTitleText: '秒杀详情',
@@ -92,26 +91,14 @@ const SeckillDetailPage: React.FC = () => {
setCountdown(`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`)
}
const handleBuyClick = () => {
const handleBuyNow = () => {
if (!seckill) return
if (seckill.status === 0) {
Taro.showToast({ title: '活动尚未开始,请耐心等待', icon: 'none' })
return
}
if (seckill.status === 2 || new Date(seckill.endTime).getTime() <= Date.now()) {
Taro.showToast({ title: '活动已结束,下次再来', icon: 'none' })
return
}
if (seckill.stock <= 0) {
Taro.showToast({ title: '已抢光,下次再来', icon: 'none' })
Taro.showToast({ title: '已抢光', icon: 'none' })
return
}
if (submitting) return
// 先拉取商品规格(如果没有的话)
if (!product && seckill.goodsId) {
Taro.showLoading({ title: '加载规格...' })
@@ -200,13 +187,12 @@ const SeckillDetailPage: React.FC = () => {
{/* 价格和倒计时 */}
<View className="px-4 py-4" style={{ background: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)' }}>
<View className="flex items-baseline gap-2">
<Price
price={seckill.seckillPrice}
original={seckill.product?.price || 0}
size='large'
color='#ffffff'
loginMask
/>
<Text className="text-3xl font-bold text-white">
¥{seckill.seckillPrice}
</Text>
<Text className="text-sm text-red-200 line-through">
¥{seckill.product?.price || 0}
</Text>
</View>
<View className="flex items-center justify-between mt-3">
<View className="bg-white rounded px-2 py-1">
@@ -269,11 +255,8 @@ const SeckillDetailPage: React.FC = () => {
<View className="bg-white border-t border-gray-100 px-4 py-3">
<View
className="w-full py-3 rounded-full text-white text-center text-base font-medium"
style={{
backgroundColor: isActive && !submitting ? '#ef4444' : '#d1d5db',
opacity: submitting ? 0.7 : 1,
}}
onClick={handleBuyClick}
style={{ backgroundColor: isActive ? '#ef4444' : '#d1d5db' }}
onClick={isActive && !submitting ? handleBuyNow : undefined}
>
<Text>
{submitting ? '抢购中...' : isActive ? (seckill.stock > 0 ? '立即抢购' : '已抢光') : seckill.status === 0 ? '即将开始' : '已结束'}

View File

@@ -4,7 +4,6 @@ import Taro from '@tarojs/taro'
import { pageShopSeckill } from '@/api/shop/shopSeckill'
import type { ShopSeckill } from '@/api/shop/shopSeckill/model'
import EmptyState from '@/components/common/EmptyState'
import Price from '@/components/common/Price'
definePageConfig({
navigationBarTitleText: '限时秒杀',
@@ -122,12 +121,12 @@ const SeckillListPage: React.FC = () => {
<View className='flex items-center justify-between mt-1'>
<View className='flex items-baseline gap-1'>
<Price
price={item.seckillPrice}
original={item.product?.price || 0}
size='small'
loginMask
/>
<Text className='text-lg font-bold text-red-500'>
{'\u00A5'}{item.seckillPrice}
</Text>
<Text className='text-xs text-gray-400 line-through'>
{'\u00A5'}{item.product?.price || 0}
</Text>
</View>
<View
className='px-3 py-1 rounded-full text-white text-xs'

View File

@@ -300,7 +300,7 @@ const StoreListPage: React.FC = () => {
) : (
<View className='flex items-center mb-1'>
<Text className='text-xs text-gray-400 mr-1 shrink-0'>🕐</Text>
<Text className='text-xs text-gray-400'></Text>
<Text className='text-xs text-gray-400'>9:00 ~ 18:00</Text>
</View>
)}

View File

@@ -0,0 +1,3 @@
export default {
navigationBarTitleText: '关于我们',
}

View File

@@ -0,0 +1,85 @@
import React from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
definePageConfig({
navigationBarTitleText: '关于我们',
})
const AboutPage: React.FC = () => {
return (
<ScrollView scrollY className='min-h-screen bg-gray-50'>
{/* 头部品牌区 */}
<View className='bg-white py-10 flex flex-col items-center'>
<View className='w-20 h-20 rounded-2xl bg-gradient-to-br from-blue-400 to-blue-600 flex items-center justify-center shadow-lg mb-4'>
<Text className='text-white text-4xl font-bold'></Text>
</View>
<Text className='text-xl font-bold text-gray-800'></Text>
<Text className='text-sm text-gray-400 mt-1'> v1.0.0</Text>
</View>
{/* 公司简介 */}
<View className='bg-white mx-3 mt-3 rounded-xl p-4' style={{ boxShadow: '0 2px 8px rgba(0,0,0,0.04)' }}>
<Text className='text-base font-medium text-gray-800 mb-3 block'></Text>
<Text className='text-sm text-gray-600 leading-relaxed'>
</Text>
<Text className='text-sm text-gray-600 leading-relaxed mt-2'>
"诚信经营、服务至上"
便
</Text>
</View>
{/* 核心业务 */}
<View className='bg-white mx-3 mt-3 rounded-xl p-4' style={{ boxShadow: '0 2px 8px rgba(0,0,0,0.04)' }}>
<Text className='text-base font-medium text-gray-800 mb-3 block'></Text>
{[
{ icon: '📺', title: '家电零售', desc: '电视、冰箱、洗衣机等全品类家电销售' },
{ icon: '❄️', title: '空调服务', desc: '空调销售、安装、加氟、清洗及维修' },
{ icon: '🍳', title: '厨房电器', desc: '油烟机、燃气灶、热水器等厨卫电器' },
{ icon: '🛠️', title: '安装维修', desc: '专业师傅上门安装、检测及故障维修' },
{ icon: '🔧', title: '保养维护', desc: '定期清洗保养,延长家电使用寿命' },
{ icon: '✅', title: '品质保障', desc: '正品货源、透明报价、售后无忧' },
].map((item, idx) => (
<View
key={idx}
className={`flex items-start gap-3 py-3 ${idx < 5 ? 'border-b border-gray-50' : ''}`}
>
<Text className='text-2xl mt-0.5'>{item.icon}</Text>
<View className='flex-1'>
<Text className='text-sm font-medium text-gray-700'>{item.title}</Text>
<Text className='text-xs text-gray-400 mt-0.5'>{item.desc}</Text>
</View>
</View>
))}
</View>
{/* 联系我们 */}
<View className='bg-white mx-3 mt-3 rounded-xl p-4' style={{ boxShadow: '0 2px 8px rgba(0,0,0,0.04)' }}>
<Text className='text-base font-medium text-gray-800 mb-3 block'></Text>
<View className='flex flex-col gap-2'>
{[
{ label: '经营部名称', value: '玉林市玉州区鑫龙家电经营部' },
{ label: '经营地址', value: '大新里南718号' },
{ label: '联系电话', value: '13260472256' },
{ label: '营业时间', value: '9:00 ~ 18:00' },
].map((item, idx) => (
<View key={idx} className='flex items-start justify-between'>
<Text className='text-sm text-gray-500 flex-shrink-0'>{item.label}</Text>
<Text className='text-sm text-gray-700 text-right ml-2'>{item.value}</Text>
</View>
))}
</View>
</View>
{/* 底部版权 */}
<View className='flex flex-col items-center py-8'>
<Text className='text-xs text-gray-400'>Copyright © 2024 </Text>
<Text className='text-xs text-gray-400 mt-1'>All Rights Reserved</Text>
</View>
</ScrollView>
)
}
export default AboutPage

View File

@@ -5,7 +5,6 @@ import { listShopGoodsFavorite } from '@/api/shop/shopGoodsFavorite'
import type { ShopGoodsFavorite } from '@/api/shop/shopGoodsFavorite/model'
import EmptyState from '@/components/common/EmptyState'
import LoadMore from '@/components/common/LoadMore'
import Price from '@/components/common/Price'
definePageConfig({
navigationBarTitleText: '我的收藏',
@@ -77,9 +76,9 @@ const FavoriteListPage: React.FC = () => {
<Text className='text-sm text-gray-800 line-clamp-2 block'>
{item.goodsName}
</Text>
<View className='mt-1'>
<Price price={item.salePrice || '0'} size='small' loginMask />
</View>
<Text className='text-red-500 text-sm font-medium mt-1 block'>
¥{item.salePrice || '0'}
</Text>
</View>
</View>
))}

View File

@@ -36,7 +36,7 @@ const helpData: HelpCategory[] = [
title: '支付与退款',
icon: '💰',
questions: [
{ id: 201, title: '支持哪些支付方式?', content: '我们支持微信支付、支付宝支付(如有)、货到付款、积分抵扣等多种支付方式。' },
{ id: 201, title: '支持哪些支付方式?', content: '我们支持微信支付、支付宝支付(如有)、余额支付、积分抵扣等多种支付方式。' },
{ id: 202, title: '如何申请退款?', content: '在订单详情页点击"申请退款"按钮,填写退款原因和说明,提交后等待商家审核。' },
{ id: 203, title: '退款多久到账?', content: '退款审核通过后原路退回您的支付账户。微信支付一般1-3个工作日到账余额支付即时到账。' },
]

View File

@@ -7,6 +7,17 @@ import MemberBadge from '@/components/business/MemberBadge'
const UserPage: React.FC = () => {
const { user, isLoggedIn } = useUser()
// TabBar 页面列表
const tabBarPages = ['/pages/index/index', '/pages/shop/index', '/pages/order/list', '/pages/user/user']
const handleNavigate = (url: string) => {
if (tabBarPages.includes(url)) {
Taro.switchTab({ url })
} else {
Taro.navigateTo({ url })
}
}
const menuItems = [
{ icon: '📦', label: '我的订单', url: '/pages/order/list' },
{ icon: '📅', label: '预约订单', url: '/pages/booking/list' },
@@ -30,68 +41,56 @@ const UserPage: React.FC = () => {
<ScrollView scrollY className='flex-1'>
{/* 顶部渐变背景区域 */}
<View className='relative'>
{/* 渐变背景 — 绿色系 */}
{/* 渐变背景 */}
<View
className='absolute inset-0'
style={{
background: 'linear-gradient(135deg, #0d9488 0%, #059669 50%, #10b981 100%)',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)',
}}
/>
{/* 装饰圆形 */}
<View
className='absolute'
style={{
width: '240px',
height: '240px',
borderRadius: '120px',
width: '200px',
height: '200px',
borderRadius: '100px',
background: 'rgba(255, 255, 255, 0.1)',
top: '-80px',
right: '-60px',
}}
/>
<View
className='absolute'
style={{
width: '150px',
height: '150px',
borderRadius: '75px',
background: 'rgba(255, 255, 255, 0.08)',
top: '-100px',
right: '-80px',
}}
/>
<View
className='absolute'
style={{
width: '180px',
height: '180px',
borderRadius: '90px',
background: 'rgba(255, 255, 255, 0.06)',
top: '-60px',
left: '-60px',
}}
/>
<View
className='absolute'
style={{
width: '120px',
height: '120px',
borderRadius: '60px',
background: 'rgba(255, 255, 255, 0.05)',
bottom: '20px',
right: '-30px',
top: '-40px',
left: '-40px',
}}
/>
{/* 用户信息卡片 */}
<View className='relative mx-3 mt-3 p-4 rounded-2xl' style={{
background: 'rgba(255, 255, 255, 0.15)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.2)',
background: 'rgba(255, 255, 255, 0.95)',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
}}>
<View className='flex items-center gap-4' onClick={handleLogin}>
{/* 头像区域 */}
<View className='relative'>
{isLoggedIn && user?.avatar ? (
<Image
className='w-16 h-16 rounded-full border-3 border-white/30'
style={{ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.2)' }}
className='w-16 h-16 rounded-full border-4 border-white'
style={{ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)' }}
src={user.avatar}
mode='aspectFill'
/>
) : (
<View
className='w-16 h-16 rounded-full border-3 border-white/30 flex items-center justify-center'
style={{ background: 'rgba(255, 255, 255, 0.25)', boxShadow: '0 4px 16px rgba(0, 0, 0, 0.2)' }}
className='w-16 h-16 rounded-full border-4 border-white flex items-center justify-center'
style={{ background: 'linear-gradient(135deg, #667eea, #764ba2)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)' }}
>
<Text className='text-2xl text-white'>👤</Text>
</View>
@@ -100,7 +99,7 @@ const UserPage: React.FC = () => {
{isLoggedIn && user?.memberLevelName && (
<View
className='absolute -bottom-1 -right-1 px-1 py-1 rounded text-xs text-white'
style={{ background: 'linear-gradient(135deg, #fbbf24, #f59e0b)' }}
style={{ background: 'linear-gradient(135deg, #f093fb, #f5576c)' }}
>
VIP
</View>
@@ -110,7 +109,7 @@ const UserPage: React.FC = () => {
{/* 用户信息 */}
<View className='flex-1'>
<View className='flex items-center gap-2'>
<Text className='text-lg font-bold text-white'>
<Text className='text-lg font-bold text-gray-800'>
{isLoggedIn ? (user?.nickname || user?.phone || '用户') : '点击登录'}
</Text>
{isLoggedIn && user?.memberLevelName && (
@@ -118,59 +117,50 @@ const UserPage: React.FC = () => {
)}
</View>
{isLoggedIn ? (
<Text className='text-xs text-white/60 mt-1'>ID: {(user as any)?.id || '暂无'}</Text>
<Text className='text-xs text-gray-400 mt-1'>ID: {(user as any)?.id || '暂无'}</Text>
) : (
<Text className='text-xs text-white/60 mt-1'></Text>
<Text className='text-xs text-gray-400 mt-1'></Text>
)}
</View>
{/* 箭头 */}
<View className='w-6 h-6 rounded-full bg-white/20 flex items-center justify-center'>
<Text className='text-white/80 text-xs'>{'>'}</Text>
<View className='w-6 h-6 rounded-full bg-gray-100 flex items-center justify-center'>
<Text className='text-gray-400 text-xs'>{'>'}</Text>
</View>
</View>
{/* 数据概览 — 4列白色文字 */}
{/* 数据概览 */}
{isLoggedIn && (
<View
className='grid grid-cols-4 gap-2 mt-4 pt-4'
style={{ borderTop: '1px solid rgba(255, 255, 255, 0.2)' }}
className='grid grid-cols-3 gap-2 mt-4 pt-4 rounded-xl'
style={{ background: 'rgba(102, 126, 234, 0.05)' }}
>
<View
className='text-center py-2'
onClick={() => Taro.navigateTo({ url: '/pages/user/wallet' })}
className='text-center py-2 rounded-lg'
onClick={() => Taro.switchTab({ url: '/pages/points/index' })}
>
<Text className='text-xl font-bold text-white'>
¥{((user as any)?.balance || '0.00')}
</Text>
<Text className='text-xs text-white/70 mt-1 block'></Text>
</View>
<View
className='text-center py-2'
onClick={() => Taro.navigateTo({ url: '/pages/points/index' })}
>
<Text className='text-xl font-bold text-white'>
<Text className='text-xl font-bold text-transparent' style={{ background: 'linear-gradient(135deg, #667eea, #764ba2)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
{(user as any)?.points || 0}
</Text>
<Text className='text-xs text-white/70 mt-1 block'></Text>
<Text className='text-xs text-gray-500 mt-1'></Text>
</View>
<View
className='text-center py-2'
className='text-center py-2 rounded-lg'
onClick={() => Taro.navigateTo({ url: '/pages/user/coupon-list' })}
>
<Text className='text-xl font-bold text-white'>
<Text className='text-xl font-bold text-transparent' style={{ background: 'linear-gradient(135deg, #f093fb, #f5576c)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
0
</Text>
<Text className='text-xs text-white/70 mt-1 block'></Text>
<Text className='text-xs text-gray-500 mt-1'></Text>
</View>
<View
className='text-center py-2'
onClick={() => Taro.navigateTo({ url: '/pages/order/list' })}
className='text-center py-2 rounded-lg'
onClick={() => Taro.switchTab({ url: '/pages/order/list' })}
>
<Text className='text-xl font-bold text-white'>
<Text className='text-xl font-bold text-transparent' style={{ background: 'linear-gradient(135deg, #4facfe, #00f2fe)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
0
</Text>
<Text className='text-xs text-white/70 mt-1 block'></Text>
<Text className='text-xs text-gray-500 mt-1'></Text>
</View>
</View>
)}
@@ -182,7 +172,7 @@ const UserPage: React.FC = () => {
<View className='bg-white rounded-xl mx-3 mt-3 p-4' style={{ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)' }}>
<View className='flex justify-between items-center mb-3'>
<Text className='text-base font-medium text-gray-800'></Text>
<Text className='text-xs text-gray-400' onClick={() => Taro.navigateTo({ url: '/pages/order/list' })}>
<Text className='text-xs text-gray-400' onClick={() => Taro.switchTab({ url: '/pages/order/list' })}>
{'>'}
</Text>
</View>
@@ -197,7 +187,7 @@ const UserPage: React.FC = () => {
key={item.status}
className='flex flex-col items-center py-2 rounded-lg'
style={{ background: `${item.color}10` }}
onClick={() => Taro.navigateTo({ url: `/pages/order/list?tab=${item.status}` })}
onClick={() => { Taro.setStorageSync('order_tab', item.status); Taro.switchTab({ url: '/pages/order/list' }) }}
>
<Text className='text-xl mb-1'>{item.icon}</Text>
<Text className='text-xs text-gray-600'>{item.label}</Text>
@@ -213,7 +203,7 @@ const UserPage: React.FC = () => {
className={`flex items-center justify-between px-4 py-3 ${
idx < menuItems.length - 1 ? 'border-b border-gray-50' : ''
}`}
onClick={() => Taro.navigateTo({ url: item.url })}
onClick={() => handleNavigate(item.url)}
>
<View className='flex items-center gap-3'>
<Text className='text-base'>{item.icon}</Text>

View File

@@ -39,7 +39,7 @@ const SettingPage: React.FC = () => {
const menuItems = [
{ label: '清除缓存', action: handleClearCache },
{ label: '关于我们', action: () => Taro.showToast({ title: 'v1.0.0', icon: 'none' }) },
{ label: '关于我们', action: () => Taro.navigateTo({ url: '/pages/user/about/index' }) },
{ label: '用户协议', action: () => Taro.navigateTo({ url: '/passport/agreement' }) },
{ label: '隐私政策', action: () => Taro.navigateTo({ url: '/passport/agreement' }) },
]

View File

@@ -155,7 +155,7 @@ const UserPage: React.FC = () => {
<View className='bg-white rounded-xl mx-3 mt-3 p-4'>
<View className='flex justify-between items-center mb-3'>
<Text className='text-base font-medium text-gray-800'></Text>
<Text className='text-xs text-gray-400' onClick={() => Taro.navigateTo({ url: '/pages/order/list' })}>
<Text className='text-xs text-gray-400' onClick={() => Taro.switchTab({ url: '/pages/order/list' })}>
{'>'}
</Text>
</View>
@@ -169,7 +169,7 @@ const UserPage: React.FC = () => {
<View
key={item.status}
className='flex flex-col items-center py-2 relative'
onClick={() => Taro.navigateTo({ url: `/pages/order/list?tab=${item.status}` })}
onClick={() => { Taro.setStorageSync('order_tab', item.status); Taro.switchTab({ url: '/pages/order/list' }) }}
>
<Text className='text-xl mb-1'>{item.icon}</Text>
<Text className='text-xs text-gray-600'>{item.label}</Text>

View File

@@ -45,10 +45,17 @@ const WalletPage: React.FC = () => {
<View
className='flex-1 py-2 rounded-full text-center'
style={{ backgroundColor: 'rgba(255,255,255,0.2)' }}
onClick={() => Taro.navigateTo({ url: '/pages/user/recharge' })}
onClick={() => Taro.navigateTo({ url: '/pages/user/redeem/index' })}
>
<Text className='text-white text-sm font-medium'></Text>
</View>
{/*<View*/}
{/* className='flex-1 py-2 rounded-full text-center'*/}
{/* style={{ backgroundColor: 'rgba(255,255,255,0.2)' }}*/}
{/* onClick={() => Taro.navigateTo({ url: '/pages/user/withdraw/index' })}*/}
{/*>*/}
{/* <Text className='text-white text-sm font-medium'>提现</Text>*/}
{/*</View>*/}
</View>
</View>
@@ -76,6 +83,13 @@ const WalletPage: React.FC = () => {
<Text className='text-lg mb-1'>🎫</Text>
<Text className='text-xs text-gray-600'></Text>
</View>
{/*<View*/}
{/* className='flex-1 flex flex-col items-center py-2'*/}
{/* onClick={() => Taro.navigateTo({ url: '/pages/user/withdraw/index' })}*/}
{/*>*/}
{/* <Text className='text-lg mb-1'>💰</Text>*/}
{/* <Text className='text-xs text-gray-600'>提现记录</Text>*/}
{/*</View>*/}
</View>
</View>

View File

@@ -1,76 +1,28 @@
.withdraw-page {
display: flex;
flex-direction: column;
min-height: 100vh;
background: #f5f6fa;
padding-bottom: env(safe-area-inset-bottom);
height: 100vh;
background: #f5f5f5;
}
/* ========== 余额卡片 ========== */
.balance-card {
margin: 24rpx;
padding: 40rpx 32rpx 32rpx;
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
border-radius: 20rpx;
color: #fff;
}
.balance-header {
margin-bottom: 16rpx;
}
.balance-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.85);
}
.balance-amount-row {
display: flex;
align-items: baseline;
}
.currency-symbol-lg {
font-size: 40rpx;
font-weight: bold;
color: #fff;
margin-right: 8rpx;
}
.balance-number {
font-size: 72rpx;
font-weight: bold;
color: #fff;
line-height: 1;
}
.balance-tips {
margin-top: 16rpx;
}
.balance-tip-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.7);
}
/* ========== 提现输入卡片 ========== */
.withdraw-card {
margin: 24rpx;
padding: 32rpx;
background: #fff;
border-radius: 20rpx;
border-radius: 16rpx;
}
.card-title {
font-size: 30rpx;
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 28rpx;
margin-bottom: 32rpx;
}
.amount-input-wrap {
display: flex;
align-items: center;
padding: 20rpx 0;
padding: 24rpx 0;
border-bottom: 2rpx solid #eee;
margin-bottom: 16rpx;
}
@@ -89,162 +41,65 @@
color: #333;
}
.all-withdraw {
flex-shrink: 0;
padding: 8rpx 20rpx;
background: #fff3ee;
color: #ff6b35;
.amount-tips {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 24rpx;
color: #999;
}
.all-withdraw {
color: #ff6b35;
font-weight: bold;
border-radius: 24rpx;
border: 2rpx solid #ffd4bc;
}
.fee-tips {
display: flex;
align-items: center;
gap: 8rpx;
margin-top: 16rpx;
padding: 12rpx 16rpx;
font-size: 24rpx;
color: #e67e22;
background: #fef9f5;
border-radius: 8rpx;
margin-top: 12rpx;
font-size: 22rpx;
color: #ff9a56;
}
.fee-free {
color: #27ae60;
background: #f0faf4;
}
.fee-icon {
font-size: 24rpx;
}
/* ========== 提现规则区域(核心审核项)========== */
.rules-section {
.config-card {
margin: 0 24rpx 24rpx;
padding: 24rpx 32rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
border-radius: 16rpx;
}
/* 标题栏 */
.rules-header {
.config-item {
display: flex;
align-items: center;
gap: 12rpx;
padding: 28rpx 32rpx 20rpx;
background: linear-gradient(135deg, #fef0eb 0%, #fff8f5 100%);
border-bottom: 2rpx solid #fce8e1;
}
.rules-header-icon {
font-size: 36rpx;
}
.rules-header-title {
font-size: 30rpx;
font-weight: bold;
color: #c0392b;
}
/* 规则网格 - 两列 */
.rules-grid {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 24rpx 24rpx 8rpx;
gap: 16rpx;
}
.rule-cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20rpx 12rpx;
background: #fafafa;
border-radius: 12rpx;
border: 2rpx solid #f0f0f0;
}
.rule-cell-wide {
grid-column: span 2;
flex-direction: row;
justify-content: space-between;
padding: 18rpx 24rpx;
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.rule-cell-label {
font-size: 23rpx;
color: #999;
margin-bottom: 8rpx;
.config-item:last-child {
border-bottom: none;
}
.rule-cell-value {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.rule-cell-wide .rule-cell-label {
margin-bottom: 0;
}
/* 详细规则文字列表 */
.rules-detail {
padding: 20rpx 32rpx 28rpx;
}
.detail-item {
display: flex;
align-items: flex-start;
gap: 10rpx;
padding: 10rpx 0;
}
.detail-bullet {
flex-shrink: 0;
.config-label {
font-size: 26rpx;
color: #ff6b35;
margin-top: 2rpx;
}
.detail-text {
font-size: 25rpx;
color: #666;
line-height: 1.7;
}
/* ========== 底部按钮区 ========== */
.bottom-area {
margin-top: auto;
padding: 20rpx 40rpx 40rpx;
.config-value {
font-size: 26rpx;
color: #333;
font-weight: bold;
}
.submit-btn {
width: 100%;
padding: 26rpx 0;
margin: 48rpx 24rpx;
padding: 24rpx 0;
text-align: center;
background: linear-gradient(135deg, #ff6b35 0%, #ff9a56 100%);
color: #fff;
font-size: 32rpx;
font-weight: bold;
border-radius: 48rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.35);
}
.submit-btn.disabled {
opacity: 0.45;
box-shadow: none;
}
.submit-disclaimer {
text-align: center;
margin-top: 16rpx;
}
.disclaimer-text {
font-size: 22rpx;
color: #bbb;
opacity: 0.5;
}

View File

@@ -24,9 +24,9 @@ export default function WithdrawPage() {
const loadConfig = async () => {
try {
const res: any = await getWithdrawConfig();
if (res.code === 200 || res.code === 0) {
setConfig(res.data || {});
const res = await getWithdrawConfig();
if (res.code === 200) {
setConfig(res.data);
}
} catch (error) {
console.error('加载配置失败', error);
@@ -35,25 +35,15 @@ export default function WithdrawPage() {
const loadStats = async () => {
try {
const res: any = await getWithdrawStats();
if (res.code === 200 || res.code === 0) {
setStats(res.data || {});
const res = await getWithdrawStats();
if (res.code === 200) {
setStats(res.data);
}
} catch (error) {
console.error('加载统计失败', error);
}
};
// 从配置或默认值读取
const minAmount = config.withdraw_min_amount || '10';
const maxAmount = config.withdraw_max_amount || '200';
const dailyLimit = config.withdraw_daily_limit || '2000';
const dailyCount = config.withdraw_daily_count || 3;
const timeStart = config.withdraw_time_start || '';
const timeEnd = config.withdraw_time_end || '';
const feeRate = config.withdraw_fee_rate || '0';
const arrivalTime = config.arrival_time || '';
const handleWithdraw = () => {
if (!amount || parseFloat(amount) <= 0) {
Taro.showToast({ title: '请输入提现金额', icon: 'none' });
@@ -61,12 +51,15 @@ export default function WithdrawPage() {
}
const amountNum = parseFloat(amount);
if (amountNum < parseFloat(minAmount)) {
const minAmount = parseFloat(config.withdraw_min_amount || '10');
const maxAmount = parseFloat(config.withdraw_max_amount || '200');
if (amountNum < minAmount) {
Taro.showToast({ title: `最低提现金额为${minAmount}`, icon: 'none' });
return;
}
if (amountNum > parseFloat(maxAmount)) {
if (amountNum > maxAmount) {
Taro.showToast({ title: `单次最大提现金额为${maxAmount}`, icon: 'none' });
return;
}
@@ -76,12 +69,12 @@ export default function WithdrawPage() {
const handlePayConfirm = async (payType: number) => {
setShowPayModal(false);
if (payType !== 0) return;
if (payType !== 0) return; // 0微信支付
setSubmitting(true);
try {
const res: any = await applyWithdraw({ amount: parseFloat(amount) });
if (res.code === 200 || res.code === 0) {
const res = await applyWithdraw({ amount: parseFloat(amount) });
if (res.code === 200) {
Taro.showToast({ title: '提现申请已提交', icon: 'success' });
setTimeout(() => {
Taro.navigateBack();
@@ -98,156 +91,65 @@ export default function WithdrawPage() {
};
const handleAllWithdraw = () => {
const available = stats.withdrawingTotal || stats.balance || '0.00';
// 可提现余额
const available = stats.withdrawingTotal || '0';
setAmount(available);
};
const availableBalance = stats.withdrawingTotal || stats.balance || '0.00';
return (
<View className="withdraw-page">
{/* 余额卡片 */}
<View className="balance-card">
<View className="balance-header">
<Text className="balance-label"></Text>
</View>
<View className="balance-amount-row">
<Text className="currency-symbol-lg">¥</Text>
<Text className="balance-number">{availableBalance}</Text>
</View>
<View className="balance-tips">
<Text className="balance-tip-text"></Text>
</View>
</View>
{/* 提现输入 */}
<View className="withdraw-card">
<View className="card-title"></View>
<View className="amount-input-wrap">
<Text className="currency-symbol">¥</Text>
<Input
className="amount-input"
type="digit"
placeholder={`最低 ${minAmount}`}
placeholder="请输入提现金额"
value={amount}
onInput={(e) => setAmount(e.detail.value)}
/>
<Text
className="all-withdraw"
onClick={handleAllWithdraw}
>
</View>
<View className="amount-tips">
<Text>: ¥{stats.withdrawingTotal || '0.00'}</Text>
<Text className="all-withdraw" onClick={handleAllWithdraw}>
</Text>
</View>
{feeRate && parseFloat(feeRate) > 0 ? (
{config.withdraw_fee_rate && parseFloat(config.withdraw_fee_rate) > 0 && (
<View className="fee-tips">
<Text className="fee-icon"></Text>
<Text> {feeRate}% </Text>
</View>
) : (
<View className="fee-tips fee-free">
<Text className="fee-icon"></Text>
<Text></Text>
: {config.withdraw_fee_rate}%
</View>
)}
</View>
{/* ====== 提现规则展示(核心审核项)====== */}
<View className="rules-section">
{/* 标题栏 */}
<View className="rules-header">
<Text className="rules-header-icon">📋</Text>
<Text className="rules-header-title"></Text>
<View className="config-card">
<View className="config-item">
<Text className="config-label"></Text>
<Text className="config-value">¥{config.withdraw_max_amount || '200'}</Text>
</View>
{/* 规则网格 - 两列布局更醒目 */}
<View className="rules-grid">
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{minAmount} </Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{maxAmount} /</Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{dailyLimit} /</Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{dailyCount} /</Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{timeStart && timeEnd ? `${timeStart}-${timeEnd}` : '全天可提'}</Text>
</View>
<View className="rule-cell">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">{arrivalTime || '1-3个工作日'}</Text>
</View>
<View className="rule-cell rule-cell-wide">
<Text className="rule-cell-label"></Text>
<Text className="rule-cell-value">
{feeRate && parseFloat(feeRate) > 0 ? `${feeRate}%` : '免手续费'}
<View className="config-item">
<Text className="config-label"></Text>
<Text className="config-value">¥{config.withdraw_daily_limit || '2000'}</Text>
</View>
{config.withdraw_time_start && (
<View className="config-item">
<Text className="config-label"></Text>
<Text className="config-value">
{config.withdraw_time_start} - {config.withdraw_time_end}
</Text>
</View>
</View>
{/* 详细规则文字 */}
<View className="rules-detail">
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> ¥{minAmount} ~ ¥{maxAmount}</Text>
</View>
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> {dailyCount} ¥{dailyLimit}</Text>
</View>
{timeStart && timeEnd ? (
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> {timeStart} {timeEnd} </Text>
</View>
) : (
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> 24 </Text>
</View>
)}
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> {arrivalTime || '1-3个工作日'} </Text>
</View>
{feeRate && parseFloat(feeRate) > 0 ? (
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"> {feeRate}% </Text>
</View>
) : (
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text"></Text>
</View>
)}
<View className="detail-item">
<Text className="detail-bullet"></Text>
<Text className="detail-text">退</Text>
</View>
</View>
)}
</View>
{/* 底部按钮 */}
<View className="bottom-area">
<View
className={`submit-btn ${amount && !submitting ? '' : 'disabled'}`}
onClick={handleWithdraw}
>
{submitting ? '提交中...' : '确认提现'}
</View>
<View className="submit-disclaimer">
<Text className="disclaimer-text"></Text>
</View>
<View
className={`submit-btn ${amount && !submitting ? '' : 'disabled'}`}
onClick={handleWithdraw}
>
{submitting ? '提交中...' : '确认提现'}
</View>
<PayModal