Procházet zdrojové kódy

feat: add comprehensive Cypress mock handlers for all DR API endpoints

- Create 11 mock handler modules covering system, i18n, user, auth, resource, protocol, study, image, report, device, and exam
- Implement ~62 mock responses for all major API endpoints
- Add unified export in cypress/support/mock/index.ts
- Include complete usage documentation in README.md
- Follow consistent JSDoc comment standards with examples
dengdx před 2 měsíci
rodič
revize
1b5de54d1b

+ 478 - 0
cypress/support/mock/README.md

@@ -0,0 +1,478 @@
+# Cypress Mock Handlers 使用文档
+
+本目录包含所有 Cypress 测试的 API mock handlers,用于模拟后端接口响应。
+
+## 目录结构
+
+```
+cypress/support/mock/
+├── handlers/           # Mock handler 文件
+│   ├── system.ts      # 系统信息相关
+│   ├── i18n.ts        # 国际化相关
+│   ├── user.ts        # 用户相关
+│   ├── auth.ts        # 认证相关
+│   ├── resource.ts    # 资源配置相关
+│   ├── protocol.ts    # 协议相关
+│   ├── study.ts       # 检查信息管理
+│   ├── image.ts       # 图像相关
+│   ├── report.ts      # 报告相关
+│   ├── device.ts      # 设备相关
+│   ├── exam.ts        # 检查执行相关
+│   └── quota.ts       # 配额相关
+├── index.ts           # 统一导出
+└── README.md          # 本文档
+```
+
+## 快速开始
+
+### 1. 导入 Mock Handlers
+
+在测试文件中导入需要的 mock handlers:
+
+```typescript
+import {
+  mockLoginSuccess,
+  mockGetSoftwareInfoSuccess,
+  mockI18nSuccess,
+} from '../support/mock';
+```
+
+### 2. 在测试中使用
+
+```typescript
+describe('登录功能测试', () => {
+  beforeEach(() => {
+    // 设置 mock
+    mockLoginSuccess();
+    mockGetSoftwareInfoSuccess();
+  });
+
+  it('应该成功登录', () => {
+    cy.visit('/login');
+    cy.get('[data-testid="username"]').type('admin');
+    cy.get('[data-testid="password"]').type('123456');
+    cy.get('[data-testid="login-btn"]').click();
+
+    // 等待并验证 mock 响应
+    cy.wait('@loginSuccess');
+    cy.url().should('include', '/dashboard');
+  });
+});
+```
+
+## 模块说明
+
+### System - 系统信息
+
+```typescript
+import { mockGetSoftwareInfoSuccess } from '../support/mock';
+
+// 使用
+mockGetSoftwareInfoSuccess();
+cy.wait('@getSoftwareInfoSuccess');
+```
+
+**可用函数:**
+
+- `mockGetSoftwareInfoSuccess()` - 获取软件信息成功
+- `mockGetSoftwareInfoFail()` - 获取软件信息失败
+
+### I18n - 国际化
+
+```typescript
+import { mockGetLanguageListSuccess, mockI18nSuccess } from '../support/mock';
+
+// 获取语言列表
+mockGetLanguageListSuccess();
+
+// 获取翻译文件
+mockI18nSuccess('zh'); // 或 'en'
+```
+
+**可用函数:**
+
+- `mockGetLanguageListSuccess()` - 获取语言列表成功
+- `mockGetLanguageListEmpty()` - 获取空语言列表
+- `mockI18nSuccess(locale)` - 获取翻译文件成功
+- `mockI18nError(locale)` - 获取翻译文件失败(404)
+- `mockI18nServerError(locale)` - 服务器错误(500)
+- `mockI18nTimeout(locale)` - 请求超时
+- `mockI18nInvalidFormat(locale)` - 返回无效格式
+- `mockI18nEmptyData(locale)` - 返回空数据
+- `mockI18nNetworkError(locale)` - 网络错误
+
+### User - 用户
+
+```typescript
+import { mockLoginSuccess, mockGetUsersSuccess } from '../support/mock';
+
+// 登录
+mockLoginSuccess();
+
+// 获取用户列表
+mockGetUsersSuccess();
+```
+
+**可用函数:**
+
+- `mockLoginSuccess()` - 登录成功
+- `mockLoginFail()` - 登录失败(用户名或密码错误)
+- `mockGetUsersSuccess()` - 获取用户列表成功
+- `mockGetUsersEmpty()` - 获取空用户列表
+
+### Auth - 认证
+
+```typescript
+import { mockChangePasswordSuccess } from '../support/mock';
+
+mockChangePasswordSuccess();
+cy.wait('@changePasswordSuccess');
+```
+
+**可用函数:**
+
+- `mockChangePasswordSuccess()` - 修改密码成功
+- `mockChangePasswordWrongOldPassword()` - 旧密码错误
+- `mockChangePasswordNotMatch()` - 新密码不匹配
+
+### Resource - 资源配置
+
+```typescript
+import { mockGetOptionsSexFull, mockGetImageSuccess } from '../support/mock';
+
+// 获取性别选项
+mockGetOptionsSexFull();
+
+// 获取示意图
+mockGetImageSuccess();
+```
+
+**可用函数:**
+
+- `mockGetOptionsSexFull()` - 获取性别选项
+- `mockGetOptionsPatientSize()` - 获取患者体型选项
+- `mockGetConfigSimulatorGEN()` - 获取发生器仿真配置
+- `mockGetConfigSimulatorFPD()` - 获取探测器仿真配置
+- `mockGetImageSuccess()` - 获取示意图成功
+- `mockGetImageNotFound()` - 示意图不存在(404)
+
+### Protocol - 协议
+
+```typescript
+import {
+  mockGetPatientTypeHuman,
+  mockGetBodyPartHuman,
+  mockGetProcedureListSuccess,
+} from '../support/mock';
+
+// 获取患者类型
+mockGetPatientTypeHuman();
+
+// 获取身体部位
+mockGetBodyPartHuman();
+
+// 获取协议列表
+mockGetProcedureListSuccess();
+```
+
+**可用函数:**
+
+- `mockGetPatientTypeHuman()` - 获取人医患者类型
+- `mockGetPatientTypeAll()` - 获取所有患者类型
+- `mockGetBodyPartHuman()` - 获取人医身体部位
+- `mockGetProcedureListSuccess()` - 获取协议列表
+- `mockGetViewListSuccess()` - 获取体位列表
+- `mockGetViewDetailSuccess()` - 获取体位详情
+- `mockGetAprByViewSuccess()` - 获取APR详情(通过view_id)
+- `mockGetAprDeviceSuccess()` - 获取APR设备信息
+- `mockGetAprTechParamsSuccess()` - 获取APR默认曝光参数
+
+### Study - 检查信息管理
+
+```typescript
+import {
+  mockRegisterStudySuccess,
+  mockGetStudyArrived,
+  mockUpdateStudySuccess,
+} from '../support/mock';
+
+// 登记检查
+mockRegisterStudySuccess();
+
+// 获取检查信息
+mockGetStudyArrived();
+
+// 更新检查信息
+mockUpdateStudySuccess();
+```
+
+**可用函数:**
+
+- `mockRegisterStudySuccess()` - 登记检查成功
+- `mockGetStudyArrived()` - 获取检查信息(Arrived状态)
+- `mockGetStudyStatSuccess()` - 获取检查统计信息
+- `mockUpdateStudySuccess()` - 更新检查信息成功
+- `mockDeleteStudyBatchSuccess()` - 批量删除检查成功
+- `mockStorePortraitSuccess()` - 存储急诊患者影像成功
+- `mockLeaveStudyInProgress()` - 挂起检查
+- `mockLeaveStudyCompleted()` - 完成检查
+
+### Image - 图像
+
+```typescript
+import {
+  mockAppendImagesSuccess,
+  mockDeleteImageSuccess,
+} from '../support/mock';
+
+// 添加体位
+mockAppendImagesSuccess();
+
+// 删除体位
+mockDeleteImageSuccess();
+```
+
+**可用函数:**
+
+- `mockAppendImagesSuccess()` - 批量添加体位成功
+- `mockCopyImageSuccess()` - 复制体位成功
+- `mockSortImagesSuccess()` - 体位重新排序成功
+- `mockDeleteImageSuccess()` - 删除体位成功
+- `mockStorePostProcSuccess()` - 存储后处理DCM成功
+- `mockGetDcmFileSuccess()` - 获取DCM文件成功
+- `mockGetThumbnailSuccess()` - 获取缩略图成功
+
+### Report - 报告
+
+```typescript
+import {
+  mockReportPreviewSuccess,
+  mockSaveReportSuccess,
+} from '../support/mock';
+
+// 预览报告
+mockReportPreviewSuccess();
+
+// 保存报告
+mockSaveReportSuccess();
+```
+
+**可用函数:**
+
+- `mockReportPreviewSuccess()` - 报告预览成功
+- `mockSaveReportSuccess()` - 保存报告成功
+- `mockGetReportSuccess()` - 获取报告成功
+- `mockGetReportNotFound()` - 报告不存在(404)
+
+### Device - 设备
+
+```typescript
+import {
+  mockOpenDeviceSuccess,
+  mockDeviceGetSuccess,
+  mockDeviceActionSuccess,
+} from '../support/mock';
+
+// 打开设备
+mockOpenDeviceSuccess();
+
+// 获取设备状态
+mockDeviceGetSuccess();
+
+// 执行设备动作
+mockDeviceActionSuccess();
+```
+
+**可用函数:**
+
+- `mockOpenDeviceSuccess()` - 打开设备成功
+- `mockDeviceGetSuccess()` - 执行Get操作成功
+- `mockDeviceActionSuccess()` - 执行Action操作成功
+
+### Exam - 检查执行
+
+```typescript
+import {
+  mockStartInspectionSuccess,
+  mockGetGlobalStatusAllReady,
+  mockTriggerInspectionSuccess,
+} from '../support/mock';
+
+// 开始检查
+mockStartInspectionSuccess();
+
+// 获取全局状态
+mockGetGlobalStatusAllReady();
+
+// 触发曝光
+mockTriggerInspectionSuccess();
+```
+
+**可用函数:**
+
+- `mockStartInspectionSuccess()` - 开始检查成功
+- `mockGetGlobalStatusAllReady()` - 获取全局状态(全部就绪)
+- `mockGetGlobalStatusNotReady()` - 获取全局状态(未就绪)
+- `mockTriggerInspectionSuccess()` - 软曝光成功
+- `mockJudgeImageAccept()` - 接受图像
+- `mockJudgeImageReject()` - 拒绝图像
+
+### Quota - 配额
+
+```typescript
+import { mockQuotaSufficient, mockQuotaInsufficient } from '../support/mock';
+
+// 配额充足
+mockQuotaSufficient();
+
+// 配额不足
+mockQuotaInsufficient();
+```
+
+## 完整示例
+
+### 示例1:登录流程测试
+
+```typescript
+import {
+  mockLoginSuccess,
+  mockGetSoftwareInfoSuccess,
+  mockI18nSuccess,
+  mockGetLanguageListSuccess,
+} from '../support/mock';
+
+describe('登录流程', () => {
+  beforeEach(() => {
+    // 设置所有必要的 mocks
+    mockGetSoftwareInfoSuccess();
+    mockGetLanguageListSuccess();
+    mockI18nSuccess('zh');
+    mockLoginSuccess();
+  });
+
+  it('应该成功登录并跳转到主页', () => {
+    cy.visit('/login');
+
+    // 填写登录表单
+    cy.get('[data-testid="username"]').type('admin');
+    cy.get('[data-testid="password"]').type('123456');
+    cy.get('[data-testid="login-btn"]').click();
+
+    // 验证请求
+    cy.wait('@loginSuccess').its('response.statusCode').should('eq', 200);
+
+    // 验证跳转
+    cy.url().should('include', '/dashboard');
+  });
+});
+```
+
+### 示例2:患者登记测试
+
+```typescript
+import {
+  mockGetPatientTypeHuman,
+  mockGetBodyPartHuman,
+  mockGetProcedureListSuccess,
+  mockRegisterStudySuccess,
+} from '../support/mock';
+
+describe('患者登记', () => {
+  beforeEach(() => {
+    mockGetPatientTypeHuman();
+    mockGetBodyPartHuman();
+    mockGetProcedureListSuccess();
+    mockRegisterStudySuccess();
+  });
+
+  it('应该成功登记患者', () => {
+    cy.visit('/patient/register');
+
+    // 填写患者信息
+    cy.get('[name="patient_name"]').type('测试患者');
+    cy.get('[name="patient_id"]').type('P001');
+
+    // 选择协议
+    cy.get('[data-testid="select-protocol"]').click();
+    cy.wait('@getProcedureListSuccess');
+
+    // 提交
+    cy.get('[data-testid="submit-btn"]').click();
+    cy.wait('@registerStudySuccess');
+
+    // 验证成功提示
+    cy.contains('登记成功').should('be.visible');
+  });
+});
+```
+
+### 示例3:曝光流程测试
+
+```typescript
+import {
+  mockStartInspectionSuccess,
+  mockGetGlobalStatusAllReady,
+  mockTriggerInspectionSuccess,
+  mockJudgeImageAccept,
+} from '../support/mock';
+
+describe('曝光流程', () => {
+  beforeEach(() => {
+    mockStartInspectionSuccess();
+    mockGetGlobalStatusAllReady();
+    mockTriggerInspectionSuccess();
+    mockJudgeImageAccept();
+  });
+
+  it('应该成功完成曝光', () => {
+    cy.visit('/exam/exposure');
+
+    // 等待设备就绪
+    cy.wait('@getGlobalStatusAllReady');
+    cy.get('[data-testid="ready-indicator"]').should('have.class', 'ready');
+
+    // 触发曝光
+    cy.get('[data-testid="trigger-btn"]').click();
+    cy.wait('@triggerInspectionSuccess');
+
+    // 接受图像
+    cy.get('[data-testid="accept-btn"]').click();
+    cy.wait('@judgeImageAccept');
+
+    // 验证成功
+    cy.contains('图像已保存').should('be.visible');
+  });
+});
+```
+
+## 注意事项
+
+1. **Mock 顺序**:在 `beforeEach` 中设置 mock,确保每个测试都有干净的状态
+
+2. **等待响应**:使用 `cy.wait('@aliasName')` 等待 mock 响应,避免时序问题
+
+3. **验证响应**:可以验证 mock 响应的状态码和数据:
+
+   ```typescript
+   cy.wait('@loginSuccess').its('response.statusCode').should('eq', 200);
+   ```
+
+4. **组合使用**:根据测试场景组合使用多个 mock handlers
+
+5. **清理**:Cypress 会在每个测试后自动清理 intercept,无需手动清理
+
+## 参考文档
+
+- API 文档:`docs/DR.md`
+- Cypress 官方文档:https://docs.cypress.io/
+- Intercept 文档:https://docs.cypress.io/api/commands/intercept
+
+## 贡献
+
+添加新的 mock handler 时,请遵循以下规范:
+
+1. 使用统一的 JSDoc 注释格式
+2. 包含完整的参数和返回值说明
+3. 提供使用示例
+4. 在 `index.ts` 中导出
+5. 更新本文档

