Procházet zdrojové kódy

feat: 优化多语言测试断言和修复Redux store访问问题

- 修复src/app.tsx: 在Cypress测试环境下暴露Redux store到window对象
- 优化cypress/e2e/i18n/i18n-loading-success.cy.ts: 使用data-testid替代文本断言,解决竞态条件问题
- 重构cypress/e2e/i18n/i18n-loading-success.cy.ts: 将forEach循环改为独立测试用例,修复拦截器冲突
- 新增cypress/e2e/i18n/目录: 完整的多语言测试套件
- 新增cypress/support/mock/handlers/i18n.ts: 多语言API mock处理器
- 更新cypress支持文件: commands.js, e2e.ts, LoginPage.ts
- 更新src/log/logger.js: 日志配置优化
dengdx před 2 týdny
rodič
revize
fe7d44858f

+ 413 - 0
cypress/e2e/i18n/i18n-content-validation.cy.ts

@@ -0,0 +1,413 @@
+import { mockI18nSuccess, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
+import LoginPage from '../../support/pageObjects/LoginPage';
+
+describe('多语言资源内容验证测试', () => {
+  const loginPage = new LoginPage();
+
+  beforeEach(() => {
+    cy.clearAllSessionStorage();
+    cy.clearAllLocalStorage();
+    
+    // Mock所有必要的API,避免影响页面加载
+    mockAllRequiredAPIs();
+  });
+
+  it('验证中文翻译内容的正确性', () => {
+    mockI18nSuccess('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证Redux状态中的中文翻译
+    cy.window().its('store').invoke('getState').then((state) => {
+      const messages = state.i18n.messages;
+      
+      // 验证基础翻译
+      expect(messages).to.have.property('greeting', '你好,世界!');
+      expect(messages).to.have.property('name', '张三');
+      expect(messages).to.have.property('patient', '患者管理');
+      expect(messages).to.have.property('register', '注册');
+      expect(messages).to.have.property('worklist', '任务清单');
+      
+      // 验证嵌套键名翻译
+      expect(messages).to.have.property('worklist.operationPanel', '操作面板');
+      expect(messages).to.have.property('register.basicInfoPanel', '基本信息表单区域');
+      expect(messages).to.have.property('worklistTable.patientId', '患者编号');
+      expect(messages).to.have.property('worklistTable.name', '患者姓名');
+      
+      // 验证表单字段翻译
+      expect(messages).to.have.property('register.patientId', '患者编号');
+      expect(messages).to.have.property('register.patientName', '患者姓名');
+      expect(messages).to.have.property('register.gender', '性别');
+      expect(messages).to.have.property('register.gender.male', '男');
+      expect(messages).to.have.property('register.gender.female', '女');
+    });
+    
+    // 验证页面显示的中文内容
+    cy.get('body').should('contain', '患者管理');
+  });
+
+  it('验证英文翻译内容的正确性', () => {
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证Redux状态中的英文翻译
+    cy.window().its('store').invoke('getState').then((state) => {
+      const messages = state.i18n.messages;
+      
+      // 验证基础翻译
+      expect(messages).to.have.property('greeting', 'Hello, world!');
+      expect(messages).to.have.property('name', 'John Doe');
+      expect(messages).to.have.property('patient', 'Patient Management');
+      expect(messages).to.have.property('register', 'Register');
+      expect(messages).to.have.property('worklist', 'Task List');
+      
+      // 验证嵌套键名翻译
+      expect(messages).to.have.property('worklist.operationPanel', 'Operation Panel');
+      expect(messages).to.have.property('register.basicInfoPanel', 'Basic Information Form Area');
+      expect(messages).to.have.property('worklistTable.patientId', 'Patient ID');
+      expect(messages).to.have.property('worklistTable.name', 'Patient Name');
+      
+      // 验证表单字段翻译
+      expect(messages).to.have.property('register.patientId', 'Patient ID');
+      expect(messages).to.have.property('register.patientName', 'Patient Name');
+      expect(messages).to.have.property('register.gender', 'Gender');
+      expect(messages).to.have.property('register.gender.male', 'Male');
+      expect(messages).to.have.property('register.gender.female', 'Female');
+    });
+    
+    // 验证页面显示的英文内容
+    cy.get('body').should('contain', 'Patient Management');
+  });
+
+  it('验证中英文翻译的对应关系', () => {
+    // 定义需要验证的关键翻译对
+    const translationPairs = [
+      { key: 'patient', zh: '患者管理', en: 'Patient Management' },
+      { key: 'register', zh: '注册', en: 'Register' },
+      { key: 'worklist', zh: '任务清单', en: 'Task List' },
+      { key: 'register.patientId', zh: '患者编号', en: 'Patient ID' },
+      { key: 'register.patientName', zh: '患者姓名', en: 'Patient Name' },
+      { key: 'register.gender.male', zh: '男', en: 'Male' },
+      { key: 'register.gender.female', zh: '女', en: 'Female' }
+    ];
+
+    // 先验证中文
+    mockI18nSuccess('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    cy.window().its('store').invoke('getState').then((zhState) => {
+      const zhMessages = zhState.i18n.messages;
+      
+      // 验证中文翻译
+      translationPairs.forEach(({ key, zh }) => {
+        expect(zhMessages).to.have.property(key, zh);
+      });
+    });
+    
+    // 再验证英文
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    cy.window().its('store').invoke('getState').then((enState) => {
+      const enMessages = enState.i18n.messages;
+      
+      // 验证英文翻译
+      translationPairs.forEach(({ key, en }) => {
+        expect(enMessages).to.have.property(key, en);
+      });
+    });
+  });
+
+  it('验证嵌套键名的正确解析', () => {
+    mockI18nSuccess('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    cy.window().its('store').invoke('getState').then((state) => {
+      const messages = state.i18n.messages;
+      
+      // 验证多层嵌套的键名
+      expect(messages).to.have.property('worklist.operationPanel', '操作面板');
+      expect(messages).to.have.property('register.basicInfoPanel', '基本信息表单区域');
+      expect(messages).to.have.property('register.protocolListPanel', '待选择协议列表区域');
+      expect(messages).to.have.property('register.selectedProtocolListPanel', '已选择协议列表区域');
+      
+      // 验证表格相关的嵌套键名
+      expect(messages).to.have.property('worklistTable.patientId', '患者编号');
+      expect(messages).to.have.property('worklistTable.name', '患者姓名');
+      expect(messages).to.have.property('worklistTable.birthDate', '出生日期');
+      expect(messages).to.have.property('worklistTable.gender', '性别');
+      
+      // 验证表单字段的嵌套键名
+      expect(messages).to.have.property('register.patientId', '患者编号');
+      expect(messages).to.have.property('register.patientName', '患者姓名');
+      expect(messages).to.have.property('register.gender.male', '男');
+      expect(messages).to.have.property('register.gender.female', '女');
+    });
+  });
+
+  it('验证特殊字符和格式的处理', () => {
+    // 创建包含特殊字符的mock数据
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: {
+          'special.chars': '特殊字符:@#$%^&*()',
+          'unicode.emoji': '表情符号:😀 🎉 ✅ ❌',
+          'html.content': '<strong>粗体文本</strong>',
+          'quotes.single': "包含'单引号'的文本",
+          'quotes.double': '包含"双引号"的文本',
+          'newlines': '第一行\n第二行\n第三行',
+          'spaces': '   前后有空格   ',
+          'numbers': '数字:123456789',
+          'mixed': '混合内容:123 ABC 中文 @#$ 😀'
+        }
+      });
+    }).as('getI18nZHSpecial');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSpecial');
+    
+    cy.window().its('store').invoke('getState').then((state) => {
+      const messages = state.i18n.messages;
+      
+      // 验证特殊字符正确保存
+      expect(messages).to.have.property('special.chars', '特殊字符:@#$%^&*()');
+      expect(messages).to.have.property('unicode.emoji', '表情符号:😀 🎉 ✅ ❌');
+      expect(messages).to.have.property('html.content', '<strong>粗体文本</strong>');
+      expect(messages).to.have.property('quotes.single', "包含'单引号'的文本");
+      expect(messages).to.have.property('quotes.double', '包含"双引号"的文本');
+      expect(messages).to.have.property('newlines', '第一行\n第二行\n第三行');
+      expect(messages).to.have.property('spaces', '   前后有空格   ');
+      expect(messages).to.have.property('numbers', '数字:123456789');
+      expect(messages).to.have.property('mixed', '混合内容:123 ABC 中文 @#$ 😀');
+    });
+  });
+
+  it('验证翻译内容的完整性', () => {
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    cy.window().its('store').invoke('getState').then((state) => {
+      const messages = state.i18n.messages;
+      
+      // 验证必要的翻译键都存在
+      const requiredKeys = [
+        'greeting',
+        'name',
+        'patient',
+        'register',
+        'worklist',
+        'register.patientId',
+        'register.patientName',
+        'register.gender',
+        'register.gender.male',
+        'register.gender.female',
+        'worklistTable.patientId',
+        'worklistTable.name'
+      ];
+      
+      requiredKeys.forEach(key => {
+        expect(messages).to.have.property(key);
+        expect(messages[key]).to.be.a('string');
+        expect(messages[key]).to.not.be.empty;
+      });
+      
+      // 验证翻译内容不包含占位符或错误标记
+      Object.values(messages).forEach((value: any) => {
+        expect(value).to.not.include('TODO');
+        expect(value).to.not.include('FIXME');
+        expect(value).to.not.include('{{');
+        expect(value).to.not.include('}}');
+        expect(value).to.not.include('[MISSING]');
+      });
+    });
+  });
+
+  it('验证翻译内容的一致性', () => {
+    // 验证相同概念在不同上下文中的翻译一致性
+    mockI18nSuccess('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    cy.window().its('store').invoke('getState').then((state) => {
+      const messages = state.i18n.messages;
+      
+      // 验证"患者"相关翻译的一致性
+      expect(messages['register.patientId']).to.include('患者');
+      expect(messages['register.patientName']).to.include('患者');
+      expect(messages['worklistTable.patientId']).to.include('患者');
+      expect(messages['worklistTable.name']).to.include('患者');
+      
+      // 验证性别翻译的一致性
+      expect(messages['register.gender.male']).to.equal('男');
+      expect(messages['register.gender.female']).to.equal('女');
+    });
+  });
+
+  it('验证翻译内容的长度和格式', () => {
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    cy.window().its('store').invoke('getState').then((state) => {
+      const messages = state.i18n.messages;
+      
+      // 验证翻译内容的合理长度
+      Object.entries(messages).forEach(([key, value]: [string, any]) => {
+        // 翻译内容不应该过长(假设最长不超过200字符)
+        expect(value.length).to.be.lessThan(200);
+        
+        // 翻译内容不应该为空
+        expect(value.trim()).to.not.be.empty;
+        
+        // 验证特定键的格式
+        if (key.includes('placeholder')) {
+          // placeholder应该以适当的提示开始
+          expect(value).to.match(/^(Enter|Please|Input)/i);
+        }
+        
+        if (key.includes('button') || key === 'register' || key === 'print') {
+          // 按钮文本应该是动词或动作词
+          expect(value).to.not.include('.');
+          expect(value.length).to.be.lessThan(50);
+        }
+      });
+    });
+  });
+
+  it('验证动态内容的翻译支持', () => {
+    // 测试包含变量占位符的翻译(如果有的话)
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: {
+          'dynamic.welcome': '欢迎,{name}!',
+          'dynamic.count': '共有 {count} 个项目',
+          'dynamic.date': '日期:{date}',
+          'validation.required': '{field} 是必填项',
+          'validation.minLength': '{field} 至少需要 {min} 个字符'
+        }
+      });
+    }).as('getI18nZHDynamic');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHDynamic');
+    
+    cy.window().its('store').invoke('getState').then((state) => {
+      const messages = state.i18n.messages;
+      
+      // 验证动态内容的占位符格式正确
+      expect(messages).to.have.property('dynamic.welcome', '欢迎,{name}!');
+      expect(messages).to.have.property('dynamic.count', '共有 {count} 个项目');
+      expect(messages).to.have.property('dynamic.date', '日期:{date}');
+      expect(messages).to.have.property('validation.required', '{field} 是必填项');
+      expect(messages).to.have.property('validation.minLength', '{field} 至少需要 {min} 个字符');
+      
+      // 验证占位符格式一致性
+      Object.values(messages).forEach((value: any) => {
+        if (value.includes('{')) {
+          // 确保占位符格式正确:{变量名}
+          const placeholders = value.match(/\{[^}]+\}/g);
+          if (placeholders) {
+            placeholders.forEach((placeholder: string) => {
+              expect(placeholder).to.match(/^\{[a-zA-Z][a-zA-Z0-9]*\}$/);
+            });
+          }
+        }
+      });
+    });
+  });
+});

