feat(ui): theme-adapt all page/dialog components for dark mode
Replace hardcoded Tailwind colors (bg-white, text-slate-*, border-slate-*, bg-gray-*, text-gray-*) with shadcn semantic tokens (bg-card, text-foreground, text-muted-foreground, border-border, bg-muted, bg-accent, border-input) across 23 renderer components. This enables proper light/dark mode rendering for all pages and dialogs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -142,7 +142,7 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
</div>
|
||||
<p
|
||||
id="execution-dialog-progress-desc"
|
||||
className="text-sm text-gray-600 mb-4"
|
||||
className="text-sm text-muted-foreground mb-4"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
@@ -161,7 +161,7 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
<CheckCircle className="w-12 h-12 text-green-500" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{dryRun
|
||||
? '预览模式 - 未实际删除数据'
|
||||
: hasErrors
|
||||
@@ -183,9 +183,9 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-around py-4 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<div className="flex justify-around py-4 bg-muted rounded-lg border border-border">
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-xs text-gray-600">订单进度</span>
|
||||
<span className="text-xs text-muted-foreground">订单进度</span>
|
||||
<span className="text-lg font-semibold text-blue-600">
|
||||
{Math.min(progress!.currentOrderIndex, progress!.totalOrders)} /{' '}
|
||||
{progress!.totalOrders}
|
||||
@@ -193,14 +193,14 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
</div>
|
||||
{progress!.totalMaterialsInOrder > 0 && (
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-xs text-gray-600">物料进度</span>
|
||||
<span className="text-xs text-muted-foreground">物料进度</span>
|
||||
<span className="text-lg font-semibold text-blue-600">
|
||||
{progress!.currentMaterialIndex} / {progress!.totalMaterialsInOrder}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-xs text-gray-600">总进度</span>
|
||||
<span className="text-xs text-muted-foreground">总进度</span>
|
||||
<span className="text-lg font-semibold text-blue-600">
|
||||
{Math.round(progress?.progress || 0)}%
|
||||
</span>
|
||||
@@ -210,8 +210,8 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
{progress?.currentOrderNumber && (
|
||||
<div className="flex items-center justify-center gap-2 p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<Package size={14} className="text-blue-600" />
|
||||
<span className="text-sm text-gray-600">当前订单:</span>
|
||||
<span className="text-sm font-medium text-gray-900 font-mono">
|
||||
<span className="text-sm text-muted-foreground">当前订单:</span>
|
||||
<span className="text-sm font-medium text-foreground font-mono">
|
||||
{progress.currentOrderNumber}
|
||||
</span>
|
||||
</div>
|
||||
@@ -220,7 +220,7 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
{elapsedTime && (
|
||||
<div className="flex flex-col items-center gap-2 p-3 bg-blue-50 rounded-lg border border-blue-200 mb-2">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-600">已运行</span>
|
||||
<span className="text-muted-foreground">已运行</span>
|
||||
<span className="font-semibold text-blue-600">{elapsedTime.formatted}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,15 +229,15 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
{estimatedTime && (
|
||||
<div className="flex flex-col items-center gap-2 p-3 bg-green-50 rounded-lg border border-green-200">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-600">预估还要</span>
|
||||
<span className="text-muted-foreground">预估还要</span>
|
||||
<span className="font-semibold text-green-600">
|
||||
{estimatedTime.remainingMinutes} 分钟
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-600">将会在</span>
|
||||
<span className="text-muted-foreground">将会在</span>
|
||||
<span className="font-semibold text-green-600">{estimatedTime.formattedTime}</span>
|
||||
<span className="text-gray-600">执行完毕</span>
|
||||
<span className="text-muted-foreground">执行完毕</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -246,43 +246,43 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
// Result View Content
|
||||
<>
|
||||
<div className="grid grid-cols-3 gap-3 mb-4">
|
||||
<div className="bg-gray-50 rounded-lg p-3 flex items-center gap-3 border border-gray-200">
|
||||
<div className="bg-muted rounded-lg p-3 flex items-center gap-3 border border-border">
|
||||
<div className="w-9 h-9 rounded-lg bg-blue-50 flex items-center justify-center flex-shrink-0">
|
||||
<Package size={20} className="text-blue-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-gray-600">处理订单</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{ordersProcessed}</div>
|
||||
<div className="text-xs text-muted-foreground">处理订单</div>
|
||||
<div className="text-xl font-semibold text-foreground">{ordersProcessed}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-3 flex items-center gap-3 border border-gray-200">
|
||||
<div className="bg-muted rounded-lg p-3 flex items-center gap-3 border border-border">
|
||||
<div className="w-9 h-9 rounded-lg bg-green-50 flex items-center justify-center flex-shrink-0">
|
||||
<CheckCircle size={20} className="text-green-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-gray-600">{dryRun ? '拟删除物料' : '删除物料'}</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{materialsDeleted}</div>
|
||||
<div className="text-xs text-muted-foreground">{dryRun ? '拟删除物料' : '删除物料'}</div>
|
||||
<div className="text-xl font-semibold text-foreground">{materialsDeleted}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-3 flex items-center gap-3 border border-gray-200">
|
||||
<div className="bg-muted rounded-lg p-3 flex items-center gap-3 border border-border">
|
||||
<div className="w-9 h-9 rounded-lg bg-amber-50 flex items-center justify-center flex-shrink-0">
|
||||
<SkipForward size={20} className="text-amber-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-gray-600">跳过物料</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{materialsSkipped}</div>
|
||||
<div className="text-xs text-muted-foreground">跳过物料</div>
|
||||
<div className="text-xl font-semibold text-foreground">{materialsSkipped}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasErrors && (
|
||||
<div className="bg-gray-50 rounded-lg p-3 flex items-center gap-3 border border-gray-200 col-span-3">
|
||||
<div className="bg-muted rounded-lg p-3 flex items-center gap-3 border border-border col-span-3">
|
||||
<div className="w-9 h-9 rounded-lg bg-red-50 flex items-center justify-center flex-shrink-0">
|
||||
<XCircle size={20} className="text-red-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-gray-600">错误数量</div>
|
||||
<div className="text-xs text-muted-foreground">错误数量</div>
|
||||
<div className="text-xl font-semibold text-red-600">{errors.length}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -290,7 +290,7 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
|
||||
{hasRetries && (
|
||||
<>
|
||||
<div className="bg-gray-50 rounded-lg p-3 flex items-center gap-3 border border-gray-200">
|
||||
<div className="bg-muted rounded-lg p-3 flex items-center gap-3 border border-border">
|
||||
<div className="w-9 h-9 rounded-lg bg-purple-50 flex items-center justify-center flex-shrink-0">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -311,18 +311,18 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-gray-600">重试订单</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{retriedOrders}</div>
|
||||
<div className="text-xs text-muted-foreground">重试订单</div>
|
||||
<div className="text-xl font-semibold text-foreground">{retriedOrders}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 rounded-lg p-3 flex items-center gap-3 border border-gray-200">
|
||||
<div className="bg-muted rounded-lg p-3 flex items-center gap-3 border border-border">
|
||||
<div className="w-9 h-9 rounded-lg bg-emerald-50 flex items-center justify-center flex-shrink-0">
|
||||
<CheckCircle size={20} className="text-emerald-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs text-gray-600">成功重试</div>
|
||||
<div className="text-xl font-semibold text-gray-900">{successfulRetries}</div>
|
||||
<div className="text-xs text-muted-foreground">成功重试</div>
|
||||
<div className="text-xl font-semibold text-foreground">{successfulRetries}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -353,7 +353,7 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
)}
|
||||
|
||||
{hasErrors && (
|
||||
<div className="mt-4 pt-4 border-t border-gray-200">
|
||||
<div className="mt-4 pt-4 border-t border-border">
|
||||
<div className="text-sm font-semibold text-red-600 mb-2">错误详情</div>
|
||||
<div className="flex flex-col gap-2 max-h-40 overflow-y-auto">
|
||||
{errors.map((error, index) => (
|
||||
@@ -362,7 +362,7 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
|
||||
className="flex items-start gap-2 p-2 bg-red-50 rounded border border-red-200"
|
||||
>
|
||||
<XCircle size={14} className="text-red-600 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-gray-900 break-words">{error}</span>
|
||||
<span className="text-sm text-foreground break-words">{error}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -238,11 +238,11 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="操作历史" size="3xl">
|
||||
<div className="flex flex-col h-[70vh]">
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-start justify-between mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="flex items-start justify-between mb-4 pb-4 border-b border-border">
|
||||
<div className="flex-1">
|
||||
{isAdmin && allUsers.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<div className="text-xs text-gray-500 mb-2">筛选用户:</div>
|
||||
<div className="text-xs text-muted-foreground mb-2">筛选用户:</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{allUsers.map((username) => {
|
||||
const isSelected = selectedUsers.includes(username)
|
||||
@@ -253,7 +253,7 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium transition-all ${
|
||||
isSelected
|
||||
? 'bg-blue-600 text-white shadow-sm'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
: 'bg-muted text-foreground hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{username}
|
||||
@@ -272,7 +272,7 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-gray-600">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{isAdmin ? (
|
||||
<span className="text-amber-600 font-medium">
|
||||
管理员模式:{selectedUsers.length > 0 ? `已选择 ${selectedUsers.length} 个用户` : '显示所有用户记录'}
|
||||
@@ -282,7 +282,7 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
)}
|
||||
</span>
|
||||
{batches.length > 0 && (
|
||||
<span className="text-sm text-gray-500">共 {batches.length} 条批次</span>
|
||||
<span className="text-sm text-muted-foreground">共 {batches.length} 条批次</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -307,9 +307,9 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
{/* Batch list */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{loading && batches.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-32 text-gray-500">加载中...</div>
|
||||
<div className="flex items-center justify-center h-32 text-muted-foreground">加载中...</div>
|
||||
) : batches.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-32 text-gray-500">暂无操作记录</div>
|
||||
<div className="flex items-center justify-center h-32 text-muted-foreground">暂无操作记录</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
{batches.map((batch) => {
|
||||
@@ -320,33 +320,33 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
return (
|
||||
<div
|
||||
key={batch.batchId}
|
||||
className="border border-gray-200 rounded-lg overflow-hidden"
|
||||
className="border border-border rounded-lg overflow-hidden"
|
||||
>
|
||||
{/* Batch summary */}
|
||||
<div
|
||||
className={`flex items-center justify-between p-4 cursor-pointer transition-colors ${
|
||||
isExpanded ? 'bg-gray-50' : 'hover:bg-gray-50'
|
||||
isExpanded ? 'bg-muted' : 'hover:bg-muted'
|
||||
}`}
|
||||
onClick={() => toggleBatchExpansion(batch.batchId)}
|
||||
>
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<button className="p-1 hover:bg-gray-200 rounded">
|
||||
<button className="p-1 hover:bg-accent rounded">
|
||||
{isExpanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
|
||||
</button>
|
||||
|
||||
<div className="flex-1 grid grid-cols-6 gap-4 text-sm">
|
||||
<div>
|
||||
<div className="text-gray-500 text-xs">操作时间</div>
|
||||
<div className="font-medium text-gray-900">
|
||||
<div className="text-muted-foreground text-xs">操作时间</div>
|
||||
<div className="font-medium text-foreground">
|
||||
{formatDateTime(batch.operationTime)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-500 text-xs">操作用户</div>
|
||||
<div className="font-medium text-gray-900">{batch.username}</div>
|
||||
<div className="text-muted-foreground text-xs">操作用户</div>
|
||||
<div className="font-medium text-foreground">{batch.username}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-500 text-xs">状态</div>
|
||||
<div className="text-muted-foreground text-xs">状态</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{statusIcons[batch.status] || statusIcons.pending}
|
||||
<span
|
||||
@@ -359,16 +359,16 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-500 text-xs">订单数</div>
|
||||
<div className="font-medium text-gray-900">{batch.totalOrders}</div>
|
||||
<div className="text-muted-foreground text-xs">订单数</div>
|
||||
<div className="font-medium text-foreground">{batch.totalOrders}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-500 text-xs">记录数</div>
|
||||
<div className="font-medium text-gray-900">{batch.totalRecords}</div>
|
||||
<div className="text-muted-foreground text-xs">记录数</div>
|
||||
<div className="font-medium text-foreground">{batch.totalRecords}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-500 text-xs">成功/失败</div>
|
||||
<div className="font-medium text-gray-900">
|
||||
<div className="text-muted-foreground text-xs">成功/失败</div>
|
||||
<div className="font-medium text-foreground">
|
||||
<span className="text-green-600">{batch.successCount}</span>
|
||||
{batch.failedCount > 0 && (
|
||||
<>
|
||||
@@ -383,7 +383,7 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
|
||||
{isAdmin && (
|
||||
<button
|
||||
className="p-2 hover:bg-red-50 text-gray-400 hover:text-red-600 rounded transition-colors disabled:opacity-50"
|
||||
className="p-2 hover:bg-red-50 text-muted-foreground hover:text-red-600 rounded transition-colors disabled:opacity-50"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
void handleDeleteBatch(batch.batchId)
|
||||
@@ -398,16 +398,16 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
|
||||
{/* Batch details */}
|
||||
{isExpanded && details.length > 0 && (
|
||||
<div className="border-t border-gray-200 bg-white">
|
||||
<div className="border-t border-border bg-card">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-gray-50">
|
||||
<thead className="bg-muted">
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left font-medium text-gray-600">
|
||||
<th className="px-4 py-2 text-left font-medium text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
总排号
|
||||
<button
|
||||
className="p-1 hover:bg-gray-200 rounded transition-colors"
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
onClick={() =>
|
||||
void handleCopyColumn('productionId', batch.batchId)
|
||||
}
|
||||
@@ -415,16 +415,16 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
>
|
||||
<Copy
|
||||
size={14}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left font-medium text-gray-600">
|
||||
<th className="px-4 py-2 text-left font-medium text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
订单号
|
||||
<button
|
||||
className="p-1 hover:bg-gray-200 rounded transition-colors"
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
onClick={() =>
|
||||
void handleCopyColumn('orderNumber', batch.batchId)
|
||||
}
|
||||
@@ -432,29 +432,29 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
>
|
||||
<Copy
|
||||
size={14}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left font-medium text-gray-600">
|
||||
<th className="px-4 py-2 text-left font-medium text-muted-foreground">
|
||||
状态
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left font-medium text-gray-600">
|
||||
<th className="px-4 py-2 text-left font-medium text-muted-foreground">
|
||||
记录数
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left font-medium text-gray-600">
|
||||
<th className="px-4 py-2 text-left font-medium text-muted-foreground">
|
||||
错误信息
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100">
|
||||
<tbody className="divide-y divide-border">
|
||||
{details.map((detail) => (
|
||||
<tr key={detail.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-2 text-gray-900">
|
||||
<tr key={detail.id} className="hover:bg-muted">
|
||||
<td className="px-4 py-2 text-foreground">
|
||||
{detail.productionId || '-'}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-gray-900 font-mono text-xs">
|
||||
<td className="px-4 py-2 text-foreground font-mono text-xs">
|
||||
{detail.orderNumber}
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
@@ -467,7 +467,7 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
{statusLabels[detail.status] || detail.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-2 text-gray-900">
|
||||
<td className="px-4 py-2 text-foreground">
|
||||
{detail.recordCount ?? '-'}
|
||||
</td>
|
||||
<td className="px-4 py-2 text-red-600 text-xs max-w-xs truncate">
|
||||
@@ -488,7 +488,7 @@ export const ExtractorOperationHistoryModal: React.FC<ExtractorOperationHistoryM
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="pt-4 border-t border-gray-200 flex justify-end">
|
||||
<div className="pt-4 border-t border-border flex justify-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
|
||||
@@ -94,14 +94,14 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm text-gray-600">当前计算机:{computerName}</div>
|
||||
<div className="text-sm text-muted-foreground">当前计算机:{computerName}</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-slate-700 mb-1">用户名:</label>
|
||||
<label className="block text-sm text-muted-foreground mb-1">用户名:</label>
|
||||
<input
|
||||
ref={usernameInputRef}
|
||||
type="text"
|
||||
className="border border-slate-300 rounded-md p-2 w-full text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="border border-input rounded-md p-2 w-full text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="请输入用户名"
|
||||
@@ -110,10 +110,10 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-slate-700 mb-1">密码:</label>
|
||||
<label className="block text-sm text-muted-foreground mb-1">密码:</label>
|
||||
<input
|
||||
type="password"
|
||||
className="border border-slate-300 rounded-md p-2 w-full text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="border border-input rounded-md p-2 w-full text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="请输入密码"
|
||||
@@ -124,7 +124,7 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({
|
||||
|
||||
<div className="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
className="px-4 py-2 rounded-md bg-slate-100 hover:bg-slate-200 text-slate-700 transition-colors"
|
||||
className="px-4 py-2 rounded-md bg-muted hover:bg-accent text-foreground transition-colors"
|
||||
onClick={onCancel}
|
||||
disabled={isLoggingIn}
|
||||
>
|
||||
@@ -139,7 +139,7 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-xs text-gray-400 text-right">v1.0</div>
|
||||
<div className="mt-4 text-xs text-muted-foreground text-right">v1.0</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -343,9 +343,9 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
<div onKeyDown={handleKeyDown}>
|
||||
{/* Manager filter (admin only) */}
|
||||
{isAdmin && (
|
||||
<div className="mb-4 p-3 bg-slate-50 rounded-lg border border-slate-200">
|
||||
<div className="mb-4 p-3 bg-muted rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-slate-700">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
||||
<Users size={16} />
|
||||
按负责人筛选
|
||||
</div>
|
||||
@@ -358,7 +358,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedManagers(new Set())}
|
||||
className="text-xs text-slate-500 hover:underline"
|
||||
className="text-xs text-muted-foreground hover:underline"
|
||||
>
|
||||
取消全选
|
||||
</button>
|
||||
@@ -368,7 +368,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
{managers.map((manager) => (
|
||||
<label
|
||||
key={manager}
|
||||
className="flex items-center gap-1.5 text-xs text-slate-600 cursor-pointer hover:bg-white px-2 py-1 rounded"
|
||||
className="flex items-center gap-1.5 text-xs text-muted-foreground cursor-pointer hover:bg-card px-2 py-1 rounded"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -433,23 +433,23 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="border border-slate-200 rounded-lg overflow-hidden max-h-[400px] overflow-y-auto">
|
||||
<div className="border border-border rounded-lg overflow-hidden max-h-[400px] overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-12 text-slate-500">加载中...</div>
|
||||
<div className="flex items-center justify-center py-12 text-muted-foreground">加载中...</div>
|
||||
) : (
|
||||
<table ref={tableRef} className="w-full text-sm">
|
||||
<thead className="bg-slate-100 sticky top-0">
|
||||
<thead className="bg-muted sticky top-0">
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left font-medium text-slate-700 w-64">
|
||||
<th className="px-4 py-2 text-left font-medium text-foreground w-64">
|
||||
物料名称关键词
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left font-medium text-slate-700">负责人</th>
|
||||
<th className="px-4 py-2 text-left font-medium text-foreground">负责人</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
<tbody className="divide-y divide-border">
|
||||
{filteredRows.filter((r) => r.state !== 'deleted').length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={2} className="px-4 py-8 text-center text-slate-400">
|
||||
<td colSpan={2} className="px-4 py-8 text-center text-muted-foreground">
|
||||
暂无数据,点击"新增"按钮添加物料类型关键词
|
||||
</td>
|
||||
</tr>
|
||||
@@ -470,10 +470,10 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
<tr
|
||||
key={index}
|
||||
style={getRowStyle(row)}
|
||||
className={`${isSelected ? 'ring-2 ring-blue-300 ring-inset' : ''} hover:bg-slate-50 cursor-pointer`}
|
||||
className={`${isSelected ? 'ring-2 ring-blue-300 ring-inset' : ''} hover:bg-muted cursor-pointer`}
|
||||
onClick={() => setSelectedRowIndex(originalIndex)}
|
||||
>
|
||||
<td className="px-4 py-2 border-r border-slate-100">
|
||||
<td className="px-4 py-2 border-r border-border">
|
||||
{isEditingMaterial ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
@@ -493,7 +493,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
onDoubleClick={() => startEdit(originalIndex, 'materialName')}
|
||||
>
|
||||
{row.record.materialName || (
|
||||
<span className="text-slate-400 italic">双击编辑</span>
|
||||
<span className="text-muted-foreground italic">双击编辑</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -539,7 +539,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
onDoubleClick={() => startEdit(originalIndex, 'managerName')}
|
||||
>
|
||||
{row.record.managerName || (
|
||||
<span className="text-slate-400 italic">双击编辑</span>
|
||||
<span className="text-muted-foreground italic">双击编辑</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -554,7 +554,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
</div>
|
||||
|
||||
{/* Footer info */}
|
||||
<div className="mt-3 text-xs text-slate-500 flex justify-between">
|
||||
<div className="mt-3 text-xs text-muted-foreground flex justify-between">
|
||||
<span>
|
||||
双击单元格编辑 | Insert 新增 | Delete 删除
|
||||
{isAdmin && ' | 绿色=新增 | 黄色=已修改'}
|
||||
|
||||
@@ -75,7 +75,7 @@ const OrderNumberInput: React.FC<OrderNumberInputProps> = ({
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm font-medium text-slate-700">{label}</label>
|
||||
<label className="text-sm font-medium text-muted-foreground">{label}</label>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="bg-blue-50 text-blue-600 px-2 py-0.5 rounded-full text-xs font-medium">
|
||||
{count} 个
|
||||
@@ -103,7 +103,7 @@ const OrderNumberInput: React.FC<OrderNumberInputProps> = ({
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className="flex-1 w-full border border-slate-300 rounded-lg p-3 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="flex-1 w-full border border-input rounded-lg p-3 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none bg-muted disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
userSelect: disabled ? 'none' : 'text',
|
||||
cursor: disabled ? 'not-allowed' : 'text'
|
||||
@@ -115,7 +115,7 @@ const OrderNumberInput: React.FC<OrderNumberInputProps> = ({
|
||||
<button
|
||||
onClick={onReset}
|
||||
disabled={disabled}
|
||||
className="flex items-center gap-1 px-3 py-1.5 text-xs text-slate-500 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="flex items-center gap-1 px-3 py-1.5 text-xs text-muted-foreground hover:text-red-600 hover:bg-red-50 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<X size={14} />
|
||||
清空
|
||||
|
||||
@@ -137,10 +137,10 @@ export default function PlaywrightDownloadDialog({
|
||||
{/* Progress Bar */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-slate-600">下载进度</span>
|
||||
<span className="text-muted-foreground">下载进度</span>
|
||||
<span className="font-semibold text-blue-600">{progress?.percent ?? 0}%</span>
|
||||
</div>
|
||||
<div className="h-3 w-full overflow-hidden rounded-full bg-slate-200">
|
||||
<div className="h-3 w-full overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-blue-500 to-blue-600 transition-all duration-300 ease-out"
|
||||
style={{ width: `${progress?.percent ?? 0}%` }}
|
||||
@@ -150,10 +150,10 @@ export default function PlaywrightDownloadDialog({
|
||||
|
||||
{/* Current File */}
|
||||
{progress?.currentFile && (
|
||||
<div className="rounded-lg bg-slate-50 px-4 py-3">
|
||||
<div className="text-xs font-medium text-slate-500">当前文件</div>
|
||||
<div className="rounded-lg bg-muted px-4 py-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">当前文件</div>
|
||||
<div
|
||||
className="mt-1 truncate text-sm text-slate-700"
|
||||
className="mt-1 truncate text-sm text-foreground"
|
||||
title={progress.currentFile}
|
||||
>
|
||||
{progress.currentFile}
|
||||
@@ -163,31 +163,31 @@ export default function PlaywrightDownloadDialog({
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="rounded-lg border border-slate-200 px-4 py-3">
|
||||
<div className="text-xs text-slate-500">已下载</div>
|
||||
<div className="mt-1 text-lg font-semibold text-slate-900">
|
||||
<div className="rounded-lg border border-border px-4 py-3">
|
||||
<div className="text-xs text-muted-foreground">已下载</div>
|
||||
<div className="mt-1 text-lg font-semibold text-foreground">
|
||||
{progress ? formatBytes(progress.downloadedBytes) : '0 B'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-slate-200 px-4 py-3">
|
||||
<div className="text-xs text-slate-500">总大小</div>
|
||||
<div className="mt-1 text-lg font-semibold text-slate-900">
|
||||
<div className="rounded-lg border border-border px-4 py-3">
|
||||
<div className="text-xs text-muted-foreground">总大小</div>
|
||||
<div className="mt-1 text-lg font-semibold text-foreground">
|
||||
{progress && progress.totalBytes > 0
|
||||
? formatBytes(progress.totalBytes)
|
||||
: '计算中...'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-slate-200 px-4 py-3">
|
||||
<div className="text-xs text-slate-500">速度</div>
|
||||
<div className="mt-1 text-lg font-semibold text-slate-900">
|
||||
<div className="rounded-lg border border-border px-4 py-3">
|
||||
<div className="text-xs text-muted-foreground">速度</div>
|
||||
<div className="mt-1 text-lg font-semibold text-foreground">
|
||||
{progress && progress.speed >= 0
|
||||
? `${formatBytes(progress.speed)}/秒`
|
||||
: '计算中...'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-slate-200 px-4 py-3">
|
||||
<div className="text-xs text-slate-500">剩余时间</div>
|
||||
<div className="mt-1 text-lg font-semibold text-slate-900">
|
||||
<div className="rounded-lg border border-border px-4 py-3">
|
||||
<div className="text-xs text-muted-foreground">剩余时间</div>
|
||||
<div className="mt-1 text-lg font-semibold text-foreground">
|
||||
{progress?.eta !== undefined && progress.eta >= 0
|
||||
? formatETA(progress.eta)
|
||||
: '计算中...'}
|
||||
@@ -231,7 +231,7 @@ export default function PlaywrightDownloadDialog({
|
||||
{isDownloading && !progress && (
|
||||
<div className="flex flex-col items-center justify-center py-8">
|
||||
<LoaderCircle size={48} className="animate-spin text-blue-500" />
|
||||
<div className="mt-4 text-sm text-slate-600">正在初始化下载...</div>
|
||||
<div className="mt-4 text-sm text-muted-foreground">正在初始化下载...</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -253,7 +253,7 @@ export default function PlaywrightDownloadDialog({
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
onClick={handleCloseConfirm}
|
||||
className="rounded-md border border-slate-200 px-4 py-2 text-sm text-slate-600 hover:bg-slate-50"
|
||||
className="rounded-md border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-muted"
|
||||
>
|
||||
继续下载
|
||||
</button>
|
||||
|
||||
@@ -125,10 +125,10 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-slate-900/50 backdrop-blur-sm animate-in fade-in duration-200">
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-[900px] max-w-[90vw] h-[80vh] flex flex-col border border-slate-200 overflow-hidden animate-in zoom-in-95 duration-200">
|
||||
<div className="bg-card rounded-2xl shadow-2xl w-[900px] max-w-[90vw] h-[80vh] flex flex-col border border-border overflow-hidden animate-in zoom-in-95 duration-200">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-200 bg-slate-50 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 text-slate-800">
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-border bg-muted flex-shrink-0">
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<FileText size={20} className="text-blue-600" />
|
||||
<h2 className="text-lg font-semibold">执行报告浏览器</h2>
|
||||
</div>
|
||||
@@ -136,7 +136,7 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
{isAdmin && onOpenAnalysis && (
|
||||
<button
|
||||
onClick={onOpenAnalysis}
|
||||
className="text-xs bg-white border border-slate-300 text-slate-700 px-3 py-1.5 rounded shadow-sm hover:bg-slate-50 flex items-center gap-1.5 font-medium transition-colors"
|
||||
className="text-xs bg-card border border-input text-foreground px-3 py-1.5 rounded shadow-sm hover:bg-muted flex items-center gap-1.5 font-medium transition-colors"
|
||||
>
|
||||
<BarChart3 size={14} className="text-blue-600" />
|
||||
报告分析
|
||||
@@ -144,7 +144,7 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
)}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-slate-400 hover:text-slate-600 hover:bg-slate-200/50 p-1.5 rounded-lg transition-colors"
|
||||
className="text-muted-foreground hover:text-muted-foreground hover:bg-muted p-1.5 rounded-lg transition-colors"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
@@ -152,14 +152,14 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="px-6 py-4 border-b border-slate-200 bg-white flex-shrink-0">
|
||||
<div className="px-6 py-4 border-b border-border bg-card flex-shrink-0">
|
||||
<div className="flex items-center gap-4">
|
||||
<label className="text-sm font-medium text-slate-700 flex-shrink-0">选择报告:</label>
|
||||
<label className="text-sm font-medium text-foreground flex-shrink-0">选择报告:</label>
|
||||
<div className="relative flex-1 max-w-2xl">
|
||||
<Combobox value={selectedReport} onChange={handleReportChange} nullable>
|
||||
<div className="relative">
|
||||
<Combobox.Input
|
||||
className="w-full bg-slate-50 border border-slate-300 text-slate-700 py-2 pl-3 pr-10 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 disabled:opacity-50 text-sm"
|
||||
className="w-full bg-muted border border-input text-foreground py-2 pl-3 pr-10 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 disabled:opacity-50 text-sm"
|
||||
displayValue={(report: ReportMetadata | null) => {
|
||||
if (!report) return '请选择要查看的报告...'
|
||||
return `${isAdmin ? `[${report.username}] ` : ''}${report.filename} (${formatDate(report.lastModified)})`
|
||||
@@ -168,7 +168,7 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
placeholder="请选择要查看的报告..."
|
||||
disabled={isLoadingList}
|
||||
/>
|
||||
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center px-2 text-slate-500 hover:text-slate-700 transition-colors">
|
||||
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center px-2 text-muted-foreground hover:text-foreground transition-colors">
|
||||
<ChevronDown size={20} className="h-5 w-5" />
|
||||
</Combobox.Button>
|
||||
</div>
|
||||
@@ -180,9 +180,9 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0"
|
||||
>
|
||||
<Combobox.Options className="absolute z-50 w-full mt-1 bg-white border border-slate-200 rounded-lg shadow-lg max-h-60 overflow-auto focus:outline-none">
|
||||
<Combobox.Options className="absolute z-50 w-full mt-1 bg-card border border-border rounded-lg shadow-lg max-h-60 overflow-auto focus:outline-none">
|
||||
{filteredReports.length === 0 && query ? (
|
||||
<div className="px-4 py-8 text-center text-slate-400 text-sm">
|
||||
<div className="px-4 py-8 text-center text-muted-foreground text-sm">
|
||||
未找到匹配的报告
|
||||
</div>
|
||||
) : (
|
||||
@@ -192,7 +192,7 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
value={null}
|
||||
className={({ active }) =>
|
||||
`px-4 py-2.5 text-sm cursor-pointer ${
|
||||
active ? 'bg-slate-100 text-slate-900' : 'text-slate-500 italic'
|
||||
active ? 'bg-muted text-foreground' : 'text-muted-foreground italic'
|
||||
}`
|
||||
}
|
||||
>
|
||||
@@ -208,7 +208,7 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
? 'bg-blue-500 text-white'
|
||||
: active
|
||||
? 'bg-blue-50 text-blue-700'
|
||||
: 'text-slate-900'
|
||||
: 'text-foreground'
|
||||
}`
|
||||
}
|
||||
>
|
||||
@@ -219,7 +219,7 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
{report.filename}
|
||||
</span>
|
||||
<span
|
||||
className={`ml-2 text-xs ${selected ? 'text-blue-100' : 'text-slate-400'}`}
|
||||
className={`ml-2 text-xs ${selected ? 'text-blue-100' : 'text-muted-foreground'}`}
|
||||
>
|
||||
{formatDate(report.lastModified)}
|
||||
</span>
|
||||
@@ -243,15 +243,15 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 bg-slate-50 overflow-hidden relative">
|
||||
<div className="flex-1 bg-muted overflow-hidden relative">
|
||||
{isLoadingContent ? (
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-slate-500 bg-white/80 z-10">
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-muted-foreground bg-card/80 z-10">
|
||||
<Loader2 size={32} className="animate-spin text-blue-500 mb-4" />
|
||||
<p>正在加载报告内容...</p>
|
||||
</div>
|
||||
) : reportContent ? (
|
||||
<div className="h-full overflow-y-auto p-8">
|
||||
<div className="markdown-body bg-white p-8 rounded-xl shadow-sm border border-slate-200">
|
||||
<div className="markdown-body bg-card p-8 rounded-xl shadow-sm border border-border">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeSlug, rehypeAutolinkHeadings, rehypeHighlight]}
|
||||
@@ -261,8 +261,8 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-slate-400">
|
||||
<FileText size={48} className="mb-4 text-slate-300 opacity-50" />
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-muted-foreground">
|
||||
<FileText size={48} className="mb-4 text-muted-foreground opacity-50" />
|
||||
<p>请在上方选择一个报告进行浏览</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function UpdateDialog({
|
||||
const renderReleaseList = (title: string, releases: UpdateRelease[]) => {
|
||||
if (releases.length === 0) {
|
||||
return (
|
||||
<div className="rounded-lg border border-dashed border-slate-200 px-3 py-4 text-sm text-slate-500">
|
||||
<div className="rounded-lg border border-dashed border-border px-3 py-4 text-sm text-muted-foreground">
|
||||
暂无版本
|
||||
</div>
|
||||
)
|
||||
@@ -53,7 +53,7 @@ export default function UpdateDialog({
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-slate-500">{title}</div>
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">{title}</div>
|
||||
{releases.map((release) => {
|
||||
const selected = isSelectedRelease(release)
|
||||
return (
|
||||
@@ -67,18 +67,18 @@ export default function UpdateDialog({
|
||||
className={`w-full rounded-lg border px-3 py-3 text-left transition ${
|
||||
selected
|
||||
? 'border-blue-500 bg-blue-50 text-blue-900'
|
||||
: 'border-slate-200 bg-white text-slate-700 hover:border-slate-300'
|
||||
: 'border-border bg-card text-foreground hover:border-input'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="font-medium">V{release.version}</span>
|
||||
<span className="rounded-full bg-slate-100 px-2 py-0.5 text-xs uppercase text-slate-600">
|
||||
<span className="rounded-full bg-muted px-2 py-0.5 text-xs uppercase text-muted-foreground">
|
||||
{release.channel}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-slate-500">{release.publishedAt}</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">{release.publishedAt}</div>
|
||||
{release.notesSummary && (
|
||||
<div className="mt-2 text-xs text-slate-600">{release.notesSummary}</div>
|
||||
<div className="mt-2 text-xs text-muted-foreground">{release.notesSummary}</div>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
@@ -97,10 +97,10 @@ export default function UpdateDialog({
|
||||
disableEscapeKey={isBusy}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between rounded-lg bg-slate-50 px-4 py-3 text-sm">
|
||||
<div className="text-slate-600">
|
||||
当前版本 <span className="font-semibold text-slate-900">V{status?.currentVersion}</span>
|
||||
<span className="ml-2 rounded-full bg-slate-200 px-2 py-0.5 text-xs uppercase text-slate-700">
|
||||
<div className="flex items-center justify-between rounded-lg bg-muted px-4 py-3 text-sm">
|
||||
<div className="text-muted-foreground">
|
||||
当前版本 <span className="font-semibold text-foreground">V{status?.currentVersion}</span>
|
||||
<span className="ml-2 rounded-full bg-muted px-2 py-0.5 text-xs uppercase text-foreground">
|
||||
{status?.currentChannel ?? __APP_CHANNEL__}
|
||||
</span>
|
||||
</div>
|
||||
@@ -121,20 +121,20 @@ export default function UpdateDialog({
|
||||
{renderReleaseList('Stable', catalog.channels?.stable ?? [])}
|
||||
{renderReleaseList('Preview', catalog.channels?.preview ?? [])}
|
||||
</div>
|
||||
<div className="rounded-xl border border-slate-200 bg-white">
|
||||
<div className="border-b border-slate-200 px-4 py-3">
|
||||
<div className="text-sm font-semibold text-slate-900">
|
||||
<div className="rounded-xl border border-border bg-card">
|
||||
<div className="border-b border-border px-4 py-3">
|
||||
<div className="text-sm font-semibold text-foreground">
|
||||
{selectedRelease
|
||||
? `${selectedRelease.channel.toUpperCase()} V${selectedRelease.version}`
|
||||
: '请选择一个版本'}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-slate-500">
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
{selectedRelease?.notesSummary ?? '选择版本后可查看详细更新说明。'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-[420px] overflow-auto px-4 py-4 prose prose-slate max-w-none prose-headings:mb-2 prose-p:my-2 prose-li:my-1">
|
||||
{isLoadingChangelog ? (
|
||||
<div className="flex items-center gap-2 text-sm text-slate-500">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<LoaderCircle size={16} className="animate-spin" />
|
||||
正在加载更新说明...
|
||||
</div>
|
||||
@@ -147,12 +147,12 @@ export default function UpdateDialog({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-xl border border-slate-200 bg-white">
|
||||
<div className="border-b border-slate-200 px-4 py-3">
|
||||
<div className="text-sm font-semibold text-slate-900">
|
||||
<div className="rounded-xl border border-border bg-card">
|
||||
<div className="border-b border-border px-4 py-3">
|
||||
<div className="text-sm font-semibold text-foreground">
|
||||
{selectedRelease ? `发现稳定版 V${selectedRelease.version}` : '暂无可用更新'}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-slate-500">
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
{status?.currentChannel === 'preview'
|
||||
? '当前设备正在运行预览版,普通用户将更新回稳定版。'
|
||||
: '更新包已在后台准备完成,确认后将自动关闭并重启应用。'}
|
||||
@@ -160,7 +160,7 @@ export default function UpdateDialog({
|
||||
</div>
|
||||
<div className="max-h-[420px] overflow-auto px-4 py-4 prose prose-slate max-w-none prose-headings:mb-2 prose-p:my-2 prose-li:my-1">
|
||||
{isLoadingChangelog ? (
|
||||
<div className="flex items-center gap-2 text-sm text-slate-500">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<LoaderCircle size={16} className="animate-spin" />
|
||||
正在加载更新说明...
|
||||
</div>
|
||||
@@ -179,8 +179,8 @@ export default function UpdateDialog({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between border-t border-slate-200 pt-4">
|
||||
<div className="text-sm text-slate-500">{status?.message ?? ' '}</div>
|
||||
<div className="flex items-center justify-between border-t border-border pt-4">
|
||||
<div className="text-sm text-muted-foreground">{status?.message ?? ' '}</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -78,7 +78,7 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
ariaDescribedBy="user-selection-description"
|
||||
>
|
||||
<div ref={dialogRef} className="max-h-[60vh] flex flex-col">
|
||||
<div className="text-sm text-gray-600 mb-4">当前登录:{currentUsername}</div>
|
||||
<div className="text-sm text-muted-foreground mb-4">当前登录:{currentUsername}</div>
|
||||
<p id="user-selection-description" className="sr-only">
|
||||
从列表中选择一个用户,双击可直接确认选择
|
||||
</p>
|
||||
@@ -88,7 +88,7 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
{users.map((user) => (
|
||||
<div
|
||||
key={user.id}
|
||||
className={`user-item p-3 border border-gray-200 rounded-lg cursor-pointer transition-all hover:border-blue-500 hover:bg-green-50 ${
|
||||
className={`user-item p-3 border border-border rounded-lg cursor-pointer transition-all hover:border-blue-500 hover:bg-green-50 ${
|
||||
selectedUserId === user.id ? 'border-blue-500 bg-blue-50' : ''
|
||||
}`}
|
||||
onClick={() => setSelectedUserId(user.id)}
|
||||
@@ -101,7 +101,7 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-gray-900">{user.username}</span>
|
||||
<span className="text-sm font-medium text-foreground">{user.username}</span>
|
||||
<span
|
||||
className={`text-xs px-2 py-1 rounded font-medium ${userTypeStyles[user.userType]}`}
|
||||
>
|
||||
@@ -109,7 +109,7 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
</span>
|
||||
</div>
|
||||
{user.createTime && (
|
||||
<div className="mt-1 text-xs text-gray-500">
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
创建于:{new Date(user.createTime).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
)}
|
||||
@@ -118,7 +118,7 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-4">
|
||||
<div className="border-t border-border pt-4">
|
||||
<div className="flex justify-center gap-3">
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -134,7 +134,7 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-center text-xs text-gray-500 mt-3">双击用户可直接选择</div>
|
||||
<div className="text-center text-xs text-muted-foreground mt-3">双击用户可直接选择</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -81,7 +81,7 @@ export function AuthenticatedAppShell({
|
||||
: '发现新版本'
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-slate-50 text-slate-800 font-sans overflow-hidden">
|
||||
<div className="flex flex-col h-screen bg-background text-foreground font-sans overflow-hidden">
|
||||
<header
|
||||
className="h-16 bg-slate-900 text-slate-300 flex items-center justify-between px-4 shadow-md z-20 flex-shrink-0"
|
||||
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
|
||||
@@ -173,13 +173,13 @@ export function AuthenticatedAppShell({
|
||||
</header>
|
||||
|
||||
<div className="flex flex-1 overflow-hidden relative">
|
||||
<main className="flex-1 overflow-hidden bg-slate-50 p-6 h-full">
|
||||
<main className="flex-1 overflow-hidden bg-background p-6 h-full">
|
||||
{currentPage === 'home' && (
|
||||
<div className="h-full overflow-auto">
|
||||
<div className="max-w-4xl mx-auto mt-10 text-center animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<LayoutDashboard size={48} className="mx-auto text-blue-500 mb-4" />
|
||||
<h1 className="text-3xl font-bold text-slate-800 mb-4">欢迎使用 ERP Auto</h1>
|
||||
<p className="text-slate-500 text-lg max-w-2xl mx-auto">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-4">欢迎使用 ERP Auto</h1>
|
||||
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
|
||||
自动化处理 ERP 系统中的数据提取和清理任务。请使用上方导航栏选择您需要的功能模块。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -34,9 +34,9 @@ export function CleanerExecutionBar({
|
||||
executeButtonRef
|
||||
}: CleanerExecutionBarProps): React.JSX.Element {
|
||||
return (
|
||||
<div className="bg-white border-t border-slate-200 p-4 flex justify-between items-center shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] z-10 flex-shrink-0">
|
||||
<div className="bg-card border-t border-border p-4 flex justify-between items-center shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] z-10 flex-shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-sm text-slate-600 font-medium">
|
||||
<div className="text-sm text-muted-foreground font-medium">
|
||||
共计 {filteredCount} 条记录 | 已选中{' '}
|
||||
<span className="text-blue-600">{selectedCount}</span> 条
|
||||
</div>
|
||||
@@ -51,12 +51,12 @@ export function CleanerExecutionBar({
|
||||
<Settings2 size={16} /> 执行设置
|
||||
</Button>
|
||||
{showSettingsMenu && (
|
||||
<div className="absolute bottom-full right-0 mb-2 bg-white rounded-lg shadow-lg border border-slate-200 py-3 px-4 min-w-[280px] z-50">
|
||||
<div className="absolute bottom-full right-0 mb-2 bg-card rounded-lg shadow-lg border border-border py-3 px-4 min-w-[280px] z-50">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-800">预览模式 (Dry-Run)</div>
|
||||
<div className="text-xs text-slate-500 mt-0.5">
|
||||
<div className="text-sm font-medium text-foreground">预览模式 (Dry-Run)</div>
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
仅执行页面操作定位,不保存更改
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,11 +67,11 @@ export function CleanerExecutionBar({
|
||||
{dryRun ? <ToggleRight size={32} /> : <ToggleLeft size={32} />}
|
||||
</button>
|
||||
</div>
|
||||
<div className="border-t border-slate-100 pt-3">
|
||||
<div className="border-t border-border pt-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-800">后台模式 (Headless)</div>
|
||||
<div className="text-xs text-slate-500 mt-0.5">
|
||||
<div className="text-sm font-medium text-foreground">后台模式 (Headless)</div>
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
浏览器在后台运行,不显示界面
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,10 +83,10 @@ export function CleanerExecutionBar({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-slate-100 pt-3 space-y-3">
|
||||
<div className="border-t border-border pt-3 space-y-3">
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-800">并行处理数量</div>
|
||||
<div className="text-xs text-slate-500 mt-0.5">
|
||||
<div className="text-sm font-medium text-foreground">并行处理数量</div>
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
同时处理详情页数量,范围 1-20
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-3">
|
||||
@@ -96,9 +96,9 @@ export function CleanerExecutionBar({
|
||||
max={20}
|
||||
value={processConcurrency}
|
||||
onChange={(e) => updateProcessConcurrency(Number(e.target.value))}
|
||||
className="flex-1 h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600"
|
||||
className="flex-1 h-2 bg-muted rounded-lg appearance-none cursor-pointer accent-blue-600"
|
||||
/>
|
||||
<span className="text-sm font-medium text-slate-700 w-8 text-center">
|
||||
<span className="text-sm font-medium text-foreground w-8 text-center">
|
||||
{processConcurrency}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@ export function CleanerResultsTable({
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<table className="w-full text-left text-sm whitespace-nowrap table-fixed">
|
||||
<thead className="bg-slate-100 text-slate-600 sticky top-0 shadow-sm z-10 text-xs font-semibold">
|
||||
<thead className="bg-muted text-muted-foreground sticky top-0 shadow-sm z-10 text-xs font-semibold">
|
||||
<tr>
|
||||
<th className="px-4 py-3 w-16 text-center">选择</th>
|
||||
<th className="px-4 py-3 w-64">材料名称</th>
|
||||
@@ -54,14 +54,14 @@ export function CleanerResultsTable({
|
||||
<th className="px-4 py-3">规格</th>
|
||||
<th className="px-4 py-3">型号</th>
|
||||
<th className="px-4 py-3 w-40">
|
||||
负责人 <span className="text-[10px] font-normal text-slate-400">(双击编辑)</span>
|
||||
负责人 <span className="text-[10px] font-normal text-muted-foreground">(双击编辑)</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
<tbody className="divide-y divide-border">
|
||||
{filteredResults.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-4 py-8 text-center text-slate-400 text-sm">
|
||||
<td colSpan={6} className="px-4 py-8 text-center text-muted-foreground text-sm">
|
||||
{validationResults.length === 0
|
||||
? '暂无数据,请点击左侧按钮获取并校验物料'
|
||||
: '当前筛选条件下暂无数据'}
|
||||
@@ -73,10 +73,10 @@ export function CleanerResultsTable({
|
||||
const noManager = !result.managerName?.trim()
|
||||
const isEditingManager =
|
||||
editingCell?.rowIndex === rowIndex && editingCell?.field === 'managerName'
|
||||
const rowClassName = `${isChecked ? 'bg-blue-50/30' : 'hover:bg-slate-50'} transition-colors ${noManager && isChecked ? 'bg-amber-50/20' : ''}`
|
||||
const rowClassName = `${isChecked ? 'bg-blue-50/30' : 'hover:bg-muted'} transition-colors ${noManager && isChecked ? 'bg-amber-50/20' : ''}`
|
||||
const managerCellClassName = noManager
|
||||
? 'text-amber-500 text-xs italic'
|
||||
: 'text-slate-700'
|
||||
: 'text-foreground'
|
||||
|
||||
return (
|
||||
<tr key={result.materialCode} className={rowClassName}>
|
||||
@@ -96,24 +96,24 @@ export function CleanerResultsTable({
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
className="px-4 py-3 font-medium text-slate-800 truncate"
|
||||
className="px-4 py-3 font-medium text-foreground truncate"
|
||||
title={result.materialName}
|
||||
>
|
||||
{result.materialName}
|
||||
</td>
|
||||
<td
|
||||
className="px-4 py-3 font-mono text-xs text-slate-600 truncate"
|
||||
className="px-4 py-3 font-mono text-xs text-muted-foreground truncate"
|
||||
title={result.materialCode}
|
||||
>
|
||||
{result.materialCode}
|
||||
</td>
|
||||
<td
|
||||
className="px-4 py-3 text-slate-500 text-xs truncate"
|
||||
className="px-4 py-3 text-muted-foreground text-xs truncate"
|
||||
title={result.specification}
|
||||
>
|
||||
{result.specification || '-'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-slate-500 text-xs truncate" title={result.model}>
|
||||
<td className="px-4 py-3 text-muted-foreground text-xs truncate" title={result.model}>
|
||||
{result.model || '-'}
|
||||
</td>
|
||||
<td
|
||||
@@ -149,7 +149,7 @@ export function CleanerResultsTable({
|
||||
}}
|
||||
>
|
||||
{result.managerName || (
|
||||
<span className="text-slate-400 italic">双击编辑</span>
|
||||
<span className="text-muted-foreground italic">双击编辑</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -18,15 +18,15 @@ export function CleanerSidebar({
|
||||
}: CleanerSidebarProps): React.JSX.Element {
|
||||
return (
|
||||
<div className="w-full xl:w-[380px] flex-shrink-0 flex flex-col gap-5 xl:self-start overflow-auto">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-5">
|
||||
<h3 className="text-base font-semibold mb-4 flex items-center gap-2 text-slate-800">
|
||||
<div className="bg-card rounded-xl shadow-sm border border-border p-5">
|
||||
<h3 className="text-base font-semibold mb-4 flex items-center gap-2 text-foreground">
|
||||
<DatabaseZap size={18} className="text-blue-500" />
|
||||
校验数据来源
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<label
|
||||
className={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${valMode === 'full' ? 'bg-blue-50 border-blue-200' : 'hover:bg-slate-50 border-slate-200'}`}
|
||||
className={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${valMode === 'full' ? 'bg-blue-50 border-blue-200' : 'hover:bg-muted border-border'}`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
@@ -36,13 +36,13 @@ export function CleanerSidebar({
|
||||
onChange={() => setValMode('full')}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-slate-800">数据库 - 全表校验</div>
|
||||
<div className="text-xs text-slate-500 mt-0.5">校验数据库中所有待处理物料</div>
|
||||
<div className="text-sm font-medium text-foreground">数据库 - 全表校验</div>
|
||||
<div className="text-xs text-muted-foreground mt-0.5">校验数据库中所有待处理物料</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
className={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${valMode === 'filtered' ? 'bg-blue-50 border-blue-200' : 'hover:bg-slate-50 border-slate-200'}`}
|
||||
className={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${valMode === 'filtered' ? 'bg-blue-50 border-blue-200' : 'hover:bg-muted border-border'}`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
@@ -52,8 +52,8 @@ export function CleanerSidebar({
|
||||
onChange={() => setValMode('filtered')}
|
||||
/>
|
||||
<div className="w-full">
|
||||
<div className="text-sm font-medium text-slate-800">数据库 - ProductionID 过滤</div>
|
||||
<div className="text-xs text-slate-500 mt-0.5">仅校验指定订单号相关的物料</div>
|
||||
<div className="text-sm font-medium text-foreground">数据库 - ProductionID 过滤</div>
|
||||
<div className="text-xs text-muted-foreground mt-0.5">仅校验指定订单号相关的物料</div>
|
||||
{valMode === 'filtered' && (
|
||||
<div className="mt-3 text-xs text-blue-700 bg-blue-100/50 rounded p-2 flex items-start gap-1.5">
|
||||
<Layers size={14} className="mt-0.5 flex-shrink-0" />
|
||||
@@ -67,9 +67,9 @@ export function CleanerSidebar({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-5">
|
||||
<div className="bg-card rounded-xl shadow-sm border border-border p-5">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-base font-semibold flex items-center gap-2 text-slate-800">
|
||||
<h3 className="text-base font-semibold flex items-center gap-2 text-foreground">
|
||||
<Users size={18} className="text-indigo-500" />
|
||||
筛选 (按负责人)
|
||||
</h3>
|
||||
@@ -82,7 +82,7 @@ export function CleanerSidebar({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedManagers(new Set())}
|
||||
className="text-xs text-slate-500 hover:underline"
|
||||
className="text-xs text-muted-foreground hover:underline"
|
||||
>
|
||||
取消全选
|
||||
</button>
|
||||
@@ -93,7 +93,7 @@ export function CleanerSidebar({
|
||||
{managers.map((manager) => (
|
||||
<label
|
||||
key={manager}
|
||||
className="flex items-center gap-2 text-sm text-slate-700 cursor-pointer hover:bg-slate-50 p-1.5 rounded"
|
||||
className="flex items-center gap-2 text-sm text-foreground cursor-pointer hover:bg-muted p-1.5 rounded"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -111,7 +111,7 @@ export function CleanerSidebar({
|
||||
{manager || '未分配'}
|
||||
</label>
|
||||
))}
|
||||
{managers.length === 0 && <div className="text-sm text-slate-400">暂无负责人数据</div>}
|
||||
{managers.length === 0 && <div className="text-sm text-muted-foreground">暂无负责人数据</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ export function CleanerToolbar({
|
||||
}: CleanerToolbarProps): React.JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<div className="p-4 border-b border-slate-200 flex-shrink-0">
|
||||
<div className="p-4 border-b border-border flex-shrink-0">
|
||||
<Button
|
||||
onClick={() => void handleValidation()}
|
||||
disabled={isValidationRunning}
|
||||
@@ -60,7 +60,7 @@ export function CleanerToolbar({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-50 border-b border-slate-200 px-4 py-3 flex justify-between items-center flex-shrink-0">
|
||||
<div className="bg-muted border-b border-border px-4 py-3 flex justify-between items-center flex-shrink-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button
|
||||
onClick={() => setSelectedItems(new Set(filteredResults.map((r) => r.materialCode)))}
|
||||
@@ -81,10 +81,10 @@ export function CleanerToolbar({
|
||||
}}
|
||||
variant="outline" size="sm"
|
||||
>
|
||||
<Square size={14} className="text-slate-400" /> 取消
|
||||
<Square size={14} className="text-muted-foreground" /> 取消
|
||||
</Button>
|
||||
|
||||
<div className="w-px h-4 bg-slate-300 mx-1"></div>
|
||||
<div className="w-px h-4 bg-border mx-1"></div>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -106,7 +106,7 @@ export function CleanerToolbar({
|
||||
<Eye size={14} /> 显示全部
|
||||
</Button>
|
||||
|
||||
<div className="w-px h-4 bg-slate-300 mx-1"></div>
|
||||
<div className="w-px h-4 bg-border mx-1"></div>
|
||||
|
||||
<Button
|
||||
onClick={() => void handleConfirmDeletion()}
|
||||
|
||||
@@ -29,8 +29,8 @@ export const ComparisonTooltip = React.memo(
|
||||
const firstMetric = Array.from(selectedMetrics)[0]
|
||||
|
||||
return (
|
||||
<div className="bg-white p-4 border border-slate-200 shadow-lg rounded-lg max-w-sm">
|
||||
<p className="font-semibold text-slate-800 mb-2 border-b border-slate-100 pb-2">{label}</p>
|
||||
<div className="bg-card p-4 border border-border shadow-lg rounded-lg max-w-sm">
|
||||
<p className="font-semibold text-foreground mb-2 border-b border-border pb-2">{label}</p>
|
||||
|
||||
<div className="space-y-1.5 text-sm">
|
||||
{displayUsers.map((user: string) => {
|
||||
@@ -39,14 +39,14 @@ export const ComparisonTooltip = React.memo(
|
||||
|
||||
return (
|
||||
<div key={user} className="flex justify-between items-center gap-4">
|
||||
<span className="flex items-center gap-1.5 text-slate-600">
|
||||
<span className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<span
|
||||
className="w-2.5 h-2.5 rounded-full"
|
||||
style={{ backgroundColor: userEntry.color }}
|
||||
/>
|
||||
{user || '未分配'}:
|
||||
</span>
|
||||
<span className="font-medium text-slate-900">
|
||||
<span className="font-medium text-foreground">
|
||||
{firstMetric === 'executionTimeSecs' ? Number(userEntry.value).toFixed(1) : userEntry.value} {firstMetric === 'executionTimeSecs' ? '秒' : ''}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -26,20 +26,20 @@ export const CustomTooltip = React.memo(
|
||||
const dailyData = chartData.find((d) => d.date === label)
|
||||
|
||||
return (
|
||||
<div className="bg-white p-4 border border-slate-200 shadow-lg rounded-lg max-w-sm">
|
||||
<p className="font-semibold text-slate-800 mb-2 border-b border-slate-100 pb-2">{label}</p>
|
||||
<div className="bg-card p-4 border border-border shadow-lg rounded-lg max-w-sm">
|
||||
<p className="font-semibold text-foreground mb-2 border-b border-border pb-2">{label}</p>
|
||||
|
||||
<div className="space-y-1.5 text-sm">
|
||||
{payload.map((entry: any, index: number) => (
|
||||
<div key={`${entry.name}-${index}`} className="flex justify-between items-center gap-4">
|
||||
<span className="flex items-center gap-1.5 text-slate-600">
|
||||
<span className="flex items-center gap-1.5 text-muted-foreground">
|
||||
<span
|
||||
className="w-2.5 h-2.5 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
></span>
|
||||
{entry.name}:
|
||||
</span>
|
||||
<span className="font-medium text-slate-900">
|
||||
<span className="font-medium text-foreground">
|
||||
{entry.dataKey === 'executionTimeSecs' ? Number(entry.value).toFixed(1) : entry.value} {entry.dataKey === 'executionTimeSecs' ? '秒' : ''}
|
||||
</span>
|
||||
</div>
|
||||
@@ -47,7 +47,7 @@ export const CustomTooltip = React.memo(
|
||||
</div>
|
||||
|
||||
{dailyData && (
|
||||
<div className="mt-3 pt-2 border-t border-slate-100 text-xs text-slate-500">
|
||||
<div className="mt-3 pt-2 border-t border-border text-xs text-muted-foreground">
|
||||
<p>操作用户: {dailyData.users.join(', ')}</p>
|
||||
<p className="mt-1">报告总数: {dailyData.reportCount}</p>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export const MetricSelector: React.FC<MetricSelectorProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div className="mb-8">
|
||||
<h3 className="text-sm font-medium text-slate-700 mb-3">
|
||||
<h3 className="text-sm font-medium text-foreground mb-3">
|
||||
选择呈现内容 ({viewMode === 'comparison' ? '单选' : '多选'})
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -37,7 +37,7 @@ export const MetricSelector: React.FC<MetricSelectorProps> = ({
|
||||
className={`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors flex items-center gap-1.5 ${
|
||||
isSelected
|
||||
? 'bg-blue-50 border-blue-200 text-blue-700'
|
||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
|
||||
: 'bg-card border-border text-muted-foreground hover:bg-muted'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -37,12 +37,12 @@ export const UserFilter: React.FC<UserFilterProps> = ({
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-medium text-slate-700">筛选用户</h3>
|
||||
<h3 className="text-sm font-medium text-foreground">筛选用户</h3>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={onSelectAll} className="text-xs text-blue-600 hover:underline">
|
||||
全选
|
||||
</button>
|
||||
<button onClick={onClearAll} className="text-xs text-slate-500 hover:underline">
|
||||
<button onClick={onClearAll} className="text-xs text-muted-foreground hover:underline">
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
@@ -59,8 +59,8 @@ export const UserFilter: React.FC<UserFilterProps> = ({
|
||||
onClick={() => onUserToggle(user)}
|
||||
className={`px-3 py-1.5 rounded-full text-xs font-medium border transition-colors flex items-center gap-1.5 ${
|
||||
isSelected
|
||||
? 'bg-white border-current'
|
||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
|
||||
? 'bg-card border-current'
|
||||
: 'bg-card border-border text-muted-foreground hover:bg-muted'
|
||||
}`}
|
||||
style={isSelected ? { color, borderColor: color } : {}}
|
||||
>
|
||||
@@ -75,7 +75,7 @@ export const UserFilter: React.FC<UserFilterProps> = ({
|
||||
</div>
|
||||
|
||||
{selectedUsers.size === 0 && (
|
||||
<p className="text-xs text-slate-500 mt-2">未选择用户时将显示所有用户数据</p>
|
||||
<p className="text-xs text-muted-foreground mt-2">未选择用户时将显示所有用户数据</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -19,14 +19,14 @@ interface ViewModeToggleProps {
|
||||
export const ViewModeToggle: React.FC<ViewModeToggleProps> = ({ viewMode, onViewModeChange }) => {
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<h3 className="text-sm font-medium text-slate-700 mb-3">视图模式</h3>
|
||||
<h3 className="text-sm font-medium text-foreground mb-3">视图模式</h3>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => onViewModeChange('aggregated')}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium border transition-colors ${
|
||||
viewMode === 'aggregated'
|
||||
? 'bg-blue-50 border-blue-200 text-blue-700'
|
||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
|
||||
: 'bg-card border-border text-muted-foreground hover:bg-muted'
|
||||
}`}
|
||||
>
|
||||
按日期聚合
|
||||
@@ -36,7 +36,7 @@ export const ViewModeToggle: React.FC<ViewModeToggleProps> = ({ viewMode, onView
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium border transition-colors ${
|
||||
viewMode === 'comparison'
|
||||
? 'bg-blue-50 border-blue-200 text-blue-700'
|
||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
|
||||
: 'bg-card border-border text-muted-foreground hover:bg-muted'
|
||||
}`}
|
||||
>
|
||||
用户对比
|
||||
|
||||
@@ -62,23 +62,23 @@ export const ReportAnalysisDialog: React.FC<ReportAnalysisDialogProps> = ({
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[110] flex items-center justify-center bg-slate-900/50 backdrop-blur-sm animate-in fade-in duration-200">
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-[1000px] max-w-[95vw] h-[85vh] flex flex-col border border-slate-200 overflow-hidden animate-in zoom-in-95 duration-200">
|
||||
<div className="bg-card rounded-2xl shadow-2xl w-[1000px] max-w-[95vw] h-[85vh] flex flex-col border border-border overflow-hidden animate-in zoom-in-95 duration-200">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-200 bg-slate-50 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 text-slate-800">
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-border bg-muted flex-shrink-0">
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<BarChart3 size={20} className="text-blue-600" />
|
||||
<h2 className="text-lg font-semibold">执行报告分析</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-slate-400 hover:text-slate-600 hover:bg-slate-200/50 p-1.5 rounded-lg transition-colors"
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-muted p-1.5 rounded-lg transition-colors"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-hidden flex flex-col bg-white">
|
||||
<div className="flex-1 overflow-hidden flex flex-col bg-card">
|
||||
{isLoading ? (
|
||||
<LoadingState />
|
||||
) : error ? (
|
||||
@@ -111,7 +111,7 @@ export const ReportAnalysisDialog: React.FC<ReportAnalysisDialogProps> = ({
|
||||
// ============================================================================
|
||||
|
||||
const LoadingState: React.FC = () => (
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-slate-500">
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-muted-foreground">
|
||||
<Loader2 size={32} className="animate-spin text-blue-500 mb-4" />
|
||||
<p>正在分析报告数据,可能需要几秒钟...</p>
|
||||
</div>
|
||||
@@ -132,8 +132,8 @@ const ErrorState: React.FC<{ error: string; onRetry: () => void }> = ({ error, o
|
||||
)
|
||||
|
||||
const EmptyState: React.FC = () => (
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-slate-400">
|
||||
<BarChart3 size={48} className="mb-4 opacity-50 text-slate-300" />
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-muted-foreground">
|
||||
<BarChart3 size={48} className="mb-4 opacity-50 text-muted-foreground" />
|
||||
<p>暂无报告数据可供分析</p>
|
||||
</div>
|
||||
)
|
||||
@@ -198,7 +198,7 @@ const MainContent: React.FC<MainContentProps> = ({
|
||||
/>
|
||||
|
||||
{/* Description */}
|
||||
<div className="mt-4 text-center text-xs text-slate-400">
|
||||
<div className="mt-4 text-center text-xs text-muted-foreground">
|
||||
{viewMode === 'aggregated'
|
||||
? '数据以天为单位进行聚合统计。展示的是选定时间段内的总量。'
|
||||
: selectedUsers.size === 0
|
||||
|
||||
@@ -81,7 +81,7 @@ const CleanerPage: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex-1 bg-white rounded-xl shadow-sm border border-slate-200 flex flex-col overflow-hidden">
|
||||
<div className="flex-1 bg-card rounded-xl shadow-sm border border-border flex flex-col overflow-hidden">
|
||||
<CleanerToolbar
|
||||
validationResults={validationResults}
|
||||
filteredResults={filteredResults}
|
||||
|
||||
@@ -59,9 +59,9 @@ const ExtractorPage: React.FC<ExtractorPageProps> = ({ currentUser }) => {
|
||||
|
||||
return (
|
||||
<div className="flex h-full gap-4 relative">
|
||||
<aside className="w-80 flex-shrink-0 bg-white border border-slate-200 flex flex-col shadow-sm rounded-xl overflow-hidden h-full">
|
||||
<div className="p-4 border-b border-slate-100">
|
||||
<h3 className="text-sm font-semibold text-slate-800">订单号输入</h3>
|
||||
<aside className="w-80 flex-shrink-0 bg-card border border-border flex flex-col shadow-sm rounded-xl overflow-hidden h-full">
|
||||
<div className="p-4 border-b border-border">
|
||||
<h3 className="text-sm font-semibold text-foreground">订单号输入</h3>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col p-4 min-h-0">
|
||||
<OrderNumberInput
|
||||
@@ -77,13 +77,13 @@ const ExtractorPage: React.FC<ExtractorPageProps> = ({ currentUser }) => {
|
||||
</aside>
|
||||
|
||||
<div className="flex-1 min-w-0 flex flex-col gap-4 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-5 flex items-center justify-between">
|
||||
<div className="bg-card rounded-xl shadow-sm border border-border p-5 flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-lg font-semibold flex items-center gap-2 text-slate-800 mb-1">
|
||||
<h2 className="text-lg font-semibold flex items-center gap-2 text-foreground mb-1">
|
||||
<Download size={20} className="text-blue-600" />
|
||||
批量数据提取
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500">遍历订单列表,自动执行数据导出并保存至数据库</p>
|
||||
<p className="text-sm text-muted-foreground">遍历订单列表,自动执行数据导出并保存至数据库</p>
|
||||
{error && <p className="text-sm text-red-500 mt-2">{error}</p>}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -74,8 +74,8 @@ const SettingsPage: React.FC = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[500px]">
|
||||
<div className="text-center">
|
||||
<div className="w-10 h-10 border-4 border-slate-200 border-t-blue-500 rounded-full animate-spin mx-auto mb-4"></div>
|
||||
<p className="text-slate-500">加载中...</p>
|
||||
<div className="w-10 h-10 border-4 border-border border-t-blue-500 rounded-full animate-spin mx-auto mb-4"></div>
|
||||
<p className="text-muted-foreground">加载中...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -83,22 +83,22 @@ const SettingsPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="flex justify-center animate-in fade-in slide-in-from-bottom-4 duration-500 mt-6">
|
||||
<div className="w-full max-w-xl bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div className="border-b border-slate-100 bg-slate-50 px-6 py-5">
|
||||
<h2 className="text-lg font-semibold flex items-center gap-2 text-slate-800">
|
||||
<SettingsIcon size={20} className="text-slate-600" />
|
||||
<div className="w-full max-w-xl bg-card rounded-xl shadow-sm border border-border overflow-hidden">
|
||||
<div className="border-b border-border bg-muted px-6 py-5">
|
||||
<h2 className="text-lg font-semibold flex items-center gap-2 text-foreground">
|
||||
<SettingsIcon size={20} className="text-muted-foreground" />
|
||||
ERP 账号配置
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
设置 ERP 系统的登录账号和密码。此配置将存储在数据库中,按用户管理。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6 bg-white">
|
||||
<div className="p-6 space-y-6 bg-card">
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">
|
||||
<label className="block text-sm font-medium text-muted-foreground mb-1.5">
|
||||
<span className="flex items-center gap-2">
|
||||
<User size={16} className="text-slate-500" />
|
||||
<User size={16} className="text-muted-foreground" />
|
||||
ERP 登录账号
|
||||
</span>
|
||||
</label>
|
||||
@@ -110,14 +110,14 @@ const SettingsPage: React.FC = () => {
|
||||
setCredentials((prev) => ({ ...prev, username: e.target.value }))
|
||||
setIsModified(true)
|
||||
}}
|
||||
className="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-slate-50"
|
||||
className="w-full border border-input rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-muted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-md mx-auto">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">
|
||||
<label className="block text-sm font-medium text-muted-foreground mb-1.5">
|
||||
<span className="flex items-center gap-2">
|
||||
<Key size={16} className="text-slate-500" />
|
||||
<Key size={16} className="text-muted-foreground" />
|
||||
ERP 登录密码
|
||||
</span>
|
||||
</label>
|
||||
@@ -129,11 +129,11 @@ const SettingsPage: React.FC = () => {
|
||||
setCredentials((prev) => ({ ...prev, password: e.target.value }))
|
||||
setIsModified(true)
|
||||
}}
|
||||
className="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-slate-50"
|
||||
className="w-full border border-input rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-muted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-md mx-auto pt-6 mt-2 border-t border-slate-100 flex justify-center">
|
||||
<div className="w-full max-w-md mx-auto pt-6 mt-2 border-t border-border flex justify-center">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSaveCredentials}
|
||||
|
||||
Reference in New Issue
Block a user