+ 101 - 0
cypress/support/mock/handlers/auth.ts

@@ -0,0 +1,101 @@
+/**
+ * Auth Mock Handlers
+ * 认证相关的 mock 处理器
+ */
+
+/**
+ * 修改密码 - 成功场景
+ * 
+ * @description 修改当前用户自己的密码
+ * @method POST
+ * @url /api/v1/auth/settings/password
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.old_password - 旧密码
+ * @param {string} requestBody.new_password - 新密码
+ * @param {string} requestBody.confirm_new_password - 确认密码
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockChangePasswordSuccess();
+ * cy.get('[name="old_password"]').type('123456');
+ * cy.get('[name="new_password"]').type('newPassword123');
+ * cy.get('[name="confirm_new_password"]').type('newPassword123');
+ * cy.get('[type="submit"]').click();
+ * cy.wait('@changePasswordSuccess');
+ * 
+ * @see docs/DR.md - 章节8
+ */
+export function mockChangePasswordSuccess() {
+  cy.intercept('POST', '/api/v1/auth/settings/password', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {}
+    }
+  }).as('changePasswordSuccess');
+}
+
+/**
+ * 修改密码 - 旧密码错误
+ * 
+ * @description 修改密码失败,旧密码不正确
+ * @method POST
+ * @url /api/v1/auth/settings/password
+ * @access 需要认证
+ * 
+ * @returns {Object} 错误响应
+ * @returns {string} code - 错误码
+ * @returns {string} description - 错误描述
+ * 
+ * @example
+ * mockChangePasswordWrongOldPassword();
+ * cy.wait('@changePasswordWrongOldPassword');
+ * 
+ * @see docs/DR.md - 章节8
+ */
+export function mockChangePasswordWrongOldPassword() {
+  cy.intercept('POST', '/api/v1/auth/settings/password', {
+    statusCode: 200,
+    body: {
+      code: "0x000002",
+      description: "Old password is incorrect",
+      solution: "Please check your old password",
+      data: {}
+    }
+  }).as('changePasswordWrongOldPassword');
+}
+
+/**
+ * 修改密码 - 新密码不匹配
+ * 
+ * @description 修改密码失败,新密码和确认密码不匹配
+ * @method POST
+ * @url /api/v1/auth/settings/password
+ * @access 需要认证
+ * 
+ * @returns {Object} 错误响应
+ * @returns {string} code - 错误码
+ * @returns {string} description - 错误描述
+ * 
+ * @example
+ * mockChangePasswordNotMatch();
+ * cy.wait('@changePasswordNotMatch');
+ * 
+ * @see docs/DR.md - 章节8
+ */
+export function mockChangePasswordNotMatch() {
+  cy.intercept('POST', '/api/v1/auth/settings/password', {
+    statusCode: 200,
+    body: {
+      code: "0x000003",
+      description: "New password and confirmation do not match",
+      solution: "Please ensure both passwords are the same",
+      data: {}
+    }
+  }).as('changePasswordNotMatch');
+}

+ 111 - 0
cypress/support/mock/handlers/device.ts