+ 199 - 0
cypress/e2e/i18n/i18n-error-handling.cy.ts

@@ -0,0 +1,199 @@
+import { mockI18nError, mockI18nServerError, mockI18nNetworkError, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
+import LoginPage from '../../support/pageObjects/LoginPage';
+
+describe('多语言资源错误处理测试', () => {
+  const loginPage = new LoginPage();
+
+  beforeEach(() => {
+    cy.clearAllSessionStorage();
+    cy.clearAllLocalStorage();
+    
+    // Mock所有必要的API,避免影响页面加载
+    mockAllRequiredAPIs();
+  });
+
+  it('API返回404时显示错误提示并支持重新加载', () => {
+    // 设置404错误mock
+    mockI18nError('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    // 等待API调用失败
+    cy.wait('@getI18nZHError');
+    
+    // 验证错误提示显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+    
+    // 验证错误信息包含具体错误
+    cy.get('body').should('contain', 'Failed to load i18n messages');
+    
+    // 测试重新加载按钮功能
+    // 重新设置成功的mock
+    mockI18nError('zh'); // 再次设置错误mock来测试重试
+    
+    cy.contains('重新加载').click();
+    
+    // 验证再次发起API请求
+    cy.wait('@getI18nZHError');
+    
+    // 验证错误提示仍然显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+  });
+
+  it('API返回500服务器错误时正确处理', () => {
+    mockI18nServerError('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENServerError');
+    
+    // 验证错误提示显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+    
+    // 验证Redux状态正确
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+      expect(state.i18n.messages).to.deep.equal({});
+    });
+  });
+
+  it('网络连接错误时正确处理', () => {
+    mockI18nNetworkError('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHNetworkError');
+    
+    // 验证错误提示显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+  });
+
+  it('验证错误状态下的用户体验', () => {
+    mockI18nError('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHError');
+    
+    // 验证错误页面的样式和布局
+    cy.get('div').contains('多语言资源加载失败').should('be.visible')
+      .parent().should('have.css', 'display', 'flex')
+      .and('have.css', 'justify-content', 'center')
+      .and('have.css', 'align-items', 'center');
+    
+    // 验证重新加载按钮的样式
+    cy.contains('重新加载').should('be.visible')
+      .and('have.css', 'margin-top', '16px')
+      .and('have.css', 'padding', '8px 16px');
+  });
+
+  it('验证错误处理不影响其他功能', () => {
+    mockI18nError('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENError');
+    
+    // 验证错误状态下页面基本结构仍然存在
+    cy.get('body').should('exist');
+    cy.get('div').should('exist');
+    
+    // 验证Redux store的其他状态不受影响
+    cy.window().its('store').invoke('getState').then((state) => {
+      // 验证其他slice的状态正常
+      expect(state.product).to.exist;
+      expect(state.userInfo).to.exist;
+      // i18n状态应该反映错误
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+    });
+  });
+
+  it('验证多次重新加载的行为', () => {
+    let callCount = 0;
+    
+    // 设置动态mock,前两次失败,第三次成功
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      callCount++;
+      if (callCount <= 2) {
+        req.reply({
+          statusCode: 404,
+          body: { message: 'Not Found' }
+        });
+      } else {
+        req.reply({
+          statusCode: 200,
+          body: {
+            greeting: '你好,世界!',
+            patient: '患者管理'
+          }
+        });
+      }
+    }).as('getI18nZHDynamic');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    // 第一次失败
+    cy.wait('@getI18nZHDynamic');
+    cy.contains('多语言资源加载失败').should('be.visible');
+    
+    // 第一次重试,仍然失败
+    cy.contains('重新加载').click();
+    cy.wait('@getI18nZHDynamic');
+    cy.contains('多语言资源加载失败').should('be.visible');
+    
+    // 第二次重试,成功
+    cy.contains('重新加载').click();
+    cy.wait('@getI18nZHDynamic');
+    
+    // 验证成功加载
+    cy.contains('多语言资源加载失败').should('not.exist');
+    cy.get('body').should('contain', '患者管理');
+  });
+});

+ 289 - 0
cypress/e2e/i18n/i18n-invalid-format.cy.ts

@@ -0,0 +1,289 @@
+import { mockI18nInvalidFormat, mockI18nEmptyData, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
+import LoginPage from '../../support/pageObjects/LoginPage';
+
+describe('多语言资源格式异常测试', () => {
+  const loginPage = new LoginPage();
+
+  beforeEach(() => {
+    cy.clearAllSessionStorage();
+    cy.clearAllLocalStorage();
+    
+    // Mock所有必要的API,避免影响页面加载
+    mockAllRequiredAPIs();
+  });
+
+  it('API返回非JSON格式数据时正确处理', () => {
+    mockI18nInvalidFormat('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHInvalidFormat');
+    
+    // 验证错误提示显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+    
+    // 验证Redux状态反映错误
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+      expect(state.i18n.messages).to.deep.equal({});
+    });
+  });
+
+  it('API返回空对象时正确处理', () => {
+    mockI18nEmptyData('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENEmptyData');
+    
+    // 空数据应该被正常处理,不显示错误
+    cy.contains('多语言资源加载失败').should('not.exist');
+    
+    // 验证Redux状态
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.currentLocale).to.equal('en');
+      expect(state.i18n.messages).to.deep.equal({});
+    });
+  });
+
+  it('API返回null数据时正确处理', () => {
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: null
+      });
+    }).as('getI18nZHNull');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHNull');
+    
+    // 验证错误处理
+    cy.contains('多语言资源加载失败').should('be.visible');
+    
+    // 验证Redux状态
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+    });
+  });
+
+  it('API返回数组格式数据时正确处理', () => {
+    cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: ['invalid', 'array', 'format']
+      });
+    }).as('getI18nENArray');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENArray');
+    
+    // 数组格式应该被当作错误处理
+    cy.contains('多语言资源加载失败').should('be.visible');
+    
+    // 验证Redux状态
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+    });
+  });
+
+  it('API返回部分缺失字段的数据时正确处理', () => {
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: {
+          // 只有部分字段
+          greeting: '你好',
+          // 缺少其他必要字段
+        }
+      });
+    }).as('getI18nZHPartial');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHPartial');
+    
+    // 部分数据应该被正常处理
+    cy.contains('多语言资源加载失败').should('not.exist');
+    
+    // 验证Redux状态包含部分数据
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.currentLocale).to.equal('zh');
+      expect(state.i18n.messages).to.have.property('greeting', '你好');
+      expect(state.i18n.messages).to.not.have.property('patient');
+    });
+  });
+
+  it('API返回包含特殊字符的数据时正确处理', () => {
+    cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: {
+          'special.key': 'Value with special chars: @#$%^&*()',
+          'unicode.key': '测试 Unicode 字符 🚀 ✅ ❌',
+          'html.key': '<script>alert("test")</script>',
+          'json.key': '{"nested": "json"}',
+          'empty.key': '',
+          'space.key': '   ',
+          'newline.key': 'Line 1\nLine 2\nLine 3'
+        }
+      });
+    }).as('getI18nENSpecial');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSpecial');
+    
+    // 特殊字符数据应该被正常处理
+    cy.contains('多语言资源加载失败').should('not.exist');
+    
+    // 验证Redux状态包含特殊字符数据
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.currentLocale).to.equal('en');
+      expect(state.i18n.messages).to.have.property('special.key', 'Value with special chars: @#$%^&*()');
+      expect(state.i18n.messages).to.have.property('unicode.key', '测试 Unicode 字符 🚀 ✅ ❌');
+      expect(state.i18n.messages).to.have.property('html.key', '<script>alert("test")</script>');
+      expect(state.i18n.messages).to.have.property('empty.key', '');
+    });
+  });
+
+  it('API返回超大数据时正确处理', () => {
+    // 生成大量数据
+    const largeData = {};
+    for (let i = 0; i < 1000; i++) {
+      largeData[`key_${i}`] = `Value ${i} - ${'x'.repeat(100)}`;
+    }
+    
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: largeData
+      });
+    }).as('getI18nZHLarge');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHLarge');
+    
+    // 大数据应该被正常处理
+    cy.contains('多语言资源加载失败').should('not.exist');
+    
+    // 验证Redux状态包含大量数据
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.currentLocale).to.equal('zh');
+      expect(Object.keys(state.i18n.messages)).to.have.length(1000);
+      expect(state.i18n.messages).to.have.property('key_0', 'Value 0 - ' + 'x'.repeat(100));
+      expect(state.i18n.messages).to.have.property('key_999', 'Value 999 - ' + 'x'.repeat(100));
+    });
+  });
+
+  it('验证格式异常后的重新加载功能', () => {
+    // 首先返回无效格式
+    mockI18nInvalidFormat('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHInvalidFormat');
+    
+    // 验证错误显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+    
+    // 设置正确的mock用于重新加载
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: {
+          greeting: '你好,世界!',
+          patient: '患者管理'
+        }
+      });
+    }).as('getI18nZHFixed');
+    
+    // 点击重新加载
+    cy.contains('重新加载').click();
+    
+    cy.wait('@getI18nZHFixed');
+    
+    // 验证成功加载
+    cy.contains('多语言资源加载失败').should('not.exist');
+    cy.get('body').should('contain', '患者管理');
+    
+    // 验证Redux状态恢复正常
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.currentLocale).to.equal('zh');
+      expect(state.i18n.messages).to.have.property('patient', '患者管理');
+    });
+  });
+});

