feat(admin): 从文章详情页面改为文章管理页面

- 修改页面配置,设置新的导航栏标题和样式
- 重新设计页面布局,增加搜索栏、文章列表和操作按钮
- 添加文章搜索、分页加载和删除功能
- 优化文章列表项的样式和交互
- 新增礼品卡相关API和组件
- 更新优惠券组件,增加到期提醒和筛选功能
This commit is contained in:
2025-08-13 10:11:57 +08:00
parent 0e457f66d8
commit a1cacc04e8
67 changed files with 6278 additions and 2816 deletions

403
src/user/gift/index.tsx Normal file
View File

@@ -0,0 +1,403 @@
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;