feat(user): 新增收货地址管理及售后申请页面
- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
259
src_bak/pages/user/coupon-list.tsx
Normal file
259
src_bak/pages/user/coupon-list.tsx
Normal file
@@ -0,0 +1,259 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { View, Text, ScrollView } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { pageShopUserCoupon, removeShopUserCoupon } from '@/api/shop/shopUserCoupon'
|
||||
import type { ShopUserCoupon, ShopUserCouponParam } from '@/api/shop/shopUserCoupon/model'
|
||||
import CouponCard from '@/components/business/CouponCard'
|
||||
import EmptyState from '@/components/common/EmptyState'
|
||||
import LoadMore from '@/components/common/LoadMore'
|
||||
|
||||
definePageConfig({
|
||||
navigationBarTitleText: '我的优惠券',
|
||||
})
|
||||
|
||||
// Tab 配置
|
||||
type CouponTab = 'available' | 'used' | 'expired'
|
||||
|
||||
const tabList: { title: string; key: CouponTab }[] = [
|
||||
{ title: '未使用', key: 'available' },
|
||||
{ title: '已使用', key: 'used' },
|
||||
{ title: '已过期', key: 'expired' },
|
||||
]
|
||||
|
||||
interface TabState {
|
||||
coupons: ShopUserCoupon[]
|
||||
page: number
|
||||
finished: boolean
|
||||
loading: boolean
|
||||
initialized: boolean
|
||||
}
|
||||
|
||||
const CouponListPage: React.FC = () => {
|
||||
// 根据 URL 参数初始化 tab
|
||||
const { tab: initTab } = Taro.getCurrentInstance().router?.params || {}
|
||||
const getInitTabIndex = () => {
|
||||
switch (initTab) {
|
||||
case 'used': return 1
|
||||
case 'expired': return 2
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
const [tabIndex, setTabIndex] = useState(getInitTabIndex())
|
||||
|
||||
// 每个 tab 独立维护状态
|
||||
const [tabStates, setTabStates] = useState<TabState[]>(
|
||||
tabList.map(() => ({
|
||||
coupons: [],
|
||||
page: 1,
|
||||
finished: false,
|
||||
loading: false,
|
||||
initialized: false,
|
||||
}))
|
||||
)
|
||||
|
||||
const currentState = tabStates[tabIndex]
|
||||
|
||||
const updateTabState = useCallback(
|
||||
(index: number, partial: Partial<TabState>) => {
|
||||
setTabStates(prev => {
|
||||
const next = [...prev]
|
||||
next[index] = { ...next[index], ...partial }
|
||||
return next
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const loadCoupons = useCallback(
|
||||
async (targetIndex: number, p: number) => {
|
||||
const state = tabStates[targetIndex]
|
||||
if (state.loading) return
|
||||
|
||||
updateTabState(targetIndex, { loading: true })
|
||||
|
||||
try {
|
||||
const tabKey = tabList[targetIndex].key
|
||||
const params: ShopUserCouponParam = { page: p, limit: 10 }
|
||||
|
||||
// 根据 tab 设置筛选条件
|
||||
// status: 0=未使用, 1=已使用, 2=已过期
|
||||
switch (tabKey) {
|
||||
case 'available':
|
||||
params.status = 0
|
||||
break
|
||||
case 'used':
|
||||
params.status = 1
|
||||
break
|
||||
case 'expired':
|
||||
params.status = 2
|
||||
break
|
||||
}
|
||||
|
||||
const res = await pageShopUserCoupon(params)
|
||||
const list = res?.list || []
|
||||
|
||||
updateTabState(targetIndex, {
|
||||
coupons: p === 1 ? list : [...state.coupons, ...list],
|
||||
page: p,
|
||||
finished: list.length < 10,
|
||||
loading: false,
|
||||
initialized: true,
|
||||
})
|
||||
} catch {
|
||||
updateTabState(targetIndex, { loading: false, initialized: true })
|
||||
}
|
||||
},
|
||||
[tabStates, updateTabState]
|
||||
)
|
||||
|
||||
// 切换 tab 时加载数据
|
||||
useEffect(() => {
|
||||
if (!tabStates[tabIndex].initialized) {
|
||||
loadCoupons(tabIndex, 1)
|
||||
}
|
||||
}, [tabIndex])
|
||||
|
||||
// 加载更多
|
||||
const handleLoadMore = () => {
|
||||
if (!currentState.finished && !currentState.loading) {
|
||||
loadCoupons(tabIndex, currentState.page + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换 tab
|
||||
const handleTabChange = (index: number) => {
|
||||
if (index !== tabIndex) {
|
||||
setTabIndex(index)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除优惠券
|
||||
const handleDelete = (id: string) => {
|
||||
Taro.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该优惠券吗?',
|
||||
confirmColor: '#0e932e',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await removeShopUserCoupon(Number(id))
|
||||
// 从当前 tab 移除
|
||||
updateTabState(tabIndex, {
|
||||
coupons: tabStates[tabIndex].coupons.filter(c => c.id !== id)
|
||||
})
|
||||
Taro.showToast({ title: '删除成功', icon: 'success' })
|
||||
} catch {
|
||||
Taro.showToast({ title: '删除失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取空状态文本
|
||||
const getEmptyText = () => {
|
||||
switch (tabList[tabIndex].key) {
|
||||
case 'available': return '暂无可用优惠券'
|
||||
case 'used': return '暂无已使用优惠券'
|
||||
case 'expired': return '暂无已过期优惠券'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取空状态操作
|
||||
const getEmptyAction = () => {
|
||||
if (tabList[tabIndex].key === 'available') {
|
||||
return {
|
||||
text: '去领取',
|
||||
action: () => Taro.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 判断是否禁用
|
||||
const isDisabled = () => {
|
||||
const key = tabList[tabIndex].key
|
||||
return key === 'used' || key === 'expired'
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='min-h-screen bg-gray-50 flex flex-col'>
|
||||
{/* 自定义 Tabs 标题栏 */}
|
||||
<View className='bg-white flex flex-row items-center border-b border-gray-100'>
|
||||
{tabList.map((tab, index) => (
|
||||
<View
|
||||
key={tab.key}
|
||||
className='flex-1 flex flex-col items-center justify-center py-3'
|
||||
onClick={() => handleTabChange(index)}
|
||||
>
|
||||
<Text
|
||||
className='text-sm font-medium'
|
||||
style={{
|
||||
color: index === tabIndex ? '#ee0a24' : '#666',
|
||||
}}
|
||||
>
|
||||
{tab.title}
|
||||
</Text>
|
||||
{index === tabIndex && (
|
||||
<View
|
||||
className='mt-1 rounded-full'
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '3px',
|
||||
backgroundColor: '#ee0a24',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 内容区域 - 每个 tab 独立渲染 */}
|
||||
<View className='flex-1 relative'>
|
||||
{tabList.map((tab, idx) => (
|
||||
<View
|
||||
key={tab.key}
|
||||
className='absolute inset-0'
|
||||
style={{
|
||||
display: idx === tabIndex ? 'flex' : 'none',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<ScrollView
|
||||
scrollY
|
||||
className='flex-1'
|
||||
onScrollToLower={handleLoadMore}
|
||||
lowerThreshold={100}
|
||||
>
|
||||
<View className='p-3'>
|
||||
{tabStates[idx].coupons.length > 0 ? (
|
||||
tabStates[idx].coupons.map(coupon => (
|
||||
<CouponCard
|
||||
key={coupon.id}
|
||||
coupon={coupon}
|
||||
disabled={isDisabled()}
|
||||
showDelete={isDisabled()}
|
||||
onDelete={() => handleDelete(coupon.id!)}
|
||||
/>
|
||||
))
|
||||
) : tabStates[idx].initialized && !tabStates[idx].loading ? (
|
||||
<EmptyState
|
||||
text={getEmptyText()}
|
||||
actionText={getEmptyAction()?.text}
|
||||
onAction={getEmptyAction()?.action}
|
||||
/>
|
||||
) : null}
|
||||
<LoadMore
|
||||
loading={tabStates[idx].loading}
|
||||
finished={tabStates[idx].finished}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default CouponListPage
|
||||
Reference in New Issue
Block a user