+ 336 - 0
cypress/e2e/i18n/i18n-language-fallback.cy.ts

@@ -0,0 +1,336 @@
+import { mockI18nSuccess, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
+import LoginPage from '../../support/pageObjects/LoginPage';
+
+describe('多语言资源不支持语言回退测试', () => {
+  const loginPage = new LoginPage();
+
+  beforeEach(() => {
+    cy.clearAllSessionStorage();
+    cy.clearAllLocalStorage();
+    
+    // Mock所有必要的API,避免影响页面加载
+    mockAllRequiredAPIs();
+  });
+
+  it('不支持的语言自动回退到英文', () => {
+    // 设置英文mock,因为不支持的语言会回退到英文
+    mockI18nSuccess('en');
+    
+    const unsupportedLanguages = [
+      'fr-FR', // 法语
+      'de-DE', // 德语
+      'ja-JP', // 日语
+      'ko-KR', // 韩语
+      'es-ES', // 西班牙语
+      'it-IT', // 意大利语
+      'pt-BR', // 葡萄牙语
+      'ru-RU', // 俄语
+      'ar-SA', // 阿拉伯语
+      'hi-IN'  // 印地语
+    ];
+
+    unsupportedLanguages.forEach((lang, index) => {
+      mockI18nSuccess('en'); // 每次都重新设置英文mock
+      
+      cy.window().then((win) => {
+        Object.defineProperty(win.navigator, 'language', {
+          value: lang,
+          writable: false
+        });
+      });
+      
+      loginPage.visit();
+      
+      // 验证请求的是英文资源
+      cy.wait('@getI18nENSuccess');
+      
+      // 验证显示英文内容
+      cy.get('body').should('contain', 'Patient Management');
+      
+      // 验证Redux状态为英文
+      cy.window().its('store').invoke('getState').then((state) => {
+        expect(state.i18n.currentLocale).to.equal('en');
+        expect(state.i18n.messages).to.have.property('patient', 'Patient Management');
+      });
+      
+      // 清理,准备下一个测试
+      if (index < unsupportedLanguages.length - 1) {
+        cy.clearAllSessionStorage();
+        cy.clearAllLocalStorage();
+      }
+    });
+  });
+
+  it('无效的语言代码回退到英文', () => {
+    mockI18nSuccess('en');
+    
+    const invalidLanguages = [
+      'invalid',
+      'xx-XX',
+      '123',
+      'zh-INVALID',
+      'en-INVALID',
+      '',
+      null,
+      undefined
+    ];
+
+    invalidLanguages.forEach((lang, index) => {
+      mockI18nSuccess('en');
+      
+      cy.window().then((win) => {
+        if (lang === null || lang === undefined) {
+          // 模拟navigator.language为null或undefined的情况
+          Object.defineProperty(win.navigator, 'language', {
+            value: lang,
+            writable: false
+          });
+        } else {
+          Object.defineProperty(win.navigator, 'language', {
+            value: lang,
+            writable: false
+          });
+        }
+      });
+      
+      loginPage.visit();
+      
+      cy.wait('@getI18nENSuccess');
+      
+      // 验证回退到英文
+      cy.get('body').should('contain', 'Patient Management');
+      
+      cy.window().its('store').invoke('getState').then((state) => {
+        expect(state.i18n.currentLocale).to.equal('en');
+      });
+      
+      if (index < invalidLanguages.length - 1) {
+        cy.clearAllSessionStorage();
+        cy.clearAllLocalStorage();
+      }
+    });
+  });
+
+  it('浏览器不支持navigator.language时回退到英文', () => {
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      // 模拟navigator.language不存在的情况
+      Object.defineProperty(win.navigator, 'language', {
+        value: undefined,
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证回退到英文
+    cy.get('body').should('contain', 'Patient Management');
+    
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.currentLocale).to.equal('en');
+      expect(state.i18n.messages).to.have.property('patient', 'Patient Management');
+    });
+  });
+
+  it('验证语言回退的逻辑正确性', () => {
+    // 测试语言检测和回退的完整逻辑
+    const testCases = [
+      // 支持的语言应该正常加载
+      { input: 'zh-CN', expected: 'zh', expectedText: '患者管理' },
+      { input: 'zh-TW', expected: 'zh', expectedText: '患者管理' },
+      { input: 'en-US', expected: 'en', expectedText: 'Patient Management' },
+      { input: 'en-GB', expected: 'en', expectedText: 'Patient Management' },
+      
+      // 不支持的语言应该回退到英文
+      { input: 'fr-FR', expected: 'en', expectedText: 'Patient Management' },
+      { input: 'de-DE', expected: 'en', expectedText: 'Patient Management' },
+      { input: 'ja-JP', expected: 'en', expectedText: 'Patient Management' },
+      
+      // 边界情况
+      { input: 'zh', expected: 'zh', expectedText: '患者管理' },
+      { input: 'en', expected: 'en', expectedText: 'Patient Management' },
+      { input: 'fr', expected: 'en', expectedText: 'Patient Management' }
+    ];
+
+    testCases.forEach(({ input, expected, expectedText }, index) => {
+      mockI18nSuccess(expected as 'zh' | 'en');
+      
+      cy.window().then((win) => {
+        Object.defineProperty(win.navigator, 'language', {
+          value: input,
+          writable: false
+        });
+      });
+      
+      loginPage.visit();
+      
+      cy.wait(`@getI18n${expected.toUpperCase()}Success`);
+      
+      // 验证正确的语言内容显示
+      cy.get('body').should('contain', expectedText);
+      
+      // 验证Redux状态
+      cy.window().its('store').invoke('getState').then((state) => {
+        expect(state.i18n.currentLocale).to.equal(expected);
+      });
+      
+      if (index < testCases.length - 1) {
+        cy.clearAllSessionStorage();
+        cy.clearAllLocalStorage();
+      }
+    });
+  });
+
+  it('验证回退语言的完整性', () => {
+    mockI18nSuccess('en');
+    
+    // 使用不支持的语言
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'fr-FR',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证回退到英文后,所有必要的翻译都存在
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.currentLocale).to.equal('en');
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      
+      // 验证关键翻译存在
+      expect(state.i18n.messages).to.have.property('patient', 'Patient Management');
+      expect(state.i18n.messages).to.have.property('register', 'Register');
+      expect(state.i18n.messages).to.have.property('worklist', 'Task List');
+      expect(state.i18n.messages).to.have.property('register.patientId', 'Patient ID');
+      expect(state.i18n.messages).to.have.property('register.patientName', 'Patient Name');
+    });
+    
+    // 验证页面显示正确
+    cy.get('body').should('contain', 'Patient Management');
+  });
+
+  it('验证多次语言回退的稳定性', () => {
+    // 连续测试多个不支持的语言,验证每次都能正确回退
+    const unsupportedSequence = ['fr-FR', 'de-DE', 'ja-JP', 'ko-KR', 'es-ES'];
+    
+    unsupportedSequence.forEach((lang, index) => {
+      mockI18nSuccess('en');
+      
+      cy.window().then((win) => {
+        Object.defineProperty(win.navigator, 'language', {
+          value: lang,
+          writable: false
+        });
+      });
+      
+      loginPage.visit();
+      
+      cy.wait('@getI18nENSuccess');
+      
+      // 每次都应该回退到英文
+      cy.get('body').should('contain', 'Patient Management');
+      
+      cy.window().its('store').invoke('getState').then((state) => {
+        expect(state.i18n.currentLocale).to.equal('en');
+        expect(state.i18n.loading).to.be.false;
+        expect(state.i18n.error).to.be.null;
+      });
+      
+      if (index < unsupportedSequence.length - 1) {
+        cy.clearAllSessionStorage();
+        cy.clearAllLocalStorage();
+      }
+    });
+  });
+
+  it('验证语言回退时的请求参数', () => {
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'fr-FR', // 不支持的语言
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    // 验证请求的是英文资源,而不是法语资源
+    cy.wait('@getI18nENSuccess').then((interception) => {
+      expect(interception.request.url).to.include('/dr/api/v1/pub/trans/en/en.js');
+      expect(interception.request.url).to.not.include('/dr/api/v1/pub/trans/fr/fr.js');
+      expect(interception.request.method).to.equal('GET');
+    });
+  });
+
+  it('验证回退语言加载失败时的处理', () => {
+    // 模拟回退语言(英文)加载失败的情况
+    cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
+      req.reply({
+        statusCode: 404,
+        body: { message: 'Not Found' }
+      });
+    }).as('getI18nENError');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'fr-FR', // 不支持的语言,会回退到英文
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENError');
+    
+    // 验证即使回退语言加载失败,也能正确显示错误
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+    
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+      // 注意:currentLocale可能仍然是'en',因为这是尝试加载的语言
+      expect(state.i18n.messages).to.deep.equal({});
+    });
+  });
+
+  it('验证语言回退不影响其他功能', () => {
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'de-DE', // 不支持的语言
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证语言回退后,其他Redux状态正常
+    cy.window().its('store').invoke('getState').then((state) => {
+      // 验证其他slice的状态不受影响
+      expect(state.product).to.exist;
+      expect(state.userInfo).to.exist;
+      
+      // i18n状态正确
+      expect(state.i18n.currentLocale).to.equal('en');
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+    });
+    
+    // 验证页面正常渲染
+    cy.get('body').should('exist');
+    cy.get('body').should('contain', 'Patient Management');
+  });
+});

