Przeglądaj źródła

Merge branch 'develop' of http://192.168.0.253:3000/bozzysadmb/ISCS-Client-V1.0 into develop

wyn 3 miesięcy temu
rodzic
commit
23da4e6123

+ 4 - 2
src/Dashboard.tsx

@@ -129,7 +129,7 @@ export default function Dashboard() {
       sessionStorage.removeItem('lastActiveMenu');
       sessionStorage.removeItem('navigateToMenu');
       sessionStorage.removeItem('cabinetDetailSource');
-      toast.success(t('common.success'));
+      // 退出登录不再弹出全局成功提示(避免频繁/停留过久影响体验)
       navigate('/login');
     } catch (error: any) {
       // 即使API失败也清除本地数据并跳转
@@ -1321,11 +1321,13 @@ export default function Dashboard() {
 
             {/* 右侧功能区 */}
             <div className="flex items-center gap-2 lg:gap-4 flex-shrink-0">
-              {/* 语言切换 */}
+              {/* 语言切换 - 鼠标悬停显示,移开自动收起 */}
               <Dropdown
                 menu={{ items: languageMenuItems }}
                 trigger={['hover']}
                 placement="bottomRight"
+                mouseEnterDelay={0}
+                mouseLeaveDelay={0.15}
               >
                 <button
                   className="flex items-center gap-2 px-3 py-2 hover:bg-gray-100 rounded-xl transition-colors group"

+ 96 - 96
src/components/Dashboard.tsx

@@ -331,7 +331,7 @@ export default function Dashboard() {
       setStatistics(statisticsData);
     } catch (error: any) {
       console.error('获取驾驶舱统计数据失败:', error);
-      toast.error(error.message || '获取统计数据失败');
+      toast.error(error.message || t('cockpit.getStatsFailed'));
     } finally {
       setLoading(false);
     }
@@ -384,7 +384,7 @@ export default function Dashboard() {
       setManagerWorkCount(data || {});
     } catch (error: any) {
       console.error('获取管理员工作统计失败:', error);
-      toast.error(error.message || '获取管理员工作统计失败');
+      toast.error(error.message || t('cockpit.getManagerWorkStatsFailed'));
       setManagerWorkCount({});
     }
   };
@@ -406,7 +406,7 @@ export default function Dashboard() {
       console.log('获取管理员工作列表成功,设置的数据:', data, '数据长度:', data.length);
     } catch (error: any) {
       console.error('获取管理员工作列表失败:', error);
-      toast.error(error.message || '获取管理员工作列表失败');
+      toast.error(error.message || t('cockpit.getManagerWorkListFailed'));
       setManagerWorkList([]);
     }
   };
@@ -429,7 +429,7 @@ export default function Dashboard() {
       setManagerDayWorkCount(data);
     } catch (error: any) {
       console.error('获取管理员每日工作统计失败:', error);
-      toast.error(error.message || '获取管理员每日工作统计失败');
+      toast.error(error.message || t('cockpit.getManagerDayStatsFailed'));
       setManagerDayWorkCount([]);
     }
   };
@@ -1143,7 +1143,7 @@ export default function Dashboard() {
     
     if (!task.nodeId) {
       console.warn('Dashboard: 节点ID不存在', task);
-      message.warning('节点ID不存在');
+      message.warning(t('cockpit.nodeIdNotExist'));
       return;
     }
     
@@ -1305,11 +1305,11 @@ export default function Dashboard() {
             }, 100);
           } else {
             console.warn('Dashboard: formData 中缺少 conf 或 fields', { conf: !!conf, fields: !!fields });
-            message.warning('表单数据不完整');
+            message.warning(t('cockpit.formDataIncomplete'));
           }
         } catch (e) {
           console.error('Dashboard: 解析 formData 失败', e);
-          message.error('解析表单数据失败: ' + (e instanceof Error ? e.message : String(e)));
+          message.error(t('cockpit.parseFormDataFailed') + ': ' + (e instanceof Error ? e.message : String(e)));
         } finally {
           setFormLoading(false);
         }
@@ -1327,7 +1327,7 @@ export default function Dashboard() {
             const numericFormId = typeof formId === 'string' ? parseInt(formId, 10) : formId;
             if (isNaN(numericFormId)) {
               console.error('Dashboard: formId 不是有效数字', formId);
-              message.error('表单ID无效');
+              message.error(t('cockpit.formIdInvalid'));
               setFormLoading(false);
               return;
             }
@@ -1428,11 +1428,11 @@ export default function Dashboard() {
               }
             } else {
               console.warn('Dashboard: 表单详情缺少配置或字段', { conf: !!conf, fields: !!fields });
-              message.warning('表单配置不完整');
+              message.warning(t('cockpit.formConfigIncomplete'));
             }
           } catch (e) {
             console.error('Dashboard: 获取表单详情失败', e);
-            message.error('获取表单详情失败: ' + (e instanceof Error ? e.message : String(e)));
+            message.error(t('cockpit.getFormDetailFailed') + ': ' + (e instanceof Error ? e.message : String(e)));
           } finally {
             setFormLoading(false);
           }
