| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- 'use client';
- import { Eye, Clock, CheckCircle, XCircle, AlertCircle, HelpCircle, User, FileText } from 'lucide-react';
- // 1. 引入 LocalTime 组件
- import LocalTime from '@/components/common/LocalTime';
- // 定义工单数据结构 (对应 API: VasTicketOut)
- export interface AdminTicket {
- id: number;
- order_id: string;
- user_id: string;
- type: string; // refund, dispute, change_request
- status: string; // pending, info_required, resolved, rejected
- reason: string;
- admin_comment?: string;
- created_at: string;
- updated_at: string;
- }
- interface TicketTableProps {
- tickets: AdminTicket[];
- loading: boolean;
- onViewDetail: (ticket: AdminTicket) => void;
- }
- export default function TicketTable({ tickets, loading, onViewDetail }: TicketTableProps) {
-
- 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 (tickets.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 getStatusBadge = (status: string) => {
- switch (status) {
- case 'pending':
- return <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800"><Clock size={12}/> 待处理</span>;
- case 'info_required':
- return <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"><HelpCircle size={12}/> 需补充资料</span>;
- case 'resolved':
- return <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800"><CheckCircle size={12}/> 已解决</span>;
- case 'rejected':
- return <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800"><XCircle size={12}/> 已拒绝</span>;
- default:
- return <span className="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">{status}</span>;
- }
- };
- const getTypeText = (type: string) => {
- const map: Record<string, string> = {
- refund: '退款申请',
- dispute: '交易纠纷',
- change_request: '变更请求'
- };
- return map[type] || type;
- };
- 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">ID</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-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">
- {tickets.map((ticket) => (
- <tr key={ticket.id} className="hover:bg-slate-50 transition-colors">
- <td className="px-6 py-4 text-sm font-mono text-gray-500">#{ticket.id}</td>
- <td className="px-6 py-4 text-sm font-bold text-gray-800">{getTypeText(ticket.type)}</td>
- <td className="px-6 py-4">
- <div className="text-sm font-medium text-blue-600 font-mono">{ticket.order_id}</div>
- <div className="text-xs text-gray-500 mt-0.5">{ticket.user_id}</div>
- </td>
- <td className="px-6 py-4">
- <div className="text-sm text-gray-600 max-w-[200px] truncate" title={ticket.reason}>
- {ticket.reason}
- </div>
- </td>
- <td className="px-6 py-4 whitespace-nowrap">
- {getStatusBadge(ticket.status)}
- </td>
- <td className="px-6 py-4 text-sm text-gray-500 whitespace-nowrap">
- <LocalTime date={ticket.created_at} />
- </td>
- <td className="px-6 py-4 text-right">
- <button
- onClick={() => onViewDetail(ticket)}
- className="text-blue-600 hover:text-blue-900 inline-flex items-center text-sm font-medium"
- >
- <Eye size={16} className="mr-1" /> 处理/详情
- </button>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- </div>
- {/* ======================= */}
- {/* 2. Mobile View (Cards) - 仅在小屏幕显示 */}
- {/* ======================= */}
- <div className="md:hidden space-y-4">
- {tickets.map((ticket) => (
- <div key={ticket.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">
- <div className="flex items-center gap-2">
- <span className="text-sm font-mono text-slate-500 font-bold">#{ticket.id}</span>
- <span className="text-sm font-bold text-slate-900">{getTypeText(ticket.type)}</span>
- </div>
- <div>{getStatusBadge(ticket.status)}</div>
- </div>
- {/* Info Grid */}
- <div className="space-y-2 text-sm text-slate-600 mb-4 bg-slate-50 p-3 rounded-lg border border-slate-100">
- <div className="flex justify-between">
- <span className="flex items-center text-xs text-slate-400">
- <FileText size={12} className="mr-1"/> 订单号
- </span>
- <span className="font-mono text-blue-600">{ticket.order_id}</span>
- </div>
- <div className="flex justify-between">
- <span className="flex items-center text-xs text-slate-400">
- <User size={12} className="mr-1"/> 用户
- </span>
- <span className="truncate max-w-[150px]">{ticket.user_id}</span>
- </div>
- <div className="flex justify-between">
- <span className="flex items-center text-xs text-slate-400">
- <Clock size={12} className="mr-1"/> 时间
- </span>
- <span><LocalTime date={ticket.created_at} /></span>
- </div>
-
- {/* 描述摘要 */}
- <div className="pt-2 border-t border-slate-200 mt-2">
- <span className="text-xs text-slate-400 block mb-1">描述:</span>
- <p className="text-slate-800 line-clamp-2">{ticket.reason}</p>
- </div>
- </div>
- {/* Action Button */}
- <button
- onClick={() => onViewDetail(ticket)}
- className="w-full flex items-center justify-center py-2.5 bg-blue-600 text-white rounded-lg text-sm font-bold active:scale-95 transition-transform shadow-sm shadow-blue-200"
- >
- <Eye size={16} className="mr-2" /> 处理工单
- </button>
- </div>
- ))}
- </div>
- </div>
- );
- }
|