+ 275 - 0
cypress/e2e/i18n/i18n-language-switching.cy.ts

@@ -0,0 +1,275 @@
+import { mockI18nSuccess, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
+import LoginPage from '../../support/pageObjects/LoginPage';
+
+describe('多语言资源语言切换测试', () => {
+  const loginPage = new LoginPage();
+
+  beforeEach(() => {
+    cy.clearAllSessionStorage();
+    cy.clearAllLocalStorage();
+    
+    // Mock所有必要的API,避免影响页面加载
+    mockAllRequiredAPIs();
+  });
+
+  it('从中文切换到英文', () => {
+    // 首先加载中文
+    mockI18nSuccess('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证中文内容显示
+    cy.get('body').should('contain', '患者管理');
+    
+    // 验证Redux状态为中文
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.currentLocale).to.equal('zh');
+      expect(state.i18n.messages).to.have.property('patient', '患者管理');
+    });
+    
+    // 模拟语言切换 - 重新访问页面并设置英文语言
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证英文内容显示
+    cy.get('body').should('contain', 'Patient Management');
+    
+    // 验证Redux状态为英文
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.currentLocale).to.equal('en');
+      expect(state.i18n.messages).to.have.property('patient', 'Patient Management');
+    });
+  });
+
+  it('从英文切换到中文', () => {
+    // 首先加载英文
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证英文内容显示
+    cy.get('body').should('contain', 'Patient Management');
+    
+    // 模拟语言切换到中文
+    mockI18nSuccess('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证中文内容显示
+    cy.get('body').should('contain', '患者管理');
+    
+    // 验证Redux状态正确更新
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.currentLocale).to.equal('zh');
+      expect(state.i18n.messages).to.have.property('patient', '患者管理');
+    });
+  });
+
+  it('验证不同地区变体的语言检测', () => {
+    const testCases = [
+      // 中文变体都应该加载中文资源
+      { browserLang: 'zh', expectedLocale: 'zh', expectedText: '患者管理' },
+      { browserLang: 'zh-CN', expectedLocale: 'zh', expectedText: '患者管理' },
+      { browserLang: 'zh-TW', expectedLocale: 'zh', expectedText: '患者管理' },
+      { browserLang: 'zh-HK', expectedLocale: 'zh', expectedText: '患者管理' },
+      { browserLang: 'zh-SG', expectedLocale: 'zh', expectedText: '患者管理' },
+      
+      // 英文变体都应该加载英文资源
+      { browserLang: 'en', expectedLocale: 'en', expectedText: 'Patient Management' },
+      { browserLang: 'en-US', expectedLocale: 'en', expectedText: 'Patient Management' },
+      { browserLang: 'en-GB', expectedLocale: 'en', expectedText: 'Patient Management' },
+      { browserLang: 'en-AU', expectedLocale: 'en', expectedText: 'Patient Management' },
+      { browserLang: 'en-CA', expectedLocale: 'en', expectedText: 'Patient Management' }
+    ];
+
+    testCases.forEach(({ browserLang, expectedLocale, expectedText }, index) => {
+      mockI18nSuccess(expectedLocale as 'zh' | 'en');
+      
+      cy.window().then((win) => {
+        Object.defineProperty(win.navigator, 'language', {
+          value: browserLang,
+          writable: false
+        });
+      });
+      
+      loginPage.visit();
+      
+      cy.wait(`@getI18n${expectedLocale.toUpperCase()}Success`);
+      
+      // 验证正确的语言内容显示
+      cy.get('body').should('contain', expectedText);
+      
+      // 验证Redux状态
+      cy.window().its('store').invoke('getState').then((state) => {
+        expect(state.i18n.currentLocale).to.equal(expectedLocale);
+      });
+      
+      // 清理,准备下一个测试
+      if (index < testCases.length - 1) {
+        cy.clearAllSessionStorage();
+        cy.clearAllLocalStorage();
+      }
+    });
+  });
+
+  it('验证语言切换时的加载状态', () => {
+    // 首先加载中文
+    mockI18nSuccess('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSuccess');
+    cy.get('body').should('contain', '患者管理');
+    
+    // 切换到英文,验证加载过程
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    // 验证加载状态显示
+    cy.contains('加载多语言资源中...').should('be.visible');
+    
+    // 等待加载完成
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证加载状态消失,新语言内容显示
+    cy.contains('加载多语言资源中...').should('not.exist');
+    cy.get('body').should('contain', 'Patient Management');
+  });
+
+  it('验证语言切换时Redux状态的完整性', () => {
+    // 加载中文
+    mockI18nSuccess('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证中文状态
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.currentLocale).to.equal('zh');
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.messages).to.have.property('register', '注册');
+      expect(state.i18n.messages).to.have.property('worklist', '任务清单');
+    });
+    
+    // 切换到英文
+    mockI18nSuccess('en');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证英文状态完全替换了中文状态
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.currentLocale).to.equal('en');
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.messages).to.have.property('register', 'Register');
+      expect(state.i18n.messages).to.have.property('worklist', 'Task List');
+      // 确保中文内容已被替换
+      expect(state.i18n.messages.register).to.not.equal('注册');
+      expect(state.i18n.messages.worklist).to.not.equal('任务清单');
+    });
+  });
+
+  it('验证快速语言切换的稳定性', () => {
+    // 快速切换多次语言,验证系统稳定性
+    const switchSequence = [
+      { lang: 'zh-CN', locale: 'zh', text: '患者管理' },
+      { lang: 'en-US', locale: 'en', text: 'Patient Management' },
+      { lang: 'zh-TW', locale: 'zh', text: '患者管理' },
+      { lang: 'en-GB', locale: 'en', text: 'Patient Management' },
+      { lang: 'zh-CN', locale: 'zh', text: '患者管理' }
+    ];
+
+    switchSequence.forEach(({ lang, locale, text }, index) => {
+      mockI18nSuccess(locale as 'zh' | 'en');
+      
+      cy.window().then((win) => {
+        Object.defineProperty(win.navigator, 'language', {
+          value: lang,
+          writable: false
+        });
+      });
+      
+      loginPage.visit();
+      
+      cy.wait(`@getI18n${locale.toUpperCase()}Success`);
+      cy.get('body').should('contain', text);
+      
+      // 验证每次切换后状态都正确
+      cy.window().its('store').invoke('getState').then((state) => {
+        expect(state.i18n.currentLocale).to.equal(locale);
+        expect(state.i18n.loading).to.be.false;
+        expect(state.i18n.error).to.be.null;
+      });
+    });
+  });
+});