@@ -0,0 +1,111 @@
+/**
+ * Device Mock Handlers
+ * 设备相关的 mock 处理器
+ */
+
+/**
+ * 打开设备 - 成功场景
+ * 
+ * @description 打开指定设备
+ * @method POST
+ * @url /api/v1/auth/device/open
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.deviceUri - 设备URI
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockOpenDeviceSuccess();
+ * cy.wait('@openDeviceSuccess');
+ * 
+ * @see docs/DR.md - 章节28
+ */
+export function mockOpenDeviceSuccess() {
+  cy.intercept('POST', '/api/v1/auth/device/open', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/google.protobuf.Empty",
+        value: {}
+      }
+    }
+  }).as('openDeviceSuccess');
+}
+
+/**
+ * 执行设备Get操作 - 成功场景
+ * 
+ * @description 执行设备的Get操作,获取设备状态或参数
+ * @method POST
+ * @url /api/v1/auth/device/get
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.deviceUri - 设备URI
+ * @param {string} requestBody.reqName - 请求名称
+ * 
+ * @returns {Object} data - 设备状态数据(JSON字符串)
+ * 
+ * @example
+ * mockDeviceGetSuccess();
+ * cy.wait('@deviceGetSuccess');
+ * 
+ * @see docs/DR.md - 章节29
+ */
+export function mockDeviceGetSuccess() {
+  cy.intercept('POST', '/api/v1/auth/device/get', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: JSON.stringify({
+        IsDemo: { Value: "1" },
+        GENERATORSTATUS: { Value: "4" },
+        KV: { Value: "70.0" },
+        MA: { Value: "125.0" }
+      })
+    }
+  }).as('deviceGetSuccess');
+}
+
+/**
+ * 执行设备Action操作 - 成功场景
+ * 
+ * @description 执行设备的Action操作,控制设备行为
+ * @method POST
+ * @url /api/v1/auth/device/action
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.deviceUri - 设备URI
+ * @param {string} requestBody.reqName - 请求名称
+ * @param {string} requestBody.reqParam - 请求参数
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockDeviceActionSuccess();
+ * cy.wait('@deviceActionSuccess');
+ * 
+ * @see docs/DR.md - 章节30
+ */
+export function mockDeviceActionSuccess() {
+  cy.intercept('POST', '/api/v1/auth/device/action', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/google.protobuf.Empty",
+        value: {}
+      }
+    }
+  }).as('deviceActionSuccess');
+}

+ 218 - 0
cypress/support/mock/handlers/exam.ts

@@ -0,0 +1,218 @@
+/**
+ * Exam Mock Handlers
+ * 检查执行相关的 mock 处理器
+ */
+
+/**
+ * 开始检查 - 成功场景
+ * 
+ * @description 进入曝光页面时调用,告知后端当前开始的检查
+ * @method POST
+ * @url /api/v1/auth/task/inspection/start
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.instance_uid - 图像实例UID
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockStartInspectionSuccess();
+ * cy.wait('@startInspectionSuccess');
+ * 
+ * @see docs/DR.md - 章节31
+ */
+export function mockStartInspectionSuccess() {
+  cy.intercept('POST', '/api/v1/auth/task/inspection/start', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/google.protobuf.Empty",
+        value: {}
+      }
+    }
+  }).as('startInspectionSuccess');
+}
+
+/**
+ * 获取全局状态 - 所有设备就绪
+ * 
+ * @description 获取当前发生器、探测器和后端服务的状态(全部就绪)
+ * @method GET
+ * @url /api/v1/auth/task/inspection/status
+ * @access 需要认证
+ * 
+ * @returns {Object} data - 全局状态
+ * @returns {string} data.gen_status - 发生器状态
+ * @returns {boolean} data.gen_ready - 发生器是否就绪
+ * @returns {string} data.fpd_status - 探测器状态
+ * @returns {boolean} data.fpd_ready - 探测器是否就绪
+ * @returns {boolean} data.all_ready - 全部设备是否就绪
+ * 
+ * @example
+ * mockGetGlobalStatusAllReady();
+ * cy.wait('@getGlobalStatusAllReady');
+ * 
+ * @see docs/DR.md - 章节32
+ */
+export function mockGetGlobalStatusAllReady() {
+  cy.intercept('GET', '/api/v1/auth/task/inspection/status', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.task.TaskGlobalStatus",
+        gen_status: "GENERATOR_STATUS_STANDBY",
+        gen_ready: true,
+        fpd_status: "DETECTOR_STATUS_STANDBY",
+        fpd_ready: true,
+        insp_status: "TASK_Ready",
+        insp_ready: true,
+        all_ready: true
+      }
+    }
+  }).as('getGlobalStatusAllReady');
+}
+
+/**
+ * 获取全局状态 - 设备未就绪
+ * 
+ * @description 获取全局状态(设备未就绪)
+ * @method GET
+ * @url /api/v1/auth/task/inspection/status
+ * @access 需要认证
+ * 
+ * @returns {Object} data - 全局状态
+ * @returns {boolean} data.all_ready - 全部设备未就绪
+ * 
+ * @example
+ * mockGetGlobalStatusNotReady();
+ * cy.wait('@getGlobalStatusNotReady');
+ * 
+ * @see docs/DR.md - 章节32
+ */
+export function mockGetGlobalStatusNotReady() {
+  cy.intercept('GET', '/api/v1/auth/task/inspection/status', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.task.TaskGlobalStatus",
+        gen_status: "GENERATOR_STATUS_BUSY",
+        gen_ready: false,
+        fpd_status: "DETECTOR_STATUS_BUSY",
+        fpd_ready: false,
+        insp_status: "TASK_Busy",
+        insp_ready: false,
+        all_ready: false
+      }
+    }
+  }).as('getGlobalStatusNotReady');
+}
+
+/**
+ * 软曝光 - 成功场景
+ * 
+ * @description 触发曝光操作
+ * @method POST
+ * @url /api/v1/auth/task/inspection/trigger
+ * @access 需要认证
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockTriggerInspectionSuccess();
+ * cy.wait('@triggerInspectionSuccess');
+ * 
+ * @see docs/DR.md - 章节33
+ */
+export function mockTriggerInspectionSuccess() {
+  cy.intercept('POST', '/api/v1/auth/task/inspection/trigger', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {}
+    }
+  }).as('triggerInspectionSuccess');
+}
+
+/**
+ * 接受图像 - 成功场景
+ * 
+ * @description 接受当前图像
+ * @method POST
+ * @url /api/v1/auth/task/inspection/judge
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.instance_uid - 图像实例UID
+ * @param {boolean} requestBody.accept - true表示接受
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockJudgeImageAccept();
+ * cy.wait('@judgeImageAccept');
+ * 
+ * @see docs/DR.md - 章节34
+ */
+export function mockJudgeImageAccept() {
+  cy.intercept('POST', '/api/v1/auth/task/inspection/judge', (req) => {
+    if (req.body.accept === true) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: "0x000000",
+          description: "Success",
+          solution: "",
+          data: {}
+        }
+      });
+    }
+  }).as('judgeImageAccept');
+}
+
+/**
+ * 拒绝图像 - 成功场景
+ * 
+ * @description 拒绝当前图像
+ * @method POST
+ * @url /api/v1/auth/task/inspection/judge
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.instance_uid - 图像实例UID
+ * @param {boolean} requestBody.accept - false表示拒绝
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockJudgeImageReject();
+ * cy.wait('@judgeImageReject');
+ * 
+ * @see docs/DR.md - 章节34
+ */
+export function mockJudgeImageReject() {
+  cy.intercept('POST', '/api/v1/auth/task/inspection/judge', (req) => {
+    if (req.body.accept === false) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: "0x000000",
+          description: "Success",
+          solution: "",
+          data: {}
+        }
+      });
+    }
+  }).as('judgeImageReject');
+}

+ 89 - 2
cypress/support/mock/handlers/i18n.ts

@@ -1,6 +1,93 @@
-// I18n-related mock handlers
+/**
+ * I18n Mock Handlers
+ * 国际化相关的 mock 处理器
+ */
 