@@ -1451,7 +1451,7 @@ export default function Dashboard() {
       console.log('Dashboard: 弹框已打开', { detailVisible: true, detailData: detailDataWithWorkInfo });
     } catch (error: any) {
       console.error('Dashboard: 获取节点详情失败', error);
-      toast.error(error.message || '获取节点详情失败');
+      toast.error(error.message || t('cockpit.getNodeDetailFailed'));
       if (!taskDetailData) {
         setTaskDetailData({
           id: task.nodeId,
@@ -1472,20 +1472,20 @@ export default function Dashboard() {
 
   // 获取任务状态文本(使用字典)
   const getTaskStatusText = (status: string | number | undefined): string => {
-    if (!status) return '未知';
+    if (!status) return t('cockpit.unknown');
     const statusStr = String(status).toLowerCase();
     const statusItem = approvalStatusDictList.find(item => String(item.value).toLowerCase() === statusStr);
     if (statusItem) {
-      return statusItem.label || '未知';
+      return statusItem.label || t('cockpit.unknown');
     }
     // 如果没有找到字典值,使用默认映射
     const statusMap: Record<string, string> = {
-      'pending': '待审核',
-      'approved': '已通过',
-      'rejected': '已驳回',
-      'unaudited': '未审核',
+      'pending': t('cockpit.pendingAudit'),
+      'approved': t('cockpit.approved'),
+      'rejected': t('cockpit.rejected'),
+      'unaudited': t('cockpit.unaudited'),
     };
-    return statusMap[statusStr] || '未知';
+    return statusMap[statusStr] || t('cockpit.unknown');
   };
 
   // 获取任务状态样式(审批状态)
@@ -1555,44 +1555,44 @@ export default function Dashboard() {
   // 获取作业状态标签样式
   const getStatusBadge = (status: string) => {
     const statusMap: Record<string, { label: string; className: string }> = {
-      '待执行': { label: '待执行', className: 'bg-gray-100 text-gray-700 border-gray-200' },
-      'pending': { label: '待执行', className: 'bg-gray-100 text-gray-700 border-gray-200' },
-      'PENDING': { label: '待执行', className: 'bg-gray-100 text-gray-700 border-gray-200' },
-      '进行中': { label: '进行中', className: 'bg-blue-100 text-blue-700 border-blue-200' },
-      'in_progress': { label: '进行中', className: 'bg-blue-100 text-blue-700 border-blue-200' },
-      'IN_PROGRESS': { label: '进行中', className: 'bg-blue-100 text-blue-700 border-blue-200' },
-      '已完成': { label: '已完成', className: 'bg-green-100 text-green-700 border-green-200' },
-      'completed': { label: '已完成', className: 'bg-green-100 text-green-700 border-green-200' },
-      'COMPLETED': { label: '已完成', className: 'bg-green-100 text-green-700 border-green-200' },
-      '逾期': { label: '逾期', className: 'bg-red-100 text-red-700 border-red-200' },
-      'overdue': { label: '逾期', className: 'bg-red-100 text-red-700 border-red-200' },
-      'OVERDUE': { label: '逾期', className: 'bg-red-100 text-red-700 border-red-200' },
+      '待执行': { label: t('cockpit.pending'), className: 'bg-gray-100 text-gray-700 border-gray-200' },
+      'pending': { label: t('cockpit.pending'), className: 'bg-gray-100 text-gray-700 border-gray-200' },
+      'PENDING': { label: t('cockpit.pending'), className: 'bg-gray-100 text-gray-700 border-gray-200' },
+      '进行中': { label: t('cockpit.inProgress'), className: 'bg-blue-100 text-blue-700 border-blue-200' },
+      'in_progress': { label: t('cockpit.inProgress'), className: 'bg-blue-100 text-blue-700 border-blue-200' },
+      'IN_PROGRESS': { label: t('cockpit.inProgress'), className: 'bg-blue-100 text-blue-700 border-blue-200' },
+      '已完成': { label: t('cockpit.completed'), className: 'bg-green-100 text-green-700 border-green-200' },
+      'completed': { label: t('cockpit.completed'), className: 'bg-green-100 text-green-700 border-green-200' },
+      'COMPLETED': { label: t('cockpit.completed'), className: 'bg-green-100 text-green-700 border-green-200' },
+      '逾期': { label: t('cockpit.overdue'), className: 'bg-red-100 text-red-700 border-red-200' },
+      'overdue': { label: t('cockpit.overdue'), className: 'bg-red-100 text-red-700 border-red-200' },
+      'OVERDUE': { label: t('cockpit.overdue'), className: 'bg-red-100 text-red-700 border-red-200' },
     };
     
-    return statusMap[status] || { label: status || '未知', className: 'bg-gray-100 text-gray-700 border-gray-200' };
+    return statusMap[status] || { label: status || t('cockpit.unknown'), className: 'bg-gray-100 text-gray-700 border-gray-200' };
   };
 
   // 从ticketName中提取作业类型
   const getJobType = (ticketName: string): string => {
     if (ticketName.includes('换产') || ticketName.includes('投产')) {
-      return '投产';
+      return t('cockpit.production');
     } else if (ticketName.includes('维修')) {
-      return '维修';
+      return t('cockpit.maintenance');
     } else if (ticketName.includes('PM')) {
       return 'PM';
     }
-    return '未知';
+    return t('cockpit.unknown');
   };
 
   // 获取作业状态显示文本和样式(从字典获取)
   const getJobStatusInfo = (status: string | number | undefined) => {
     if (!status) {
-      return { label: '未知', className: 'bg-gray-100 text-gray-700 border-gray-200' };
+      return { label: t('cockpit.unknown'), className: 'bg-gray-100 text-gray-700 border-gray-200' };
     }
     
     // 从字典中查找状态文本
     const statusItem = jobStatusDictList.find(item => String(item.value) === String(status));
-    const statusText = statusItem ? (statusItem.label || '') : String(status || '未知');
+    const statusText = statusItem ? (statusItem.label || '') : String(status || t('cockpit.unknown'));
     const statusTextLower = statusText.toLowerCase();
     
     // 根据状态文本判断样式
@@ -1627,7 +1627,7 @@ export default function Dashboard() {
   if (loading) {
     return (
       <div className="flex items-center justify-center h-96">
-        <div className="text-gray-500">加载中...</div>
+        <div className="text-gray-500">{t('cockpit.loading')}</div>
       </div>
     );
   }
@@ -1676,14 +1676,14 @@ export default function Dashboard() {
             }}
           >
             <div className="flex items-start justify-between mb-3">
-              <div className="text-sm text-gray-500 font-medium">待发布作业</div>
+              <div className="text-sm text-gray-500 font-medium">{t('cockpit.pendingJobs')}</div>
               <div className="p-3 rounded-xl flex items-center justify-center flex-shrink-0" style={{ backgroundColor: 'rgba(188, 185, 183, 0.2)' }}>
                 <Clock className="w-6 h-6 text-black-700" strokeWidth={2} />
               </div>
             </div>
             <div>
               <div className="text-2xl font-bold text-gray-900 leading-none">
-                {managerWorkCount.unreleasedCount ?? statistics.pendingJobsCount ?? 0} <span className="text-base font-normal text-gray-500"></span>
+                {managerWorkCount.unreleasedCount ?? statistics.pendingJobsCount ?? 0} <span className="text-base font-normal text-gray-500">{t('cockpit.items')}</span>
               </div>
             </div>
           </div>
@@ -1704,14 +1704,14 @@ export default function Dashboard() {
             }}
           >
             <div className="flex items-start justify-between mb-3">
-              <div className="text-sm text-gray-500 font-medium">进行中作业</div>
+              <div className="text-sm text-gray-500 font-medium">{t('cockpit.inProgressJobs')}</div>
               <div className="p-3 rounded-xl flex items-center justify-center flex-shrink-0" style={{ backgroundColor: 'rgba(59, 130, 246, 0.2)' }}>
                 <PlayCircle className="w-6 h-6 text-blue-700" strokeWidth={2} />
               </div>
             </div>
             <div>
               <div className="text-2xl font-bold text-gray-900 leading-none">
-                {managerWorkCount.runningCount ?? statistics.inProgressJobsCount ?? 0} <span className="text-base font-normal text-gray-500"></span>
+                {managerWorkCount.runningCount ?? statistics.inProgressJobsCount ?? 0} <span className="text-base font-normal text-gray-500">{t('cockpit.items')}</span>
               </div>
             </div>
           </div>
@@ -1732,14 +1732,14 @@ export default function Dashboard() {
             }}
           >
             <div className="flex items-start justify-between mb-3">
-              <div className="text-sm text-gray-500 font-medium">已完成作业</div>
+              <div className="text-sm text-gray-500 font-medium">{t('cockpit.completedJobs')}</div>
               <div className="p-3 rounded-xl flex items-center justify-center flex-shrink-0" style={{ backgroundColor: 'rgba(34, 197, 94, 0.2)' }}>
                 <CheckCircle className="w-6 h-6 text-green-700" strokeWidth={2} />
               </div>
             </div>
             <div>
               <div className="text-2xl font-bold text-gray-900 leading-none">
-                {managerWorkCount.completedCount ?? statistics.completedJobsCount ?? 0} <span className="text-base font-normal text-gray-500"></span>
+                {managerWorkCount.completedCount ?? statistics.completedJobsCount ?? 0} <span className="text-base font-normal text-gray-500">{t('cockpit.items')}</span>
               </div>
             </div>
           </div>
@@ -1748,7 +1748,7 @@ export default function Dashboard() {
           {showOverdueJobs && (
             <div className="flex-1 bg-white rounded-lg border border-red-200 p-4 shadow-sm hover:shadow-md transition-shadow relative overflow-hidden">
               <div className="flex items-start justify-between mb-3">
-                <div className="text-sm text-gray-500 font-medium">逾期作业</div>
+                <div className="text-sm text-gray-500 font-medium">{t('cockpit.overdueJobs')}</div>
                 <div className="p-3 rounded-xl flex items-center justify-center flex-shrink-0" style={{ backgroundColor: 'rgba(239, 68, 68, 0.2)' }}>
                   <AlertTriangle className="w-6 h-6 text-red-700" strokeWidth={2} />
                 </div>
@@ -1756,14 +1756,14 @@ export default function Dashboard() {
               <div className="flex items-center justify-between">
                 <div>
                   <div className="text-2xl font-bold text-gray-900 leading-none">
-                    {statistics.overdueJobsCount ?? 0} <span className="text-base font-normal text-gray-500"></span>
+                    {statistics.overdueJobsCount ?? 0} <span className="text-base font-normal text-gray-500">{t('cockpit.items')}</span>
                   </div>
                 </div>
                 <button
                   onClick={() => setShowOverdueJobs(false)}
                   className="text-red-600 text-sm font-medium hover:text-red-700 transition-colors"
                 >
-                  隐藏
+                  {t('cockpit.hide')}
                 </button>
               </div>
             </div>
@@ -1774,7 +1774,7 @@ export default function Dashboard() {
         <Card
           title={
             <div className="flex items-center justify-between">
-              <span>作业列表 (含任务明细)</span>
+              <span>{t('cockpit.jobListWithTasks')}</span>
               <button
                 onClick={() => {
                   // 通过 sessionStorage 传递参数,让 Dashboard 自动切换到作业管理菜单
@@ -1799,14 +1799,14 @@ export default function Dashboard() {
                 onMouseEnter={(e) => e.currentTarget.style.color = '#1d4ed8'}
                 onMouseLeave={(e) => e.currentTarget.style.color = '#2563eb'}
               >
-                查看全部作业
+                {t('cockpit.viewAllJobs')}
               </button>
             </div>
           }
         >
           <div className="border border-gray-200 rounded-lg overflow-hidden">
             {managerDataLoading ? (
-              <div className="p-8 text-center text-gray-500">加载中...</div>
+              <div className="p-8 text-center text-gray-500">{t('cockpit.loading')}</div>
             ) : managerWorkList.length > 0 ? (
               <Collapse
                 activeKey={Array.from(expandedJobs).map(id => String(id))}
@@ -1852,7 +1852,7 @@ export default function Dashboard() {
                   const isSelected = selectedJob === job.id;
                   const statusInfo = getTaskStatusInfo(job.status);
                   const jobName = job.name || job.orderNo || job.code || `作业#${job.id}`;
-                  const responsiblePerson = job.initiatorName || job.initiator || '未分配';
+                  const responsiblePerson = job.initiatorName || job.initiator || t('cockpit.unassigned');
                   
                   return (
                     <Panel
@@ -1888,7 +1888,7 @@ export default function Dashboard() {
                             <span className={`text-sm truncate block ${
                               isSelected ? '' : 'text-gray-900'
                             }`} style={isSelected ? { color: '#2563eb' } : { color: '#111827' }}>
-                              <span>作业发起人:</span>{responsiblePerson}
+                              <span>{t('cockpit.jobInitiator')}:</span>{responsiblePerson}
                             </span>
                           </div>
                           {/* 整体进度 - 固定宽度和位置,右对齐 */}
@@ -1896,7 +1896,7 @@ export default function Dashboard() {
                             <span className={`text-sm whitespace-nowrap ${
                               isSelected ? '' : 'text-gray-900'
                             }`} style={isSelected ? { color: '#2563eb' } : { color: '#111827' }}>
-                              <span>整体进度:</span>{calculateJobProgress(job)}%
+                              <span>{t('cockpit.overallProgress')}:</span>{calculateJobProgress(job)}%
                             </span>
                           </div>
                           {/* 右侧弹性空间 */}
@@ -1917,16 +1917,16 @@ export default function Dashboard() {
                       <div className="px-3 pb-3 pt-3 bg-white">
                         <div className="overflow-x-auto">
                           {taskLoadingMap.get(job.id!) ? (
-                            <div className="px-4 py-8 text-center text-sm text-gray-500">加载中...</div>
+                            <div className="px-4 py-8 text-center text-sm text-gray-500">{t('cockpit.loading')}</div>
                           ) : (
                             <table className="w-full border-collapse">
                               <thead>
                                 <tr className="bg-gray-50 border-b border-gray-200">
-                                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">任务名称</th>
-                                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">任务负责人</th>
-                                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">开始时间</th>
-                                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">状态</th>
-                                  <th className="px-4 py-3 text-center text-xs font-medium text-gray-700">操作</th>
+                                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.taskName')}</th>
+                                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.taskOwner')}</th>
+                                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.startTime')}</th>
+                                  <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.status')}</th>
+                                  <th className="px-4 py-3 text-center text-xs font-medium text-gray-700">{t('cockpit.action')}</th>
                                 </tr>
                               </thead>
                               <tbody className="bg-white">
@@ -1994,7 +1994,7 @@ export default function Dashboard() {
                 })}
               </Collapse>
             ) : (
-              <div className="p-8 text-center text-gray-500">暂无作业数据</div>
+              <div className="p-8 text-center text-gray-500">{t('cockpit.noJobData')}</div>
             )}
           </div>
         </Card>
@@ -2003,7 +2003,7 @@ export default function Dashboard() {
       {/* 全系统作业完成趋势图表 */}
       <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
         <h3 className="text-lg font-semibold text-gray-900 mb-6">
-          全系统作业完成趋势 (最近30天)
+          {t('cockpit.systemJobCompleteTrend')}
         </h3>
         <div className="h-80">
           <ResponsiveContainer width="100%" height="100%">
@@ -2069,7 +2069,7 @@ export default function Dashboard() {
                 strokeWidth={2}
                 fill="url(#colorCompleted)"
                 dot={{ fill: '#3b82f6', r: 4, strokeWidth: 2, stroke: '#fff' }}
-                name="每日完成作业数"
+                name={t('cockpit.dailyCompletedCount')}
               />
             </AreaChart>
           </ResponsiveContainer>
@@ -2078,14 +2078,14 @@ export default function Dashboard() {
 
       {/* 物料管理部分 */}
       <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
-        <h3 className="text-lg font-semibold text-gray-900 mb-4">物料管理</h3>
+        <h3 className="text-lg font-semibold text-gray-900 mb-4">{t('cockpit.materialManagement')}</h3>
         <div className="flex gap-4">
           {/* 可用物料 - 绿色 */}
           <div className="flex-1 bg-green-50 rounded-lg border border-green-200 p-5 text-center">
             <div className="text-3xl font-bold text-green-900 mb-2">
               {statistics.availableMaterialsCount || 0}
             </div>
-            <div className="text-sm text-green-700">可用物料</div>
+            <div className="text-sm text-green-700">{t('cockpit.availableMaterials')}</div>
           </div>
           
           {/* 借用中物料 - 橙色 */}
@@ -2093,7 +2093,7 @@ export default function Dashboard() {
             <div className="text-3xl font-bold text-orange-900 mb-2">
               {statistics.loanMaterialsCount || 0}
             </div>
-            <div className="text-sm text-orange-700">借用中物料</div>
+            <div className="text-sm text-orange-700">{t('cockpit.borrowedMaterials')}</div>
           </div>
           
           {/* 异常物料 - 红色 */}
@@ -2101,7 +2101,7 @@ export default function Dashboard() {
             <div className="text-3xl font-bold text-red-900 mb-2">
               {statistics.exceptionMaterialsCount || 0}
             </div>
-            <div className="text-sm text-red-700">异常物料</div>
+            <div className="text-sm text-red-700">{t('cockpit.exceptionMaterials')}</div>
           </div>
           
           {/* 待归还物料 - 灰色 */}
@@ -2109,7 +2109,7 @@ export default function Dashboard() {
             <div className="text-3xl font-bold text-gray-900 mb-2">
               {statistics.returnMaterialsCount || 0}
             </div>
-            <div className="text-sm text-gray-600">待归还物料</div>
+            <div className="text-sm text-gray-600">{t('cockpit.returnPendingMaterials')}</div>
           </div>
         </div>
       </div>
@@ -2120,7 +2120,7 @@ export default function Dashboard() {
           <div className="flex items-center justify-between">
             <div className="flex items-center gap-2">
               <List className="w-5 h-5 text-gray-600" />
-              <span>物料状态列表</span>
+              <span>{t('cockpit.materialStatusList')}</span>
             </div>
             <button
               className="text-sm font-medium border-0 bg-transparent p-0 cursor-pointer"
@@ -2128,7 +2128,7 @@ export default function Dashboard() {
               onMouseEnter={(e) => e.currentTarget.style.color = '#1d4ed8'}
               onMouseLeave={(e) => e.currentTarget.style.color = '#2563eb'}
             >
-              查看全部物料
+              {t('cockpit.viewAllMaterials')}
             </button>
           </div>
         }
@@ -2137,12 +2137,12 @@ export default function Dashboard() {
           <table className="w-full border-collapse">
             <thead>
               <tr className="bg-gray-50 border-b border-gray-200">
-                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">物料编号</th>
-                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">物料名称</th>
-                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">物料类型</th>
-                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">状态</th>
-                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">借用人员</th>
-                <th className="px-4 py-3 text-center text-xs font-medium text-gray-700">操作</th>
+                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.materialCode')}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.materialName')}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.materialType')}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.status')}</th>
+                <th className="px-4 py-3 text-left text-xs font-medium text-gray-700">{t('cockpit.borrower')}</th>
+                <th className="px-4 py-3 text-center text-xs font-medium text-gray-700">{t('cockpit.action')}</th>
               </tr>
             </thead>
             <tbody className="bg-white">
@@ -2152,7 +2152,7 @@ export default function Dashboard() {
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900">隔离锁具 (通用型)</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">锁具</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm">
-                  <span className="px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-gray-900">借用中</span>
+                  <span className="px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-gray-900">{t('cockpit.borrowed')}</span>
                 </td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">张三</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-center">
@@ -2163,7 +2163,7 @@ export default function Dashboard() {
                       onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#0958d9'}
                       onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#1677ff'}
                     >
-                      查看
+                      {t('cockpit.view')}
                     </button>
                     <button 
                       className="px-3 py-1.5 text-xs font-medium rounded transition-colors"
@@ -2171,7 +2171,7 @@ export default function Dashboard() {
                       onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#d46b08'}
                       onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#fa8c16'}
                     >
-                      催还
+                      {t('cockpit.urgeReturn')}
                     </button>
                   </div>
                 </td>
@@ -2181,7 +2181,7 @@ export default function Dashboard() {
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900">警示标签 (大号)</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">标签</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm">
-                  <span className="px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700">异常</span>
+                  <span className="px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700">{t('cockpit.exception')}</span>
                 </td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">-</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-center">
@@ -2192,7 +2192,7 @@ export default function Dashboard() {
                       onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#0958d9'}
                       onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#1677ff'}
                     >
-                      查看
+                      {t('cockpit.view')}
                     </button>
                     <button 
                       className="px-3 py-1.5 text-xs font-medium rounded transition-colors"
@@ -2200,7 +2200,7 @@ export default function Dashboard() {
                       onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#cf1322'}
                       onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#ff4d4f'}
                     >
-                      处理异常
+                      {t('cockpit.handleException')}
                     </button>
                   </div>
                 </td>
@@ -2210,7 +2210,7 @@ export default function Dashboard() {
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900">断电测试仪</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">仪器</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm">
-                  <span className="px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-gray-900">可用</span>
+                  <span className="px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-gray-900">{t('cockpit.available')}</span>
                 </td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">-</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-center">
@@ -2221,7 +2221,7 @@ export default function Dashboard() {
                       onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#0958d9'}
                       onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#1677ff'}
                     >
-                      查看
+                      {t('cockpit.view')}
                     </button>
                     <button 
                       className="px-3 py-1.5 text-xs font-medium rounded transition-colors"
@@ -2229,7 +2229,7 @@ export default function Dashboard() {
                       onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#389e0d'}
                       onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#52c41a'}
                     >
-                      申领
+                      {t('cockpit.claim')}
                     </button>
                   </div>
                 </td>
@@ -2239,7 +2239,7 @@ export default function Dashboard() {
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900">隔离锁具 (防爆型)</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">锁具</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm">
-                  <span className="px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700">待归还</span>
+                  <span className="px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700">{t('cockpit.returnPending')}</span>
                 </td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">李四</td>
                 <td className="px-4 py-3 whitespace-nowrap text-sm text-center">
@@ -2250,7 +2250,7 @@ export default function Dashboard() {
                       onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#0958d9'}
                       onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#1677ff'}
                     >
-                      查看
+                      {t('cockpit.view')}
                     </button>
                     <button 
                       className="px-3 py-1.5 text-xs font-medium rounded transition-colors"
@@ -2258,7 +2258,7 @@ export default function Dashboard() {
                       onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#d46b08'}
                       onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#fa8c16'}
                     >
-                      催还
+                      {t('cockpit.urgeReturn')}
                     </button>
                   </div>
                 </td>
@@ -2273,26 +2273,26 @@ export default function Dashboard() {
         title={
           <div className="flex items-center gap-2">
             <Rocket className="w-5 h-5 text-gray-600" />
-            <span>物料管理快捷操作</span>
+            <span>{t('cockpit.materialQuickActions')}</span>
           </div>
         }
       >
         <div className="flex gap-4">
           <button className="flex-1 bg-white border border-gray-200 rounded-lg p-4 hover:bg-gray-50 hover:border-gray-300 transition-all flex flex-col items-center justify-center gap-2">
             <Plus className="w-6 h-6 text-gray-600" />
-            <span className="text-sm font-medium text-gray-700">新增物料</span>
+            <span className="text-sm font-medium text-gray-700">{t('cockpit.addMaterial')}</span>
           </button>
           <button className="flex-1 bg-white border border-gray-200 rounded-lg p-4 hover:bg-gray-50 hover:border-gray-300 transition-all flex flex-col items-center justify-center gap-2">
             <RefreshCw className="w-6 h-6 text-gray-600" />
-            <span className="text-sm font-medium text-gray-700">刷新物料状态</span>
+            <span className="text-sm font-medium text-gray-700">{t('cockpit.refreshMaterialStatus')}</span>
           </button>
           <button className="flex-1 bg-white border border-gray-200 rounded-lg p-4 hover:bg-gray-50 hover:border-gray-300 transition-all flex flex-col items-center justify-center gap-2">
             <AlertCircle className="w-6 h-6 text-gray-600" />
-            <span className="text-sm font-medium text-gray-700">处理异常物料</span>
+            <span className="text-sm font-medium text-gray-700">{t('cockpit.handleExceptionMaterials')}</span>
           </button>
           <button className="flex-1 bg-white border border-gray-200 rounded-lg p-4 hover:bg-gray-50 hover:border-gray-300 transition-all flex flex-col items-center justify-center gap-2">
             <FileText className="w-6 h-6 text-gray-600" />
-            <span className="text-sm font-medium text-gray-700">物料台账导出</span>
+            <span className="text-sm font-medium text-gray-700">{t('cockpit.materialExport')}</span>
           </button>
         </div>
       </Card>
@@ -2341,7 +2341,7 @@ export default function Dashboard() {
                   flexShrink: 0
                 }}></div>
                 <h2 className="text-xl font-semibold mb-2" style={{ color: '#025fff', marginBottom: 0 }}>
-                  {taskDetailData.workName || taskDetailData.name || '任务详情'}
+                  {taskDetailData.workName || taskDetailData.name || t('cockpit.taskDetail')}
                 </h2>
                 <span 
                   className="inline-flex px-3 py-1 rounded-full text-xs font-medium"
@@ -2351,10 +2351,10 @@ export default function Dashboard() {
                 </span>
               </div>
               <div className="text-sm flex gap-4" style={{ color: '#898f9a', marginTop: '12px' }}>
-                <span>工单编号:{taskDetailData.orderNo || '-'}</span>
-                <span>作业发起人:{taskDetailData.initiatorName || '-'}</span>
-                <span>任务负责人:{taskDetailData.workerUserName || '-'}</span>
-                <span>开始时间:{taskDetailData.workTime ? dateFormatter(taskDetailData.workTime) : '-'}</span>
+                <span>{t('cockpit.orderNo')}:{taskDetailData.orderNo || '-'}</span>
+                <span>{t('cockpit.jobInitiatorLabel')}:{taskDetailData.initiatorName || '-'}</span>
+                <span>{t('cockpit.taskOwnerLabel')}:{taskDetailData.workerUserName || '-'}</span>
+                <span>{t('cockpit.startTime')}:{taskDetailData.workTime ? dateFormatter(taskDetailData.workTime) : '-'}</span>
               </div>
             </div>
 

+ 6 - 6
src/components/DictTypeManagement.tsx

@@ -678,14 +678,14 @@ export default function DictTypeManagement() {
         <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
           <div className="flex items-center justify-between">
             <div className="text-sm text-gray-600">
-              共 <span className="text-blue-600 font-medium">{total}</span> 条记录
+              {t('common.total')} <span className="text-blue-600 font-medium">{total}</span> {t('common.records')}
             </div>
             <div className="flex gap-2">
               <Button
                 onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo! - 1 })}
                 disabled={queryParams.pageNo! <= 1}
               >
-                上一页
+                {t('common.prevPage')}
               </Button>
               <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                 {queryParams.pageNo} / {Math.ceil(total / queryParams.pageSize!) || 1}
@@ -694,7 +694,7 @@ export default function DictTypeManagement() {
                 onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo! + 1 })}
                 disabled={queryParams.pageNo! >= Math.ceil(total / queryParams.pageSize!)}
               >
-                下一页
+                {t('common.nextPage')}
               </Button>
             </div>
           </div>
@@ -754,14 +754,14 @@ export default function DictTypeManagement() {
             <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
               <div className="flex items-center justify-between">
                 <div className="text-sm text-gray-600">
-                  共 <span className="text-blue-600 font-medium">{dictDataTotal}</span> 条记录
+                  {t('common.total')} <span className="text-blue-600 font-medium">{dictDataTotal}</span> {t('common.records')}
                 </div>
                 <div className="flex gap-2">
                   <Button
                     onClick={() => handleDictDataPageChange(dictDataPageParams.pageNo! - 1, dictDataPageParams.pageSize!)}
                     disabled={dictDataPageParams.pageNo! <= 1}
                   >
-                    上一页
+                    {t('common.prevPage')}
                   </Button>
                   <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                     {dictDataPageParams.pageNo} / {Math.ceil(dictDataTotal / dictDataPageParams.pageSize!) || 1}
@@ -770,7 +770,7 @@ export default function DictTypeManagement() {
                     onClick={() => handleDictDataPageChange(dictDataPageParams.pageNo! + 1, dictDataPageParams.pageSize!)}
                     disabled={dictDataPageParams.pageNo! >= Math.ceil(dictDataTotal / dictDataPageParams.pageSize!)}
                   >
-                    下一页
+                    {t('common.nextPage')}
                   </Button>
                 </div>
               </div>

+ 66 - 76
src/components/ExecutorDashboard.tsx

@@ -235,7 +235,7 @@ export default function ExecutorDashboard() {
       setNodeList(nodeListData || []);
     } catch (error: any) {
       console.error('获取数据失败', error);
-      toast.error(error.message || '获取数据失败');
+      toast.error(error.message || t('cockpit.getDataFailed'));
       setNodeCount({ unreleasedCount: 0, runningCount: 0, completedCount: 0 });
       setTrendData([]);
       setNodeList([]);
@@ -271,44 +271,34 @@ export default function ExecutorDashboard() {
     }));
   }, [nodeList]);
   
-  // 获取任务状态文本(根据 approvalStatus 数字值判断)
-  const getTaskStatusText = (status: string | number | undefined): string => {
-    if (status === undefined || status === null) return '待执行';
-    
-    // 如果 status 是数字,根据接口文档:1-待执行,2-进行中,3-已完成
+  // 解析任务状态类型:pending | inProgress | completed
+  const getTaskStatusKind = (status: string | number | undefined): 'pending' | 'inProgress' | 'completed' => {
+    if (status === undefined || status === null) return 'pending';
     const statusNum = typeof status === 'number' ? status : parseInt(String(status), 10);
     if (!isNaN(statusNum)) {
-      if (statusNum === 1) return '待执行';
-      if (statusNum === 2) return '进行中';
-      if (statusNum === 3) return '已完成';
+      if (statusNum === 1) return 'pending';
+      if (statusNum === 2) return 'inProgress';
+      if (statusNum === 3) return 'completed';
     }
-    
-    // 兼容字符串状态
     const statusStr = String(status).toLowerCase();
-    if (statusStr === 'pending' || statusStr === '待执行' || statusStr === '待开始' || statusStr === '未开始' || statusStr === 'unaudited' || statusStr === '1') {
-      return '待执行';
-    }
-    if (statusStr === 'running' || statusStr === '进行中' || statusStr === 'in_progress' || statusStr === '执行中' || statusStr === '2') {
-      return '进行中';
-    }
-    if (statusStr === 'completed' || statusStr === '已完成' || statusStr === 'approved' || statusStr === '已通过' || statusStr === '3') {
-      return '已完成';
-    }
-    return '待执行';
+    if (statusStr === 'running' || statusStr === '进行中' || statusStr === 'in_progress' || statusStr === '执行中' || statusStr === '2') return 'inProgress';
+    if (statusStr === 'completed' || statusStr === '已完成' || statusStr === 'approved' || statusStr === '已通过' || statusStr === '3') return 'completed';
+    return 'pending';
+  };
+
+  // 获取任务状态文本(根据 approvalStatus 数字值判断)
+  const getTaskStatusText = (status: string | number | undefined): string => {
+    const kind = getTaskStatusKind(status);
+    if (kind === 'inProgress') return t('cockpit.inProgress');
+    if (kind === 'completed') return t('cockpit.completed');
+    return t('cockpit.pending');
   };
   
   // 获取任务状态样式
   const getTaskStatusStyle = (status: string | number | undefined) => {
-    const statusText = getTaskStatusText(status);
-    if (statusText === '待执行') {
-      return { backgroundColor: '#e5e5e5', color: '#333333' };
-    }
-    if (statusText === '进行中') {
-      return { backgroundColor: '#1677ff', color: '#ffffff' };
-    }
-    if (statusText === '已完成') {
-      return { backgroundColor: '#0acb57', color: '#ffffff' };
-    }
+    const kind = getTaskStatusKind(status);
+    if (kind === 'inProgress') return { backgroundColor: '#1677ff', color: '#ffffff' };
+    if (kind === 'completed') return { backgroundColor: '#0acb57', color: '#ffffff' };
     return { backgroundColor: '#e5e5e5', color: '#333333' };
   };
   
@@ -332,7 +322,7 @@ export default function ExecutorDashboard() {
         icon: (
           <img 
             src={urgecy1Icon} 
-            alt="一般" 
+            alt={t('cockpit.priorityNormal')} 
             className="w-5 h-5 flex-shrink-0 mr-1.5"
             style={{ objectFit: 'contain' }}
           />
@@ -350,7 +340,7 @@ export default function ExecutorDashboard() {
         icon: (
           <img 
             src={urgecy2Icon} 
-            alt="紧急" 
+            alt={t('cockpit.priorityUrgent')} 
             className="w-5 h-5 flex-shrink-0 mr-1.5"
             style={{ objectFit: 'contain' }}
           />
@@ -369,7 +359,7 @@ export default function ExecutorDashboard() {
         icon: (
           <img 
             src={urgecy3Icon} 
-            alt="非常紧急" 
+            alt={t('cockpit.priorityVeryUrgent')} 
             className="w-5 h-5 flex-shrink-0 mr-1.5"
             style={{ objectFit: 'contain' }}
           />
@@ -433,7 +423,7 @@ export default function ExecutorDashboard() {
     // 处理容器类型(card 和 grid)
     if (field.type === 'card') {
       const children = field.children || [];
-      const cardTitle = field.label || field.cardTitle || '卡片容器';
+      const cardTitle = field.label || field.cardTitle || t('cockpit.cardContainer');
       return (
         <div key={field.id} style={spanStyle} className="mb-4">
           <Card title={cardTitle} className="w-full">
@@ -988,7 +978,7 @@ export default function ExecutorDashboard() {
     const nodeId = node.id || node.uuid;
     if (!nodeId) {
       console.warn('ExecutorDashboard: 节点ID不存在', node);
-      message.warning('节点ID不存在');
+      message.warning(t('cockpit.nodeIdNotExist'));
       return;
     }
     
@@ -1001,7 +991,7 @@ export default function ExecutorDashboard() {
       const numericNodeId = typeof nodeId === 'number' ? nodeId : (typeof nodeId === 'string' ? parseInt(nodeId, 10) : Number(nodeId));
       if (isNaN(numericNodeId)) {
         console.warn('ExecutorDashboard: 节点ID无效', nodeId);
-        message.warning('节点ID无效');
+        message.warning(t('cockpit.nodeIdInvalid'));
         setTaskDetailLoading(false);
         return;
       }
@@ -1159,11 +1149,11 @@ export default function ExecutorDashboard() {
             }, 100);
           } else {
             console.warn('ExecutorDashboard: formData 中缺少 conf 或 fields', { conf: !!conf, fields: !!fields });
-            message.warning('表单数据不完整');
+            message.warning(t('cockpit.formDataIncomplete'));
           }
         } catch (e) {
           console.error('ExecutorDashboard: 解析 formData 失败', e);
-          message.error('解析表单数据失败: ' + (e instanceof Error ? e.message : String(e)));
+          message.error(t('cockpit.parseFormDataFailed') + ': ' + (e instanceof Error ? e.message : String(e)));
         } finally {
           setFormLoading(false);
         }
@@ -1181,7 +1171,7 @@ export default function ExecutorDashboard() {
             const numericFormId = typeof formId === 'string' ? parseInt(formId, 10) : formId;
             if (isNaN(numericFormId)) {
               console.error('ExecutorDashboard: formId 不是有效数字', formId);
-              message.error('表单ID无效');
+              message.error(t('cockpit.formIdInvalid'));
               setFormLoading(false);
               return;
             }
@@ -1282,11 +1272,11 @@ export default function ExecutorDashboard() {
               }
             } else {
               console.warn('ExecutorDashboard: 表单详情缺少配置或字段', { conf: !!conf, fields: !!fields });
-              message.warning('表单配置不完整');
+              message.warning(t('cockpit.formConfigIncomplete'));
             }
           } catch (e) {
             console.error('ExecutorDashboard: 获取表单详情失败', e);
-            message.error('获取表单详情失败: ' + (e instanceof Error ? e.message : String(e)));
+            message.error(t('cockpit.getFormDetailFailed') + ': ' + (e instanceof Error ? e.message : String(e)));
           } finally {
             setFormLoading(false);
           }
@@ -1305,7 +1295,7 @@ export default function ExecutorDashboard() {
       console.log('ExecutorDashboard: 弹框已打开', { detailVisible: true, detailData: detailDataWithWorkInfo });
     } catch (error: any) {
       console.error('ExecutorDashboard: 获取节点详情失败', error);
-      toast.error(error.message || '获取节点详情失败');
+      toast.error(error.message || t('cockpit.getNodeDetailFailed'));
       if (!taskDetailData) {
         setTaskDetailData({
           id: nodeId,
@@ -1332,7 +1322,7 @@ export default function ExecutorDashboard() {
   // 表格列定义
   const columns = [
     {
-      title: '作业编号',
+      title: t('cockpit.jobNo'),
       dataIndex: 'orderNo',
       key: 'orderNo',
       width: 120,
@@ -1341,7 +1331,7 @@ export default function ExecutorDashboard() {
       },
     },
     {
-      title: '作业名称',
+      title: t('cockpit.jobName'),
       dataIndex: 'name',
       key: 'name',
       width: 200,
@@ -1351,7 +1341,7 @@ export default function ExecutorDashboard() {
       },
     },
     {
-      title: '任务名称',
+      title: t('cockpit.taskName'),
       dataIndex: 'currentNodeName',
       key: 'currentNodeName',
       width: 200,
@@ -1375,14 +1365,14 @@ export default function ExecutorDashboard() {
           return { color: '#6b7280' }; // 一般:灰色
         };
         
-        const nodeName = text || record.currentNodeName || record.nodeName || record.description || '未知任务';
+        const nodeName = text || record.currentNodeName || record.nodeName || record.description || t('cockpit.unknownTask');
         
         return (
           <div>
             <div>{nodeName}</div>
             {urgencyValue !== undefined && urgencyValue !== null && urgencyValue !== '' && (
               <div className="text-xs mt-1" style={getPriorityStyle(urgencyValue)}>
-                紧急程度:{priorityText}
+                {t('cockpit.urgencyLevel')}:{priorityText}
               </div>
             )}
           </div>
@@ -1390,7 +1380,7 @@ export default function ExecutorDashboard() {
       },
     },
     {
-      title: '作业类型',
+      title: t('cockpit.jobType'),
       dataIndex: 'workType',
       key: 'workType',
       width: 120,
@@ -1401,7 +1391,7 @@ export default function ExecutorDashboard() {
       },
     },
     {
-      title: '开始时间',
+      title: t('cockpit.startTime'),
       dataIndex: 'workTime',
       key: 'workTime',
       width: 180,
@@ -1425,7 +1415,7 @@ export default function ExecutorDashboard() {
       },
     },
     {
-      title: '状态',
+      title: t('cockpit.status'),
       key: 'status',
       width: 100,
       render: (_: any, record: ExecutorNodeVO) => {
@@ -1440,7 +1430,7 @@ export default function ExecutorDashboard() {
       },
     },
     {
-      title: '操作',
+      title: t('cockpit.action'),
       key: 'action',
       width: 100,
       render: (_: any, record: ExecutorNodeVO) => {
@@ -1464,7 +1454,7 @@ export default function ExecutorDashboard() {
         <Card className="border border-gray-200 shadow-sm">
           <div className="flex items-center justify-between">
             <div>
-              <div className="text-sm text-gray-500 mb-1">待执行任务</div>
+              <div className="text-sm text-gray-500 mb-1">{t('cockpit.pendingTask')}</div>
               <div className="text-2xl font-bold text-gray-700">{nodeCount.unreleasedCount || 0}</div>
             </div>
             <div className="w-12 h-12 rounded-lg bg-gray-100 flex items-center justify-center">
@@ -1476,7 +1466,7 @@ export default function ExecutorDashboard() {
         <Card className="border border-blue-200 shadow-sm bg-blue-50">
           <div className="flex items-center justify-between">
             <div>
-              <div className="text-sm text-gray-500 mb-1">进行中任务</div>
+              <div className="text-sm text-gray-500 mb-1">{t('cockpit.inProgressTask')}</div>
               <div className="text-2xl font-bold text-blue-600">{nodeCount.runningCount || 0}</div>
             </div>
             <div className="w-12 h-12 rounded-lg bg-blue-100 flex items-center justify-center">
@@ -1488,7 +1478,7 @@ export default function ExecutorDashboard() {
         <Card className="border border-green-200 shadow-sm bg-green-50">
           <div className="flex items-center justify-between">
             <div>
-              <div className="text-sm text-gray-500 mb-1">已完成任务</div>
+              <div className="text-sm text-gray-500 mb-1">{t('cockpit.completedTask')}</div>
               <div className="text-2xl font-bold text-green-600">{nodeCount.completedCount || 0}</div>
             </div>
             <div className="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center">
@@ -1503,10 +1493,10 @@ export default function ExecutorDashboard() {
         <div className="flex items-center justify-between mb-4">
           <div className="flex items-center gap-2">
             <AlertCircle className="w-5 h-5 text-orange-500" />
-            <h3 className="text-lg font-semibold text-gray-900">我的作业任务</h3>
+            <h3 className="text-lg font-semibold text-gray-900">{t('cockpit.myJobTasks')}</h3>
           </div>
           <div className="flex items-center gap-4">
-            <span className="text-sm text-gray-500">请及时处理您的作业任务</span>
+            <span className="text-sm text-gray-500">{t('cockpit.processJobTasksPrompt')}</span>
             <Button 
               type="link" 
               size="small" 
@@ -1529,7 +1519,7 @@ export default function ExecutorDashboard() {
                 }
               }}
             >
-              查看全部
+              {t('cockpit.viewAll')}
             </Button>
           </div>
         </div>
@@ -1547,7 +1537,7 @@ export default function ExecutorDashboard() {
       {/* 任务完成趋势 */}
       <Card className="border border-gray-200 shadow-sm mb-8" style={{marginBottom: '20px'}}>
         <h3 className="text-lg font-semibold text-gray-900 mb-6">
-          任务完成趋势(最近30天)
+          {t('cockpit.taskCompleteTrend')}
         </h3>
         <div className="h-80">
           <ResponsiveContainer width="100%" height="100%">
@@ -1589,7 +1579,7 @@ export default function ExecutorDashboard() {
                 stroke="#1677ff" 
                 strokeWidth={2}
                 fill="url(#colorCompletedCount)"
-                name="每日完成任务数"
+                name={t('cockpit.dailyTaskCompletedCount')}
                 dot={{ fill: '#1677ff', r: 4 }}
                 activeDot={{ r: 6 }}
               />
@@ -1602,7 +1592,7 @@ export default function ExecutorDashboard() {
       <Card className="border border-gray-200 shadow-sm">
         <div className="flex items-center gap-2 mb-4">
           <Rocket className="w-5 h-5 text-gray-600" />
-          <h3 className="text-lg font-semibold text-gray-900">快捷操作</h3>
+          <h3 className="text-lg font-semibold text-gray-900">{t('cockpit.quickActions')}</h3>
         </div>
         <div className="flex gap-4">
           <button
@@ -1610,28 +1600,28 @@ export default function ExecutorDashboard() {
             onClick={() => navigate('/my-task')}
           >
             <CheckCircle className="w-6 h-6 text-gray-600" />
-            <span className="text-sm font-medium text-gray-700">提交任务完成</span>
+            <span className="text-sm font-medium text-gray-700">{t('cockpit.submitTaskComplete')}</span>
           </button>
           <button
             className="flex-1 bg-white border border-gray-200 rounded-lg p-4 hover:bg-gray-50 hover:border-gray-300 transition-all flex flex-col items-center justify-center gap-2"
             onClick={() => navigate('/my-task')}
           >
             <Eye className="w-6 h-6 text-gray-600" />
-            <span className="text-sm font-medium text-gray-700">查看我的作业</span>
+            <span className="text-sm font-medium text-gray-700">{t('cockpit.viewMyJobs')}</span>
           </button>
           <button
             className="flex-1 bg-white border border-gray-200 rounded-lg p-4 hover:bg-gray-50 hover:border-gray-300 transition-all flex flex-col items-center justify-center gap-2"
-            onClick={() => message.info('功能开发中')}
+            onClick={() => message.info(t('cockpit.featureDeveloping'))}
           >
             <ShoppingCart className="w-6 h-6 text-gray-600" />
-            <span className="text-sm font-medium text-gray-700">申领作业物料</span>
+            <span className="text-sm font-medium text-gray-700">{t('cockpit.claimJobMaterial')}</span>
           </button>
           <button
             className="flex-1 bg-white border border-gray-200 rounded-lg p-4 hover:bg-gray-50 hover:border-gray-300 transition-all flex flex-col items-center justify-center gap-2"
-            onClick={() => message.info('功能开发中')}
+            onClick={() => message.info(t('cockpit.featureDeveloping'))}
           >
             <HelpCircle className="w-6 h-6 text-gray-600" />
-            <span className="text-sm font-medium text-gray-700">问题反馈</span>
+            <span className="text-sm font-medium text-gray-700">{t('cockpit.feedback')}</span>
           </button>
         </div>
       </Card>
@@ -1661,7 +1651,7 @@ export default function ExecutorDashboard() {
         }}
       >
         {taskDetailLoading ? (
-          <div className="py-8 text-center text-gray-500">加载中...</div>
+          <div className="py-8 text-center text-gray-500">{t('cockpit.loading')}</div>
         ) : taskDetailData ? (
           <div style={{ 
             display: 'flex',
@@ -1680,7 +1670,7 @@ export default function ExecutorDashboard() {
                   flexShrink: 0
                 }}></div>
                 <h2 className="text-xl font-semibold mb-2" style={{ color: '#025fff', marginBottom: 0 }}>
-                  {taskDetailData.workName || taskDetailData.name || '任务详情'}
+                  {taskDetailData.workName || taskDetailData.name || t('cockpit.taskDetail')}
                 </h2>
                 <span 
                   className="inline-flex px-3 py-1 rounded-full text-xs font-medium"
@@ -1690,10 +1680,10 @@ export default function ExecutorDashboard() {
                 </span>
               </div>
               <div className="text-sm flex gap-4" style={{ color: '#898f9a', marginTop: '12px' }}>
-                <span>工单编号:{taskDetailData.orderNo || '-'}</span>
-                <span>作业负责人:{taskDetailData.initiatorName || '-'}</span>
-                <span>任务负责人:{taskDetailData.workerUserName || '-'}</span>
-                <span>开始时间:{taskDetailData.workTime ? dateFormatter(taskDetailData.workTime) : '-'}</span>
+                <span>{t('cockpit.orderNo')}:{taskDetailData.orderNo || '-'}</span>
+                <span>{t('cockpit.jobInitiatorLabel')}:{taskDetailData.initiatorName || '-'}</span>
+                <span>{t('cockpit.taskOwnerLabel')}:{taskDetailData.workerUserName || '-'}</span>
+                <span>{t('cockpit.startTime')}:{taskDetailData.workTime ? dateFormatter(taskDetailData.workTime) : '-'}</span>
               </div>
             </div>
 
@@ -1768,7 +1758,7 @@ export default function ExecutorDashboard() {
                       <div className="space-y-6" style={{ padding: '0 24px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
                         {/* 自定义表单 */}
                         {formLoading ? (
-                          <div className="py-8 text-center text-gray-500">表单加载中...</div>
+                          <div className="py-8 text-center text-gray-500">{t('cockpit.formLoading')}</div>
                         ) : formData.rule && formData.rule.length > 0 ? (
                           <div>
                             {(() => {
@@ -1875,7 +1865,7 @@ export default function ExecutorDashboard() {
                               rows={4}
                               value={approvalComment}
                               onChange={(e) => setApprovalComment(e.target.value)}
-                              placeholder="请输入审核意见(可选)"
+                              placeholder={t('cockpit.auditOpinionPlaceholder')}
                               maxLength={500}
                               showCount
                               disabled={isApproved}
@@ -1895,7 +1885,7 @@ export default function ExecutorDashboard() {
                     <div className="space-y-6" style={{ padding: '0 24px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
                       {/* 自定义表单 */}
                       {formLoading ? (
-                        <div className="py-8 text-center text-gray-500">表单加载中...</div>
+                        <div className="py-8 text-center text-gray-500">{t('cockpit.formLoading')}</div>
                       ) : formData.rule && formData.rule.length > 0 ? (
                         <div>
                           {(() => {

+ 10 - 8
src/components/HardwareManagement.tsx

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
 import { Plus, Search, Edit2, Trash2, MoreVertical, Package, Key, Lock, Briefcase, QrCode, MapPin } from 'lucide-react';
 import { Button, Tooltip } from 'antd';
 import { Button as UIButton } from './ui/button';
+import { useTranslation } from 'react-i18next';
 import PadLockManagement from './PadLockManagement';
 import KeyManagement from './KeyManagement';
 import HardwareLockCabinetManagement from './lockCabinet/HardwareLockCabinetManagement';
@@ -16,8 +17,13 @@ interface HardwareManagementProps {
 }
 
 export default function HardwareManagement({ subMenu }: HardwareManagementProps) {
+  const { t } = useTranslation();
+  const [searchTerm, setSearchTerm] = useState('');
+  const [showAddModal, setShowAddModal] = useState(false);
+  const [editingItem, setEditingItem] = useState<TableRow | null>(null);
+
   console.log('HardwareManagement: 接收到的 subMenu:', subMenu);
-  
+
   // 如果是挂锁,使用专门的挂锁管理组件
   // subMenu 可能是 '挂锁'、'padlock' 或 'lock'
   if (subMenu === '挂锁' || subMenu === 'padlock' || subMenu === 'lock') {
@@ -45,10 +51,6 @@ export default function HardwareManagement({ subMenu }: HardwareManagementProps)
     return <KeyManagement subMenu={subMenu} />;
   }
 
-  const [searchTerm, setSearchTerm] = useState('');
-  const [showAddModal, setShowAddModal] = useState(false);
-  const [editingItem, setEditingItem] = useState<TableRow | null>(null);
-
   // 机柜数据
   const cabinetData: TableRow[] = [
     { 
@@ -819,13 +821,13 @@ export default function HardwareManagement({ subMenu }: HardwareManagementProps)
         <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
           <div className="flex items-center justify-between">
             <div className="text-sm text-gray-600">
-              共 <span className="text-blue-600 font-medium">{filteredData.length}</span> 条记录
+              {t('common.total')} <span className="text-blue-600 font-medium">{filteredData.length}</span> {t('common.records')}
             </div>
             <div className="flex gap-2">
               <Button
                 disabled={true}
               >
-                上一页
+                {t('common.prevPage')}
               </Button>
               <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                 1 / 1
@@ -833,7 +835,7 @@ export default function HardwareManagement({ subMenu }: HardwareManagementProps)
               <Button
                 disabled={true}
               >
-                下一页
+                {t('common.nextPage')}
               </Button>
             </div>
           </div>

+ 5 - 3
src/components/LocationManagement.tsx

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
 import { Plus, Search, Edit2, Trash2, MoreVertical, MapPin, Eye } from 'lucide-react';
 import { Button } from 'antd';
 import { Button as UIButton } from './ui/button';
+import { useTranslation } from 'react-i18next';
 
 interface TableRow {
   id: number;
@@ -9,6 +10,7 @@ interface TableRow {
 }
 
 export default function LocationManagement() {
+  const { t } = useTranslation();
   const [searchTerm, setSearchTerm] = useState('');
   const [showAddModal, setShowAddModal] = useState(false);
   const [editingItem, setEditingItem] = useState<TableRow | null>(null);
@@ -354,13 +356,13 @@ export default function LocationManagement() {
         <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
           <div className="flex items-center justify-between">
             <div className="text-sm text-gray-600">
-              共 <span className="text-blue-600 font-medium">{filteredData.length}</span> 条记录
+              {t('common.total')} <span className="text-blue-600 font-medium">{filteredData.length}</span> {t('common.records')}
             </div>
             <div className="flex gap-2">
               <Button
                 disabled={true}
               >
-                上一页
+                {t('common.prevPage')}
               </Button>
               <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                 1 / 1
@@ -368,7 +370,7 @@ export default function LocationManagement() {
               <Button
                 disabled={true}
               >
-                下一页
+                {t('common.nextPage')}
               </Button>
             </div>
           </div>

+ 5 - 3
src/components/NotificationManagement.tsx

@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
 import { Plus, Search, Edit2, Trash2, MoreVertical, Mail, Phone, MessageSquare, Smartphone } from 'lucide-react';
 import { Button } from 'antd';
 import { Button as UIButton } from './ui/button';
+import { useTranslation } from 'react-i18next';
 import EmailNotifyManagement from './notification/EmailNotifyManagement';
 import InboxMessage from './InboxMessage';
 import SmsMessage from './SmsMessage';
@@ -17,6 +18,7 @@ interface NotificationManagementProps {
 }
 
 export default function NotificationManagement({ subMenu }: NotificationManagementProps) {
+  const { t } = useTranslation();
   const [searchTerm, setSearchTerm] = useState('');
   const [showAddModal, setShowAddModal] = useState(false);
   const [editingItem, setEditingItem] = useState<TableRow | null>(null);
@@ -397,13 +399,13 @@ export default function NotificationManagement({ subMenu }: NotificationManageme
         <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
           <div className="flex items-center justify-between">
             <div className="text-sm text-gray-600">
-              共 <span className="text-blue-600 font-medium">{filteredData.length}</span> 条记录
+              {t('common.total')} <span className="text-blue-600 font-medium">{filteredData.length}</span> {t('common.records')}
             </div>
             <div className="flex gap-2">
               <Button
                 disabled={true}
               >
-                上一页
+                {t('common.prevPage')}
               </Button>
               <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                 1 / 1
@@ -411,7 +413,7 @@ export default function NotificationManagement({ subMenu }: NotificationManageme
               <Button
                 disabled={true}
               >
-                下一页
+                {t('common.nextPage')}
               </Button>
             </div>
           </div>

+ 3 - 3
src/components/PostManagement.tsx

@@ -451,14 +451,14 @@ export default function PostManagement() {
         <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
           <div className="flex items-center justify-between">
             <div className="text-sm text-gray-600">
-              共 <span className="text-blue-600 font-medium">{total}</span> 条记录
+              {t('common.total')} <span className="text-blue-600 font-medium">{total}</span> {t('common.records')}
             </div>
             <div className="flex gap-2">
               <Button
                 onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo! - 1 })}
                 disabled={queryParams.pageNo! <= 1}
               >
-                上一页
+                {t('common.prevPage')}
               </Button>
               <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                 {queryParams.pageNo} / {Math.ceil(total / queryParams.pageSize!) || 1}
@@ -467,7 +467,7 @@ export default function PostManagement() {
                 onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo! + 1 })}
                 disabled={queryParams.pageNo! >= Math.ceil(total / queryParams.pageSize!)}
               >
-                下一页
+                {t('common.nextPage')}
               </Button>
             </div>
           </div>

+ 40 - 8
src/components/ProcessDesigner.tsx

@@ -1842,10 +1842,10 @@ export default function ProcessDesigner() {
     [history, historyIndex, nodes]
   );
 
-  // 节点点击处理
-  const onNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
+  /** 统一的“选中节点”处理(用于点击与框选/程序选中等场景) */
+  const selectNodeAndSyncRightPanel = useCallback((node: Node) => {
     setSelectedNode(node);
-    setSelectedEdge(null); // 点击节点时取消连线选择
+    setSelectedEdge(null); // 选中节点时取消连线选择
     setActiveTabKey('info'); // 切换节点时重置到第一个tab
     // 清除所有边的选中状态
     setEdges((eds) =>
@@ -1921,7 +1921,27 @@ export default function ProcessDesigner() {
     if (node.data?.type === 'createJob' && activeTabKey === 'form') {
       setActiveTabKey('info');
     }
-  }, [loadNodeCache, nodes, setEdges]);
+  }, [activeTabKey, loadNodeCache, nodes, setEdges]);
+
+  // 节点点击处理
+  const onNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
+    selectNodeAndSyncRightPanel(node);
+  }, [selectNodeAndSyncRightPanel]);
+
+  // 兜底:当 ReactFlow 选中态变化(例如多次切换、框选、或某些情况下 click 事件未触发)时,同步右侧面板
+  const onSelectionChange = useCallback((params: { nodes: Node[]; edges: Edge[] }) => {
+    const node = params.nodes?.[0];
+    if (node && node.id !== selectedNode?.id) {
+      selectNodeAndSyncRightPanel(node);
+    }
+  }, [selectNodeAndSyncRightPanel, selectedNode?.id]);
+
+  // 兜底:当选中节点变化时,强制把右侧面板切回“节点信息”
+  // 解决在“提交表单”Tab 下切换节点偶发不刷新/不切换的问题
+  useEffect(() => {
+    if (!selectedNode) return;
+    setActiveTabKey('info');
+  }, [selectedNode?.id]);
 
   // 画布点击处理(取消选择)
   const onPaneClick = useCallback(() => {
@@ -2193,6 +2213,17 @@ export default function ProcessDesigner() {
     event.dataTransfer.dropEffect = 'move';
   }, []);
 
+  /** 是否为真正会影响设计内容的变更(排除纯选中态/尺寸测量等 UI 变更) */
+  const isMeaningfulRfChange = useCallback((changes: Array<{ type?: string; dragging?: boolean }>) => {
+    return changes.some((ch) => {
+      // ReactFlow 的 select/dimensions 多为 UI 变化,不应标记“未保存”
+      if (ch.type === 'select' || ch.type === 'dimensions') return false;
+      // position 只有在拖拽时才视为修改(避免点击选中触发的非拖拽 position 事件)
+      if (ch.type === 'position') return Boolean(ch.dragging);
+      return true;
+    });
+  }, []);
+
   // 拖拽节点时的水平线磁吸:与画布上其他节点 Y 接近时对齐到同一水平线;节点变更时标记为已修改
   const onNodesChange = useCallback(
     (changes: NodeChange[]) => {
@@ -2210,23 +2241,23 @@ export default function ProcessDesigner() {
         }
         return ch;
       });
-      if (!isLoadingFromServerOrRestoreRef.current) {
+      if (!isLoadingFromServerOrRestoreRef.current && isMeaningfulRfChange(modified as any)) {
         setHasUnsavedChanges(true);
       }
       onNodesChangeBase(modified);
     },
-    [nodes, onNodesChangeBase]
+    [nodes, onNodesChangeBase, isMeaningfulRfChange]
   );
 
   // 边变更时标记为已修改(仅用户操作会触发 onEdgesChange);加载/恢复阶段不标记
   const onEdgesChangeWithDirty = useCallback(
     (changes: Parameters<typeof onEdgesChange>[0]) => {
-      if (!isLoadingFromServerOrRestoreRef.current) {
+      if (!isLoadingFromServerOrRestoreRef.current && isMeaningfulRfChange(changes as any)) {
         setHasUnsavedChanges(true);
       }
       onEdgesChange(changes);
     },
-    [onEdgesChange]
+    [onEdgesChange, isMeaningfulRfChange]
   );
 
   // 更新节点配置
@@ -3127,6 +3158,7 @@ export default function ProcessDesigner() {
             onEdgeUpdate={onEdgeUpdate}
             isValidConnection={isValidConnection}
             onNodeClick={onNodeClick}
+            onSelectionChange={onSelectionChange}
             onNodeContextMenu={onNodeContextMenu}
             onEdgeClick={onEdgeClick}
             onPaneClick={onPaneClick}

+ 9 - 7
src/components/SystemConfig.tsx

@@ -1,6 +1,7 @@
 import React, { useState } from 'react';
 import { Plus, Search, Edit2, Trash2, MoreVertical, ChevronRight, ChevronDown } from 'lucide-react';
 import { Button } from 'antd';
+import { useTranslation } from 'react-i18next';
 import { Button as UIButton } from './ui/button';
 import DepartmentManagement from './DepartmentManagement';
 import MenuManagement from './MenuManagement';
@@ -19,6 +20,7 @@ interface SystemConfigProps {
 }
 
 export default function SystemConfig({ subMenu }: SystemConfigProps) {
+  const { t } = useTranslation();
   console.log('SystemConfig: 组件渲染,subMenu =', subMenu, '类型:', typeof subMenu);
   const [searchTerm, setSearchTerm] = useState('');
   const [showAddModal, setShowAddModal] = useState(false);
@@ -733,17 +735,17 @@ export default function SystemConfig({ subMenu }: SystemConfigProps) {
               {/* 底部分页 */}
               <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200 flex items-center justify-between flex-shrink-0">
                 <div className="text-sm text-gray-600">
-                  共 <span className="text-blue-600">12</span> 条记录
+                  {t('common.total')} <span className="text-blue-600">12</span> {t('common.records')}
                 </div>
                 <div className="flex gap-2">
                   <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
-                    上一页
+                    {t('common.prevPage')}
                   </button>
                   <button className="px-4 py-2 text-sm text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition-colors">
                     1
                   </button>
                   <button className="px-4 py-2 text-sm text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors">
-                    下一页
+                    {t('common.nextPage')}
                   </button>
                 </div>
               </div>
@@ -947,7 +949,7 @@ export default function SystemConfig({ subMenu }: SystemConfigProps) {
           <div className="px-6 py-4 bg-gray-50/50 border-t border-gray-200">
             <div className="flex items-center justify-between">
               <div className="text-sm text-gray-600">
-                共 <span className="text-blue-600">{menuTreeData.length}</span> 个一级菜单
+                {t('common.total')} <span className="text-blue-600">{menuTreeData.length}</span> {t('common.firstLevelMenus')}
               </div>
             </div>
           </div>
@@ -1045,13 +1047,13 @@ export default function SystemConfig({ subMenu }: SystemConfigProps) {
             <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
               <div className="flex items-center justify-between">
                 <div className="text-sm text-gray-600">
-                  共 <span className="text-blue-600 font-medium">{filteredData.length}</span> 条记录
+                  {t('common.total')} <span className="text-blue-600 font-medium">{filteredData.length}</span> {t('common.records')}
                 </div>
                 <div className="flex gap-2">
                   <Button
                     disabled={true}
                   >
-                    上一页
+                    {t('common.prevPage')}
                   </Button>
                   <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                     1 / 1
@@ -1059,7 +1061,7 @@ export default function SystemConfig({ subMenu }: SystemConfigProps) {
                   <Button
                     disabled={true}
                   >
-                    下一页
+                    {t('common.nextPage')}
                   </Button>
                 </div>
               </div>

+ 5 - 5
src/components/UserManagement.tsx

@@ -617,15 +617,15 @@ export default function UserManagement({ subMenu }: UserManagementProps) {
             <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
               <div className="flex items-center justify-between">
                 <div className="text-sm text-gray-600">
-                  共 <span className="text-blue-600 font-medium">{total}</span> 条记录
+                  {t('common.total')} <span className="text-blue-600 font-medium">{total}</span> {t('common.records')}
                 </div>
                 <div className="flex gap-2">
                   <Button
                     onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo - 1 })}
                     disabled={queryParams.pageNo <= 1}
                   >
-                    上一页
-                  </Button>
+{t('common.prevPage')}
+                    </Button>
                   <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                     {queryParams.pageNo} / {Math.ceil(total / queryParams.pageSize) || 1}
                   </span>
@@ -633,8 +633,8 @@ export default function UserManagement({ subMenu }: UserManagementProps) {
                     onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo + 1 })}
                     disabled={queryParams.pageNo >= Math.ceil(total / queryParams.pageSize)}
                   >
-                    下一页
-                  </Button>
+{t('common.nextPage')}
+                    </Button>
                 </div>
               </div>
             </div>

+ 4 - 2
src/components/WorkJobArchiveReport.tsx

@@ -11,6 +11,7 @@ import { workflowDesignApi } from '../api/WorkflowDesign';
 import { formatDateWithFormat } from '../utils/formatTime';
 import { getUser, getTenantName } from '../utils/auth';
 import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
+import { useTranslation } from 'react-i18next';
 import './WorkJobArchiveReport.css';
 
 /** 生成水印背景样式:显示 用户名@租户名称 */
@@ -352,6 +353,7 @@ function buildFlowFromNodeList(
 }
 
 export default function WorkJobArchiveReport() {
+  const { t } = useTranslation();
   const [searchParams] = useSearchParams();
   const navigate = useNavigate();
   const jobId = searchParams.get('id');
@@ -850,7 +852,7 @@ export default function WorkJobArchiveReport() {
           </div>
         </section>
 
-        <div className="page-num">第 1 页 / 共 2 页</div>
+        <div className="page-num">{t('common.pageOfTotal', { current: 1, total: 2 })}</div>
       </div>
 
       {/* ================= 第 2 页:完整作业流程拓扑图(React Flow,可拖拽;打印时整体缩小) ================= */}
@@ -902,7 +904,7 @@ export default function WorkJobArchiveReport() {
           <div className="flow-legend-item"><div className="icon icon-pending" /> 未执行</div>
         </div>
 
-        <div className="page-num">第 2 页 / 共 2 页</div>
+        <div className="page-num">{t('common.pageOfTotal', { current: 2, total: 2 })}</div>
       </div>
     </div>
   );

+ 74 - 71
src/components/WorkJobDetail.tsx

@@ -41,6 +41,7 @@ import { workflowDesignApi } from '../api/WorkflowDesign';
 import { getFormPage } from '../api/bpm/form';
 import { segregationPointApi } from '../api/spm';
 import { Select, Input, Checkbox, Tabs, Tooltip } from 'antd';
+import { useTranslation } from 'react-i18next';
 
 interface WorkflowStep {
   id: string;
@@ -115,6 +116,7 @@ const getIconPathByFileName = (fileName: string | undefined): string | null => {
 
 // 简化的自定义节点组件(用于作业详情页面)
 function SimpleCustomNode({ data, selected, id }: any) {
+  const { t } = useTranslation();
   const nodeId = data.nodeId || (id ? String(parseInt(id.split('-').pop() || '0') % 1000).padStart(3, '0') : '001');
   
   // 获取节点状态
@@ -151,16 +153,16 @@ function SimpleCustomNode({ data, selected, id }: any) {
   }
   
   // 获取状态显示文本
-  const getStatusText = (status: 'completed' | 'in_progress' | 'pending'): string => {
-    switch (status) {
+  const getStatusText = (s: 'completed' | 'in_progress' | 'pending'): string => {
+    switch (s) {
       case 'completed':
-        return '已完成';
+        return t('workJobDetail.completed');
       case 'in_progress':
-        return '进行中';
+        return t('workJobDetail.inProgress');
       case 'pending':
-        return '待执行';
+        return t('workJobDetail.statusPending');
       default:
-        return '待执行';
+        return t('workJobDetail.statusPending');
     }
   };
   
@@ -273,7 +275,7 @@ function SimpleCustomNode({ data, selected, id }: any) {
           )}
         </div>
         <div className="font-semibold text-sm text-gray-900 leading-tight text-center break-words w-full flex-1 flex items-center justify-center px-1">
-          {data.label || data.nodeName || '节点'}
+          {data.label || data.nodeName || t('workJobDetail.node')}
         </div>
         <div className="text-xs text-gray-500 text-center flex-shrink-0">
           ID: {nodeId}
@@ -1077,13 +1079,13 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
                     <span className={`font-semibold text-xs ${
                       branchStatus === 'pending' ? 'text-gray-500' : 'text-gray-900'
                     }`}>
-                      {branchNode.nodeName || '未知节点'}
+                      {branchNode.nodeName || t('workJobDetail.unknownNode')}
                     </span>
                     <Tag 
                       color={getTimelineColor(branchStatus)}
                       className="text-xs"
                     >
-                      {branchStatus === 'completed' ? '已完成' : branchStatus === 'in_progress' ? '进行中' : '待处理'}
+                      {branchStatus === 'completed' ? t('workJobDetail.completed') : branchStatus === 'in_progress' ? t('workJobDetail.inProgress') : t('workJobDetail.pending')}
                     </Tag>
                   </div>
                   {branchExecutorInfo && (
@@ -1145,13 +1147,13 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
                   <span className={`font-semibold text-sm ${
                     status === 'pending' ? 'text-gray-500' : 'text-gray-900'
                   }`}>
-                    {node.nodeName || '未知节点'}
-                  </span>
-                  <Tag 
-                    color={getTimelineColor(status)}
-                    className="text-xs"
-                  >
-                    {status === 'completed' ? '已完成' : status === 'in_progress' ? '进行中' : '待处理'}
+{node.nodeName || t('workJobDetail.unknownNode')}
+                    </span>
+                    <Tag 
+                      color={getTimelineColor(status)}
+                      className="text-xs"
+                    >
+                      {status === 'completed' ? t('workJobDetail.completed') : status === 'in_progress' ? t('workJobDetail.inProgress') : t('workJobDetail.pending')}
                   </Tag>
                 </div>
                 {executorInfo && (
@@ -1174,7 +1176,7 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
                 <button
                   onClick={() => toggleBranchGroup(branchParentUuid)}
                   className="flex items-center justify-center w-6 h-6 rounded hover:bg-gray-100 transition-colors flex-shrink-0"
-                  title={isExpanded ? '收起分支' : '展开分支'}
+                  title={isExpanded ? t('workJobDetail.collapseBranch') : t('workJobDetail.expandBranch')}
                 >
                   {isExpanded ? (
                     <Minus className="w-3.5 h-3.5 text-gray-600" />
@@ -1352,6 +1354,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
   isolationPoints,
   userNameMap,
 }) => {
+  const { t } = useTranslation();
   // 找到对应的 workflowWorkNodeDO
   const workNode = workflowWorkNodeDOList.find(n => n.uuid === node.id);
   const nodeData = node.data || {};
@@ -1470,13 +1473,13 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
       <div>
         <h4 className="flex items-center gap-3 text-base font-semibold text-gray-900 mb-4">
           <span className="w-1 h-5 bg-blue-500 rounded-full flex-shrink-0" style={{ minWidth: '4px', minHeight: '20px' }}></span>
-          节点信息
+          {t('workJobDetail.nodeInfo')}
         </h4>
         <div className="space-y-5">
           {/* 节点名称 */}
           <div>
             <label className="block text-sm font-medium text-gray-700 mb-2">
-              节点名称 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+              {t('workJobDetail.nodeNameLabel')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
             </label>
             <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
               {nodeConfig.nodeName || '-'}
@@ -1487,13 +1490,13 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
           {nodeData.type !== 'createJob' && nodeData.type !== 'isolation' && nodeData.type !== 'releaseIsolation' && (
             <div>
               <label className="block text-sm font-medium text-gray-700 mb-2">
-                负责人 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                {t('workJobDetail.responsible')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
               </label>
               <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
                 {getResponsibleName()}
               </div>
               <p className="text-xs text-gray-500 mt-1.5 leading-relaxed">
-                对该任务或步骤节点进行处理的人员,若不选则需要在创建作业时进行选择。
+                {t('workJobDetail.responsibleHint')}
               </p>
             </div>
           )}
@@ -1502,7 +1505,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
           {nodeData.type !== 'createJob' && nodeData.type !== 'confirm' && nodeData.type !== 'review' && nodeData.type !== 'inputInfo' && nodeData.type !== 'isolation' && nodeData.type !== 'releaseIsolation' && nodeData.type !== 'returnLock' && nodeData.type !== 'complete' && (
             <div>
               <label className="block text-sm font-medium text-gray-700 mb-2">
-                备注
+                {t('workJobDetail.remark')}
               </label>
               <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200 min-h-[60px]">
                 {nodeConfig.remark || '-'}
@@ -1517,12 +1520,12 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
         <div>
           <h4 className="flex items-center gap-3 text-base font-semibold text-gray-900 mb-4">
             <span className="w-1 h-5 bg-blue-500 rounded-full flex-shrink-0" style={{ minWidth: '4px', minHeight: '20px' }}></span>
-            提交表单
+            {t('workJobDetail.submitForm')}
           </h4>
           <div className="space-y-5">
             <div>
               <label className="block text-sm font-medium text-gray-700 mb-2">
-                业务表单
+                {t('workJobDetail.businessForm')}
                 {/* 只有确认节点、审核节点、录入信息节点显示必填标记 */}
                 {['confirm', 'review', 'inputInfo'].includes(nodeData.type || '') && (
                   <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
@@ -1540,7 +1543,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
                 {nodeData.type === 'releaseIsolation' && nodeConfig.isolationNodeUuid && (
                   <div>
                     <label className="block text-sm font-medium text-gray-700 mb-2">
-                      选择隔离节点 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                      {t('workJobDetail.selectIsolationNode')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                     </label>
                     <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
                       {(() => {
@@ -1554,7 +1557,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
                 {/* 隔离方式 */}
                 <div>
                   <label className="block text-sm font-medium text-gray-700 mb-2">
-                    隔离方式 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                    {t('workJobDetail.isolationType')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                   </label>
                   <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
                     {getIsolationTypeName()}
@@ -1564,7 +1567,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
                 {/* 隔离点选择(可多选) */}
                 <div>
                   <label className="block text-sm font-medium text-gray-700 mb-2">
-                    隔离点选择(可多选) <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                    {t('workJobDetail.isolationPointsSelect')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                   </label>
                   <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
                     {getIsolationPointNames()}
@@ -1575,7 +1578,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
                 {(nodeConfig.isolationType === '0' || nodeConfig.isolationType === '2') && (
                   <div>
                     <label className="block text-sm font-medium text-gray-700 mb-2">
-                      负责人 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                      {t('workJobDetail.responsible')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                     </label>
                     <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
                       {getResponsibleName()}
@@ -1588,7 +1591,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
                   <>
                     <div>
                       <label className="block text-sm font-medium text-gray-700 mb-2">
-                        上锁人 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                        {t('workJobDetail.lockerPerson')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                       </label>
                       <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
                         {getLockPersonName()}
@@ -1596,7 +1599,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
                     </div>
                     <div>
                       <label className="block text-sm font-medium text-gray-700 mb-2">
-                        共锁人(可多选)
+                        {t('workJobDetail.colockerPerson')}
                       </label>
                       <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
                         {getCoLockPersonNames()}
@@ -1615,32 +1618,32 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
         <div>
           <h4 className="flex items-center gap-3 text-base font-semibold text-gray-900 mb-4">
             <span className="w-1 h-5 bg-blue-500 rounded-full flex-shrink-0" style={{ minWidth: '4px', minHeight: '20px' }}></span>
-            通知消息
+            {t('workJobDetail.notificationMessage')}
           </h4>
           <div className="space-y-5">
             <div>
               <label className="block text-sm font-medium text-gray-700 mb-3">
-                通知方式
+                {t('workJobDetail.notificationMethod')}
               </label>
               <div className="bg-gray-50 p-3 rounded-lg">
                 <div className="mb-3">
                   <Checkbox checked={nodeConfig.notificationMethods.sms} disabled>
-                    短信
+                    {t('workJobDetail.sms')}
                   </Checkbox>
                 </div>
                 <div className="mb-3">
                   <Checkbox checked={nodeConfig.notificationMethods.message} disabled>
-                    站内信
+                    {t('workJobDetail.webmail')}
                   </Checkbox>
                 </div>
                 <div className="mb-3">
                   <Checkbox checked={nodeConfig.notificationMethods.email} disabled>
-                    邮件
+                    {t('workJobDetail.email')}
                   </Checkbox>
                 </div>
                 <div>
                   <Checkbox checked={nodeConfig.notificationMethods.app} disabled>
-                    APP通知
+                    {t('workJobDetail.appNotify')}
                   </Checkbox>
                 </div>
               </div>
@@ -1653,6 +1656,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
 };
 
 export default function WorkJobDetail() {
+  const { t } = useTranslation();
   const navigate = useNavigate();
   const [searchParams] = useSearchParams();
   const jobId = searchParams.get('id');
@@ -2013,7 +2017,7 @@ export default function WorkJobDetail() {
         const nodeData = node.data || {};
         
         // 节点名称:优先从 workNode 获取
-        const nodeName = workNode?.nodeName || nodeData.label || node.nodeName || '节点';
+        const nodeName = workNode?.nodeName || nodeData.label || node.nodeName || t('workJobDetail.node');
         
         // 节点类型:优先从 workNode 获取
         const nodeType = workNode?.type || node.type || 'createJob';
@@ -2355,7 +2359,7 @@ export default function WorkJobDetail() {
 
       return {
         uuid: workNode.uuid || '',
-        nodeName: workNode.nodeName || '未知节点',
+        nodeName: workNode.nodeName || t('workJobDetail.unknownNode'),
         type: workNode.type || 'createJob',
         status,
         workNode,
@@ -2392,25 +2396,24 @@ export default function WorkJobDetail() {
 
   // 格式化状态文本(使用字典)
   const getStatusText = (status: string | number | undefined): string => {
-    if (!status) return '未知';
+    if (!status) return t('workJobDetail.statusUnknown');
     const statusStr = String(status).toLowerCase();
     const statusItem = jobStatusDictList.find(item => String(item.value).toLowerCase() === statusStr);
     if (statusItem) {
-      return statusItem.label || '未知';
+      return statusItem.label || t('workJobDetail.statusUnknown');
     }
-    // 如果没有找到字典值,使用默认映射
     const statusMap: Record<string, string> = {
-      'pending': '待执行',
-      'running': '执行中',
-      'completed': '执行完成',
-      'rejected': '已退回',
-      'skipped': '已跳过',
-      '进行中': '执行中',
-      '已完成': '执行完成',
-      '待开始': '待执行',
-      '已取消': '已取消',
+      'pending': t('workJobDetail.statusPending'),
+      'running': t('workJobDetail.statusRunning'),
+      'completed': t('workJobDetail.statusCompleted'),
+      'rejected': t('workJobDetail.statusRejected'),
+      'skipped': t('workJobDetail.statusSkipped'),
+      '进行中': t('workJobDetail.statusRunning'),
+      '已完成': t('workJobDetail.statusCompleted'),
+      '待开始': t('workJobDetail.statusPending'),
+      '已取消': t('workJobDetail.statusCancelled'),
     };
-    return statusMap[statusStr] || String(status) || '未知';
+    return statusMap[statusStr] || String(status) || t('workJobDetail.statusUnknown');
   };
 
   // 获取作业状态样式(与列表颜色一致)
@@ -2545,7 +2548,7 @@ export default function WorkJobDetail() {
     if (!workNode) {
       // 创建作业节点如果没有数据,返回空字符串
       if (type === 'createJob') return '';
-      return '待处理';
+      return t('workJobDetail.pending');
     }
     
     let executorName = '';
@@ -2593,7 +2596,7 @@ export default function WorkJobDetail() {
     // 创建作业节点如果没有执行人,返回空字符串
     if (!executorName) {
       if (type === 'createJob') return '';
-      return '待处理';
+      return t('workJobDetail.pending');
     }
     
     if (type === 'createJob') {
@@ -2610,7 +2613,7 @@ export default function WorkJobDetail() {
   if (loading) {
     return (
       <div className="min-h-screen bg-gray-50 flex items-center justify-center">
-        <div className="text-gray-500">加载中...</div>
+        <div className="text-gray-500">{t('workJobDetail.loading')}</div>
       </div>
     );
   }
@@ -2618,7 +2621,7 @@ export default function WorkJobDetail() {
   if (!jobDetail) {
     return (
       <div className="min-h-screen bg-gray-50 flex items-center justify-center">
-        <div className="text-gray-500">作业不存在</div>
+        <div className="text-gray-500">{t('workJobDetail.jobNotFound')}</div>
       </div>
     );
   }
@@ -2641,7 +2644,7 @@ export default function WorkJobDetail() {
                     flexShrink: 0
                   }}></div>
                   <h2 className="text-xl font-semibold mb-2" style={{ color: '#025fff', marginBottom: 0 }}>
-                    {jobDetail?.name || '作业详情'}
+                    {jobDetail?.name || t('workJobDetail.title')}
                   </h2>
                   <span 
                     className="inline-flex px-3 py-1 rounded-full text-xs font-medium"
@@ -2651,9 +2654,9 @@ export default function WorkJobDetail() {
                   </span>
                 </div>
                 <div className="text-sm flex gap-4" style={{ color: '#898f9a', marginTop: '12px' }}>
-                  <span>作业编号:{jobDetail?.orderNo || jobDetail?.code || '-'}</span>
-                  <span>作业负责人:{jobDetail?.initiatorName || jobDetail?.initiator || '-'}</span>
-                  <span>发起时间:{jobDetail?.initiationTime ? formatDateWithFormat(jobDetail.initiationTime as string | number | Date) : '-'}</span>
+                  <span>{t('workJobDetail.orderNo')}:{jobDetail?.orderNo || jobDetail?.code || '-'}</span>
+                  <span>{t('workJobDetail.jobInitiator')}:{jobDetail?.initiatorName || jobDetail?.initiator || '-'}</span>
+                  <span>{t('workJobDetail.initiationTime')}:{jobDetail?.initiationTime ? formatDateWithFormat(jobDetail.initiationTime as string | number | Date) : '-'}</span>
                 </div>
               </div>
             </div>
@@ -2663,7 +2666,7 @@ export default function WorkJobDetail() {
                 type="primary"
                 onClick={() => navigate(`/work-job/archive${jobId ? `?id=${jobId}` : ''}`)}
               >
-                归档信息
+                {t('workJobDetail.archiveInfo')}
               </Button>
               <button
                 onClick={() => {
@@ -2675,7 +2678,7 @@ export default function WorkJobDetail() {
                   navigate('/dashboard');
                 }}
                 className="flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:bg-gray-50 hover:border-gray-400 transition-colors"
-                title="返回作业管理"
+                title={t('workJobDetail.backToWorkManagement')}
               >
                 <ArrowLeft className="w-5 h-5 text-gray-600" />
               </button>
@@ -2698,7 +2701,7 @@ export default function WorkJobDetail() {
             <div className="px-6 py-4 border-b border-gray-200">
               <h2 className="text-lg font-semibold text-blue-600 flex items-center gap-2">
                 <FileText className="w-5 h-5" />
-                作业流程
+                {t('workJobDetail.jobFlow')}
               </h2>
             </div>
             <div className="flex-1 overflow-hidden" ref={reactFlowWrapper} style={{ minHeight: '600px' }}>
@@ -2753,7 +2756,7 @@ export default function WorkJobDetail() {
                   </ReactFlow>
                 </>
               ) : (
-                <div className="text-center text-gray-400 py-8">暂无流程数据</div>
+                <div className="text-center text-gray-400 py-8">{t('workJobDetail.noFlowData')}</div>
               )}
             </div>
           </div>
@@ -2773,7 +2776,7 @@ export default function WorkJobDetail() {
               <div className="px-6 py-4 border-b border-gray-200">
                 <h2 className="text-lg font-semibold text-blue-600 flex items-center gap-2">
                   <FileText className="w-5 h-5" />
-                  作业信息
+                  {t('workJobDetail.jobInfo')}
                 </h2>
               </div>
               <div className="p-4">
@@ -2806,7 +2809,7 @@ export default function WorkJobDetail() {
                   }}
                 >
                   <Descriptions.Item 
-                    label="流程模板"
+                    label={t('workJobDetail.workflowTemplate')}
                   >
                     <Tooltip title={(() => {
                       const template = workflowTemplateList.find(t => t.id === jobDetail?.designId);
@@ -2822,7 +2825,7 @@ export default function WorkJobDetail() {
                   </Descriptions.Item>
                   
                   <Descriptions.Item 
-                    label="作业分类"
+                    label={t('workJobDetail.jobCategory')}
                   >
                     <Tooltip title={(() => {
                       const item = workTypeDictList.find(i => i.value === jobDetail?.type);
@@ -2838,7 +2841,7 @@ export default function WorkJobDetail() {
                   </Descriptions.Item>
                   
                   <Descriptions.Item 
-                    label="作业名称"
+                    label={t('workJobDetail.jobName')}
                   >
                     <Tooltip title={jobDetail?.name || '-'}>
                       <div style={{ width: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
@@ -2848,7 +2851,7 @@ export default function WorkJobDetail() {
                   </Descriptions.Item>
                   
                   <Descriptions.Item 
-                    label="作业内容"
+                    label={t('workJobDetail.jobContent')}
                   >
                     <Tooltip title={jobDetail?.description || jobDetail?.content || '-'}>
                       <div style={{ width: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
@@ -2857,7 +2860,7 @@ export default function WorkJobDetail() {
                     </Tooltip>
                   </Descriptions.Item>
                   
-                  <Descriptions.Item label="紧急程度">
+                  <Descriptions.Item label={t('workJobDetail.urgencyLevel')}>
                     <Tooltip title={(() => {
                       const item = urgencyLevelDictList.find(i => String(i.value) === String(jobDetail?.urgencyLevel || ''));
                       return item?.label || '-';
@@ -2879,7 +2882,7 @@ export default function WorkJobDetail() {
               <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
                 <h2 className="text-lg font-semibold text-blue-600 flex items-center gap-2">
                   <Clock className="w-5 h-5" />
-                  当前任务
+                  {t('workJobDetail.currentTask')}
                 </h2>
               </div>
               <div className="flex-1 overflow-y-auto p-4">
@@ -2897,7 +2900,7 @@ export default function WorkJobDetail() {
                   />
                 ) : (
                   <div className="text-center text-gray-400 py-8">
-                    <p className="text-sm">请点击左侧画布中的节点查看节点信息</p>
+                    <p className="text-sm">{t('workJobDetail.clickNodeToViewInfo')}</p>
                   </div>
                 )}
               </div>

+ 3 - 3
src/components/lockCabinet/HardwareLockCabinetManagement.tsx

@@ -411,7 +411,7 @@ export default function HardwareLockCabinetManagement() {
         <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
           <div className="flex items-center justify-between">
             <div className="text-sm text-gray-600">
-              共 <span className="text-blue-600 font-medium">{total}</span> 条记录
+              {t('common.total')} <span className="text-blue-600 font-medium">{total}</span> {t('common.records')}
             </div>
             <div className="flex gap-2">
               <Button
@@ -422,7 +422,7 @@ export default function HardwareLockCabinetManagement() {
                 }}
                 disabled={queryParams.pageNo <= 1}
               >
-                上一页
+                {t('common.prevPage')}
               </Button>
               <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                 {queryParams.pageNo} / {Math.ceil(total / queryParams.pageSize) || 1}
@@ -435,7 +435,7 @@ export default function HardwareLockCabinetManagement() {
                 }}
                 disabled={queryParams.pageNo >= Math.ceil(total / queryParams.pageSize)}
               >
-                下一页
+                {t('common.nextPage')}
               </Button>
             </div>
           </div>

+ 5 - 3
src/components/lockCabinet/SlotsList.tsx

@@ -5,12 +5,14 @@ import { slotApi, LockCabinetSlotVO, SlotPageParam } from '../../api/lockCabinet
 import { DICT_TYPE, getIntDictOptions, getDictLabel } from '../../utils/dict';
 import { dateFormatter } from '../../utils/formatTime';
 import type { ColumnsType } from 'antd/es/table';
+import { useTranslation } from 'react-i18next';
 
 interface SlotsListProps {
   cabinetId: string;
 }
 
 export default function SlotsList({ cabinetId }: SlotsListProps) {
+  const { t } = useTranslation();
   const [loading, setLoading] = useState(true);
   const [list, setList] = useState<LockCabinetSlotVO[]>([]);
   const [total, setTotal] = useState(0);
@@ -190,7 +192,7 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
         <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
           <div className="flex items-center justify-between">
             <div className="text-sm text-gray-600">
-              共 <span className="text-blue-600 font-medium">{total}</span> 条记录
+              {t('common.total')} <span className="text-blue-600 font-medium">{total}</span> {t('common.records')}
             </div>
             <div className="flex gap-2">
               <Button
@@ -201,7 +203,7 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
                 }}
                 disabled={queryParams.pageNo <= 1}
               >
-                上一页
+                {t('common.prevPage')}
               </Button>
               <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
                 {queryParams.pageNo} / {Math.ceil(total / queryParams.pageSize) || 1}
@@ -214,7 +216,7 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
                 }}
                 disabled={queryParams.pageNo >= Math.ceil(total / queryParams.pageSize)}
               >
-                下一页
+                {t('common.nextPage')}
               </Button>
             </div>
           </div>

+ 160 - 0
src/locales/en.json

@@ -327,6 +327,8 @@
     "records": "records",
     "prevPage": "Previous",
     "nextPage": "Next",
+    "pageOfTotal": "Page {{current}} of {{total}}",
+    "firstLevelMenus": " first-level menus",
     "formNotInitialized": "Form component not initialized, please refresh the page and try again",
     "addTemplate": "Add Template",
     "editTemplate": "Edit Process Template",
@@ -966,6 +968,164 @@
     "passwordChangeSuccess": "Password changed successfully! Please login again with your new password",
     "passwordChangeFailed": "Password change failed, please check if the old password is correct",
     "nicknamePlaceholder": "Please enter nickname"
+  },
+  "cockpit": {
+    "pendingJobs": "Pending Jobs",
+    "inProgressJobs": "In Progress",
+    "completedJobs": "Completed Jobs",
+    "overdueJobs": "Overdue Jobs",
+    "items": "items",
+    "hide": "Hide",
+    "jobListWithTasks": "Job List (with tasks)",
+    "loading": "Loading...",
+    "jobInitiator": "Job Initiator",
+    "overallProgress": "Overall Progress",
+    "taskName": "Task Name",
+    "taskOwner": "Task Owner",
+    "startTime": "Start Time",
+    "status": "Status",
+    "action": "Action",
+    "unassigned": "Unassigned",
+    "noJobData": "No job data",
+    "dailyCompletedCount": "Daily Completed Count",
+    "systemJobCompleteTrend": "Overall System Job Completion Trend (Last 30 Days)",
+    "materialManagement": "Material Management",
+    "availableMaterials": "Available",
+    "borrowedMaterials": "Borrowed",
+    "exceptionMaterials": "Exception",
+    "returnPendingMaterials": "Return Pending",
+    "materialStatusList": "Material Status List",
+    "viewAllMaterials": "View All Materials",
+    "materialCode": "Material Code",
+    "materialName": "Material Name",
+    "materialType": "Material Type",
+    "borrower": "Borrower",
+    "view": "View",
+    "urgeReturn": "Urge Return",
+    "handleException": "Handle Exception",
+    "claim": "Claim",
+    "materialQuickActions": "Material Quick Actions",
+    "addMaterial": "Add Material",
+    "refreshMaterialStatus": "Refresh Status",
+    "handleExceptionMaterials": "Handle Exceptions",
+    "materialExport": "Export Ledger",
+    "taskDetail": "Task Detail",
+    "orderNo": "Order No.",
+    "pending": "Pending",
+    "inProgress": "In Progress",
+    "completed": "Completed",
+    "overdue": "Overdue",
+    "unknown": "Unknown",
+    "production": "Production",
+    "maintenance": "Maintenance",
+    "normal": "Normal",
+    "attention": "Attention",
+    "active": "Active",
+    "inactive": "Inactive",
+    "exception": "Exception",
+    "borrowed": "Borrowed",
+    "returnPending": "Return Pending",
+    "available": "Available",
+    "cardContainer": "Card Container",
+    "pendingTask": "Pending Tasks",
+    "inProgressTask": "In Progress",
+    "completedTask": "Completed Tasks",
+    "myJobTasks": "My Job Tasks",
+    "processJobTasksPrompt": "Please process your job tasks in time",
+    "viewAll": "View All",
+    "viewAllJobs": "View All Jobs",
+    "dailyTaskCompletedCount": "Daily Completed Tasks",
+    "taskCompleteTrend": "Task Completion Trend (Last 30 Days)",
+    "quickActions": "Quick Actions",
+    "submitTaskComplete": "Submit Task Complete",
+    "viewMyJobs": "View My Jobs",
+    "claimJobMaterial": "Claim Materials",
+    "feedback": "Feedback",
+    "jobNo": "Job No.",
+    "jobName": "Job Name",
+    "jobType": "Job Type",
+    "jobInitiatorLabel": "Job Initiator",
+    "taskOwnerLabel": "Task Owner",
+    "urgencyLevel": "Urgency",
+    "unknownTask": "Unknown Task",
+    "formLoading": "Form loading...",
+    "auditOpinionPlaceholder": "Enter audit opinion (optional)",
+    "featureDeveloping": "Coming soon",
+    "priorityNormal": "Normal",
+    "priorityUrgent": "Urgent",
+    "priorityVeryUrgent": "Very Urgent",
+    "getStatsFailed": "Failed to get statistics",
+    "getManagerWorkStatsFailed": "Failed to get manager work stats",
+    "getManagerWorkListFailed": "Failed to get manager work list",
+    "getManagerDayStatsFailed": "Failed to get manager daily stats",
+    "getManagerDataFailed": "Failed to get manager data",
+    "getDataFailed": "Failed to get data",
+    "nodeIdNotExist": "Node ID does not exist",
+    "nodeIdInvalid": "Invalid node ID",
+    "formDataIncomplete": "Form data incomplete",
+    "parseFormDataFailed": "Failed to parse form data",
+    "formIdInvalid": "Invalid form ID",
+    "formConfigIncomplete": "Form config incomplete",
+    "getFormDetailFailed": "Failed to get form detail",
+    "getNodeDetailFailed": "Failed to get node detail",
+    "pendingAudit": "Pending Audit",
+    "approved": "Approved",
+    "rejected": "Rejected",
+    "unaudited": "Unaudited",
+    "returned": "Returned",
+    "skipped": "Skipped"
+  },
+  "workJobDetail": {
+    "title": "Job Detail",
+    "orderNo": "Order No.",
+    "jobInitiator": "Job Initiator",
+    "initiationTime": "Initiation Time",
+    "archiveInfo": "Archive",
+    "backToWorkManagement": "Back to Work Management",
+    "jobFlow": "Job Flow",
+    "noFlowData": "No flow data",
+    "jobInfo": "Job Info",
+    "workflowTemplate": "Workflow Template",
+    "jobCategory": "Job Category",
+    "jobName": "Job Name",
+    "jobContent": "Job Content",
+    "urgencyLevel": "Urgency",
+    "currentTask": "Current Task",
+    "clickNodeToViewInfo": "Click a node on the left canvas to view node information",
+    "loading": "Loading...",
+    "jobNotFound": "Job not found",
+    "completed": "Completed",
+    "inProgress": "In Progress",
+    "pending": "Pending",
+    "collapseBranch": "Collapse branch",
+    "expandBranch": "Expand branch",
+    "unknownNode": "Unknown node",
+    "node": "Node",
+    "statusUnknown": "Unknown",
+    "statusPending": "Pending",
+    "statusRunning": "In Progress",
+    "statusCompleted": "Completed",
+    "statusRejected": "Returned",
+    "statusSkipped": "Skipped",
+    "statusCancelled": "Cancelled",
+    "nodeInfo": "Node Info",
+    "nodeNameLabel": "Node Name",
+    "responsible": "Responsible",
+    "responsibleHint": "Person who handles this task or step. If not selected, it must be chosen when creating the job.",
+    "remark": "Remark",
+    "submitForm": "Submit Form",
+    "businessForm": "Business Form",
+    "selectIsolationNode": "Select Isolation Node",
+    "isolationType": "Isolation Type",
+    "isolationPointsSelect": "Isolation Points (multi-select)",
+    "lockerPerson": "Locker",
+    "colockerPerson": "Co-locker (multi-select)",
+    "notificationMessage": "Notification",
+    "notificationMethod": "Notification Method",
+    "sms": "SMS",
+    "webmail": "Inbox",
+    "email": "Email",
+    "appNotify": "APP"
   }
 }
 

+ 160 - 0
src/locales/zh.json

@@ -328,6 +328,8 @@
     "records": "条记录",
     "prevPage": "上一页",
     "nextPage": "下一页",
+    "pageOfTotal": "第 {{current}} 页 / 共 {{total}} 页",
+    "firstLevelMenus": "个一级菜单",
     "formNotInitialized": "表单组件未初始化,请刷新页面重试",
     "addTemplate": "新增模板",
     "editTemplate": "编辑流程模板",
@@ -968,6 +970,164 @@
     "passwordChangeSuccess": "密码修改成功!请使用新密码重新登录",
     "passwordChangeFailed": "密码修改失败,请检查旧密码是否正确",
     "nicknamePlaceholder": "请输入昵称"
+  },
+  "cockpit": {
+    "pendingJobs": "待发布作业",
+    "inProgressJobs": "进行中作业",
+    "completedJobs": "已完成作业",
+    "overdueJobs": "逾期作业",
+    "items": "项",
+    "hide": "隐藏",
+    "jobListWithTasks": "作业列表 (含任务明细)",
+    "loading": "加载中...",
+    "jobInitiator": "作业发起人",
+    "overallProgress": "整体进度",
+    "taskName": "任务名称",
+    "taskOwner": "任务负责人",
+    "startTime": "开始时间",
+    "status": "状态",
+    "action": "操作",
+    "unassigned": "未分配",
+    "noJobData": "暂无作业数据",
+    "dailyCompletedCount": "每日完成作业数",
+    "systemJobCompleteTrend": "全系统作业完成趋势 (最近30天)",
+    "materialManagement": "物料管理",
+    "availableMaterials": "可用物料",
+    "borrowedMaterials": "借用中物料",
+    "exceptionMaterials": "异常物料",
+    "returnPendingMaterials": "待归还物料",
+    "materialStatusList": "物料状态列表",
+    "viewAllMaterials": "查看全部物料",
+    "materialCode": "物料编号",
+    "materialName": "物料名称",
+    "materialType": "物料类型",
+    "borrower": "借用人员",
+    "view": "查看",
+    "urgeReturn": "催还",
+    "handleException": "处理异常",
+    "claim": "申领",
+    "materialQuickActions": "物料管理快捷操作",
+    "addMaterial": "新增物料",
+    "refreshMaterialStatus": "刷新物料状态",
+    "handleExceptionMaterials": "处理异常物料",
+    "materialExport": "物料台账导出",
+    "taskDetail": "任务详情",
+    "orderNo": "工单编号",
+    "pending": "待执行",
+    "inProgress": "进行中",
+    "completed": "已完成",
+    "overdue": "逾期",
+    "unknown": "未知",
+    "production": "投产",
+    "maintenance": "维修",
+    "normal": "正常",
+    "attention": "关注",
+    "active": "活跃",
+    "inactive": "非活跃",
+    "exception": "异常",
+    "borrowed": "借用中",
+    "returnPending": "待归还",
+    "available": "可用",
+    "cardContainer": "卡片容器",
+    "pendingTask": "待执行任务",
+    "inProgressTask": "进行中任务",
+    "completedTask": "已完成任务",
+    "myJobTasks": "我的作业任务",
+    "processJobTasksPrompt": "请及时处理您的作业任务",
+    "viewAll": "查看全部",
+    "viewAllJobs": "查看全部作业",
+    "dailyTaskCompletedCount": "每日完成任务数",
+    "taskCompleteTrend": "任务完成趋势(最近30天)",
+    "quickActions": "快捷操作",
+    "submitTaskComplete": "提交任务完成",
+    "viewMyJobs": "查看我的作业",
+    "claimJobMaterial": "申领作业物料",
+    "feedback": "问题反馈",
+    "jobNo": "作业编号",
+    "jobName": "作业名称",
+    "jobType": "作业类型",
+    "jobInitiatorLabel": "作业发起人",
+    "taskOwnerLabel": "任务负责人",
+    "urgencyLevel": "紧急程度",
+    "unknownTask": "未知任务",
+    "formLoading": "表单加载中...",
+    "auditOpinionPlaceholder": "请输入审核意见(可选)",
+    "featureDeveloping": "功能开发中",
+    "priorityNormal": "一般",
+    "priorityUrgent": "紧急",
+    "priorityVeryUrgent": "非常紧急",
+    "getStatsFailed": "获取统计数据失败",
+    "getManagerWorkStatsFailed": "获取管理员工作统计失败",
+    "getManagerWorkListFailed": "获取管理员工作列表失败",
+    "getManagerDayStatsFailed": "获取管理员每日工作统计失败",
+    "getManagerDataFailed": "获取管理员数据失败",
+    "getDataFailed": "获取数据失败",
+    "nodeIdNotExist": "节点ID不存在",
+    "nodeIdInvalid": "节点ID无效",
+    "formDataIncomplete": "表单数据不完整",
+    "parseFormDataFailed": "解析表单数据失败",
+    "formIdInvalid": "表单ID无效",
+    "formConfigIncomplete": "表单配置不完整",
+    "getFormDetailFailed": "获取表单详情失败",
+    "getNodeDetailFailed": "获取节点详情失败",
+    "pendingAudit": "待审核",
+    "approved": "已通过",
+    "rejected": "已驳回",
+    "unaudited": "未审核",
+    "returned": "已退回",
+    "skipped": "已跳过"
+  },
+  "workJobDetail": {
+    "title": "作业详情",
+    "orderNo": "作业编号",
+    "jobInitiator": "作业负责人",
+    "initiationTime": "发起时间",
+    "archiveInfo": "归档信息",
+    "backToWorkManagement": "返回作业管理",
+    "jobFlow": "作业流程",
+    "noFlowData": "暂无流程数据",
+    "jobInfo": "作业信息",
+    "workflowTemplate": "流程模板",
+    "jobCategory": "作业分类",
+    "jobName": "作业名称",
+    "jobContent": "作业内容",
+    "urgencyLevel": "紧急程度",
+    "currentTask": "当前任务",
+    "clickNodeToViewInfo": "请点击左侧画布中的节点查看节点信息",
+    "loading": "加载中...",
+    "jobNotFound": "作业不存在",
+    "completed": "已完成",
+    "inProgress": "进行中",
+    "pending": "待处理",
+    "collapseBranch": "收起分支",
+    "expandBranch": "展开分支",
+    "unknownNode": "未知节点",
+    "node": "节点",
+    "statusUnknown": "未知",
+    "statusPending": "待执行",
+    "statusRunning": "执行中",
+    "statusCompleted": "执行完成",
+    "statusRejected": "已退回",
+    "statusSkipped": "已跳过",
+    "statusCancelled": "已取消",
+    "nodeInfo": "节点信息",
+    "nodeNameLabel": "节点名称",
+    "responsible": "负责人",
+    "responsibleHint": "对该任务或步骤节点进行处理的人员,若不选则需要在创建作业时进行选择。",
+    "remark": "备注",
+    "submitForm": "提交表单",
+    "businessForm": "业务表单",
+    "selectIsolationNode": "选择隔离节点",
+    "isolationType": "隔离方式",
+    "isolationPointsSelect": "隔离点选择(可多选)",
+    "lockerPerson": "上锁人",
+    "colockerPerson": "共锁人(可多选)",
+    "notificationMessage": "通知消息",
+    "notificationMethod": "通知方式",
+    "sms": "短信",
+    "webmail": "站内信",
+    "email": "邮件",
+    "appNotify": "APP通知"
   }
 }
 

+ 4 - 2
src/views/Login.tsx

@@ -390,7 +390,7 @@ export default function Login() {
       // 延迟一下再跳转,让用户看到加载提示
       setTimeout(() => {
         toast.dismiss('loading');
-        toast.success(t('common.success') || '登录成功');
+        // 登录成功不再弹出全局成功提示(避免频繁/停留过久影响体验)
 
         sessionStorage.removeItem('lastActiveMenu');
         sessionStorage.removeItem('cabinetDetailSource');
@@ -611,12 +611,14 @@ export default function Login() {
 
   return (
     <div className="min-h-screen h-screen relative overflow-hidden bg-white">
-      {/* 语言切换 */}
+      {/* 语言切换 - 鼠标悬停显示,移开自动收起 */}
       <div className="absolute top-6 right-6 z-50">
         <Dropdown
           menu={{ items: languageMenuItems }}
           trigger={['hover']}
           placement="bottomRight"
+          mouseEnterDelay={0}
+          mouseLeaveDelay={0.15}
         >
           <button
             className="flex items-center gap-2 px-4 py-2.5 bg-white/90 backdrop-blur-sm border border-gray-200/50 rounded-xl shadow-lg hover:shadow-xl hover:bg-white transition-all group"