- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
260 lines
7.2 KiB
TypeScript
260 lines
7.2 KiB
TypeScript
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
|