new
This commit is contained in:
@@ -1,31 +1,28 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Form, Input, Button, Card, List, Tag, Typography, message, Space, Statistic, Divider, Modal, Descriptions } from 'antd';
|
||||
import { MobileOutlined, LockOutlined, SearchOutlined, CarOutlined, InboxOutlined, SafetyCertificateOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, UserOutlined, EnvironmentOutlined, PhoneOutlined } from '@ant-design/icons';
|
||||
import { queryMyOrders } from '../api';
|
||||
import { Form, Input, Button, Card, List, Tag, Typography, message, Space, Statistic, Divider, Modal, Descriptions, Tabs } from 'antd';
|
||||
import { MobileOutlined, LockOutlined, SearchOutlined, CarOutlined, InboxOutlined, SafetyCertificateOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, UserOutlined, EnvironmentOutlined, PhoneOutlined, CalendarOutlined } from '@ant-design/icons';
|
||||
import { queryMyOrders, getMySignups } from '../api';
|
||||
import { motion } from 'framer-motion';
|
||||
import LoginModal from '../components/LoginModal';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
|
||||
const MyOrders = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [orders, setOrders] = useState([]);
|
||||
const [activities, setActivities] = useState([]);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [currentOrder, setCurrentOrder] = useState(null);
|
||||
const [loginVisible, setLoginVisible] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { user, login } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
// 如果已登录,自动查询订单
|
||||
if (user.phone_number) {
|
||||
handleQueryOrders(user.phone_number);
|
||||
}
|
||||
} else {
|
||||
// Don't auto-show login modal on mount if not logged in, just show the "Please login" UI
|
||||
// setLoginVisible(true);
|
||||
handleQueryData();
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
@@ -34,36 +31,25 @@ const MyOrders = () => {
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const handleQueryOrders = async (phone) => {
|
||||
const handleQueryData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 使用 queryMyOrders 接口,这里我们需要调整该接口以支持仅传手机号(如果已登录)
|
||||
// 或者,既然已登录,后端应该能通过 Token 知道是谁,直接查这个人的订单
|
||||
// 但目前的 queryMyOrders 是 POST {phone_number, code},这主要用于免登录查询
|
||||
// 我们应该使用 OrderViewSet 的 list 方法,它已经支持 filter(wechat_user=user)
|
||||
// 但前端 api.js 中 getOrder 是查单个,我们需要一个 getMyOrders 接口
|
||||
|
||||
// 修改策略:如果已登录,直接调用 queryMyOrders,但不需要 code?
|
||||
// 后端 my_orders 接口目前强制需要 code。
|
||||
// 应该使用 OrderViewSet 的标准 list 接口,它会根据 Token 返回自己的订单。
|
||||
// api.js 中没有导出 getOrders list 接口,我们可以临时用 queryMyOrders 但绕过 code 检查?
|
||||
// 不,最好的方式是使用标准的 GET /orders/,后端 OrderViewSet.get_queryset 已经处理了 get_current_wechat_user
|
||||
|
||||
// 让我们先用 GET /orders/ 试试,需要在 api.js 确认是否有 export
|
||||
// 检查 api.js 发现没有 getOrderList, 只有 getOrder(id)
|
||||
// 我们需要修改 api.js 或在此处直接调用
|
||||
|
||||
// 为了不修改 api.js 太多,我们引入 axios 实例自己发请求,或者假设 api.js 有一个 getMyOrderList
|
||||
// 实际上,查看 api.js, queryMyOrders 是 POST /orders/my_orders/,这是免登录版本
|
||||
// 我们应该用 GET /orders/,因为 get_queryset 已经过滤了。
|
||||
|
||||
// 临时引入 api 实例
|
||||
const { default: api } = await import('../api');
|
||||
const response = await api.get('/orders/');
|
||||
setOrders(response.data);
|
||||
if (response.data.length === 0) {
|
||||
message.info('您暂时没有订单');
|
||||
|
||||
// Parallel fetch
|
||||
const [ordersRes, activitiesRes] = await Promise.allSettled([
|
||||
api.get('/orders/'),
|
||||
getMySignups()
|
||||
]);
|
||||
|
||||
if (ordersRes.status === 'fulfilled') {
|
||||
setOrders(ordersRes.value.data);
|
||||
}
|
||||
|
||||
if (activitiesRes.status === 'fulfilled') {
|
||||
setActivities(activitiesRes.value.data);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
message.error('查询出错');
|
||||
@@ -107,104 +93,176 @@ const MyOrders = () => {
|
||||
<div style={{ marginBottom: 20, textAlign: 'right', color: '#fff' }}>
|
||||
当前登录用户: <span style={{ color: '#00b96b', fontWeight: 'bold', marginRight: 10 }}>{user.nickname}</span>
|
||||
<Button
|
||||
onClick={() => handleQueryOrders(user.phone_number)}
|
||||
onClick={handleQueryData}
|
||||
loading={loading}
|
||||
icon={<SearchOutlined />}
|
||||
>
|
||||
刷新订单
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<List
|
||||
grid={{ gutter: 24, xs: 1, sm: 1, md: 2, lg: 2, xl: 3, xxl: 3 }}
|
||||
dataSource={orders}
|
||||
loading={loading}
|
||||
renderItem={order => (
|
||||
<List.Item>
|
||||
<Card
|
||||
hoverable
|
||||
onClick={() => showDetail(order)}
|
||||
title={<Space><span style={{ color: '#fff' }}>订单号: {order.id}</span> {getStatusTag(order.status)}</Space>}
|
||||
style={{
|
||||
background: 'rgba(0,0,0,0.6)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
marginBottom: 10,
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}
|
||||
headStyle={{ borderBottom: '1px solid rgba(255,255,255,0.1)' }}
|
||||
bodyStyle={{ padding: '20px' }}
|
||||
>
|
||||
<div style={{ color: '#ccc' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||
<Text strong style={{ color: '#00b96b', fontSize: 16 }}>{order.total_price} 元</Text>
|
||||
<Text style={{ color: '#888' }}>{new Date(order.created_at).toLocaleString()}</Text>
|
||||
</div>
|
||||
|
||||
<div style={{ background: 'rgba(255,255,255,0.05)', padding: 15, borderRadius: 8, marginBottom: 15 }}>
|
||||
<Space align="center" size="middle">
|
||||
{order.config_image ? (
|
||||
<img
|
||||
src={order.config_image}
|
||||
alt={order.config_name}
|
||||
style={{ width: 60, height: 60, objectFit: 'cover', borderRadius: 8, border: '1px solid rgba(255,255,255,0.1)' }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
background: 'rgba(24,144,255,0.1)',
|
||||
borderRadius: 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px solid rgba(24,144,255,0.2)'
|
||||
}}>
|
||||
<InboxOutlined style={{ fontSize: 24, color: '#1890ff' }} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div style={{ color: '#fff', fontSize: 16, fontWeight: '500', marginBottom: 4 }}>{order.config_name || `商品 ID: ${order.config}`}</div>
|
||||
<div style={{ color: '#888' }}>数量: <span style={{ color: '#00b96b' }}>x{order.quantity}</span></div>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
<Tabs defaultActiveKey="1" items={[
|
||||
{
|
||||
key: '1',
|
||||
label: <span style={{ fontSize: 16 }}>我的订单</span>,
|
||||
children: (
|
||||
<List
|
||||
grid={{ gutter: 24, xs: 1, sm: 1, md: 2, lg: 2, xl: 3, xxl: 3 }}
|
||||
dataSource={orders}
|
||||
loading={loading}
|
||||
renderItem={order => (
|
||||
<List.Item>
|
||||
<Card
|
||||
hoverable
|
||||
onClick={() => showDetail(order)}
|
||||
title={<Space><span style={{ color: '#fff' }}>订单号: {order.id}</span> {getStatusTag(order.status)}</Space>}
|
||||
style={{
|
||||
background: 'rgba(0,0,0,0.6)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
marginBottom: 10,
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}
|
||||
headStyle={{ borderBottom: '1px solid rgba(255,255,255,0.1)' }}
|
||||
bodyStyle={{ padding: '20px' }}
|
||||
>
|
||||
<div style={{ color: '#ccc' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10 }}>
|
||||
<Text strong style={{ color: '#00b96b', fontSize: 16 }}>{order.total_price} 元</Text>
|
||||
<Text style={{ color: '#888' }}>{new Date(order.created_at).toLocaleString()}</Text>
|
||||
</div>
|
||||
|
||||
<div style={{ background: 'rgba(255,255,255,0.05)', padding: 15, borderRadius: 8, marginBottom: 15 }}>
|
||||
<Space align="center" size="middle">
|
||||
{order.config_image ? (
|
||||
<img
|
||||
src={order.config_image}
|
||||
alt={order.config_name}
|
||||
style={{ width: 60, height: 60, objectFit: 'cover', borderRadius: 8, border: '1px solid rgba(255,255,255,0.1)' }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
background: 'rgba(24,144,255,0.1)',
|
||||
borderRadius: 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px solid rgba(24,144,255,0.2)'
|
||||
}}>
|
||||
<InboxOutlined style={{ fontSize: 24, color: '#1890ff' }} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div style={{ color: '#fff', fontSize: 16, fontWeight: '500', marginBottom: 4 }}>{order.config_name || `商品 ID: ${order.config}`}</div>
|
||||
<div style={{ color: '#888' }}>数量: <span style={{ color: '#00b96b' }}>x{order.quantity}</span></div>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{(order.courier_name || order.tracking_number) && (
|
||||
<div style={{ background: 'rgba(24,144,255,0.1)', padding: 15, borderRadius: 8, border: '1px solid rgba(24,144,255,0.3)' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Space>
|
||||
<CarOutlined style={{ color: '#1890ff', fontSize: 18 }} />
|
||||
<Text style={{ color: '#fff', fontSize: 16 }}>物流信息</Text>
|
||||
</Space>
|
||||
<Divider style={{ margin: '8px 0', borderColor: 'rgba(255,255,255,0.1)' }} />
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#aaa' }}>快递公司:</span>
|
||||
<span style={{ color: '#fff' }}>{order.courier_name || '未知'}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#aaa' }}>快递单号:</span>
|
||||
{order.tracking_number ? (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Paragraph
|
||||
copyable={{ text: order.tracking_number, tooltips: ['复制', '已复制'] }}
|
||||
style={{ color: '#fff', fontFamily: 'monospace', fontSize: 16, margin: 0 }}
|
||||
>
|
||||
{order.tracking_number}
|
||||
</Paragraph>
|
||||
{(order.courier_name || order.tracking_number) && (
|
||||
<div style={{ background: 'rgba(24,144,255,0.1)', padding: 15, borderRadius: 8, border: '1px solid rgba(24,144,255,0.3)' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Space>
|
||||
<CarOutlined style={{ color: '#1890ff', fontSize: 18 }} />
|
||||
<Text style={{ color: '#fff', fontSize: 16 }}>物流信息</Text>
|
||||
</Space>
|
||||
<Divider style={{ margin: '8px 0', borderColor: 'rgba(255,255,255,0.1)' }} />
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#aaa' }}>快递公司:</span>
|
||||
<span style={{ color: '#fff' }}>{order.courier_name || '未知'}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#aaa' }}>快递单号:</span>
|
||||
{order.tracking_number ? (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Paragraph
|
||||
copyable={{ text: order.tracking_number, tooltips: ['复制', '已复制'] }}
|
||||
style={{ color: '#fff', fontFamily: 'monospace', fontSize: 16, margin: 0 }}
|
||||
>
|
||||
{order.tracking_number}
|
||||
</Paragraph>
|
||||
</div>
|
||||
) : (
|
||||
<span style={{ color: '#fff', fontFamily: 'monospace', fontSize: 16 }}>暂无单号</span>
|
||||
)}
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
) : (
|
||||
<span style={{ color: '#fff', fontFamily: 'monospace', fontSize: 16 }}>暂无单号</span>
|
||||
)}
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
locale={{ emptyText: <div style={{ color: '#888', padding: 40, textAlign: 'center' }}>暂无订单信息</div> }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
locale={{ emptyText: <div style={{ color: '#888', padding: 40, textAlign: 'center' }}>暂无订单信息</div> }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: <span style={{ fontSize: 16 }}>我的活动</span>,
|
||||
children: (
|
||||
<List
|
||||
grid={{ gutter: 24, xs: 1, sm: 1, md: 2, lg: 2, xl: 3, xxl: 3 }}
|
||||
dataSource={activities}
|
||||
loading={loading}
|
||||
renderItem={item => {
|
||||
const activity = item.activity_info || item.activity || item;
|
||||
return (
|
||||
<List.Item>
|
||||
<Card
|
||||
hoverable
|
||||
onClick={() => navigate(`/activity/${activity.id}`)}
|
||||
cover={
|
||||
<div style={{ height: 160, overflow: 'hidden' }}>
|
||||
<img
|
||||
alt={activity.title}
|
||||
src={activity.cover_image || activity.banner_url || 'https://via.placeholder.com/400x200'}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
style={{
|
||||
background: 'rgba(0,0,0,0.6)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
marginBottom: 10,
|
||||
backdropFilter: 'blur(10px)',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
headStyle={{ borderBottom: '1px solid rgba(255,255,255,0.1)' }}
|
||||
bodyStyle={{ padding: '16px' }}
|
||||
>
|
||||
<div style={{ color: '#ccc' }}>
|
||||
<Title level={4} style={{ color: '#fff', marginBottom: 10, fontSize: 18 }} ellipsis={{ rows: 1 }}>{activity.title}</Title>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Space>
|
||||
<CalendarOutlined style={{ color: '#00b96b' }} />
|
||||
<Text style={{ color: '#bbb' }}>{new Date(activity.start_time).toLocaleDateString()}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Space>
|
||||
<EnvironmentOutlined style={{ color: '#00f0ff' }} />
|
||||
<Text style={{ color: '#bbb' }} ellipsis>{activity.location || '线上活动'}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 16 }}>
|
||||
<Tag color="blue">{activity.status || '已报名'}</Tag>
|
||||
<Button type="primary" size="small" ghost>查看详情</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
locale={{ emptyText: <div style={{ color: '#888', padding: 40, textAlign: 'center' }}>暂无活动报名</div> }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]} />
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user