+ 180 - 0
cypress/e2e/i18n/i18n-loading-success.cy.ts

@@ -0,0 +1,180 @@
+import { mockI18nSuccess, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
+import LoginPage from '../../support/pageObjects/LoginPage';
+
+// 语言设置辅助函数
+const setNavigatorLanguage = (language: string) => (win: Window) => {
+  Object.defineProperty(win.navigator, 'language', {
+    value: language,
+    writable: false
+  });
+};
+
+describe('多语言资源正常加载测试', () => {
+  const loginPage = new LoginPage();
+
+  beforeEach(() => {
+    // 清除所有拦截器
+    cy.clearAllSessionStorage();
+    cy.clearAllLocalStorage();
+    
+    // Mock所有必要的API,避免影响页面加载
+    mockAllRequiredAPIs();
+  });
+
+  it('访问登录页面时成功加载中文多语言资源', () => {
+    // 设置mock
+    mockI18nSuccess('zh');
+    
+    // 设置浏览器语言为中文并访问登录页面
+    loginPage.visit({
+      onBeforeLoad: setNavigatorLanguage('zh-CN')
+    });
+    
+    // 核心断言:等待用户名输入框真正可见
+    // be.visible 会检查元素是否在DOM中存在、不被隐藏、且在视口中可见
+    cy.get('[data-testid="login-username-input"]', { timeout: 10000 })
+      .should('be.visible');
+    
+    // 验证API调用确实发生了
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证其他关键UI元素也可见
+    cy.get('[data-testid="login-password-input"]').should('be.visible');
+    cy.get('[data-testid="login-submit-button"]').should('be.visible');
+  });
+
+  it('访问登录页面时成功加载英文多语言资源', () => {
+    // 设置mock
+    mockI18nSuccess('en');
+    
+    // 设置浏览器语言为英文并访问登录页面
+    loginPage.visit({
+      onBeforeLoad: setNavigatorLanguage('en-US')
+    });
+    
+    // 核心断言:等待用户名输入框真正可见
+    // be.visible 会检查元素是否在DOM中存在、不被隐藏、且在视口中可见
+    cy.get('[data-testid="login-username-input"]', { timeout: 10000 })
+      .should('be.visible');
+    
+    // 验证API调用确实发生了
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证其他关键UI元素也可见
+    cy.get('[data-testid="login-password-input"]').should('be.visible');
+    cy.get('[data-testid="login-submit-button"]').should('be.visible');
+  });
+
+  it('验证多语言资源请求参数正确', () => {
+    mockI18nSuccess('zh');
+    
+    loginPage.visit({
+      onBeforeLoad: setNavigatorLanguage('zh-CN')
+    });
+    
+    // 等待用户名输入框可见,确保多语言加载成功
+    cy.get('[data-testid="login-username-input"]', { timeout: 10000 })
+      .should('be.visible');
+    
+    // 验证请求URL正确
+    cy.wait('@getI18nZHSuccess').then((interception) => {
+      expect(interception.request.url).to.include('/dr/api/v1/pub/trans/zh/zh.js');
+      expect(interception.request.method).to.equal('GET');
+    });
+  });
+
+  it('验证加载成功后Redux状态正确', () => {
+    mockI18nSuccess('zh');
+    
+    loginPage.visit({
+      onBeforeLoad: setNavigatorLanguage('zh-CN')
+    });
+    
+    // 等待用户名输入框可见,确保多语言加载成功
+    cy.get('[data-testid="login-username-input"]', { timeout: 10000 })
+      .should('be.visible');
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证Redux store中的i18n状态
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.currentLocale).to.equal('zh');
+      expect(state.i18n.messages).to.be.an('object');
+      expect(Object.keys(state.i18n.messages).length).to.be.greaterThan(0);
+    });
+  });
+
+  it('验证浏览器语言 zh-CN 自动检测为中文', () => {
+    mockI18nSuccess('zh');
+    
+    loginPage.visit({
+      onBeforeLoad: setNavigatorLanguage('zh-CN')
+    });
+    
+    // 等待用户名输入框可见,确保多语言加载成功
+    cy.get('[data-testid="login-username-input"]', { timeout: 10000 })
+      .should('be.visible');
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证其他关键UI元素也可见
+    cy.get('[data-testid="login-password-input"]').should('be.visible');
+    cy.get('[data-testid="login-submit-button"]').should('be.visible');
+  });
+
+  it('验证浏览器语言 zh-TW 自动检测为中文', () => {
+    mockI18nSuccess('zh');
+    
+    loginPage.visit({
+      onBeforeLoad: setNavigatorLanguage('zh-TW')
+    });
+    
+    // 等待用户名输入框可见,确保多语言加载成功
+    cy.get('[data-testid="login-username-input"]', { timeout: 10000 })
+      .should('be.visible');
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证其他关键UI元素也可见
+    cy.get('[data-testid="login-password-input"]').should('be.visible');
+    cy.get('[data-testid="login-submit-button"]').should('be.visible');
+  });
+
+  it('验证浏览器语言 en-US 自动检测为英文', () => {
+    mockI18nSuccess('en');
+    
+    loginPage.visit({
+      onBeforeLoad: setNavigatorLanguage('en-US')
+    });
+    
+    // 等待用户名输入框可见,确保多语言加载成功
+    cy.get('[data-testid="login-username-input"]', { timeout: 10000 })
+      .should('be.visible');
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证其他关键UI元素也可见
+    cy.get('[data-testid="login-password-input"]').should('be.visible');
+    cy.get('[data-testid="login-submit-button"]').should('be.visible');
+  });
+
+  it('验证浏览器语言 en-GB 自动检测为英文', () => {
+    mockI18nSuccess('en');
+    
+    loginPage.visit({
+      onBeforeLoad: setNavigatorLanguage('en-GB')
+    });
+    
+    // 等待用户名输入框可见,确保多语言加载成功
+    cy.get('[data-testid="login-username-input"]', { timeout: 10000 })
+      .should('be.visible');
+    
+    cy.wait('@getI18nENSuccess');
+    
+    // 验证其他关键UI元素也可见
+    cy.get('[data-testid="login-password-input"]').should('be.visible');
+    cy.get('[data-testid="login-submit-button"]').should('be.visible');
+  });
+});

