|
|
@@ -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>
|