-// 封装获取多语言资源成功的 mock
+/**
+ * 获取多语言列表 - 成功场景
+ * 
+ * @description 获取系统支持的语言列表
+ * @method GET
+ * @url /dr/api/v1/pub/language
+ * @access 公开接口
+ * 
+ * @returns {Object[]} data - 语言列表
+ * @returns {string} data[].language - 语言代码(en/zh)
+ * @returns {string} data[].display - 语言显示名称
+ * 
+ * @example
+ * mockGetLanguageListSuccess();
+ * cy.wait('@getLanguageListSuccess').its('response.body.data').should('have.length', 2);
+ * 
+ * @see docs/DR.md - 章节4
+ */
+export function mockGetLanguageListSuccess() {
+  cy.intercept('GET', '/dr/api/v1/pub/language', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      data: [
+        {
+          language: "en",
+          display: "English"
+        },
+        {
+          language: "zh",
+          display: "简体中文"
+        }
+      ],
+      description: "Success",
+      solution: ""
+    }
+  }).as('getLanguageListSuccess');
+}
+
+/**
+ * 获取多语言列表 - 空列表场景
+ * 
+ * @description 获取多语言列表,返回空列表
+ * @method GET
+ * @url /dr/api/v1/pub/language
+ * @access 公开接口
+ * 
+ * @returns {Object[]} data - 空语言列表
+ * 
+ * @example
+ * mockGetLanguageListEmpty();
+ * cy.wait('@getLanguageListEmpty');
+ * 
+ * @see docs/DR.md - 章节4
+ */
+export function mockGetLanguageListEmpty() {
+  cy.intercept('GET', '/dr/api/v1/pub/language', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      data: [],
+      description: "Success",
+      solution: ""
+    }
+  }).as('getLanguageListEmpty');
+}
+
+/**
+ * 获取翻译文件 - 成功场景
+ * 
+ * @description 获取指定语言的翻译文件
+ * @method GET
+ * @url /dr/api/v1/pub/trans/{locale}/{locale}.js
+ * @access 公开接口
+ * 
+ * @param {string} locale - 语言代码(zh/en)
+ * 
+ * @returns {Object} 翻译键值对
+ * 
+ * @example
+ * mockI18nSuccess('zh');
+ * cy.wait('@getI18nZHSuccess');
+ * 
+ * @see docs/DR.md - 章节3
+ */
 export function mockI18nSuccess(locale: 'zh' | 'en') {
   const mockData = locale === 'zh' ? {
     greeting: '你好,世界!',

+ 235 - 0
cypress/support/mock/handlers/image.ts

@@ -0,0 +1,235 @@
+/**
+ * Image Mock Handlers
+ * 图像相关的 mock 处理器
+ */
+
+/**
+ * 批量添加体位和协议 - 成功场景
+ * 
+ * @description 批量添加体位和协议到检查中
+ * @method POST
+ * @url /dr/api/v1/auth/image
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.study_id - 检查ID
+ * @param {Object[]} requestBody.views - 体位列表
+ * 
+ * @returns {Object} data - 创建的序列和图像信息
+ * @returns {Object[]} data.series - 序列列表
+ * 
+ * @example
+ * mockAppendImagesSuccess();
+ * cy.wait('@appendImagesSuccess');
+ * 
+ * @see docs/DR.md - 章节20
+ */
+export function mockAppendImagesSuccess() {
+  cy.intercept('POST', '/dr/api/v1/auth/image', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.study.CreateImageReply",
+        series: []
+      }
+    }
+  }).as('appendImagesSuccess');
+}
+
+/**
+ * 复制体位 - 成功场景
+ * 
+ * @description 复制选中的体位
+ * @method POST
+ * @url /dr/api/v1/auth/image/copy
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.instance_uid - 图像实例UID
+ * 
+ * @returns {Object} data - 复制后的序列信息
+ * 
+ * @example
+ * mockCopyImageSuccess();
+ * cy.wait('@copyImageSuccess');
+ * 
+ * @see docs/DR.md - 章节21
+ */
+export function mockCopyImageSuccess() {
+  cy.intercept('POST', '/dr/api/v1/auth/image/copy', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.study.CreateImageReply",
+        series: []
+      }
+    }
+  }).as('copyImageSuccess');
+}
+
+/**
+ * 体位重新排序 - 成功场景
+ * 
+ * @description 重新排序体位顺序
+ * @method POST
+ * @url /dr/api/v1/auth/image/sort
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.study_id - 检查ID
+ * @param {string[]} requestBody.sop_instance_uids - 排序后的实例UID列表
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockSortImagesSuccess();
+ * cy.wait('@sortImagesSuccess');
+ * 
+ * @see docs/DR.md - 章节22
+ */
+export function mockSortImagesSuccess() {
+  cy.intercept('POST', '/dr/api/v1/auth/image/sort', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/google.protobuf.Empty",
+        value: {}
+      }
+    }
+  }).as('sortImagesSuccess');
+}
+
+/**
+ * 删除体位 - 成功场景
+ * 
+ * @description 删除study中的体位
+ * @method DELETE
+ * @url /dr/api/v1/auth/image/{id}
+ * @access 需要认证
+ * 
+ * @param {string} id - 图像实例UID(路径参数)
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockDeleteImageSuccess();
+ * cy.wait('@deleteImageSuccess');
+ * 
+ * @see docs/DR.md - 章节23
+ */
+export function mockDeleteImageSuccess() {
+  cy.intercept('DELETE', '/dr/api/v1/auth/image/*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/google.protobuf.Empty",
+        value: {}
+      }
+    }
+  }).as('deleteImageSuccess');
+}
+
+/**
+ * 存储后处理DCM - 成功场景
+ * 
+ * @description 存储后处理的DICOM图像
+ * @method POST
+ * @url /api/v1/auth/image/post_proc
+ * @access 需要认证
+ * 
+ * @param {Object} formData - 表单数据
+ * @param {string} formData.instance_uid - 实例UID
+ * @param {File} formData.data - 图像文件
+ * @param {number} formData.window_center - 窗位
+ * @param {number} formData.window_width - 窗宽
+ * 
+ * @returns {Object} data - DCM文件路径
+ * 
+ * @example
+ * mockStorePostProcSuccess();
+ * cy.wait('@storePostProcSuccess');
+ * 
+ * @see docs/DR.md - 章节24
+ */
+export function mockStorePostProcSuccess() {
+  cy.intercept('POST', '/api/v1/auth/image/post_proc', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.task.DcmPath",
+        path: "1.2.276.0.1000000.5.1.5.701601461.33458.1750830395.482043.dcm"
+      }
+    }
+  }).as('storePostProcSuccess');
+}
+
+/**
+ * 获取DCM文件 - 成功场景
+ * 
+ * @description 获取DICOM文件
+ * @method GET
+ * @url /api/v1/auth/image/dcm/{dcmfilename}
+ * @access 需要认证
+ * 
+ * @param {string} dcmfilename - DCM文件名(路径参数)
+ * 
+ * @returns {File} DICOM文件
+ * 
+ * @example
+ * mockGetDcmFileSuccess();
+ * cy.wait('@getDcmFileSuccess');
+ * 
+ * @see docs/DR.md - 章节36
+ */
+export function mockGetDcmFileSuccess() {
+  cy.intercept('GET', '/api/v1/auth/image/dcm/*', {
+    statusCode: 200,
+    headers: {
+      'content-type': 'application/dicom'
+    },
+    body: '' // 空DCM数据
+  }).as('getDcmFileSuccess');
+}
+
+/**
+ * 获取缩略图文件 - 成功场景
+ * 
+ * @description 获取图像缩略图
+ * @method GET
+ * @url /api/v1/auth/image/thumbnail/{filename}
+ * @access 需要认证
+ * 
+ * @param {string} filename - 缩略图文件名(路径参数)
+ * 
+ * @returns {File} 缩略图文件
+ * 
+ * @example
+ * mockGetThumbnailSuccess();
+ * cy.wait('@getThumbnailSuccess');
+ * 
+ * @see docs/DR.md - 章节37
+ */
+export function mockGetThumbnailSuccess() {
+  cy.intercept('GET', '/api/v1/auth/image/thumbnail/*', {
+    statusCode: 200,
+    headers: {
+      'content-type': 'image/webp'
+    },
+    body: '' // 空图像数据
+  }).as('getThumbnailSuccess');
+}

+ 503 - 0
cypress/support/mock/handlers/protocol.ts