+ 337 - 0
cypress/e2e/i18n/i18n-timeout.cy.ts

@@ -0,0 +1,337 @@
+import { mockI18nTimeout, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
+import LoginPage from '../../support/pageObjects/LoginPage';
+
+describe('多语言资源网络超时测试', () => {
+  const loginPage = new LoginPage();
+
+  beforeEach(() => {
+    cy.clearAllSessionStorage();
+    cy.clearAllLocalStorage();
+    
+    // Mock所有必要的API,避免影响页面加载
+    mockAllRequiredAPIs();
+  });
+
+  it('API请求超时时正确处理', () => {
+    mockI18nTimeout('zh');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    // 验证加载状态显示
+    cy.contains('加载多语言资源中...').should('be.visible');
+    
+    // 等待超时请求(这里我们不会真的等30秒,而是验证请求被发起)
+    cy.wait('@getI18nZHTimeout', { timeout: 5000 }).then((interception) => {
+      // 验证请求确实被发起
+      expect(interception.request.url).to.include('/dr/api/v1/pub/trans/zh/zh.js');
+    });
+    
+    // 由于实际测试中不会等待30秒,我们模拟超时后的状态
+    // 在真实场景中,这会触发错误处理
+  });
+
+  it('模拟真实超时场景的错误处理', () => {
+    // 设置一个较短的延迟来模拟超时,然后返回错误
+    cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
+      // 模拟网络超时错误
+      req.reply({
+        statusCode: 408, // Request Timeout
+        body: {
+          message: 'Request Timeout',
+          error: 'The request timed out'
+        }
+      });
+    }).as('getI18nENTimeoutError');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENTimeoutError');
+    
+    // 验证超时错误的处理
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+    
+    // 验证Redux状态反映超时错误
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+      expect(state.i18n.messages).to.deep.equal({});
+    });
+  });
+
+  it('验证超时后重新加载的成功恢复', () => {
+    // 首先模拟超时错误
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 408,
+        body: { message: 'Request Timeout' }
+      });
+    }).as('getI18nZHTimeoutFirst');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHTimeoutFirst');
+    
+    // 验证超时错误显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+    
+    // 设置成功的mock用于重新加载
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: {
+          greeting: '你好,世界!',
+          patient: '患者管理',
+          register: '注册'
+        }
+      });
+    }).as('getI18nZHSuccess');
+    
+    // 点击重新加载
+    cy.contains('重新加载').click();
+    
+    cy.wait('@getI18nZHSuccess');
+    
+    // 验证成功恢复
+    cy.contains('多语言资源加载失败').should('not.exist');
+    cy.get('body').should('contain', '患者管理');
+    
+    // 验证Redux状态恢复正常
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.be.null;
+      expect(state.i18n.currentLocale).to.equal('zh');
+      expect(state.i18n.messages).to.have.property('patient', '患者管理');
+    });
+  });
+
+  it('验证慢网络环境下的用户体验', () => {
+    // 模拟慢网络,但不超时
+    cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
+      req.reply({
+        delay: 3000, // 3秒延迟,模拟慢网络
+        statusCode: 200,
+        body: {
+          greeting: 'Hello, world!',
+          patient: 'Patient Management'
+        }
+      });
+    }).as('getI18nENSlow');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    // 验证加载状态持续显示
+    cy.contains('加载多语言资源中...').should('be.visible');
+    
+    // 验证加载状态的样式
+    cy.get('div').contains('加载多语言资源中...').should('be.visible')
+      .parent().should('have.css', 'display', 'flex')
+      .and('have.css', 'justify-content', 'center')
+      .and('have.css', 'align-items', 'center');
+    
+    // 等待慢网络请求完成
+    cy.wait('@getI18nENSlow');
+    
+    // 验证最终成功加载
+    cy.contains('加载多语言资源中...').should('not.exist');
+    cy.get('body').should('contain', 'Patient Management');
+  });
+
+  it('验证多次超时重试的行为', () => {
+    let retryCount = 0;
+    
+    // 设置动态mock,前两次超时,第三次成功
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      retryCount++;
+      if (retryCount <= 2) {
+        req.reply({
+          statusCode: 408,
+          body: { message: 'Request Timeout' }
+        });
+      } else {
+        req.reply({
+          statusCode: 200,
+          body: {
+            greeting: '你好,世界!',
+            patient: '患者管理'
+          }
+        });
+      }
+    }).as('getI18nZHRetry');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    // 第一次超时
+    cy.wait('@getI18nZHRetry');
+    cy.contains('多语言资源加载失败').should('be.visible');
+    
+    // 第一次重试,仍然超时
+    cy.contains('重新加载').click();
+    cy.wait('@getI18nZHRetry');
+    cy.contains('多语言资源加载失败').should('be.visible');
+    
+    // 第二次重试,成功
+    cy.contains('重新加载').click();
+    cy.wait('@getI18nZHRetry');
+    
+    // 验证最终成功
+    cy.contains('多语言资源加载失败').should('not.exist');
+    cy.get('body').should('contain', '患者管理');
+  });
+
+  it('验证超时错误的详细信息显示', () => {
+    cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
+      req.reply({
+        statusCode: 408,
+        body: {
+          message: 'Request Timeout',
+          error: 'The server did not respond within the expected time',
+          code: 'TIMEOUT_ERROR'
+        }
+      });
+    }).as('getI18nENTimeoutDetail');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENTimeoutDetail');
+    
+    // 验证错误信息显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    
+    // 验证Redux状态包含详细错误信息
+    cy.window().its('store').invoke('getState').then((state) => {
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+      expect(state.i18n.error).to.include('Failed to load i18n messages');
+    });
+  });
+
+  it('验证超时场景下其他功能不受影响', () => {
+    cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
+      req.reply({
+        statusCode: 408,
+        body: { message: 'Request Timeout' }
+      });
+    }).as('getI18nZHTimeoutIsolated');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'zh-CN',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nZHTimeoutIsolated');
+    
+    // 验证超时错误显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    
+    // 验证页面基本结构仍然存在
+    cy.get('body').should('exist');
+    cy.get('div').should('exist');
+    
+    // 验证Redux store的其他状态不受影响
+    cy.window().its('store').invoke('getState').then((state) => {
+      // 验证其他slice的状态正常
+      expect(state.product).to.exist;
+      expect(state.userInfo).to.exist;
+      
+      // 只有i18n状态反映超时错误
+      expect(state.i18n.loading).to.be.false;
+      expect(state.i18n.error).to.not.be.null;
+      expect(state.i18n.messages).to.deep.equal({});
+    });
+  });
+
+  it('验证网络恢复后的自动重试机制', () => {
+    // 注意:这个测试模拟的是用户手动重试,而不是自动重试
+    // 因为我们的实现中没有自动重试机制
+    
+    cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
+      req.reply({
+        statusCode: 408,
+        body: { message: 'Request Timeout' }
+      });
+    }).as('getI18nENNetworkDown');
+    
+    cy.window().then((win) => {
+      Object.defineProperty(win.navigator, 'language', {
+        value: 'en-US',
+        writable: false
+      });
+    });
+    
+    loginPage.visit();
+    
+    cy.wait('@getI18nENNetworkDown');
+    
+    // 验证网络问题时的错误显示
+    cy.contains('多语言资源加载失败').should('be.visible');
+    cy.contains('重新加载').should('be.visible');
+    
+    // 模拟网络恢复
+    cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
+      req.reply({
+        statusCode: 200,
+        body: {
+          greeting: 'Hello, world!',
+          patient: 'Patient Management'
+        }
+      });
+    }).as('getI18nENNetworkUp');
+    
+    // 用户手动重试
+    cy.contains('重新加载').click();
+    
+    cy.wait('@getI18nENNetworkUp');
+    
+    // 验证网络恢复后成功加载
+    cy.contains('多语言资源加载失败').should('not.exist');
+    cy.get('body').should('contain', 'Patient Management');
+  });
+});

