| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- import { useState } from 'react'
- import { View, Text, ScrollView, Swiper, SwiperItem, Image } from '@tarojs/components'
- import Taro from '@tarojs/taro'
- import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
- import { publicAdvertisementClient, wechatCouponStockClient } from '@/api'
- import { Button } from '@/components/ui/button'
- import { Card, CardContent, CardHeader } from '@/components/ui/card'
- import { cn } from '@/utils/cn'
- import { receiveCoupon } from '@/utils/coupon-api'
- import { Navbar } from '@/components/ui/navbar'
- import { TabBarLayout } from '@/layouts/tab-bar-layout'
- import { useAuth } from '@/utils/auth'
- import type { InferResponseType } from 'hono/client'
- // 使用RPC类型安全提取广告响应类型
- type AdvertisementResponse = InferResponseType<typeof publicAdvertisementClient.$get, 200>
- type Advertisement = AdvertisementResponse['data'][0]
- interface WechatCouponStock {
- id: number
- stockName: string
- stockDescription: string
- stockType: string
- couponAmount: number
- couponTotal: number
- couponRemaining: number
- beginTime: string
- endTime: string
- status: number
- coverImage: string
- }
- export default function IndexPage() {
- const queryClient = useQueryClient()
- const { user, isLoggedIn } = useAuth()
- // 获取广告列表
- const { data: advertisements, isLoading: adsLoading } = useQuery({
- queryKey: ['advertisements'],
- queryFn: async () => {
- const response = await publicAdvertisementClient.$get({
- query: {
- page: 1,
- pageSize: 10,
- filters: JSON.stringify({ isEnabled: 1 }),
- },
- })
- if (response.status !== 200) throw new Error('获取广告失败')
- const result = await response.json()
- return result.data
- },
- })
- // 获取代金券批次列表
- const { data: stocks, isLoading: stocksLoading } = useQuery({
- queryKey: ['wechat-coupon-stocks'],
- queryFn: async () => {
- const response = await wechatCouponStockClient.$get({
- query: {
- page: 1,
- pageSize: 20,
- filters: JSON.stringify({ status: 1 }),
- },
- })
- if (response.status !== 200) throw new Error('获取批次失败')
- const result = await response.json()
- return result.data as WechatCouponStock[]
- },
- })
- // 领取代金券
- const receiveMutation = useMutation({
- mutationFn: async (stockId: number) => {
- if (!user?.id) {
- throw new Error('请先登录')
- }
- const result = await receiveCoupon({ stockId, userId: user.id })
- if (!result.success) {
- throw new Error(result.message)
- }
- return result
- },
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['wechat-coupon-stocks'] })
- Taro.showToast({
- title: '领取成功',
- icon: 'success',
- })
- },
- onError: (error) => {
- if (error instanceof Error && error.message === '请先登录') {
- Taro.showModal({
- title: '提示',
- content: '请先登录后再领取优惠券',
- success: (res) => {
- if (res.confirm) {
- Taro.navigateTo({ url: '/pages/login/index' })
- }
- }
- })
- } else {
- Taro.showToast({
- title: error instanceof Error ? error.message : '领取失败',
- icon: 'none',
- })
- }
- }
- })
- const handleReceiveCoupon = (stockId: number) => {
- if (!isLoggedIn) {
- Taro.showModal({
- title: '提示',
- content: '请先登录后再领取优惠券',
- success: (res) => {
- if (res.confirm) {
- Taro.navigateTo({ url: '/pages/login/index' })
- }
- }
- })
- return
- }
- receiveMutation.mutate(stockId)
- }
- const handleAdClick = (ad: Advertisement) => {
- if (ad.linkUrl) {
- Taro.navigateTo({
- url: `/pages/webview/index?url=${encodeURIComponent(ad.linkUrl)}`,
- })
- }
- }
- return (
- <TabBarLayout activeKey="home">
- <Navbar title="首页" leftIcon="" leftText="" />
-
- {/* 广告轮播 */}
- <View className="bg-white">
- {adsLoading ? (
- <View className="h-48 flex items-center justify-center">
- <Text className="text-gray-400">加载中...</Text>
- </View>
- ) : advertisements && advertisements.length > 0 ? (
- <Swiper
- className="h-48"
- indicatorColor="#999"
- indicatorActiveColor="#333"
- circular
- autoplay
- >
- {advertisements.map((ad) => (
- <SwiperItem key={ad.id} onClick={() => handleAdClick(ad)}>
- <Image
- src={ad.imageUrl}
- className="w-full h-full"
- mode="aspectFill"
- />
- </SwiperItem>
- ))}
- </Swiper>
- ) : (
- <View className="h-48 flex items-center justify-center bg-gray-100">
- <Text className="text-gray-400">暂无广告</Text>
- </View>
- )}
- </View>
- {/* 代金券批次列表 */}
- <ScrollView className="flex-1 p-4">
- <View className="mb-4">
- <Text className="text-lg font-bold text-gray-900">热门券包</Text>
- <Text className="text-sm text-gray-500">限时领取,先到先得</Text>
- </View>
- {stocksLoading ? (
- <View className="flex items-center justify-center py-8">
- <Text className="text-gray-400">加载中...</Text>
- </View>
- ) : stocks && stocks.length > 0 ? (
- <View className="space-y-4">
- {stocks.map((stock) => (
- <Card key={stock.id} className="overflow-hidden">
- <CardHeader className="p-0">
- <Image
- src={stock.coverImage}
- className="w-full h-32"
- mode="aspectFill"
- />
- </CardHeader>
- <CardContent className="p-4">
- <View className="flex justify-between items-start mb-2">
- <Text className="text-lg font-bold text-gray-900">
- {stock.stockName}
- </Text>
- <Text className="text-lg font-bold text-red-500">
- ¥{stock.couponAmount}
- </Text>
- </View>
-
- <Text className="text-sm text-gray-600 mb-2">
- {stock.stockDescription}
- </Text>
-
- <View className="flex justify-between items-center">
- <Text className="text-xs text-gray-500">
- 剩余: {stock.couponRemaining}/{stock.couponTotal}
- </Text>
- <Text className="text-xs text-gray-500">
- {new Date(stock.endTime).toLocaleDateString()} 截止
- </Text>
- </View>
-
- <Button
- className={cn(
- 'w-full mt-3 h-9',
- stock.couponRemaining > 0
- ? 'bg-red-500 text-white'
- : 'bg-gray-300 text-gray-500'
- )}
- disabled={stock.couponRemaining === 0}
- onClick={() => handleReceiveCoupon(stock.id)}
- >
- {stock.couponRemaining > 0 ? '立即领取' : '已领完'}
- </Button>
- </CardContent>
- </Card>
- ))}
- </View>
- ) : (
- <View className="flex items-center justify-center py-8">
- <Text className="text-gray-400">暂无券包</Text>
- </View>
- )}
- </ScrollView>
- </TabBarLayout>
- )
- }
|