@@ -0,0 +1,503 @@
+/**
+ * Protocol Mock Handlers
+ * 协议相关的 mock 处理器
+ */
+
+/**
+ * 获取患者类型列表 - Human类型
+ * 
+ * @description 获取启用的人医患者类型列表
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/patient_type
+ * @access 需要认证
+ * 
+ * @param {boolean} [is_enabled] - 是否只返回启用的类型
+ * 
+ * @returns {Object[]} data.patient_type_list - 患者类型列表
+ * @returns {string} data.patient_type_list[].patient_type_id - 患者类型ID
+ * @returns {string} data.patient_type_list[].patient_type_name - 患者类型名称
+ * @returns {boolean} data.patient_type_list[].is_enabled - 是否启用
+ * @returns {string} data.patient_type_list[].product - 产品类型(DROS/VETDROS)
+ * 
+ * @example
+ * mockGetPatientTypeHuman();
+ * cy.wait('@getPatientTypeHuman');
+ * 
+ * @see docs/DR.md - 章节12.1
+ */
+export function mockGetPatientTypeHuman() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/patient_type*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        patient_type_list: [
+          {
+            id: "1",
+            patient_type_id: "Human",
+            patient_type_name: "Human",
+            patient_type_local: "Human",
+            patient_type_description: "Human",
+            sort: 1,
+            is_enabled: true,
+            product: "DROS",
+            is_pre_install: true
+          }
+        ]
+      }
+    }
+  }).as('getPatientTypeHuman');
+}
+
+/**
+ * 获取患者类型列表 - 所有类型
+ * 
+ * @description 获取所有患者类型列表(包括禁用的)
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/patient_type
+ * @access 需要认证
+ * 
+ * @returns {Object[]} data.patient_type_list - 患者类型列表
+ * 
+ * @example
+ * mockGetPatientTypeAll();
+ * cy.wait('@getPatientTypeAll');
+ * 
+ * @see docs/DR.md - 章节12.1
+ */
+export function mockGetPatientTypeAll() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/patient_type*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        patient_type_list: [
+          {
+            id: "1",
+            patient_type_id: "Human",
+            patient_type_name: "Human",
+            patient_type_local: "Human",
+            patient_type_description: "Human",
+            sort: 1,
+            is_enabled: true,
+            product: "DROS",
+            is_pre_install: true
+          },
+          {
+            id: "2",
+            patient_type_id: "SpecialType",
+            patient_type_name: "SpecialType",
+            patient_type_local: "SpecialType",
+            patient_type_description: "SpecialType",
+            sort: 2,
+            is_enabled: false,
+            product: "DROS",
+            is_pre_install: true
+          }
+        ]
+      }
+    }
+  }).as('getPatientTypeAll');
+}
+
+/**
+ * 获取身体部位列表 - Human
+ * 
+ * @description 根据患者类型获取支持的身体部位(人医)
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/body_part
+ * @access 需要认证
+ * 
+ * @param {string} patient_type - 患者类型
+ * @param {string} modality - 模式(DX)
+ * @param {boolean} [is_enabled] - 是否只返回启用的
+ * 
+ * @returns {Object[]} data.body_part_list - 身体部位列表
+ * @returns {string} data.body_part_list[].body_part_id - 身体部位ID
+ * @returns {string} data.body_part_list[].body_part_name - 身体部位名称
+ * 
+ * @example
+ * mockGetBodyPartHuman();
+ * cy.wait('@getBodyPartHuman');
+ * 
+ * @see docs/DR.md - 章节12.2
+ */
+export function mockGetBodyPartHuman() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/body_part*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        body_part_list: [
+          {
+            id: "1",
+            body_part_id: "Human_SKULL",
+            body_part_name: "颅骨",
+            body_part_local: "颅骨",
+            body_part_description: "Skull",
+            patient_type: "Human",
+            category: "DX",
+            sort: 1,
+            is_enabled: true,
+            product: "DROS",
+            is_pre_install: true
+          },
+          {
+            id: "2",
+            body_part_id: "Human_NECK",
+            body_part_name: "颈部",
+            body_part_local: "颈部",
+            body_part_description: "Neck",
+            patient_type: "Human",
+            category: "DX",
+            sort: 2,
+            is_enabled: true,
+            product: "DROS",
+            is_pre_install: true
+          }
+        ]
+      }
+    }
+  }).as('getBodyPartHuman');
+}
+
+/**
+ * 获取协议列表 - 成功场景
+ * 
+ * @description 根据身体部位获取协议列表
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/procedure
+ * @access 需要认证
+ * 
+ * @param {string} patient_type - 患者类型
+ * @param {string} body_part - 身体部位
+ * @param {string} [procedure_type] - 协议类型(NORMAL/EMERGENCY)
+ * @param {boolean} [is_enabled] - 是否只返回启用的
+ * @param {number} [page] - 页码
+ * @param {number} [page_size] - 每页数量
+ * 
+ * @returns {Object} data - 协议列表数据
+ * @returns {number} data.count - 总数
+ * @returns {Object[]} data.procedures - 协议列表
+ * 
+ * @example
+ * mockGetProcedureListSuccess();
+ * cy.wait('@getProcedureListSuccess');
+ * 
+ * @see docs/DR.md - 章节12.3
+ */
+export function mockGetProcedureListSuccess() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/procedure*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.protocol.ProcedureList",
+        count: 4,
+        procedures: [
+          {
+            id: "2",
+            procedure_id: "P0-0002",
+            procedure_code: "P0-0002",
+            procedure_name: "颅骨前后位 + 侧位",
+            procedure_name_local: "颅骨前后位 + 侧位",
+            procedure_other_name: "Skull AP + LAT",
+            procedure_description: "颅骨前后位 + 侧位",
+            procedure_description_local: "颅骨前后位 + 侧位",
+            patient_type: "Human",
+            body_part_id: "Human_SKULL",
+            procedure_type: "NORMAL",
+            fast_search: false,
+            protocol_laterality: "U",
+            procedure_category: "Adult",
+            modality: "DX",
+            sort: 1,
+            is_enabled: true,
+            product: "DROS",
+            is_pre_install: true
+          },
+          {
+            id: "3",
+            procedure_id: "P0-0003",
+            procedure_code: "P0-0003",
+            procedure_name: "颅骨后前位 + 侧位",
+            procedure_name_local: "颅骨后前位 + 侧位",
+            procedure_other_name: "Skull PA + LAT",
+            procedure_description: "颅骨后前位 + 侧位",
+            procedure_description_local: "颅骨后前位 + 侧位",
+            patient_type: "Human",
+            body_part_id: "Human_SKULL",
+            procedure_type: "NORMAL",
+            fast_search: false,
+            protocol_laterality: "U",
+            procedure_category: "Adult",
+            modality: "DX",
+            sort: 1,
+            is_enabled: true,
+            product: "DROS",
+            is_pre_install: true
+          }
+        ]
+      }
+    }
+  }).as('getProcedureListSuccess');
+}
+
+/**
+ * 获取体位列表 - 成功场景
+ * 
+ * @description 获取体位列表
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/view
+ * @access 需要认证
+ * 
+ * @param {string} patient_type - 患者类型
+ * @param {string} body_part - 身体部位
+ * @param {boolean} [is_enabled] - 是否只返回启用的
+ * @param {number} [page] - 页码
+ * @param {number} [page_size] - 每页数量
+ * 
+ * @returns {Object} data - 体位列表数据
+ * @returns {number} data.count - 总数
+ * @returns {Object[]} data.views - 体位列表
+ * 
+ * @example
+ * mockGetViewListSuccess();
+ * cy.wait('@getViewListSuccess');
+ * 
+ * @see docs/DR.md - 章节12.4
+ */
+export function mockGetViewListSuccess() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/view*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.protocol.ViewList",
+        count: 2,
+        views: [
+          {
+            internal_id: "View_DX_T_A_SK_AP_00",
+            view_id: "View_DX_T_A_SK_AP_00",
+            view_name: "颅骨前后位",
+            view_name_local: "",
+            view_other_name: "Skull AP",
+            view_description: "颅骨前后位",
+            view_position: "AP",
+            application: "RAD",
+            anatomic_region: "SKULL",
+            patient_type: "Human",
+            body_part_id: "Human_SKULL",
+            view_icon_name: "/Image/Position/Human/skull.ap.table.x.png",
+            modality: "DX",
+            work_station_id: 0,
+            apr_id: "View_DX_T_A_SK_AP_00",
+            img_proc_id: "View_DX_T_A_SK_AP_00",
+            sort: 1,
+            is_enabled: true,
+            product: "DROS",
+            is_pre_install: true
+          }
+        ]
+      }
+    }
+  }).as('getViewListSuccess');
+}
+
+/**
+ * 获取体位详情 - 成功场景
+ * 
+ * @description 根据ID获取体位详情
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/view/{id}
+ * @access 需要认证
+ * 
+ * @param {string} id - 体位ID(路径参数)
+ * 
+ * @returns {Object} data - 体位详情
+ * 
+ * @example
+ * mockGetViewDetailSuccess();
+ * cy.wait('@getViewDetailSuccess');
+ * 
+ * @see docs/DR.md - 章节12.6
+ */
+export function mockGetViewDetailSuccess() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/view/*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.protocol.View",
+        internal_id: "View_DX_T_A_SK_AP_00",
+        view_id: "View_DX_T_A_SK_AP_00",
+        view_name: "颅骨前后位",
+        view_description: "颅骨前后位",
+        patient_type: "Human",
+        body_part_id: "Human_SKULL",
+        modality: "DX",
+        sort: 1,
+        is_enabled: true,
+        product: "DROS",
+        is_pre_install: true
+      }
+    }
+  }).as('getViewDetailSuccess');
+}
+
+/**
+ * 获取APR详情(通过view_id)- 成功场景
+ * 
+ * @description 根据体位ID获取APR详情
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/view/{id}/apr
+ * @access 需要认证
+ * 
+ * @param {string} id - 体位ID(路径参数)
+ * 
+ * @returns {Object} data - APR详情
+ * @returns {Object[]} data.exposures - 曝光参数列表
+ * 
+ * @example
+ * mockGetAprByViewSuccess();
+ * cy.wait('@getAprByViewSuccess');
+ * 
+ * @see docs/DR.md - 章节12.7
+ */
+export function mockGetAprByViewSuccess() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/view/*/apr*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.protocol.AprReply",
+        apr_id: "View_DX_T_A_SK_AP_00",
+        apr_name: "View_DX_T_A_SK_AP_00",
+        apr_description: "颅骨前后位",
+        patient_type: "Human",
+        body_part_id: "Human_SKULL",
+        modality: "DX",
+        exposures: [
+          {
+            work_station_id: 0,
+            patient_size: "Medium",
+            config_object: {
+              Common: {
+                kV: 70,
+                mA: 125,
+                ms: 100,
+                mAs: 12.5
+              }
+            }
+          }
+        ],
+        sort: 0,
+        is_enabled: true,
+        product: "DROS",
+        is_pre_install: true
+      }
+    }
+  }).as('getAprByViewSuccess');
+}
+
+/**
+ * 获取APR设备信息 - 成功场景
+ * 
+ * @description 根据APR ID获取设备参数信息
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/apr/{id}/device
+ * @access 需要认证
+ * 
+ * @param {string} id - APR ID(路径参数)
+ * @param {number} [work_station_id] - 工作位ID(0或1)
+ * 
+ * @returns {Object} data - 设备参数
+ * 
+ * @example
+ * mockGetAprDeviceSuccess();
+ * cy.wait('@getAprDeviceSuccess');
+ * 
+ * @see docs/DR.md - 章节12.9
+ */
+export function mockGetAprDeviceSuccess() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/apr/*/device*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.protocol.AprDevice",
+        work_station_id: 1,
+        config_object: {
+          Common: {
+            APRNumber: "5",
+            SID: 115,
+            GridType: 2
+          }
+        }
+      }
+    }
+  }).as('getAprDeviceSuccess');
+}
+
+/**
+ * 获取APR默认曝光参数 - 成功场景
+ * 
+ * @description 根据APR ID获取默认曝光技术参数
+ * @method GET
+ * @url /dr/api/v1/auth/protocol/apr/{id}/tech
+ * @access 需要认证
+ * 
+ * @param {string} id - APR ID(路径参数)
+ * @param {number} [work_station_id] - 工作位ID
+ * @param {string} [patient_size] - 患者体型(Large/Medium/Small)
+ * 
+ * @returns {Object} data - 技术参数
+ * @returns {Object} data.dp - 设备参数
+ * @returns {Object} data.ep - 曝光参数
+ * 
+ * @example
+ * mockGetAprTechParamsSuccess();
+ * cy.wait('@getAprTechParamsSuccess');
+ * 
+ * @see docs/DR.md - 章节12.10
+ */
+export function mockGetAprTechParamsSuccess() {
+  cy.intercept('GET', '/dr/api/v1/auth/protocol/apr/*/tech*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.protocol.TechParam",
+        dp: {
+          SID: 115,
+          GridType: 2,
+          GridSpeed: 0
+        },
+        ep: {
+          kV: 70,
+          mA: 125,
+          ms: 100,
+          mAs: 12.5
+        }
+      }
+    }
+  }).as('getAprTechParamsSuccess');
+}