+ 10 - 1
cypress/support/commands.js

@@ -37,4 +37,13 @@ Cypress.Commands.add('logWithDate', (message, ...args) => {
   cy.log(formattedMessage);
   
   return null; // Cypress 命令需要返回 null 或 Promise
-});
+});
+
+// Cypress.on('window:before:load', (win) => {
+//   // 每打开/刷新一页都会触发
+//   cy.then(() => {
+//     console.log('[全局] 内存 href:', win.location.href);
+//     const [, stamp] = win.location.href.match(/[?&]stamp=([^&]*)/) || [];
+//     if (stamp) console.log('[全局] 当前 stamp:', stamp);
+//   });
+// });

+ 1 - 1
cypress/support/e2e.ts

@@ -9,4 +9,4 @@ import './pageObjects/WorklistPage';
 
 // Alternatively you can use CommonJS syntax:
 // require('./commands')
-// require('./pageObjects/LoginPage')
+// require('./pageObjects/LoginPage')

+ 225 - 0
cypress/support/mock/handlers/i18n.ts

@@ -0,0 +1,225 @@
+// I18n-related mock handlers
+
+// 封装获取多语言资源成功的 mock
+export function mockI18nSuccess(locale: 'zh' | 'en') {
+  const mockData = locale === 'zh' ? {
+    greeting: '你好,世界!',
+    name: '张三',
+    patient: '患者管理',
+    register: '注册',
+    tasklist: '任务清单',
+    historylist: '历史清单',
+    archivelist: '归档清单',
+    bin: '回收站',
+    outputlist: '传输清单',
+    exam: '检查',
+    examlist: '检查清单',
+    process: '处理',
+    print: '打印',
+    printlist: '打印清单',
+    worklist: '任务清单',
+    'worklist.operationPanel': '操作面板',
+    'register.basicInfoPanel': '基本信息表单区域',
+    'register.protocolListPanel': '待选择协议列表区域',
+    'register.selectedProtocolListPanel': '已选择协议列表区域',
+    'worklistTable.patientId': '患者编号',
+    'worklistTable.name': '患者姓名',
+    'register.patientId': '患者编号',
+    'register.patientName': '患者姓名',
+    'register.gender': '性别',
+    'register.gender.male': '男',
+    'register.gender.female': '女'
+  } : {
+    greeting: 'Hello, world!',
+    name: 'John Doe',
+    patient: 'Patient Management',
+    register: 'Register',
+    tasklist: 'Task List',
+    historylist: 'History List',
+    archivelist: 'Archive List',
+    bin: 'Recycle Bin',
+    outputlist: 'Transfer List',
+    exam: 'Examination',
+    examlist: 'Examination List',
+    process: 'Process',
+    print: 'Print',
+    printlist: 'Print List',
+    worklist: 'Task List',
+    'worklist.operationPanel': 'Operation Panel',
+    'register.basicInfoPanel': 'Basic Information Form Area',
+    'register.protocolListPanel': 'Protocol Selection List Area',
+    'register.selectedProtocolListPanel': 'Selected Protocol List Area',
+    'worklistTable.patientId': 'Patient ID',
+    'worklistTable.name': 'Patient Name',
+    'register.patientId': 'Patient ID',
+    'register.patientName': 'Patient Name',
+    'register.gender': 'Gender',
+    'register.gender.male': 'Male',
+    'register.gender.female': 'Female'
+  };
+
+  cy.intercept('GET', `/dr/api/v1/pub/trans/${locale}/${locale}.js`, (req) => {
+    req.reply({
+      statusCode: 200,
+      body: mockData
+    });
+  }).as(`getI18n${locale.toUpperCase()}Success`);
+}
+
+// 封装获取多语言资源失败的 mock (404错误)
+export function mockI18nError(locale: 'zh' | 'en') {
+  cy.intercept('GET', `/dr/api/v1/pub/trans/${locale}/${locale}.js`, (req) => {
+    req.reply({
+      statusCode: 404,
+      body: {
+        message: 'Not Found',
+        error: 'Translation file not found'
+      }
+    });
+  }).as(`getI18n${locale.toUpperCase()}Error`);
+}
+
+// 封装获取多语言资源服务器错误的 mock (500错误)
+export function mockI18nServerError(locale: 'zh' | 'en') {
+  cy.intercept('GET', `/dr/api/v1/pub/trans/${locale}/${locale}.js`, (req) => {
+    req.reply({
+      statusCode: 500,
+      body: {
+        message: 'Internal Server Error',
+        error: 'Server error occurred'
+      }
+    });
+  }).as(`getI18n${locale.toUpperCase()}ServerError`);
+}
+
+// 封装获取多语言资源超时的 mock
+export function mockI18nTimeout(locale: 'zh' | 'en') {
+  cy.intercept('GET', `/dr/api/v1/pub/trans/${locale}/${locale}.js`, (req) => {
+    req.reply({
+      delay: 30000, // 30秒延迟,模拟超时
+      statusCode: 200,
+      body: {}
+    });
+  }).as(`getI18n${locale.toUpperCase()}Timeout`);
+}
+
+// 封装获取多语言资源格式错误的 mock
+export function mockI18nInvalidFormat(locale: 'zh' | 'en') {
+  cy.intercept('GET', `/dr/api/v1/pub/trans/${locale}/${locale}.js`, (req) => {
+    req.reply({
+      statusCode: 200,
+      body: "invalid json format" // 返回非JSON格式数据
+    });
+  }).as(`getI18n${locale.toUpperCase()}InvalidFormat`);
+}
+
+// 封装获取多语言资源空数据的 mock
+export function mockI18nEmptyData(locale: 'zh' | 'en') {
+  cy.intercept('GET', `/dr/api/v1/pub/trans/${locale}/${locale}.js`, (req) => {
+    req.reply({
+      statusCode: 200,
+      body: {} // 返回空对象
+    });
+  }).as(`getI18n${locale.toUpperCase()}EmptyData`);
+}
+
+// 封装获取多语言资源网络错误的 mock
+export function mockI18nNetworkError(locale: 'zh' | 'en') {
+  cy.intercept('GET', `/dr/api/v1/pub/trans/${locale}/${locale}.js`, (req) => {
+    req.reply({
+      forceNetworkError: true
+    });
+  }).as(`getI18n${locale.toUpperCase()}NetworkError`);
+}
+
+// 封装软件信息API的 mock,避免影响页面加载
+export function mockSoftwareInfo() {
+  cy.intercept('GET', '/dr/api/v1/pub/software_info', (req) => {
+    req.reply({
+      statusCode: 200,
+      body: {
+        code: "0x000000",
+        data: {
+          FPD: "Simulator",
+          GEN: "Simulator",
+          guest: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTY3MjAxMTUsImlkIjoyLCJuYW1lIjoiZ3Vlc3QifQ.cDkkxM2mkiCQf7T87WsCMewITk13c7jSDoniT7gDHXQ",
+          language: ["en", "zh"],
+          product: "DROS",
+          server: {
+            auth: {
+              build: "2025-08-25 17:45:18",
+              desc: "Authentication Server repo",
+              submodule: ["3a167dd4[rpc_idl]"],
+              version: "0.3.0-13-g8b85622"
+            },
+            dcmtk: {
+              build: "2025-08-25 13:43:16",
+              desc: "Dcmtk Server repo",
+              submodule: ["0fc2b1e4[rpc_idl]"],
+              version: "0.3.0-12-gff618d4"
+            },
+            imgProc: {
+              build: "2025-08-25 13:46:23",
+              desc: "Img Proc Server repo",
+              submodule: [
+                "5e507af7[auto_wwwl]",
+                "3a75bb1f[collimator_circle]",
+                "e7b69785[collimator_rect]",
+                "6b7fbbd1[enhance]",
+                "5905e001[rpc_idl]"
+              ],
+              version: "0.3.0-7-gbb2ee0b"
+            },
+            protocol: {
+              build: "2025-08-25 17:45:23",
+              desc: "Protocol Server repo",
+              submodule: ["3a167dd4[rpc_idl]"],
+              version: "0.3.0-7-g1954756"
+            },
+            resource: {
+              build: "2025-08-25 17:45:27",
+              desc: "Resource Server repo",
+              submodule: ["0fc2b1e4[rpc_idl]"],
+              version: "0.3.0-12-g60e37c1"
+            },
+            study: {
+              build: "2025-08-25 17:45:25",
+              desc: "Study Server repo",
+              submodule: ["3a167dd4[rpc_idl]"],
+              version: "0.3.0-11-g784ba1b"
+            },
+            task: {
+              build: "2025-08-25 17:45:29",
+              desc: "Task Server repo",
+              submodule: ["0fc2b1e4[rpc_idl]"],
+              version: "0.3.0-20-ge9ec04a"
+            }
+          },
+          sn: "2edbc382-044adc78-95bed11b-51c9328a"
+        },
+        description: "Success",
+        solution: ""
+      }
+    });
+  }).as('getSoftwareInfo');
+}
+
+// 封装logger API的 mock,避免影响页面加载
+export function mockLogger() {
+  cy.intercept('POST', '/log', (req) => {
+    req.reply({
+      statusCode: 204, // No Content - 更适合日志请求,不会被误认为页面导航
+      body: null,      // 明确返回 null,避免任何可能的页面影响
+      headers: {
+        'content-type': 'application/json',
+        'cache-control': 'no-cache'
+      }
+    });
+  }).as('postLog');
+}
+
+// 封装所有必要的API mock,避免影响页面加载
+export function mockAllRequiredAPIs() {
+  mockSoftwareInfo();
+  mockLogger();
+}

