import { useEffect, useState } from 'react'; import { Button, Card, Empty, Input, Select, Space, Spin, Tag, Modal, Form, message } from 'antd'; import { SearchOutlined } from '@ant-design/icons'; import { PointsMallAPI, PointsMallCategory, PointsMallOverview, PointsMallProduct, PointsMallProductsResponse } from '../api'; type SortKey = 'popular' | 'price_asc' | 'price_desc' | 'newest'; const MOCK_OVERVIEW: PointsMallOverview = { me: { points: 1280, level: 'Lv.2' }, categories: [ { id: 'all', name: '全部', sort: 0 }, { id: 'digital', name: '虚拟权益', sort: 1 }, { id: 'tool', name: '工具周边', sort: 2 }, { id: 'gift', name: '礼品卡券', sort: 3 }, { id: 'limited', name: '限时活动', sort: 4 } ], announcements: [], banners: [ { id: 'b1', title: '本期活动', subtitle: 'Up to 25% Off', imageUrl: '', linkUrl: '' } ], promoEntries: [ { id: 'p1', title: '促销活动', subtitle: '本周精选', linkUrl: '' }, { id: 'p2', title: '积分任务', subtitle: '快速涨积分', linkUrl: '' } ] }; const MOCK_PRODUCTS: PointsMallProduct[] = Array.from({ length: 12 }).map((_, i) => ({ id: String(i + 1), categoryId: i % 2 === 0 ? 'digital' : 'tool', name: `商品 ${i + 1}`, subtitle: '这里是商品简短描述', coverUrl: '', pointsPrice: 199 + i * 10, stock: 99, sold: 12 + i, tags: i % 3 === 0 ? ['限时'] : i % 3 === 1 ? ['热卖'] : [] })); interface ExchangeFormValues { recipientName: string; phone: string; province: string; city: string; district: string; address: string; zipCode?: string; } export default function PointsMallPage() { const [overviewLoading, setOverviewLoading] = useState(false); const [productsLoading, setProductsLoading] = useState(false); const [overview, setOverview] = useState(null); const [categories, setCategories] = useState([]); const [categoryId, setCategoryId] = useState('all'); const [q, setQ] = useState(''); const [sort, setSort] = useState('popular'); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(24); const [productsRes, setProductsRes] = useState(null); const [exchangeModalVisible, setExchangeModalVisible] = useState(false); const [selectedProduct, setSelectedProduct] = useState(null); const [pendingOrderId, setPendingOrderId] = useState(null); const [exchangeLoading, setExchangeLoading] = useState(false); const [form] = Form.useForm(); const loadOverview = async () => { setOverviewLoading(true); try { const data = await PointsMallAPI.overview(); setOverview(data); setCategories(data.categories || []); if (!data.categories?.some((c) => c.id === categoryId) && data.categories?.[0]?.id) { setCategoryId(data.categories[0].id); } } catch { message.error('获取积分信息失败,请稍后重试'); setOverview({ ...MOCK_OVERVIEW, me: { points: 0, level: 'Lv.0' } }); setCategories(MOCK_OVERVIEW.categories); } finally { setOverviewLoading(false); } }; const loadProducts = async () => { setProductsLoading(true); try { const res = await PointsMallAPI.products({ categoryId: categoryId === 'all' ? undefined : categoryId, q, sort, page, pageSize }); setProductsRes(res); } catch { const filtered = MOCK_PRODUCTS.filter((p) => (categoryId === 'all' ? true : p.categoryId === categoryId)) .filter((p) => (q ? (p.name + p.subtitle).toLowerCase().includes(q.toLowerCase()) : true)); setProductsRes({ page, pageSize, total: filtered.length, items: filtered.slice((page - 1) * pageSize, page * pageSize) }); } finally { setProductsLoading(false); } }; useEffect(() => { loadOverview(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { loadProducts(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [categoryId, q, sort, page, pageSize]); const banner = overview?.banners?.[0]; const promoEntries = overview?.promoEntries || []; const products = productsRes?.items || []; const total = productsRes?.total || 0; const handleExchangeClick = async (product: PointsMallProduct) => { if (userPoints < product.pointsPrice) return; setExchangeLoading(true); try { const res = await PointsMallAPI.exchangePrepare(product.id); setSelectedProduct(product); setPendingOrderId(res.orderId); setExchangeModalVisible(true); form.resetFields(); setOverview((prev) => { if (!prev) return prev; return { ...prev, me: { ...prev.me, points: res.remainingPoints } }; }); message.success('积分扣减成功,请填写收件信息完成兑换'); } catch (e: any) { message.error(e?.message || '兑换失败,请稍后重试'); } finally { setExchangeLoading(false); } }; const handleExchangeSubmit = async () => { if (!selectedProduct || !pendingOrderId) return; try { await form.validateFields(); setExchangeLoading(true); await PointsMallAPI.exchangeSubmitShipping(pendingOrderId, form.getFieldsValue()); message.success('兑换成功!我们将尽快为您安排发货'); setExchangeModalVisible(false); setPendingOrderId(null); // 刷新积分余额 loadOverview(); } catch (error: any) { if (error?.errorFields) return; message.error(error?.message || '提交失败,请稍后重试'); } finally { setExchangeLoading(false); } }; const userPoints = overview?.me?.points || 0; const canAfford = selectedProduct ? userPoints >= selectedProduct.pointsPrice : false; return (

积分商城

使用积分兑换权益、工具和活动礼包。积分通过 API 调用消费自动累积,1 美元 = 1000 积分。

{overviewLoading ? ( ) : (
我的积分
{userPoints.toLocaleString()}
{String(overview?.me?.level || 'Lv.0')}
)}
商品分类 {categories.map((c) => ( ))}
{banner?.title || '本期活动'}
{banner?.subtitle || 'Up to 25% Off'}
banner 图片与跳转链接由后端配置
{promoEntries.slice(0, 2).map((p) => (
{p.title}
{p.subtitle}
))} {promoEntries.length < 2 && (
促销入口由后端配置
)}
{ setPage(1); setQ(e.target.value); }} prefix={} placeholder="搜索商品" allowClear className="points-mall-search-input" /> { setPage(1); setPageSize(v); }} options={[ { value: 12, label: '每页 12' }, { value: 24, label: '每页 24' }, { value: 48, label: '每页 48' } ]} />
共 {total.toLocaleString()} 件商品
{productsLoading ? ( ) : products.length === 0 ? ( ) : (
{products.map((p) => (
{p.name}
{p.subtitle}
{p.tags?.length ? ( {p.tags[0]} ) : null}
{Number(p.pointsPrice).toLocaleString()} 积分
库存 {p.stock} 已兑 {p.sold}
))}
)} {!!productsRes && (
第 {page} 页
)} {/* 兑换弹窗 */} { setExchangeModalVisible(false); setPendingOrderId(null); }} footer={null} width={500} className="points-exchange-modal" destroyOnClose > {selectedProduct && ( <>
{selectedProduct.name}
{selectedProduct.pointsPrice.toLocaleString()} 积分
{!canAfford && (
积分不足!当前余额:{userPoints.toLocaleString()} 积分,还需:{(selectedProduct.pointsPrice - userPoints).toLocaleString()} 积分
)}
收货人姓名} rules={[{ required: true, message: '请输入收货人姓名' }]} className="points-exchange-form-item" > 手机号码} rules={[ { required: true, message: '请输入手机号码' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' } ]} className="points-exchange-form-item" >
省份} rules={[{ required: true, message: '请选择省份' }]} className="points-exchange-form-item points-exchange-form-item-small" > 城市} rules={[{ required: true, message: '请选择城市' }]} className="points-exchange-form-item points-exchange-form-item-small" > 区/县} rules={[{ required: true, message: '请选择区/县' }]} className="points-exchange-form-item points-exchange-form-item-small" >
详细地址} rules={[{ required: true, message: '请输入详细地址' }]} className="points-exchange-form-item" > 邮政编码(选填)} className="points-exchange-form-item" >
)}
); }