| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- 'use client';
- import { Eye, XCircle, User, Box, Edit, Clock, FileText, Wallet } from 'lucide-react';
- import { OrderDetail } from './OrderDetailModal';
- import LocalTime from '@/components/common/LocalTime';
- interface OrderTableProps {
- orders: OrderDetail[];
- loading: boolean;
- onCancel: (orderId: string) => void;
- onViewDetail: (order: OrderDetail) => void;
- onEdit: (order: OrderDetail) => void;
- onCheckPayments: (order: OrderDetail) => void; // 新增:查看/核销支付记录
- }
- export default function OrderTable({
- orders,
- loading,
- onCancel,
- onViewDetail,
- onEdit,
- onCheckPayments
- }: OrderTableProps) {
-
- if (loading) {
- return (
- <div className="bg-white rounded-lg shadow p-12 text-center border border-slate-200">
- <div className="text-gray-500 text-sm">加载订单数据中...</div>
- </div>
- );
- }
- if (orders.length === 0) {
- return (
- <div className="bg-white rounded-lg shadow p-12 text-center border border-slate-200">
- <div className="text-gray-500 text-sm">暂无订单记录</div>
- </div>
- );
- }
- // 状态颜色映射
- const getStatusColor = (status: string) => {
- switch (status) {
- case 'paid': return 'bg-green-100 text-green-800 border-green-200';
- case 'succeeded': return 'bg-green-100 text-green-800 border-green-200';
- case 'pending': return 'bg-yellow-100 text-yellow-800 border-yellow-200';
- case 'cancelled': return 'bg-red-50 text-red-600 border-red-100';
- case 'failed': return 'bg-red-100 text-red-800 border-red-200';
- default: return 'bg-gray-100 text-gray-800 border-gray-200';
- }
- };
- // 辅助函数:是否可以取消
- const canCancel = (status: string) => status !== 'cancelled' && status !== 'completed' && status !== 'failed';
- return (
- <div className="space-y-4">
-
- {/* =========================== */}
- {/* 1. Desktop View (Table) */}
- {/* =========================== */}
- <div className="hidden md:block bg-white rounded-lg shadow overflow-hidden border border-slate-200">
- <div className="overflow-x-auto">
- <table className="min-w-full divide-y divide-slate-200">
- <thead className="bg-slate-50">
- <tr>
- <th className="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">订单号 / 创建时间</th>
- <th className="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">商品信息</th>
- <th className="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">用户信息</th>
- <th className="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">金额</th>
- <th className="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">状态</th>
- <th className="px-6 py-3 text-right text-xs font-medium text-slate-500 uppercase tracking-wider">操作</th>
- </tr>
- </thead>
- <tbody className="bg-white divide-y divide-slate-200">
- {orders.map((order) => (
- <tr key={order.id} className="hover:bg-slate-50 transition-colors">
- <td className="px-6 py-4 whitespace-nowrap">
- <div className="text-sm font-medium text-slate-900 font-mono">{order.id}</div>
- <div className="text-xs text-slate-400 mt-1">
- <LocalTime date={order.created_at} />
- </div>
- </td>
- <td className="px-6 py-4">
- <div className="flex items-center">
- <Box size={16} className="text-slate-400 mr-2 flex-shrink-0" />
- <span className="text-sm text-slate-700 truncate max-w-[150px]" title={order.product_title}>
- {order.product_name || order.product_title || '未知商品'}
- </span>
- </div>
- </td>
- <td className="px-6 py-4">
- <div className="flex items-center">
- <User size={16} className="text-slate-400 mr-2 flex-shrink-0" />
- <div className="flex flex-col">
- <span className="text-sm text-slate-700 font-medium">{order.user_name || order.applicant_name || '未填写'}</span>
- <span className="text-xs text-slate-400">{order.user_email || order.user_id}</span>
- </div>
- </div>
- </td>
- <td className="px-6 py-4 whitespace-nowrap text-sm text-slate-900 font-bold font-mono">
- {(order.base_amount / 100).toFixed(2)} {order.base_currency}
- </td>
- <td className="px-6 py-4 whitespace-nowrap">
- <span className={`px-2 py-0.5 inline-flex text-xs leading-5 font-semibold rounded-full border ${getStatusColor(order.status)}`}>
- {order.status}
- </span>
- </td>
- <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
- <div className="flex justify-end gap-2">
-
- {/* 待支付状态显示人工核销按钮 */}
- {order.status === 'pending' && (
- <button
- onClick={() => onCheckPayments(order)}
- className="group flex items-center justify-center p-1.5 rounded-md text-emerald-600 hover:text-emerald-900 bg-emerald-50 hover:bg-emerald-100 transition border border-transparent hover:border-emerald-200"
- title="查看/核销支付记录 (处理漏单)"
- >
- <Wallet size={16} />
- </button>
- )}
- <button onClick={() => onEdit(order)} className="p-1.5 rounded-md text-indigo-600 hover:bg-indigo-50 border border-transparent hover:border-indigo-200" title="修改"><Edit size={16} /></button>
- <button onClick={() => onViewDetail(order)} className="p-1.5 rounded-md text-blue-600 hover:bg-blue-50 border border-transparent hover:border-blue-200" title="详情"><Eye size={16} /></button>
-
- {canCancel(order.status) && (
- <button onClick={() => onCancel(order.id)} className="p-1.5 rounded-md text-red-600 hover:bg-red-50 border border-transparent hover:border-red-200" title="取消"><XCircle size={16} /></button>
- )}
- </div>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- </div>
- {/* =========================== */}
- {/* 2. Mobile View (Cards) */}
- {/* =========================== */}
- <div className="md:hidden space-y-4">
- {orders.map((order) => (
- <div key={order.id} className="bg-white p-4 rounded-lg shadow-sm border border-slate-200">
-
- {/* Header: ID & Status */}
- <div className="flex justify-between items-start mb-3 border-b border-slate-100 pb-2">
- <div className="flex items-center gap-2 text-slate-500">
- <FileText size={14} />
- <span className="text-xs font-mono font-bold">{order.id}</span>
- </div>
- <span className={`px-2 py-0.5 text-xs font-bold rounded border ${getStatusColor(order.status)}`}>
- {order.status.toUpperCase()}
- </span>
- </div>
- {/* Info Grid */}
- <div className="space-y-3">
- {/* Product */}
- <div className="flex items-start gap-3">
- <div className="p-1.5 bg-blue-50 rounded text-blue-600 shrink-0 mt-0.5">
- <Box size={16} />
- </div>
- <div>
- <div className="text-sm font-bold text-slate-800 line-clamp-2">
- {order.product_name || order.product_title || '未知商品'}
- </div>
- <div className="text-xs text-slate-500 mt-1 flex items-center gap-1">
- <Clock size={12} />
- <LocalTime date={order.created_at} />
- </div>
- </div>
- </div>
- {/* User & Price */}
- <div className="bg-slate-50 rounded p-3 grid grid-cols-2 gap-2 text-sm border border-slate-100">
- <div>
- <span className="text-xs text-slate-400 block mb-0.5">申请人</span>
- <div className="flex items-center gap-1.5 text-slate-700">
- <User size={12} />
- <span className="truncate font-medium">{order.user_name || order.applicant_name || '-'}</span>
- </div>
- </div>
- <div className="text-right">
- <span className="text-xs text-slate-400 block mb-0.5">金额</span>
- <div className="font-mono font-bold text-slate-900">
- {(order.base_amount / 100).toFixed(2)} <span className="text-xs font-normal">{order.base_currency}</span>
- </div>
- </div>
- </div>
- </div>
- {/* Actions */}
- <div className="grid grid-cols-4 gap-2 mt-4 pt-3 border-t border-slate-100">
-
- {order.status === 'pending' ? (
- <>
- <button onClick={() => onCheckPayments(order)} className="col-span-1 flex items-center justify-center py-2 bg-emerald-50 text-emerald-700 rounded-lg text-sm font-medium active:scale-95 transition border border-emerald-200">
- <Wallet size={16} />
- </button>
- <button onClick={() => onEdit(order)} className="col-span-1 flex items-center justify-center py-2 bg-indigo-50 text-indigo-700 rounded-lg text-sm font-medium active:scale-95 transition border border-indigo-200">
- <Edit size={16} />
- </button>
- <button onClick={() => onViewDetail(order)} className="col-span-1 flex items-center justify-center py-2 bg-blue-50 text-blue-700 rounded-lg text-sm font-medium active:scale-95 transition border border-blue-200">
- <Eye size={16} />
- </button>
- <button onClick={() => onCancel(order.id)} className="col-span-1 flex items-center justify-center py-2 bg-red-50 text-red-600 rounded-lg text-sm font-medium active:scale-95 transition border border-red-200">
- <XCircle size={16} />
- </button>
- </>
- ) : (
- <>
- <button onClick={() => onEdit(order)} className="col-span-2 flex items-center justify-center gap-1 py-2 bg-indigo-50 text-indigo-700 rounded-lg text-sm font-medium active:scale-95 transition">
- <Edit size={16} /> 编辑
- </button>
- <button onClick={() => onViewDetail(order)} className="col-span-2 flex items-center justify-center gap-1 py-2 bg-blue-50 text-blue-700 rounded-lg text-sm font-medium active:scale-95 transition">
- <Eye size={16} /> 详情
- </button>
- </>
- )}
- </div>
- </div>
- ))}
- </div>
- </div>
- );
- }
|