jerry 4 hónapja
szülő
commit
698ea00442

+ 1 - 1
.env.local

@@ -1,4 +1,4 @@
 # .env.local
 # 如果你的后端在本地运行,通常是 http://127.0.0.1:8000
 # 如果已经部署到服务器,填写服务器地址,如 https://api.visafly.com
-NEXT_PUBLIC_API_URL=http://45.137.220.138:8888
+NEXT_PUBLIC_API_URL=https://visafly.top

+ 1 - 1
src/app/admin/orders/page.tsx

@@ -129,7 +129,7 @@ export default function AdminOrdersPage() {
           <div className="relative flex-grow md:flex-grow-0">
             <input 
               type="text" 
-              placeholder="搜索订单号/邮箱..." 
+              placeholder="搜索订单号/邮箱/用户输入..." 
               className="w-full md:w-64 pl-10 pr-4 py-2 border rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none transition"
               value={keyword}
               onChange={e => setKeyword(e.target.value)}

+ 1 - 2
src/app/admin/tasks/page.tsx

@@ -142,7 +142,6 @@ export default function AdminTasksPage() {
             <option value="grabbed">待确认 (Grabbed)</option>
             <option value="completed">已完成 (Completed)</option>
             <option value="cancelled">已取消 (Cancelled)</option>
-            <option value="failed">失败 (Failed)</option>
           </select>
 
           {/* 搜索框与刷新按钮组 */}
@@ -150,7 +149,7 @@ export default function AdminTasksPage() {
             <div className="relative flex-1 sm:w-64">
               <input 
                 type="text" 
-                placeholder="TaskID / Route / OrderID" 
+                placeholder="Order / Route / User Inputs" 
                 className="w-full pl-9 pr-4 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none transition"
                 value={keyword}
                 onChange={e => setKeyword(e.target.value)}

+ 55 - 63
src/components/admin/orders/OrderDetailModal.tsx

@@ -1,12 +1,11 @@
 'use client';
 
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo } from 'react';
 import api from '@/lib/api'; 
 import { X, CreditCard, User, FileText, Package, Loader2, Phone, Mail } from 'lucide-react';
-// 1. 引入 LocalTime 组件
 import LocalTime from '@/components/common/LocalTime';
 
-// === 数据结构定义 ===
+// ... (接口定义保持不变) ...
 export interface OrderDetail {
   id: string;
   user_id: string;
@@ -16,7 +15,6 @@ export interface OrderDetail {
   base_currency: string;
   product_title?: string;
   product_name?: string;
-  
   user?: {
     email: string;
     nickname?: string;
@@ -24,7 +22,6 @@ export interface OrderDetail {
   };
   user_email?: string;
   user_name?: string;
-  
   applicant_name?: string;
   user_inputs?: Record<string, any>;
   payments?: PaymentRecord[]; 
@@ -41,7 +38,6 @@ export interface PaymentRecord {
   external_trade_no?: string;
 }
 
-// 用户详细信息接口
 interface UserDetail {
   id: string;
   email: string;
@@ -58,61 +54,56 @@ interface OrderDetailModalProps {
   order: OrderDetail | null;
 }
 
+// 1. 定义字段显示的优先级顺序 (对应 Schema 的 x-order)
+const FIELD_DISPLAY_ORDER = [
+  'first_name',           // 1
+  'last_name',            // 2
+  'birthday',             // 3
+  'email',                // 4
+  'phone_country_code',   // 5
+  'phone',                // 6
+  'social_media_account'  // 7
+];
+
 export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetailModalProps) {
-  // 支付记录状态
   const [paymentList, setPaymentList] = useState<PaymentRecord[]>([]);
   const [loadingPayments, setLoadingPayments] = useState(false);
-
-  // 用户详情状态
   const [userData, setUserData] = useState<UserDetail | null>(null);
   const [loadingUser, setLoadingUser] = useState(false);
 
-  // 初始化加载
   useEffect(() => {
     if (isOpen && order?.id) {
-      // 1. 加载支付记录
       fetchPayments(order.id);
-      
-      // 2. 加载用户详情 (如果有 user_id)
       if (order.user_id) {
         fetchUserDetails(order.user_id);
       } else {
         setUserData(null);
       }
     } else {
-      // 重置状态
       setPaymentList([]);
       setUserData(null);
     }
   }, [isOpen, order]);
 
-  // 获取支付记录
+  // ... (fetchPayments 和 fetchUserDetails 函数保持不变) ...
   const fetchPayments = async (orderId: string) => {
     setLoadingPayments(true);
     try {
-      const res = await api.get('/api/vas/payment/list_by_order', {
-        params: { order_id: orderId }
-      });
+      const res = await api.get('/api/vas/payment/list_by_order', { params: { order_id: orderId } });
       const list = Array.isArray(res.data.data) ? res.data.data : [];
       setPaymentList(list);
     } catch (error) {
-      console.warn("Fetch payments failed.", error);
       if (order?.payments) setPaymentList(order.payments);
     } finally {
       setLoadingPayments(false);
     }
   };
 
-  // 获取用户详情
   const fetchUserDetails = async (userId: string) => {
     setLoadingUser(true);
     try {
-      const res = await api.get('/api/user/detail', {
-        params: { user_id: userId }
-      });
-      if (res.data.data) {
-        setUserData(res.data.data);
-      }
+      const res = await api.get('/api/user/detail', { params: { user_id: userId } });
+      if (res.data.data) setUserData(res.data.data);
     } catch (error) {
       console.error("Fetch user details failed", error);
     } finally {
@@ -120,11 +111,31 @@ export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetail
     }
   };
 
-  if (!isOpen || !order) return null;
+  // 2. 使用 useMemo 对 user_inputs 进行排序
+  const sortedUserInputs = useMemo(() => {
+    if (!order?.user_inputs) return [];
+
+    return Object.entries(order.user_inputs).sort(([keyA], [keyB]) => {
+      const indexA = FIELD_DISPLAY_ORDER.indexOf(keyA);
+      const indexB = FIELD_DISPLAY_ORDER.indexOf(keyB);
+
+      // 如果两个都在列表中,按列表索引排序
+      if (indexA !== -1 && indexB !== -1) return indexA - indexB;
+      
+      // 如果只有 A 在列表中,A 排前面
+      if (indexA !== -1) return -1;
+      
+      // 如果只有 B 在列表中,B 排前面
+      if (indexB !== -1) return 1;
+      
+      // 如果都不在列表中,保持原有顺序 (或者按字母排序 return keyA.localeCompare(keyB))
+      return 0;
+    });
+  }, [order?.user_inputs]);
 
-  const formatMoney = (amount: number, currency: string) => 
-    `${(amount / 100).toFixed(2)} ${currency}`;
+  if (!isOpen || !order) return null;
 
+  const formatMoney = (amount: number, currency: string) => `${(amount / 100).toFixed(2)} ${currency}`;
   const getStatusBadge = (status: string) => {
     const styles: Record<string, string> = {
       paid: 'bg-green-100 text-green-800',
@@ -133,14 +144,9 @@ export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetail
       cancelled: 'bg-gray-100 text-gray-500',
       failed: 'bg-red-100 text-red-800',
     };
-    return (
-      <span className={`px-2 py-0.5 rounded text-xs font-bold uppercase ${styles[status] || 'bg-gray-100'}`}>
-        {status}
-      </span>
-    );
+    return <span className={`px-2 py-0.5 rounded text-xs font-bold uppercase ${styles[status] || 'bg-gray-100'}`}>{status}</span>;
   };
 
-  // 优先使用实时获取的 userData,降级使用 order 中的快照数据
   const displayEmail = userData?.email || order.user?.email || order.user_email || '未知';
   const displayNickname = userData?.nickname || order.user_name || order.user?.nickname || '-';
   const displayPhone = userData?.phone || order.user?.phone || '-';
@@ -156,7 +162,6 @@ export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetail
             <h3 className="font-bold text-gray-900 text-lg flex items-center gap-2">
               订单详情 <span className="font-mono text-blue-600">#{order.id}</span>
             </h3>
-            {/* 2. 使用 LocalTime 组件显示创建时间 */}
             <p className="text-xs text-gray-500 mt-1 flex items-center gap-1">
               创建时间: <LocalTime date={order.created_at} />
             </p>
@@ -169,10 +174,8 @@ export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetail
         {/* Content */}
         <div className="flex-1 overflow-y-auto p-6 space-y-6">
           
-          {/* 1. 基础信息卡片 (调整为两列布局) */}
+          {/* 1. 基础信息卡片 */}
           <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
-            
-            {/* 商品信息 */}
             <div className="bg-blue-50/50 p-4 rounded-lg border border-blue-100">
               <h4 className="text-sm font-bold text-blue-800 mb-3 flex items-center gap-2">
                 <Package size={16} /> 商品信息
@@ -193,14 +196,12 @@ export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetail
               </div>
             </div>
 
-            {/* 用户信息 (增强版) */}
             <div className="bg-gray-50 p-4 rounded-lg border border-gray-200 relative">
               <h4 className="text-sm font-bold text-gray-800 mb-3 flex items-center gap-2">
                 <User size={16} /> 下单用户
                 {loadingUser && <Loader2 size={12} className="animate-spin text-gray-400" />}
               </h4>
               <div className="flex items-start gap-3">
-                {/* 头像 */}
                 <div className="w-12 h-12 rounded-full bg-gray-200 flex-shrink-0 overflow-hidden border border-gray-300">
                   {displayAvatar ? (
                     <img src={displayAvatar} alt="Avatar" className="w-full h-full object-cover" />
@@ -208,49 +209,44 @@ export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetail
                     <div className="w-full h-full flex items-center justify-center text-gray-400"><User size={24}/></div>
                   )}
                 </div>
-                
-                {/* 详情字段 */}
                 <div className="flex-1 space-y-1.5 min-w-0">
                   <div className="flex items-center gap-2 text-sm text-gray-900 font-bold">
                     {displayNickname}
                     {userData?.role && <span className="text-[10px] px-1.5 py-0.5 bg-gray-200 rounded text-gray-600 uppercase font-normal">{userData.role}</span>}
                   </div>
-                  
                   <div className="flex items-center gap-2 text-xs text-gray-600" title="邮箱">
                     <Mail size={12} className="text-gray-400 flex-shrink-0" />
                     <span className="truncate">{displayEmail}</span>
                   </div>
-                  
                   <div className="flex items-center gap-2 text-xs text-gray-600" title="手机号">
                     <Phone size={12} className="text-gray-400 flex-shrink-0" />
                     <span>{displayPhone}</span>
                   </div>
-
-                  <div className="flex items-center gap-2 text-xs text-gray-400 font-mono mt-1" title="User ID">
+                  <div className="flex items-center gap-2 text-xs text-gray-400 font-mono mt-1">
                     ID: {order.user_id}
                   </div>
                 </div>
               </div>
             </div>
-
           </div>
 
-          {/* 2. 详细申请表单数据 */}
+          {/* 2. 详细申请表单数据 (已应用排序) */}
           <div className="border rounded-lg overflow-hidden">
             <div className="bg-slate-100 px-4 py-2 border-b flex items-center gap-2">
               <FileText size={16} className="text-slate-600" />
               <h4 className="font-bold text-sm text-slate-700">完整表单数据 (User Inputs)</h4>
             </div>
             <div className="p-4 bg-white grid grid-cols-2 md:grid-cols-4 gap-4">
-              {order.user_inputs && Object.entries(order.user_inputs).map(([key, value]) => (
-                <div key={key} className="break-words">
-                  <span className="block text-xs text-gray-400 uppercase mb-1 font-semibold">{key.replace(/_/g, ' ')}</span>
-                  <span className="text-sm font-medium text-gray-800 border-b border-dashed border-gray-200 pb-1 block">
-                    {value === null || value === undefined ? '-' : String(value)}
-                  </span>
-                </div>
-              ))}
-              {(!order.user_inputs || Object.keys(order.user_inputs).length === 0) && (
+              {sortedUserInputs.length > 0 ? (
+                sortedUserInputs.map(([key, value]) => (
+                  <div key={key} className="break-words">
+                    <span className="block text-xs text-gray-400 uppercase mb-1 font-semibold">{key.replace(/_/g, ' ')}</span>
+                    <span className="text-sm font-medium text-gray-800 border-b border-dashed border-gray-200 pb-1 block break-all">
+                      {value === null || value === undefined ? '-' : String(value)}
+                    </span>
+                  </div>
+                ))
+              ) : (
                 <span className="text-gray-400 text-sm col-span-4 text-center py-2">暂无表单数据</span>
               )}
             </div>
@@ -291,7 +287,6 @@ export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetail
                       </td>
                       <td className="px-4 py-2">{getStatusBadge(pay.status)}</td>
                       <td className="px-4 py-2 text-gray-500 text-xs">
-                        {/* 3. 使用 LocalTime 组件 */}
                         <LocalTime date={pay.created_at} />
                       </td>
                       <td className="px-4 py-2 text-gray-400 text-xs font-mono">
@@ -314,10 +309,7 @@ export default function OrderDetailModal({ isOpen, onClose, order }: OrderDetail
         
         {/* Footer */}
         <div className="p-4 border-t bg-gray-50 rounded-b-xl flex justify-end">
-          <button 
-            onClick={onClose}
-            className="px-6 py-2 bg-white border border-gray-300 rounded-lg text-sm font-medium hover:bg-gray-50 text-gray-700 transition"
-          >
+          <button onClick={onClose} className="px-6 py-2 bg-white border border-gray-300 rounded-lg text-sm font-medium hover:bg-gray-50 text-gray-700 transition">
             关闭
           </button>
         </div>

+ 76 - 75
src/components/admin/tasks/TaskTable.tsx

@@ -18,11 +18,8 @@ export interface VasTask {
   notify_count: number;
   config?: any;
   user_inputs?: any;
-  
-  // 两者结构一致:可能是 Object, Array 或 Null
   grabbed_history?: any; 
   meta?: any;
-
   created_at: string;
   updated_at: string;
   expire_at: string;
@@ -49,33 +46,39 @@ export default function TaskTable({ tasks, loading, onRetry, onManualConfirm, on
 
   const getUserSummary = (inputs: any) => {
     if (!inputs) return '-';
-    const name = inputs.last_name ? `${inputs.first_name} ${inputs.last_name}` : (inputs.name || inputs.applicant_name);
+    const name = inputs.social_media_account ? (inputs.social_media_account): `${inputs.first_name} ${inputs.last_name}`;
     return name || 'User Data';
   };
 
-  // === 通用摘要函数:用于表格预览 ===
+  // === 修改点 1:优化摘要显示,强制限制高度和行数 ===
   const getComplexSummary = (data: any, fallbackText: string = "No info") => {
     if (!data) return <span className="text-xs text-gray-300 italic">{fallbackText}</span>;
 
-    // 1. 如果是对象 (且不是数组)
+    let content = null;
+
+    // 1. 如果是对象
     if (typeof data === 'object' && !Array.isArray(data)) {
-      // 优先展示 slot_date (针对 history) 或 msg (针对 meta) 等关键字段
-      if (data.slot_date) return <div className="text-xs font-medium text-slate-700">Slot: {data.slot_date} {data.slot_time}</div>;
-      if (data.msg || data.message) return <div className="text-xs text-slate-600 truncate" title={data.msg}>{data.msg}</div>;
-      if (data.error) return <div className="text-xs text-red-600 truncate" title={data.error}>Error: {data.error}</div>;
-      
-      // 否则显示 JSON 字符串
-      return <div className="text-xs text-slate-500 truncate" title={JSON.stringify(data)}>{JSON.stringify(data)}</div>;
+      if (data.slot_date) content = `Slot: ${data.slot_date} ${data.slot_time}`;
+      else if (data.msg || data.message) content = data.msg || data.message;
+      else if (data.error) content = `Error: ${data.error}`;
+      else content = JSON.stringify(data);
     }
-
-    // 2. 如果是数组,显示最后一条
-    if (Array.isArray(data) && data.length > 0) {
+    // 2. 如果是数组
+    else if (Array.isArray(data) && data.length > 0) {
       const last = data[data.length - 1];
-      const text = typeof last === 'object' ? JSON.stringify(last) : String(last);
-      return <div className="text-xs text-slate-600 truncate" title={text}>{text}</div>;
+      content = typeof last === 'object' ? JSON.stringify(last) : String(last);
+    } 
+    // 3. 其他
+    else {
+      content = String(data);
     }
-
-    return <span className="text-xs text-gray-300 italic">{fallbackText}</span>;
+    
+    // 使用 line-clamp-2 限制最多显示两行,超出部分省略号
+    return (
+      <div className="text-xs text-slate-600 line-clamp-2 break-all max-w-full" title={String(content)}>
+        {content || fallbackText}
+      </div>
+    );
   };
 
   const renderStatus = (status: string) => (
@@ -98,16 +101,18 @@ export default function TaskTable({ tasks, loading, onRetry, onManualConfirm, on
       {/* Desktop View */}
       <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 text-sm text-left table-fixed">
+          {/* 修改点 2:给表格加上 fixed 布局,并指定列宽百分比,防止内容撑开 */}
+          <table className="w-full text-sm text-left table-fixed">
             <thead className="bg-slate-50 border-b border-slate-200">
               <tr>
-                <th className="w-12 px-4 py-3"></th>
-                <th className="w-[180px] px-4 py-3 font-medium text-slate-500">ID / Route</th>
-                <th className="w-[200px] px-4 py-3 font-medium text-slate-500">Order / User</th>
-                <th className="px-4 py-3 font-medium text-slate-500">业务进度 (History)</th>
-                <th className="px-4 py-3 font-medium text-slate-500">系统调试 (Meta)</th>
-                <th className="w-[120px] px-4 py-3 font-medium text-slate-500">状态</th>
-                <th className="w-[150px] px-4 py-3 font-medium text-slate-500 text-right">操作</th>
+                <th className="w-10 px-4 py-3"></th>
+                <th className="w-[140px] px-2 py-3 font-medium text-slate-500">ID / Route</th>
+                <th className="w-[180px] px-2 py-3 font-medium text-slate-500">Order / User</th>
+                {/* 动态列如果不设宽度,Table-fixed 会自动平分剩余空间,这里给个大概比例 */}
+                <th className="w-[20%] px-2 py-3 font-medium text-slate-500">业务进度 (History)</th>
+                <th className="w-[20%] px-2 py-3 font-medium text-slate-500">系统调试 (Meta)</th>
+                <th className="w-[100px] px-2 py-3 font-medium text-slate-500">状态</th>
+                <th className="w-[120px] px-2 py-3 font-medium text-slate-500 text-right">操作</th>
               </tr>
             </thead>
             {tasks.map((task) => {
@@ -119,37 +124,37 @@ export default function TaskTable({ tasks, loading, onRetry, onManualConfirm, on
                     <td className="px-4 py-4 text-slate-400 align-top">
                       {isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
                     </td>
-                    <td className="px-4 py-4 align-top">
+                    <td className="px-2 py-4 align-top">
                       <div className="font-mono text-slate-500 text-xs mb-1">#{task.id}</div>
-                      <span className="font-mono text-[10px] text-blue-700 bg-blue-50 px-2 py-1 rounded border border-blue-100 inline-block break-all">
+                      <span className="font-mono text-[10px] text-blue-700 bg-blue-50 px-2 py-1 rounded border border-blue-100 inline-block break-all max-w-full truncate">
                         {task.routing_key}
                       </span>
                     </td>
-                    <td className="px-4 py-4 align-top">
-                      <div className="text-slate-900 font-bold text-xs mb-1 break-all font-mono">{task.order_id}</div>
-                      <div className="text-xs text-slate-500 flex items-center gap-1 truncate">
+                    <td className="px-2 py-4 align-top">
+                      <div className="text-slate-900 font-bold text-xs mb-1 break-all font-mono truncate" title={task.order_id}>{task.order_id}</div>
+                      <div className="text-xs text-slate-500 flex items-center gap-1 truncate" title={getUserSummary(task.user_inputs)}>
                         <User size={12} className="text-slate-400 flex-shrink-0" /> {getUserSummary(task.user_inputs)}
                       </div>
                     </td>
                     
                     {/* History Column */}
-                    <td className="px-4 py-4 align-top">
+                    <td className="px-2 py-4 align-top overflow-hidden">
                       {getComplexSummary(task.grabbed_history, 'No history')}
                     </td>
 
                     {/* Meta Column */}
-                    <td className="px-4 py-4 align-top">
+                    <td className="px-2 py-4 align-top overflow-hidden">
                       {getComplexSummary(task.meta, 'No logs')}
                       <div className="text-[10px] text-slate-400 mt-1">
                          <LocalTime date={task.updated_at} options={{hour:'2-digit', minute:'2-digit', second:'2-digit'}} />
                       </div>
                     </td>
 
-                    <td className="px-4 py-4 align-top">
+                    <td className="px-2 py-4 align-top">
                       {renderStatus(task.status)}
                       {task.attempt_count > 0 && <div className="text-[10px] text-slate-400 mt-1 pl-1">Retry: {task.attempt_count}</div>}
                     </td>
-                    <td className="px-4 py-4 text-right align-top" onClick={(e) => e.stopPropagation()}>
+                    <td className="px-2 py-4 text-right align-top" onClick={(e) => e.stopPropagation()}>
                       <div className="flex justify-end gap-1 flex-wrap">
                         <button onClick={() => onEdit(task)} className="p-1.5 rounded text-indigo-600 hover:bg-indigo-50 border border-transparent hover:border-indigo-100" title="编辑"><Edit size={16} /></button>
                         <button onClick={() => onRetry(task.id)} className="p-1.5 rounded text-blue-600 hover:bg-blue-50 border border-transparent hover:border-blue-100" title="重置"><RotateCcw size={16} /></button>
@@ -160,7 +165,7 @@ export default function TaskTable({ tasks, loading, onRetry, onManualConfirm, on
                   
                   {isExpanded && (
                     <tr className="bg-slate-50 shadow-inner border-t border-slate-100">
-                      <td colSpan={7} className="px-4 py-4">
+                      <td colSpan={7} className="px-4 py-4 w-full">
                          <TaskDetailContent task={task} />
                       </td>
                     </tr>
@@ -174,19 +179,21 @@ export default function TaskTable({ tasks, loading, onRetry, onManualConfirm, on
 
       {/* Mobile View */}
       <div className="md:hidden space-y-4">
+        {/* Mobile view logic keeps mostly same but ensures break-all is used */}
         {tasks.map((task) => {
           const isExpanded = expandedRows.has(task.id);
           return (
             <div key={task.id} className="bg-white p-4 rounded-lg shadow-sm border border-slate-200">
+              {/* ... (Header Section same as before) ... */}
               <div className="flex justify-between items-start mb-3">
-                <div className="flex flex-col gap-1">
+                <div className="flex flex-col gap-1 overflow-hidden">
                   <div className="flex items-center gap-2">
-                    <span className="text-sm font-bold text-slate-900">Task #{task.id}</span>
+                    <span className="text-sm font-bold text-slate-900">#{task.id}</span>
                     {renderStatus(task.status)}
                   </div>
-                  <span className="text-xs font-mono text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded w-fit">{task.routing_key}</span>
+                  <span className="text-xs font-mono text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded w-fit truncate max-w-full">{task.routing_key}</span>
                 </div>
-                <div className="flex gap-2">
+                <div className="flex gap-2 flex-shrink-0">
                    <button onClick={() => onRetry(task.id)} className="p-2 bg-blue-50 text-blue-600 rounded-lg active:scale-95"><RotateCcw size={18}/></button>
                    <button onClick={() => onManualConfirm(task.id)} className="p-2 bg-green-50 text-green-600 rounded-lg active:scale-95"><CheckCircle size={18}/></button>
                 </div>
@@ -195,21 +202,13 @@ export default function TaskTable({ tasks, loading, onRetry, onManualConfirm, on
               <div className="bg-slate-50 p-3 rounded-lg border border-slate-100 text-sm space-y-2 mb-3">
                  <div className="flex justify-between">
                    <span className="text-xs text-slate-400">Order ID</span>
-                   <span className="font-mono font-medium">{task.order_id}</span>
+                   <span className="font-mono font-medium truncate ml-2">{task.order_id}</span>
                  </div>
-                 <div className="flex justify-between">
-                   <span className="text-xs text-slate-400">Applicant</span>
-                   <span className="font-medium truncate max-w-[150px]">{getUserSummary(task.user_inputs)}</span>
-                 </div>
-                 {/* Mobile Preview for History/Meta */}
+                 {/* ... Mobile Content ... */}
                  <div className="pt-2 border-t border-slate-200 mt-1 grid grid-cols-1 gap-1">
-                   <div className="flex justify-between items-start text-xs">
-                      <span className="text-slate-400">History:</span>
-                      <div className="text-right flex-1 ml-2">{getComplexSummary(task.grabbed_history)}</div>
-                   </div>
-                   <div className="flex justify-between items-start text-xs">
-                      <span className="text-slate-400">Meta:</span>
-                      <div className="text-right flex-1 ml-2">{getComplexSummary(task.meta)}</div>
+                   <div className="flex flex-col text-xs">
+                      <span className="text-slate-400 mb-1">History:</span>
+                      <div className="bg-white p-1 rounded border border-slate-200">{getComplexSummary(task.grabbed_history)}</div>
                    </div>
                  </div>
               </div>
@@ -239,18 +238,18 @@ export default function TaskTable({ tasks, loading, onRetry, onManualConfirm, on
   );
 }
 
-// === 核心修改:通用的数据可视化组件 (支持 Meta 和 History) ===
 function DataVisualizer({ data, emptyText, isDark = false }: { data: any, emptyText: string, isDark?: boolean }) {
   if (!data) return <span className={`italic ${isDark ? 'text-slate-500' : 'text-slate-400'}`}>{emptyText}</span>;
 
-  // 1. 如果是对象:渲染为 Key-Value 列表
+  // 1. 对象 Key-Value
   if (typeof data === 'object' && !Array.isArray(data)) {
     return (
       <div className="space-y-1">
           {Object.entries(data).map(([key, val]) => (
             <div key={key} className={`flex flex-col border-b pb-1 last:border-0 ${isDark ? 'border-slate-800' : 'border-slate-50'}`}>
               <span className={`text-[10px] uppercase font-bold ${isDark ? 'text-slate-500' : 'text-slate-400'}`}>{key.replace(/_/g, ' ')}</span>
-              <span className={`break-all font-medium ${isDark ? 'text-slate-300' : 'text-slate-700'}`}>
+              {/* 强制换行 break-all */}
+              <span className={`break-all whitespace-pre-wrap font-medium ${isDark ? 'text-slate-300' : 'text-slate-700'}`}>
                 {typeof val === 'object' ? JSON.stringify(val) : String(val)}
               </span>
             </div>
@@ -259,52 +258,54 @@ function DataVisualizer({ data, emptyText, isDark = false }: { data: any, emptyT
     );
   }
 
-  // 2. 如果是数组:渲染为列表
+  // 2. 数组列表
   if (Array.isArray(data)) {
     if (data.length === 0) return <span className={`italic ${isDark ? 'text-slate-500' : 'text-slate-400'}`}>{emptyText}</span>;
     return (
       <div className="space-y-1">
         {data.map((line, i) => (
           <div key={i} className={`flex gap-2 border-b pb-1 last:border-0 ${isDark ? 'border-slate-800' : 'border-slate-50'}`}>
-            <span className={`select-none w-5 text-right ${isDark ? 'text-slate-600' : 'text-slate-300'}`}>{i + 1}</span>
-            <span className="break-all">{typeof line === 'object' ? JSON.stringify(line) : String(line)}</span>
+            <span className={`select-none w-5 text-right flex-shrink-0 ${isDark ? 'text-slate-600' : 'text-slate-300'}`}>{i + 1}</span>
+            {/* 强制换行 break-all */}
+            <span className="break-all whitespace-pre-wrap">{typeof line === 'object' ? JSON.stringify(line) : String(line)}</span>
           </div>
         ))}
       </div>
     );
   }
 
-  // 3. 其他:直接渲染字符串
-  return <span className="break-all">{String(data)}</span>;
+  return <span className="break-all whitespace-pre-wrap">{String(data)}</span>;
 }
 
-// === 详情内容组件 ===
+// === 修改点 3:详情布局增加 min-w-0 防止 flex 子项被撑大 ===
 function TaskDetailContent({ task }: { task: VasTask }) {
   return (
-    <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 text-xs">
+    // 添加 w-full 确保在 table cell 内部占满宽度
+    <div className="w-full grid grid-cols-1 lg:grid-cols-3 gap-6 text-xs">
       
-      {/* Col 1: Config & Inputs */}
-      <div className="space-y-4">
-        <div className="bg-white border border-slate-200 rounded-lg overflow-hidden">
+      {/* Col 1: Config & Inputs - 增加 min-w-0 */}
+      <div className="space-y-4 min-w-0">
+        <div className="bg-white border border-slate-200 rounded-lg overflow-hidden flex flex-col">
           <div className="px-3 py-2 bg-slate-100 border-b border-slate-200 font-bold text-slate-600 flex items-center gap-2">
             <FileJson size={14} /> Config
           </div>
-          <pre className="p-3 overflow-x-auto text-slate-600 font-mono max-h-[200px] whitespace-pre-wrap break-all">
+          {/* 限制最大高度,如果JSON很长则滚动 */}
+          <pre className="p-3 overflow-auto text-slate-600 font-mono max-h-[200px] whitespace-pre-wrap break-all">
             {JSON.stringify(task.config, null, 2)}
           </pre>
         </div>
-        <div className="bg-white border border-slate-200 rounded-lg overflow-hidden">
+        <div className="bg-white border border-slate-200 rounded-lg overflow-hidden flex flex-col">
           <div className="px-3 py-2 bg-slate-100 border-b border-slate-200 font-bold text-blue-600 flex items-center gap-2">
             <User size={14} /> User Inputs
           </div>
-          <pre className="p-3 overflow-x-auto text-slate-600 font-mono max-h-[200px] whitespace-pre-wrap break-all">
+          <pre className="p-3 overflow-auto text-slate-600 font-mono max-h-[200px] whitespace-pre-wrap break-all">
             {JSON.stringify(task.user_inputs, null, 2)}
           </pre>
         </div>
       </div>
 
-      {/* Col 2: History (Light Theme) */}
-      <div className="bg-white border border-slate-200 rounded-lg overflow-hidden flex flex-col h-full max-h-[400px]">
+      {/* Col 2: History - 增加 min-w-0 */}
+      <div className="min-w-0 bg-white border border-slate-200 rounded-lg overflow-hidden flex flex-col h-full max-h-[400px]">
         <div className="px-3 py-2 bg-green-50 border-b border-green-100 font-bold text-green-800 flex items-center gap-2">
           <History size={14} /> Grabbed Info / History
         </div>
@@ -313,8 +314,8 @@ function TaskDetailContent({ task }: { task: VasTask }) {
         </div>
       </div>
 
-      {/* Col 3: Meta/Debug (Dark Theme) */}
-      <div className="bg-slate-900 border border-slate-800 rounded-lg overflow-hidden flex flex-col h-full max-h-[400px] text-slate-300">
+      {/* Col 3: Meta - 增加 min-w-0 */}
+      <div className="min-w-0 bg-slate-900 border border-slate-800 rounded-lg overflow-hidden flex flex-col h-full max-h-[400px] text-slate-300">
         <div className="px-3 py-2 bg-slate-950 border-b border-slate-800 font-bold text-slate-100 flex items-center gap-2">
           <Terminal size={14} /> Debug Logs (Meta)
         </div>

+ 38 - 8
src/components/dashboard/UserOrderDetailModal.tsx

@@ -1,10 +1,9 @@
 'use client';
 
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo } from 'react';
 import { X, CreditCard, FileText, Package, Check, Clock } from 'lucide-react';
 import api from '@/lib/api';
 import { useLanguage } from '@/lib/i18n/LanguageContext';
-// 1. 引入 LocalTime 组件
 import LocalTime from '@/components/common/LocalTime';
 
 export interface UserOrder {
@@ -24,6 +23,17 @@ interface UserOrderDetailModalProps {
   order: UserOrder | null;
 }
 
+// 1. 定义字段显示的优先级顺序 (对应 Schema 的 x-order)
+const FIELD_DISPLAY_ORDER = [
+  'first_name',           // 1
+  'last_name',            // 2
+  'birthday',             // 3
+  'email',                // 4
+  'phone_country_code',   // 5
+  'phone',                // 6
+  'social_media_account'  // 7
+];
+
 export default function UserOrderDetailModal({ isOpen, onClose, order }: UserOrderDetailModalProps) {
   const { t } = useLanguage();
 
@@ -38,6 +48,28 @@ export default function UserOrderDetailModal({ isOpen, onClose, order }: UserOrd
     }
   }, [isOpen, order]);
 
+  // 2. 使用 useMemo 对 user_inputs 进行排序
+  const sortedUserInputs = useMemo(() => {
+    if (!order?.user_inputs) return [];
+
+    return Object.entries(order.user_inputs).sort(([keyA], [keyB]) => {
+      const indexA = FIELD_DISPLAY_ORDER.indexOf(keyA);
+      const indexB = FIELD_DISPLAY_ORDER.indexOf(keyB);
+
+      // 如果两个都在列表中,按列表索引排序
+      if (indexA !== -1 && indexB !== -1) return indexA - indexB;
+      
+      // 如果只有 A 在列表中,A 排前面
+      if (indexA !== -1) return -1;
+      
+      // 如果只有 B 在列表中,B 排前面
+      if (indexB !== -1) return 1;
+      
+      // 如果都不在列表中,保持原有顺序
+      return 0;
+    });
+  }, [order?.user_inputs]);
+
   const fetchPayments = async (orderId: string) => {
     setLoadingPayments(true);
     try {
@@ -99,7 +131,6 @@ export default function UserOrderDetailModal({ isOpen, onClose, order }: UserOrd
                 <h4 className="font-bold text-gray-900">{order.product_title || order.product_name || t('order.unknown_service')}</h4>
                 <div className="text-sm text-gray-500 mt-1 flex items-center gap-1">
                   <Clock size={12} />
-                  {/* 2. 使用 LocalTime 组件 */}
                   {t('order.created_at')}: <LocalTime date={order.created_at} className="ml-1" />
                 </div>
               </div>
@@ -114,19 +145,19 @@ export default function UserOrderDetailModal({ isOpen, onClose, order }: UserOrd
             </div>
           </div>
 
-          {/* 2. 申请资料 */}
+          {/* 2. 申请资料 (已排序) */}
           <div className="border rounded-xl overflow-hidden">
             <div className="bg-slate-50 px-4 py-3 border-b flex items-center gap-2">
               <FileText size={18} className="text-slate-600" />
               <h4 className="font-bold text-sm text-slate-800">{t('order.application_data')}</h4>
             </div>
             <div className="p-5 bg-white">
-              {order.user_inputs && Object.keys(order.user_inputs).length > 0 ? (
+              {sortedUserInputs.length > 0 ? (
                 <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-4">
-                  {Object.entries(order.user_inputs).map(([key, value]) => (
+                  {sortedUserInputs.map(([key, value]) => (
                     <div key={key} className="border-b border-slate-100 pb-2">
                       <span className="block text-xs font-bold text-slate-400 uppercase mb-1">{key.replace(/_/g, ' ')}</span>
-                      <span className="block text-sm text-slate-800 font-medium break-words">
+                      <span className="block text-sm text-slate-800 font-medium break-words break-all">
                         {String(value)}
                       </span>
                     </div>
@@ -152,7 +183,6 @@ export default function UserOrderDetailModal({ isOpen, onClose, order }: UserOrd
                   <div key={pay.id} className="p-4 flex justify-between items-center text-sm">
                     <div>
                       <div className="font-bold text-slate-700 capitalize">{pay.provider} ({pay.channel})</div>
-                      {/* 3. 使用 LocalTime 组件 */}
                       <div className="text-xs text-gray-400 mt-0.5">
                         <LocalTime date={pay.created_at} />
                       </div>