+ 130 - 0
cypress/support/mock/handlers/report.ts

@@ -0,0 +1,130 @@
+/**
+ * Report Mock Handlers
+ * 报告相关的 mock 处理器
+ */
+
+/**
+ * 报告预览 - 成功场景
+ * 
+ * @description 预览报告,返回PDF文件
+ * @method POST
+ * @url /dr/api/v1/auth/report/preview
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {Object} requestBody.headers - 报告头信息
+ * @param {string} requestBody.findings - 影像所见
+ * @param {string} requestBody.impression - 诊断意见
+ * @param {string} requestBody.radiologist - 报告医师
+ * @param {string} requestBody.review_physician - 审核医师
+ * 
+ * @returns {File} PDF报告文件
+ * 
+ * @example
+ * mockReportPreviewSuccess();
+ * cy.wait('@reportPreviewSuccess');
+ * 
+ * @see docs/DR.md - 章节25
+ */
+export function mockReportPreviewSuccess() {
+  cy.intercept('POST', '/dr/api/v1/auth/report/preview', {
+    statusCode: 200,
+    headers: {
+      'content-type': 'application/pdf'
+    },
+    body: '' // 空PDF数据
+  }).as('reportPreviewSuccess');
+}
+
+/**
+ * 保存报告 - 成功场景
+ * 
+ * @description 保存检查报告
+ * @method POST
+ * @url /dr/api/v1/auth/study/{id}/report
+ * @access 需要认证
+ * 
+ * @param {string} id - 检查ID(路径参数)
+ * @param {Object} requestBody - 请求体
+ * @param {Object} requestBody.headers - 报告头信息
+ * @param {string} requestBody.findings - 影像所见
+ * @param {string} requestBody.impression - 诊断意见
+ * @param {string} requestBody.radiologist - 报告医师
+ * @param {string} requestBody.review_physician - 审核医师
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockSaveReportSuccess();
+ * cy.wait('@saveReportSuccess');
+ * 
+ * @see docs/DR.md - 章节26
+ */
+export function mockSaveReportSuccess() {
+  cy.intercept('POST', '/dr/api/v1/auth/study/*/report', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {}
+    }
+  }).as('saveReportSuccess');
+}
+
+/**
+ * 获取报告 - 成功场景
+ * 
+ * @description 获取已保存的检查报告
+ * @method GET
+ * @url /dr/api/v1/auth/study/{id}/report
+ * @access 需要认证
+ * 
+ * @param {string} id - 检查ID(路径参数)
+ * 
+ * @returns {File} PDF报告文件
+ * 
+ * @example
+ * mockGetReportSuccess();
+ * cy.wait('@getReportSuccess');
+ * 
+ * @see docs/DR.md - 章节27
+ */
+export function mockGetReportSuccess() {
+  cy.intercept('GET', '/dr/api/v1/auth/study/*/report', {
+    statusCode: 200,
+    headers: {
+      'content-type': 'application/pdf'
+    },
+    body: '' // 空PDF数据
+  }).as('getReportSuccess');
+}
+
+/**
+ * 获取报告 - 报告不存在
+ * 
+ * @description 获取报告失败,报告未创建
+ * @method GET
+ * @url /dr/api/v1/auth/study/{id}/report
+ * @access 需要认证
+ * 
+ * @param {string} id - 检查ID(路径参数)
+ * 
+ * @returns {Object} 404错误响应
+ * 
+ * @example
+ * mockGetReportNotFound();
+ * cy.wait('@getReportNotFound');
+ * 
+ * @see docs/DR.md - 章节27
+ */
+export function mockGetReportNotFound() {
+  cy.intercept('GET', '/dr/api/v1/auth/study/*/report', {
+    statusCode: 404,
+    body: {
+      code: "0x000001",
+      description: "Report not found",
+      solution: "Please create the report first"
+    }
+  }).as('getReportNotFound');
+}

+ 211 - 0
cypress/support/mock/handlers/resource.ts