+ 21 - 3
cypress/support/pageObjects/LoginPage.ts

@@ -1,8 +1,26 @@
 // import 'cypress';
-import {fileIndex} from '../util';
+import { fileIndex } from '../util';
 class LoginPage {
-  visit() {
-    cy.visit(`${fileIndex}#/pages/index/index`);
+  visit(options?: { onBeforeLoad?: (win: Window) => void }) {
+    // if (options?.onBeforeLoad) {
+    //   cy.visit(`${fileIndex}#/pages/index/index`, {
+    //     onBeforeLoad: options.onBeforeLoad
+    //   });
+    // } else {
+    //   cy.visit(`${fileIndex}#/pages/index/index`);
+    // }
+    cy.visit(`${fileIndex}#/pages/index/index`, {
+      onBeforeLoad(win) {
+        win.addEventListener('beforeunload', () => console.trace('beforeunload'));
+        win.addEventListener('unload', () => console.trace('unload'));
+        // ===== 新增两行:钉时间戳 =====
+        // cy.stub(win.Date, 'now').returns(1712345678901);
+        // ========================================
+
+        // 保持原透传逻辑
+        options?.onBeforeLoad?.(win);
+      }
+    });
   }
 
   getUsernameInput() {

+ 13 - 2
src/app.tsx

@@ -66,8 +66,8 @@ function AppContent({ children }: { children: ReactNode }) {
       })
       .catch((error) => {
         console.error('加载多语言资源失败:', error);
-        // 设置默认状态,让应用继续运行
-        setIsI18nReady(true);
+        // 阻止加载后面的页面
+        setIsI18nReady(false);
       });
   }, [dispatch, browserLocale]);
 
@@ -85,6 +85,9 @@ function AppContent({ children }: { children: ReactNode }) {
         }}
       >
         <div>加载多语言资源中...</div>
+        <div style={{ display: 'none' }}>
+          {children}
+        </div>
       </div>
     );
   }
@@ -110,6 +113,9 @@ function AppContent({ children }: { children: ReactNode }) {
         >
           重新加载
         </button>
+        <div style={{ display: 'none' }}>
+          {children}
+        </div>
       </div>
     );
   }
@@ -177,6 +183,11 @@ function AppContent({ children }: { children: ReactNode }) {
 }
 
 function App({ children }: { children: ReactNode }) {
+  // 只在 Cypress 测试环境下暴露 store 到 window 对象
+  if (typeof window !== 'undefined' && (window as any).Cypress) {
+    (window as any).store = store;
+  }
+
   return (
     <Provider store={store}>
       <AppContent>{children}</AppContent>

+ 11 - 1
src/log/logger.js

@@ -3,6 +3,10 @@ const isElectron = () =>
   window.require &&
   window.require('electron')?.ipcRenderer;
 
+// 检测是否在 Cypress 测试环境中
+const isTestEnvironment = () =>
+  typeof window !== 'undefined' && window.Cypress;
+
 let ipcRenderer = null;
 if (isElectron()) {
   try { ipcRenderer = window.require('electron').ipcRenderer; } catch {}
@@ -25,6 +29,12 @@ function proxyLog(level) {
   return (...args) => {
     const msg = args.map(v => typeof v === 'object' ? safeStringify(v) : String(v)).join(' ');
     original(...args);                       // ① 控制台始终打印
+    
+    // 如果在测试环境中,直接返回,不发送任何网络请求
+    if (isTestEnvironment()) {
+      return;
+    }
+    
     if (ipcRenderer) {                       // ② Electron 环境
       ipcRenderer.invoke('write-log', level, msg).catch(() => {});
     } else if (typeof fetch !== 'undefined') {
@@ -42,4 +52,4 @@ export const logger = {
   log: proxyLog('log'),
   warn: proxyLog('warn'),
   error: proxyLog('error')
-};
+};