Files
mp-10550/src/user/gift/index.tsx
赵忠林 a1cacc04e8 feat(admin): 从文章详情页面改为文章管理页面
- 修改页面配置,设置新的导航栏标题和样式
- 重新设计页面布局,增加搜索栏、文章列表和操作按钮
- 添加文章搜索、分页加载和删除功能
- 优化文章列表项的样式和交互
- 新增礼品卡相关API和组件
- 更新优惠券组件,增加到期提醒和筛选功能
2025-08-13 10:11:57 +08:00

404 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Empty, ConfigProvider, InfiniteLoading, Loading, PullToRefresh, Tabs, TabPane} from '@nutui/nutui-react-taro'
import {Gift, Plus, Board, QrCode} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components'
import {ShopGift} from "@/api/shop/shopGift/model";
import {getUserGifts} from "@/api/shop/shopGift";
import GiftCardList from "@/components/GiftCardList";
import GiftCardStats from "@/components/GiftCardStats";
import GiftCardGuide from "@/components/GiftCardGuide";
import {GiftCardProps} from "@/components/GiftCard";
const GiftCardManage = () => {
const [list, setList] = useState<ShopGift[]>([])
const [loading, setLoading] = useState(false)
const [hasMore, setHasMore] = useState(true)
// const [searchValue, setSearchValue] = useState('')
const [page, setPage] = useState(1)
// const [total, setTotal] = useState(0)
const [activeTab, setActiveTab] = useState('0') // 0-可用 1-已使用 2-已过期
const [stats, setStats] = useState({
available: 0,
used: 0,
expired: 0,
totalValue: 0
})
const [showGuide, setShowGuide] = useState(false)
// const [showRedeemModal, setShowRedeemModal] = useState(false)
// const [filters, setFilters] = useState({
// type: [] as number[],
// sortBy: 'createTime' as 'createTime' | 'expireTime' | 'faceValue' | 'useTime',
// sortOrder: 'desc' as 'asc' | 'desc'
// })
// 获取礼品卡状态过滤条件
const getStatusFilter = () => {
switch (activeTab) {
case '0': // 可用
return { useStatus: 0 }
case '1': // 已使用
return { useStatus: 1 }
case '2': // 已过期
return { useStatus: 2 }
default:
return {}
}
}
const reload = async (isRefresh = false) => {
if (isRefresh) {
setPage(1)
setList([])
setHasMore(true)
}
setLoading(true)
try {
const currentPage = isRefresh ? 1 : page
const statusFilter = getStatusFilter()
const res = await getUserGifts({
page: currentPage,
limit: 10,
// keywords: searchValue,
...statusFilter,
// 应用筛选条件
// ...(filters.type.length > 0 && { type: filters.type[0] }),
// sortBy: filters.sortBy,
// sortOrder: filters.sortOrder
})
if (res && res.list) {
const newList = isRefresh ? res.list : [...list, ...res.list]
setList(newList)
// setTotal(res.count || 0)
// 判断是否还有更多数据
setHasMore(res.list.length === 10) // 如果返回的数据等于limit说明可能还有更多
if (!isRefresh) {
setPage(currentPage + 1)
} else {
setPage(2) // 刷新后下一页是第2页
}
} else {
setHasMore(false)
// setTotal(0)
}
} catch (error) {
console.error('获取礼品卡失败:', error)
Taro.showToast({
title: '获取礼品卡失败',
icon: 'error'
});
} finally {
setLoading(false)
}
}
// 搜索功能
// const handleSearch = (value: string) => {
// setSearchValue(value)
// reload(true)
// }
// 下拉刷新
const handleRefresh = async () => {
await reload(true)
}
// Tab切换
const handleTabChange = (value: string) => {
setActiveTab(value)
setPage(1)
setList([])
setHasMore(true)
// 延迟执行reload确保状态更新完成
setTimeout(() => {
reload(true)
}, 100)
}
// 转换礼品卡数据为GiftCard组件所需格式
const transformGiftData = (gift: ShopGift): GiftCardProps => {
return {
id: gift.id || 0,
name: gift.name || '礼品卡',
description: gift.description,
code: gift.code,
goodsImage: gift.goodsImage,
faceValue: gift.faceValue,
type: gift.type,
useStatus: gift.useStatus,
expireTime: gift.expireTime,
useTime: gift.useTime,
useLocation: gift.useLocation,
contactInfo: gift.contactInfo,
showCode: gift.useStatus === 0, // 只有可用状态显示兑换码
showUseBtn: gift.useStatus === 0, // 只有可用状态显示使用按钮
showDetailBtn: true,
theme: getThemeByType(gift.type),
onUse: () => handleUseGift(gift),
onDetail: () => handleGiftDetail(gift)
}
}
// 根据礼品卡类型获取主题色
const getThemeByType = (type?: number): 'gold' | 'silver' | 'bronze' | 'blue' | 'green' | 'purple' => {
switch (type) {
case 10: return 'gold' // 实物礼品卡
case 20: return 'blue' // 虚拟礼品卡
case 30: return 'green' // 服务礼品卡
default: return 'silver'
}
}
// 使用礼品卡
const handleUseGift = (gift: ShopGift) => {
Taro.showModal({
title: '使用礼品卡',
content: `确定要使用"${gift.name}"吗?`,
success: (res) => {
if (res.confirm) {
// 跳转到礼品卡使用页面
Taro.navigateTo({
url: `/user/gift/use?id=${gift.id}`
})
}
}
})
}
// 礼品卡点击事件
const handleGiftClick = (gift: GiftCardProps, index: number) => {
console.log(gift.code)
const originalGift = list[index]
if (originalGift) {
// 显示礼品卡详情
handleGiftDetail(originalGift)
}
}
// 显示礼品卡详情
const handleGiftDetail = (gift: ShopGift) => {
// 跳转到礼品卡详情页
Taro.navigateTo({
url: `/user/gift/detail?id=${gift.id}`
})
}
// 加载礼品卡统计数据
const loadGiftStats = async () => {
try {
// 并行获取各状态的礼品卡数量
const [availableRes, usedRes, expiredRes] = await Promise.all([
getUserGifts({ page: 1, limit: 1, useStatus: 0 }),
getUserGifts({ page: 1, limit: 1, useStatus: 1 }),
getUserGifts({ page: 1, limit: 1, useStatus: 2 })
])
// 计算总价值(仅可用礼品卡)
const availableGifts = await getUserGifts({ page: 1, limit: 100, useStatus: 0 })
const totalValue = availableGifts?.list?.reduce((sum, gift) => {
return sum + parseFloat(gift.faceValue || '0')
}, 0) || 0
setStats({
available: availableRes?.count || 0,
used: usedRes?.count || 0,
expired: expiredRes?.count || 0,
totalValue
})
} catch (error) {
console.error('获取礼品卡统计失败:', error)
}
}
// 统计卡片点击事件
const handleStatsClick = (type: 'available' | 'used' | 'expired' | 'total') => {
const tabMap = {
available: '0',
used: '1',
expired: '2',
total: '0' // 总价值点击跳转到可用礼品卡
}
if (tabMap[type]) {
handleTabChange(tabMap[type])
}
}
// 兑换礼品卡
const handleRedeemGift = () => {
Taro.navigateTo({
url: '/user/gift/redeem'
})
}
// 扫码兑换礼品卡
const handleScanRedeem = () => {
Taro.scanCode({
success: (res) => {
// 处理扫码结果
const code = res.result
if (code) {
Taro.navigateTo({
url: `/user/gift/redeem?code=${encodeURIComponent(code)}`
})
}
},
fail: () => {
Taro.showToast({
title: '扫码失败',
icon: 'error'
})
}
})
}
// 加载更多
const loadMore = async () => {
if (!loading && hasMore) {
await reload(false) // 不刷新,追加数据
}
}
useDidShow(() => {
reload(true).then()
loadGiftStats().then()
});
return (
<ConfigProvider>
{/* 搜索栏和功能入口 */}
<View className="bg-white px-4 py-3">
<View className="flex items-center justify-between gap-3">
<Button
size="small"
type="primary"
icon={<Plus />}
onClick={handleRedeemGift}
>
</Button>
<Button
size="small"
fill="outline"
icon={<QrCode />}
onClick={handleScanRedeem}
>
</Button>
<Button
size="small"
fill="outline"
icon={<Board />}
onClick={() => setShowGuide(true)}
>
</Button>
</View>
</View>
{/* 礼品卡统计 */}
<GiftCardStats
availableCount={stats.available}
usedCount={stats.used}
expiredCount={stats.expired}
totalValue={stats.totalValue}
onStatsClick={handleStatsClick}
/>
{/* Tab切换 */}
<View className="bg-white">
<Tabs value={activeTab} onChange={handleTabChange}>
<TabPane title="可用" value="0">
</TabPane>
<TabPane title="已使用" value="1">
</TabPane>
<TabPane title="已过期" value="2">
</TabPane>
</Tabs>
</View>
{/* 礼品卡列表 */}
<PullToRefresh
onRefresh={handleRefresh}
headHeight={60}
>
<View style={{ height: '600px', overflowY: 'auto' }} id="gift-scroll">
{list.length === 0 && !loading ? (
<View className="flex flex-col justify-center items-center" style={{height: '500px'}}>
<Empty
description={
activeTab === '0' ? "暂无可用礼品卡" :
activeTab === '1' ? "暂无已使用礼品卡" :
"暂无已过期礼品卡"
}
style={{backgroundColor: 'transparent'}}
/>
</View>
) : (
<InfiniteLoading
target="gift-scroll"
hasMore={hasMore}
onLoadMore={loadMore}
loadingText={
<View className="flex justify-center items-center py-4">
<Loading />
<View className="ml-2">...</View>
</View>
}
loadMoreText={
<View className="text-center py-4 text-gray-500">
{list.length === 0 ? "暂无数据" : "没有更多了"}
</View>
}
>
<GiftCardList
gifts={list.map(transformGiftData)}
onGiftClick={handleGiftClick}
showEmpty={false}
/>
</InfiniteLoading>
)}
</View>
</PullToRefresh>
{/* 底部提示 */}
{activeTab === '0' && list.length === 0 && !loading && (
<View className="text-center py-8">
<View className="text-gray-400 mb-4">
<Gift size="48" />
</View>
<View className="text-gray-500 mb-2"></View>
<View className="flex gap-2 justify-center">
<Button
size="small"
type="primary"
onClick={handleRedeemGift}
>
</Button>
<Button
size="small"
fill="outline"
onClick={handleScanRedeem}
>
</Button>
</View>
</View>
)}
{/* 使用指南弹窗 */}
<GiftCardGuide
visible={showGuide}
onClose={() => setShowGuide(false)}
/>
</ConfigProvider>
);
};
export default GiftCardManage;