@@ -0,0 +1,211 @@
+/**
+ * Resource Mock Handlers
+ * 资源配置相关的 mock 处理器
+ */
+
+/**
+ * 获取选项 - 性别选项(sex_full)
+ * 
+ * @description 获取性别选项列表,包括完整的性别类型
+ * @method GET
+ * @url /dr/api/v1/auth/resource/options
+ * @access 需要认证
+ * 
+ * @param {string} group - 选项组(basic/apr_cbo)
+ * @param {string} flag - 选项标志(sex_full/patient_size)
+ * 
+ * @returns {Object[]} data.option - 选项列表
+ * @returns {string} data.option[].text - 显示文本
+ * @returns {string} data.option[].value - 选项值
+ * 
+ * @example
+ * mockGetOptionsSexFull();
+ * cy.wait('@getOptionsSexFull');
+ * 
+ * @see docs/DR.md - 章节9
+ */
+export function mockGetOptionsSexFull() {
+  cy.intercept('GET', '/dr/api/v1/auth/resource/options*', (req) => {
+    req.reply({
+      statusCode: 200,
+      body: {
+        code: "0x000000",
+        description: "Success",
+        solution: "",
+        data: {
+          "@type": "type.googleapis.com/dr.resource.OptionReply",
+          option: [
+            { text: "Male", value: "M" },
+            { text: "Female", value: "F" },
+            { text: "Other", value: "O" },
+            { text: "CM", value: "CM" },
+            { text: "SF", value: "SF" }
+          ]
+        }
+      }
+    });
+  }).as('getOptionsSexFull');
+}
+
+/**
+ * 获取选项 - 患者体型(patient_size)
+ * 
+ * @description 获取患者体型选项列表
+ * @method GET
+ * @url /dr/api/v1/auth/resource/options
+ * @access 需要认证
+ * 
+ * @param {string} group - 选项组(basic)
+ * @param {string} flag - 选项标志(patient_size)
+ * 
+ * @returns {Object[]} data.option - 选项列表
+ * 
+ * @example
+ * mockGetOptionsPatientSize();
+ * cy.wait('@getOptionsPatientSize');
+ * 
+ * @see docs/DR.md - 章节9
+ */
+export function mockGetOptionsPatientSize() {
+  cy.intercept('GET', '/dr/api/v1/auth/resource/options*patient_size*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.resource.OptionReply",
+        option: [
+          { text: "Small", value: "Small" },
+          { text: "Medium", value: "Medium" },
+          { text: "Large", value: "Large" }
+        ]
+      }
+    }
+  }).as('getOptionsPatientSize');
+}
+
+/**
+ * 获取配置项 - 发生器仿真模式
+ * 
+ * @description 获取发生器仿真模式配置
+ * @method GET
+ * @url /dr/api/v1/auth/resource/config
+ * @access 需要认证
+ * 
+ * @param {string} uri - 配置URI(System/SimulatorGEN)
+ * 
+ * @returns {Object} data - 配置数据
+ * 
+ * @example
+ * mockGetConfigSimulatorGEN();
+ * cy.wait('@getConfigSimulatorGEN');
+ * 
+ * @see docs/DR.md - 章节11
+ */
+export function mockGetConfigSimulatorGEN() {
+  cy.intercept('GET', '/dr/api/v1/auth/resource/config*SimulatorGEN*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.resource.OptionReply",
+        option: [
+          { text: "Enabled", value: "true" },
+          { text: "Disabled", value: "false" }
+        ]
+      }
+    }
+  }).as('getConfigSimulatorGEN');
+}
+
+/**
+ * 获取配置项 - 探测器仿真模式
+ * 
+ * @description 获取探测器仿真模式配置
+ * @method GET
+ * @url /dr/api/v1/auth/resource/config
+ * @access 需要认证
+ * 
+ * @param {string} uri - 配置URI(System/SimulatorFPD)
+ * 
+ * @returns {Object} data - 配置数据
+ * 
+ * @example
+ * mockGetConfigSimulatorFPD();
+ * cy.wait('@getConfigSimulatorFPD');
+ * 
+ * @see docs/DR.md - 章节11
+ */
+export function mockGetConfigSimulatorFPD() {
+  cy.intercept('GET', '/dr/api/v1/auth/resource/config*SimulatorFPD*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.resource.OptionReply",
+        option: [
+          { text: "Enabled", value: "true" },
+          { text: "Disabled", value: "false" }
+        ]
+      }
+    }
+  }).as('getConfigSimulatorFPD');
+}
+
+/**
+ * 获取示意图 - 成功场景
+ * 
+ * @description 获取体位示意图图片
+ * @method GET
+ * @url /dr/api/v1/pub/Image/{path}
+ * @access 公开接口
+ * 
+ * @returns {File} 图片文件
+ * 
+ * @example
+ * mockGetImageSuccess();
+ * cy.wait('@getImageSuccess');
+ * 
+ * @see docs/DR.md - 章节6
+ */
+export function mockGetImageSuccess() {
+  cy.intercept('GET', '/dr/api/v1/pub/Image/**', {
+    statusCode: 200,
+    headers: {
+      'content-type': 'image/gif'
+    },
+    body: '' // 空图片数据
+  }).as('getImageSuccess');
+}
+
+/**
+ * 获取示意图 - 404未找到
+ * 
+ * @description 获取示意图失败,文件不存在
+ * @method GET
+ * @url /dr/api/v1/pub/Image/{path}
+ * @access 公开接口
+ * 
+ * @returns {Object} 404错误响应
+ * 
+ * @example
+ * mockGetImageNotFound();
+ * cy.wait('@getImageNotFound');
+ * 
+ * @see docs/DR.md - 章节6
+ */
+export function mockGetImageNotFound() {
+  cy.intercept('GET', '/dr/api/v1/pub/Image/**', {
+    statusCode: 404,
+    body: {
+      code: "0x000001",
+      description: "Image not found",
+      solution: "Please check the image path"
+    }
+  }).as('getImageNotFound');
+}

+ 289 - 0
cypress/support/mock/handlers/study.ts

@@ -0,0 +1,289 @@
+/**
+ * Study Mock Handlers
+ * 检查信息管理相关的 mock 处理器
+ */
+
+/**
+ * 登记检查信息 - 成功场景
+ * 
+ * @description 登记新的检查信息
+ * @method POST
+ * @url /dr/api/v1/auth/study
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体(包含患者信息、体位列表等)
+ * 
+ * @returns {Object} data - 新创建的检查信息
+ * @returns {string} data.study_id - 检查ID
+ * @returns {Object[]} data.series - 序列列表
+ * 
+ * @example
+ * mockRegisterStudySuccess();
+ * cy.wait('@registerStudySuccess');
+ * 
+ * @see docs/DR.md - 章节13
+ */
+export function mockRegisterStudySuccess() {
+  cy.intercept('POST', '/dr/api/v1/auth/study', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.study.Study",
+        study_instance_uid: "1.2.276.0.1000000.5.1.2.701601461.33458.1750833219.482097",
+        study_id: "20250625143339389",
+        patient_name: "Test Patient",
+        patient_id: "PET007",
+        study_status: "Arrived",
+        series: []
+      }
+    }
+  }).as('registerStudySuccess');
+}
+
+/**
+ * 获取检查信息 - Arrived状态
+ * 
+ * @description 根据study_id获取检查详细信息(已到达状态)
+ * @method GET
+ * @url /dr/api/v1/auth/study/{id}
+ * @access 需要认证
+ * 
+ * @param {string} id - 检查ID(study_id),路径参数
+ * 
+ * @returns {Object} data - 检查详细信息
+ * @returns {string} data.study_status - 检查状态(Arrived/InProgress/Completed)
+ * 
+ * @example
+ * mockGetStudyArrived();
+ * cy.wait('@getStudyArrived');
+ * 
+ * @see docs/DR.md - 章节16
+ */
+export function mockGetStudyArrived() {
+  cy.intercept('GET', '/dr/api/v1/auth/study/*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        study_id: "250929163817805",
+        patient_name: "Test Patient",
+        study_status: "Arrived",
+        series: []
+      }
+    }
+  }).as('getStudyArrived');
+}
+
+/**
+ * 获取检查信息状态 - 成功场景
+ * 
+ * @description 获取检查的统计信息(总数、已曝光数)
+ * @method GET
+ * @url /dr/api/v1/auth/study/{id}/stat
+ * @access 需要认证
+ * 
+ * @param {string} id - 检查ID(study_id),路径参数
+ * 
+ * @returns {Object} data - 统计信息
+ * @returns {number} data.total - 总体位数
+ * @returns {number} data.exposed - 已曝光数
+ * 
+ * @example
+ * mockGetStudyStatSuccess();
+ * cy.wait('@getStudyStatSuccess');
+ * 
+ * @see docs/DR.md - 章节17
+ */
+export function mockGetStudyStatSuccess() {
+  cy.intercept('GET', '/dr/api/v1/auth/study/*/stat', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.study.Stat",
+        total: 3,
+        exposed: 1
+      }
+    }
+  }).as('getStudyStatSuccess');
+}
+
+/**
+ * 变更登记信息 - 成功场景
+ * 
+ * @description 更新已登记的检查信息
+ * @method PUT
+ * @url /dr/api/v1/auth/study/{id}
+ * @access 需要认证
+ * 
+ * @param {string} id - 检查ID(study_id),路径参数
+ * @param {Object} requestBody - 更新的患者信息
+ * 
+ * @returns {Object} data - 更新后的检查信息
+ * 
+ * @example
+ * mockUpdateStudySuccess();
+ * cy.wait('@updateStudySuccess');
+ * 
+ * @see docs/DR.md - 章节14
+ */
+export function mockUpdateStudySuccess() {
+  cy.intercept('PUT', '/dr/api/v1/auth/study/*', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        study_id: "20250625140057649",
+        patient_name: "Updated Patient",
+        study_status: "Arrived"
+      }
+    }
+  }).as('updateStudySuccess');
+}
+
+/**
+ * 删除检查信息(批量)- 成功场景
+ * 
+ * @description 批量删除检查信息
+ * @method DELETE
+ * @url /dr/api/v1/auth/study
+ * @access 需要认证
+ * 
+ * @param {string[]} requestBody - 检查ID列表
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockDeleteStudyBatchSuccess();
+ * cy.wait('@deleteStudyBatchSuccess');
+ * 
+ * @see docs/DR.md - 章节18
+ */
+export function mockDeleteStudyBatchSuccess() {
+  cy.intercept('DELETE', '/dr/api/v1/auth/study', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {}
+    }
+  }).as('deleteStudyBatchSuccess');
+}
+
+/**
+ * 存储急诊患者影像 - 成功场景
+ * 
+ * @description 存储拍摄的急诊患者影像
+ * @method POST
+ * @url /api/v1/auth/study/portrait
+ * @access 需要认证
+ * 
+ * @param {Object} formData - 表单数据
+ * @param {string} formData.instance_uid - study实例UID
+ * @param {File} formData.data - PNG图片文件
+ * 
+ * @returns {Object} data - DCM文件路径
+ * @returns {string} data.path - 文件路径
+ * 
+ * @example
+ * mockStorePortraitSuccess();
+ * cy.wait('@storePortraitSuccess');
+ * 
+ * @see docs/DR.md - 章节19
+ */
+export function mockStorePortraitSuccess() {
+  cy.intercept('POST', '/api/v1/auth/study/portrait', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      description: "Success",
+      solution: "",
+      data: {
+        "@type": "type.googleapis.com/dr.task.DcmPath",
+        path: "1.2.276.0.1000000.5.1.5.701601461.33458.1750830395.482043.dcm"
+      }
+    }
+  }).as('storePortraitSuccess');
+}
+
+/**
+ * 挂起检查 - 成功场景
+ * 
+ * @description 挂起当前检查(状态设为InProgress)
+ * @method POST
+ * @url /api/v1/auth/task/inspection/leave
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.study_id - 检查ID
+ * @param {string} requestBody.study_status - 检查状态(InProgress)
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockLeaveStudyInProgress();
+ * cy.wait('@leaveStudyInProgress');
+ * 
+ * @see docs/DR.md - 章节35
+ */
+export function mockLeaveStudyInProgress() {
+  cy.intercept('POST', '/api/v1/auth/task/inspection/leave', (req) => {
+    if (req.body.study_status === 'InProgress') {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: "0x000000",
+          description: "Success",
+          solution: "",
+          data: {}
+        }
+      });
+    }
+  }).as('leaveStudyInProgress');
+}
+
+/**
+ * 完成检查 - 成功场景
+ * 
+ * @description 完成当前检查(状态设为Completed)
+ * @method POST
+ * @url /api/v1/auth/task/inspection/leave
+ * @access 需要认证
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.study_id - 检查ID
+ * @param {string} requestBody.study_status - 检查状态(Completed)
+ * 
+ * @returns {Object} 成功响应
+ * 
+ * @example
+ * mockLeaveStudyCompleted();
+ * cy.wait('@leaveStudyCompleted');
+ * 
+ * @see docs/DR.md - 章节35
+ */
+export function mockLeaveStudyCompleted() {
+  cy.intercept('POST', '/api/v1/auth/task/inspection/leave', (req) => {
+    if (req.body.study_status === 'Completed') {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: "0x000000",
+          description: "Success",
+          solution: "",
+          data: {}
+        }
+      });
+    }
+  }).as('leaveStudyCompleted');
+}

+ 126 - 0
cypress/support/mock/handlers/system.ts

@@ -0,0 +1,126 @@
+/**
+ * System Mock Handlers
+ * 系统信息相关的 mock 处理器
+ */
+
+/**
+ * 获取软件信息 - 成功场景
+ * 
+ * @description 获取系统软件版本、设备类型、语言等信息
+ * @method GET
+ * @url /dr/api/v1/pub/software_info
+ * @access 公开接口
+ * 
+ * @returns {Object} 包含软件版本、服务器信息、设备信息等
+ * @returns {string} data.FPD - 探测器类型(Simulator)
+ * @returns {string} data.GEN - 发生器类型(Simulator)
+ * @returns {string} data.guest - 访客令牌
+ * @returns {string[]} data.language - 支持的语言列表
+ * @returns {string} data.product - 产品名称(DROS)
+ * @returns {string} data.sn - 设备序列号
+ * @returns {Object} data.server - 各服务器版本信息
+ * 
+ * @example
+ * mockGetSoftwareInfoSuccess();
+ * cy.wait('@getSoftwareInfoSuccess').its('response.body.data.product').should('equal', 'DROS');
+ * 
+ * @see docs/DR.md - 章节2
+ */
+export function mockGetSoftwareInfoSuccess() {
+  cy.intercept('GET', '/dr/api/v1/pub/software_info', {
+    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('getSoftwareInfoSuccess');
+}
+
+/**
+ * 获取软件信息 - 失败场景
+ * 
+ * @description 获取软件信息失败,服务器错误
+ * @method GET
+ * @url /dr/api/v1/pub/software_info
+ * @access 公开接口
+ * 
+ * @returns {Object} 错误响应
+ * @returns {string} code - 错误码
+ * @returns {string} description - 错误描述
+ * 
+ * @example
+ * mockGetSoftwareInfoFail();
+ * cy.wait('@getSoftwareInfoFail');
+ * 
+ * @see docs/DR.md - 章节2
+ */
+export function mockGetSoftwareInfoFail() {
+  cy.intercept('GET', '/dr/api/v1/pub/software_info', {
+    statusCode: 500,
+    body: {
+      code: "0x000001",
+      data: {},
+      description: "Internal Server Error",
+      solution: "Please contact system administrator"
+    }
+  }).as('getSoftwareInfoFail');
+}

+ 110 - 4
cypress/support/mock/handlers/user.ts

@@ -1,5 +1,36 @@
-// User-related mock handlers
-// 封装登录成功的 mock
+/**
+ * User Mock Handlers
+ * 用户相关的 mock 处理器
+ */
+
+/**
+ * 用户登录 - 成功场景
+ * 
+ * @description 用户登录认证,返回JWT token和用户信息
+ * @method POST
+ * @url /dr/api/v1/pub/login
+ * @access 公开接口
+ * 
+ * @param {Object} requestBody - 请求体
+ * @param {string} requestBody.username - 用户名
+ * @param {string} requestBody.password - 密码
+ * 
+ * @returns {Object} 登录成功响应
+ * @returns {string} data.token - JWT认证令牌
+ * @returns {number} data.expire - 令牌过期时间戳
+ * @returns {number} data.uid - 用户ID
+ * @returns {string} data.name - 用户名
+ * @returns {string} data.avatar - 用户头像URL
+ * 
+ * @example
+ * mockLoginSuccess();
+ * cy.get('[data-testid="username"]').type('admin');
+ * cy.get('[data-testid="password"]').type('123456');
+ * cy.get('[data-testid="login-btn"]').click();
+ * cy.wait('@loginSuccess');
+ * 
+ * @see docs/DR.md - 章节7
+ */
 export function mockLoginSuccess() {
   cy.intercept('POST', '/dr/api/v1/pub/login', (req) => {
     req.reply({
@@ -20,11 +51,30 @@ export function mockLoginSuccess() {
   }).as('loginSuccess');
 }
 
-// 封装登录失败的 mock
+/**
+ * 用户登录 - 用户名或密码错误
+ * 
+ * @description 登录失败场景:用户名或密码不正确
+ * @method POST
+ * @url /dr/api/v1/pub/login
+ * @access 公开接口
+ * 
+ * @returns {Object} 错误响应
+ * @returns {string} code - 错误码(0x000001)
+ * @returns {string} description - 错误描述
+ * 
+ * @example
+ * mockLoginInvalidCredentials();
+ * cy.get('[data-testid="login-btn"]').click();
+ * cy.wait('@loginInvalidCredentials');
+ * cy.contains('Invalid username or password').should('be.visible');
+ * 
+ * @see docs/DR.md - 章节7
+ */
 export function mockLoginFail() {
   cy.intercept('POST', '/dr/api/v1/pub/login', (req) => {
     req.reply({
-      statusCode: 200, // 一般还是200,通过 code 区分失败
+      statusCode: 200,
       body: {
         code: "0x000001",
         description: "Invalid username or password",
@@ -34,3 +84,59 @@ export function mockLoginFail() {
     });
   }).as('loginFail');
 }
+
+/**
+ * 获取用户列表 - 成功场景
+ * 
+ * @description 获取系统所有用户列表
+ * @method GET
+ * @url /dr/api/v1/pub/users
+ * @access 公开接口
+ * 
+ * @returns {string[]} data - 用户名列表
+ * 
+ * @example
+ * mockGetUsersSuccess();
+ * cy.wait('@getUsersSuccess').its('response.body.data').should('have.length.greaterThan', 0);
+ * 
+ * @see docs/DR.md - 章节5
+ */
+export function mockGetUsersSuccess() {
+  cy.intercept('GET', '/dr/api/v1/pub/users', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      data: ["service", "admin", "mgr", "tech"],
+      description: "Success",
+      solution: ""
+    }
+  }).as('getUsersSuccess');
+}
+
+/**
+ * 获取用户列表 - 空列表场景
+ * 
+ * @description 获取用户列表,返回空列表
+ * @method GET
+ * @url /dr/api/v1/pub/users
+ * @access 公开接口
+ * 
+ * @returns {string[]} data - 空用户列表
+ * 
+ * @example
+ * mockGetUsersEmpty();
+ * cy.wait('@getUsersEmpty').its('response.body.data').should('have.length', 0);
+ * 
+ * @see docs/DR.md - 章节5
+ */
+export function mockGetUsersEmpty() {
+  cy.intercept('GET', '/dr/api/v1/pub/users', {
+    statusCode: 200,
+    body: {
+      code: "0x000000",
+      data: [],
+      description: "Success",
+      solution: ""
+    }
+  }).as('getUsersEmpty');
+}

+ 40 - 5
cypress/support/mock/index.ts

@@ -1,5 +1,40 @@
-// export * from './handlers/user';
-// export * from './handlers/order';
-// export * from './handlers/common';
-// export * from './utils/mockUtils';
-// export * from './utils/constants';
+/**
+ * Cypress Mock Handlers Index
+ * 统一导出所有 mock handlers
+ */
+
+// 系统相关
+export * from './handlers/system';
+
+// 国际化相关
+export * from './handlers/i18n';
+
+// 用户相关
+export * from './handlers/user';
+
+// 认证相关
+export * from './handlers/auth';
+
+// 资源配置相关
+export * from './handlers/resource';
+
+// 协议相关
+export * from './handlers/protocol';
+
+// 检查信息管理相关
+export * from './handlers/study';
+
+// 图像相关
+export * from './handlers/image';
+
+// 报告相关
+export * from './handlers/report';
+
+// 设备相关
+export * from './handlers/device';
+
+// 检查执行相关
+export * from './handlers/exam';
+
+// 配额相关(已有)
+export * from './handlers/quota';

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 3171 - 0
docs/DR.md


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů