Browse Source

test: 添加按日期范围查询Study功能的E2E测试基础设施

- 创建完整的测试方案文档,包含16个测试用例和详细的测试策略
- 新建SearchPanelPage POM封装搜索面板操作
- 扩展WorklistPage和HistoryPage POM添加日期范围验证方法
- 新建dateRangeQuery mock handlers支持不同时间范围的查询测试
- 支持今天、7天、所有、自定义日期范围等多种查询场景

改动文件:
- docs/测试/按日期范围查询Study功能测试方案.md (新增)
- cypress/support/pageObjects/SearchPanelPage.ts (新增)
- cypress/support/mock/handlers/dateRangeQuery.ts (新增)
- cypress/support/pageObjects/WorklistPage.ts (修改)
- cypress/support/pageObjects/HistoryPage.ts (修改)
dengdx 9 hours ago
parent
commit
91a35faa9b

+ 910 - 0
cypress/support/mock/handlers/dateRangeQuery.ts

@@ -0,0 +1,910 @@
+/**
+ * Mock handlers for date range query testing
+ * 用于测试按日期范围查询 study 的功能
+ */
+
+import dayjs from 'dayjs';
+
+// ============ Aliases ============
+
+const aliasOfFetchTodayStudies = 'fetchTodayStudies';
+export const FetchTodayStudies = `@${aliasOfFetchTodayStudies}`;
+
+const aliasOfFetch7DaysStudies = 'fetch7DaysStudies';
+export const Fetch7DaysStudies = `@${aliasOfFetch7DaysStudies}`;
+
+const aliasOfFetchAllStudies = 'fetchAllStudies';
+export const FetchAllStudies = `@${aliasOfFetchAllStudies}`;
+
+const aliasOfFetchCustomRangeStudies = 'fetchCustomRangeStudies';
+export const FetchCustomRangeStudies = `@${aliasOfFetchCustomRangeStudies}`;
+
+const aliasOfFetchEmptyResult = 'fetchEmptyResult';
+export const FetchEmptyResult = `@${aliasOfFetchEmptyResult}`;
+
+const aliasOfFetchCombinedQuery = 'fetchCombinedQuery';
+export const FetchCombinedQuery = `@${aliasOfFetchCombinedQuery}`;
+
+// ============ Mock Functions ============
+
+/**
+ * Mock: 查询今天的 study(Worklist)
+ */
+export function mockFetchTodayStudiesWorklist() {
+  const today = dayjs().format('YYYY-MM-DD');
+
+  cy.intercept('GET', '/dr/api/v1/auth/study*', (req) => {
+    const url = new URL(req.url);
+    const status = url.searchParams.get('status');
+    const startTime = url.searchParams.get('start_time');
+
+    // 只匹配包含今天日期范围的请求
+    if (status === 'Arrived,InProgress' && startTime?.includes(today)) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          solution: '',
+          data: {
+            '@type': 'type.googleapis.com/dr.study.StudyList',
+            count: 2,
+            studies: [
+              {
+                study_instance_uid: '2.25.TODAY.001',
+                study_id: 'TODAY001',
+                public_study_id: '',
+                specific_character_set: 'ISO_IR 192',
+                accession_number: 'ACC_TODAY_001',
+                ref_physician: 'Dr. Wang',
+                patient_id: 'PT_TODAY_001',
+                patient_name: '今天患者1',
+                patient_english_name: 'Today Patient 1',
+                patient_former_name: '',
+                patient_size: 'Medium',
+                other_patient_ids: '',
+                other_patient_names: '',
+                patient_age: '35Y',
+                patient_dob: '1990-01-01T00:00:00Z',
+                patient_sex: 'M',
+                sex_neutered: '',
+                pregnancy_status: '',
+                patient_state: '',
+                admitting_time: null,
+                priority: '',
+                reg_source: '',
+                study_description: '',
+                study_start_datetime: `${today}T08:00:00Z`,
+                study_end_datetime: null,
+                scheduled_procedure_step_start_date: null,
+                performed_physician: '',
+                study_lock: 'Unlocked',
+                folder_path: '',
+                operator_name: 'OP001',
+                modality: 'DX',
+                weight: 70,
+                thickness: 20,
+                length: 175,
+                patient_type: 'Human',
+                study_type: 'Normal',
+                owner_name: '',
+                chip_number: '',
+                variety: '',
+                is_anaesthesia: false,
+                is_sedation: false,
+                mwl: '',
+                is_exported: false,
+                is_edited: false,
+                is_appended: false,
+                department: '',
+                mapped_status: false,
+                qc_result: false,
+                comment: '今天的检查1',
+                study_status: 'Arrived',
+                sort: 0,
+                product: 'DROS',
+                series: [],
+              },
+              {
+                study_instance_uid: '2.25.TODAY.002',
+                study_id: 'TODAY002',
+                public_study_id: '',
+                specific_character_set: 'ISO_IR 192',
+                accession_number: 'ACC_TODAY_002',
+                ref_physician: 'Dr. Wang',
+                patient_id: 'PT_TODAY_002',
+                patient_name: '今天患者2',
+                patient_english_name: 'Today Patient 2',
+                patient_former_name: '',
+                patient_size: 'Medium',
+                other_patient_ids: '',
+                other_patient_names: '',
+                patient_age: '40Y',
+                patient_dob: '1985-01-01T00:00:00Z',
+                patient_sex: 'F',
+                sex_neutered: '',
+                pregnancy_status: '',
+                patient_state: '',
+                admitting_time: null,
+                priority: '',
+                reg_source: '',
+                study_description: '',
+                study_start_datetime: `${today}T10:00:00Z`,
+                study_end_datetime: null,
+                scheduled_procedure_step_start_date: null,
+                performed_physician: '',
+                study_lock: 'Unlocked',
+                folder_path: '',
+                operator_name: 'OP002',
+                modality: 'DX',
+                weight: 65,
+                thickness: 18,
+                length: 165,
+                patient_type: 'Human',
+                study_type: 'Normal',
+                owner_name: '',
+                chip_number: '',
+                variety: '',
+                is_anaesthesia: false,
+                is_sedation: false,
+                mwl: '',
+                is_exported: false,
+                is_edited: false,
+                is_appended: false,
+                department: '',
+                mapped_status: false,
+                qc_result: false,
+                comment: '今天的检查2',
+                study_status: 'InProgress',
+                sort: 0,
+                product: 'DROS',
+                series: [],
+              },
+            ],
+          },
+        },
+      });
+    }
+  }).as(aliasOfFetchTodayStudies);
+}
+
+/**
+ * Mock: 查询最近7天的 study(Worklist)
+ */
+export function mockFetch7DaysStudiesWorklist() {
+  const today = dayjs();
+  const sevenDaysAgo = today.subtract(7, 'day');
+
+  cy.intercept('GET', '/dr/api/v1/auth/study*', (req) => {
+    const url = new URL(req.url);
+    const status = url.searchParams.get('status');
+    const startTime = url.searchParams.get('start_time');
+
+    // 只匹配包含7天前日期的请求
+    if (
+      status === 'Arrived,InProgress' &&
+      startTime?.includes(sevenDaysAgo.format('YYYY-MM-DD'))
+    ) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          solution: '',
+          data: {
+            '@type': 'type.googleapis.com/dr.study.StudyList',
+            count: 5,
+            studies: [
+              // 今天
+              {
+                study_instance_uid: '2.25.7DAYS.001',
+                study_id: '7DAYS001',
+                patient_name: '7天患者1',
+                patient_id: 'PT_7D_001',
+                accession_number: 'ACC_7D_001',
+                study_start_datetime: today.format('YYYY-MM-DD') + 'T08:00:00Z',
+                study_status: 'Arrived',
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+              // 3天前
+              {
+                study_instance_uid: '2.25.7DAYS.002',
+                study_id: '7DAYS002',
+                patient_name: '7天患者2',
+                patient_id: 'PT_7D_002',
+                accession_number: 'ACC_7D_002',
+                study_start_datetime:
+                  today.subtract(3, 'day').format('YYYY-MM-DD') + 'T10:00:00Z',
+                study_status: 'InProgress',
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+              // 5天前
+              {
+                study_instance_uid: '2.25.7DAYS.003',
+                study_id: '7DAYS003',
+                patient_name: '7天患者3',
+                patient_id: 'PT_7D_003',
+                accession_number: 'ACC_7D_003',
+                study_start_datetime:
+                  today.subtract(5, 'day').format('YYYY-MM-DD') + 'T14:00:00Z',
+                study_status: 'Arrived',
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+              // 6天前
+              {
+                study_instance_uid: '2.25.7DAYS.004',
+                study_id: '7DAYS004',
+                patient_name: '7天患者4',
+                patient_id: 'PT_7D_004',
+                accession_number: 'ACC_7D_004',
+                study_start_datetime:
+                  today.subtract(6, 'day').format('YYYY-MM-DD') + 'T09:00:00Z',
+                study_status: 'InProgress',
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+              // 7天前
+              {
+                study_instance_uid: '2.25.7DAYS.005',
+                study_id: '7DAYS005',
+                patient_name: '7天患者5',
+                patient_id: 'PT_7D_005',
+                accession_number: 'ACC_7D_005',
+                study_start_datetime:
+                  today.subtract(7, 'day').format('YYYY-MM-DD') + 'T11:00:00Z',
+                study_status: 'Arrived',
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+            ].map((study) => ({
+              ...study,
+              specific_character_set: 'ISO_IR 192',
+              ref_physician: 'Dr. Li',
+              patient_english_name: study.patient_name + ' EN',
+              patient_former_name: '',
+              patient_size: 'Medium',
+              other_patient_ids: '',
+              other_patient_names: '',
+              patient_age: '35Y',
+              patient_dob: '1990-01-01T00:00:00Z',
+              patient_sex: 'M',
+              sex_neutered: '',
+              pregnancy_status: '',
+              patient_state: '',
+              admitting_time: null,
+              priority: '',
+              reg_source: '',
+              study_description: '',
+              study_end_datetime: null,
+              scheduled_procedure_step_start_date: null,
+              performed_physician: '',
+              folder_path: '',
+              operator_name: 'OP001',
+              weight: 70,
+              thickness: 20,
+              length: 175,
+              study_type: 'Normal',
+              owner_name: '',
+              chip_number: '',
+              variety: '',
+              is_anaesthesia: false,
+              is_sedation: false,
+              mwl: '',
+              is_exported: false,
+              is_edited: false,
+              is_appended: false,
+              department: '',
+              mapped_status: false,
+              qc_result: false,
+              comment: '',
+              sort: 0,
+              public_study_id: '',
+            })),
+          },
+        },
+      });
+    }
+  }).as(aliasOfFetch7DaysStudies);
+}
+
+/**
+ * Mock: 查询所有 study(不限日期)
+ */
+export function mockFetchAllStudiesWorklist() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', (req) => {
+    const url = new URL(req.url);
+    const status = url.searchParams.get('status');
+    const startTime = url.searchParams.get('start_time');
+    const endTime = url.searchParams.get('end_time');
+
+    // 匹配不包含日期范围参数的请求
+    if (status === 'Arrived,InProgress' && !startTime && !endTime) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          solution: '',
+          data: {
+            '@type': 'type.googleapis.com/dr.study.StudyList',
+            count: 10,
+            studies: Array.from({ length: 10 }, (_, index) => ({
+              study_instance_uid: `2.25.ALL.${String(index + 1).padStart(3, '0')}`,
+              study_id: `ALL${String(index + 1).padStart(3, '0')}`,
+              public_study_id: '',
+              specific_character_set: 'ISO_IR 192',
+              accession_number: `ACC_ALL_${String(index + 1).padStart(3, '0')}`,
+              ref_physician: 'Dr. Zhang',
+              patient_id: `PT_ALL_${String(index + 1).padStart(3, '0')}`,
+              patient_name: `所有患者${index + 1}`,
+              patient_english_name: `All Patient ${index + 1}`,
+              patient_former_name: '',
+              patient_size: 'Medium',
+              other_patient_ids: '',
+              other_patient_names: '',
+              patient_age: '35Y',
+              patient_dob: '1990-01-01T00:00:00Z',
+              patient_sex: index % 2 === 0 ? 'M' : 'F',
+              sex_neutered: '',
+              pregnancy_status: '',
+              patient_state: '',
+              admitting_time: null,
+              priority: '',
+              reg_source: '',
+              study_description: '',
+              study_start_datetime: dayjs()
+                .subtract(index * 2, 'day')
+                .format('YYYY-MM-DD') + 'T10:00:00Z',
+              study_end_datetime: null,
+              scheduled_procedure_step_start_date: null,
+              performed_physician: '',
+              study_lock: 'Unlocked',
+              folder_path: '',
+              operator_name: 'OP001',
+              modality: 'DX',
+              weight: 70,
+              thickness: 20,
+              length: 175,
+              patient_type: 'Human',
+              study_type: 'Normal',
+              owner_name: '',
+              chip_number: '',
+              variety: '',
+              is_anaesthesia: false,
+              is_sedation: false,
+              mwl: '',
+              is_exported: false,
+              is_edited: false,
+              is_appended: false,
+              department: '',
+              mapped_status: false,
+              qc_result: false,
+              comment: `检查记录${index + 1}`,
+              study_status: index % 2 === 0 ? 'Arrived' : 'InProgress',
+              sort: 0,
+              product: 'DROS',
+              series: [],
+            })),
+          },
+        },
+      });
+    }
+  }).as(aliasOfFetchAllStudies);
+}
+
+/**
+ * Mock: 自定义日期范围查询
+ */
+export function mockFetchCustomRangeStudies(startDate: string, endDate: string) {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', (req) => {
+    const url = new URL(req.url);
+    const status = url.searchParams.get('status');
+    const startTime = url.searchParams.get('start_time');
+
+    // 匹配包含指定日期范围的请求
+    if (status && startTime?.includes(startDate)) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          solution: '',
+          data: {
+            '@type': 'type.googleapis.com/dr.study.StudyList',
+            count: 3,
+            studies: [
+              {
+                study_instance_uid: '2.25.CUSTOM.001',
+                study_id: 'CUSTOM001',
+                patient_name: '自定义范围患者1',
+                patient_id: 'PT_CUSTOM_001',
+                accession_number: 'ACC_CUSTOM_001',
+                study_start_datetime: startDate + 'T08:00:00Z',
+                study_status: 'Arrived',
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+              {
+                study_instance_uid: '2.25.CUSTOM.002',
+                study_id: 'CUSTOM002',
+                patient_name: '自定义范围患者2',
+                patient_id: 'PT_CUSTOM_002',
+                accession_number: 'ACC_CUSTOM_002',
+                study_start_datetime:
+                  dayjs(startDate).add(2, 'day').format('YYYY-MM-DD') + 'T10:00:00Z',
+                study_status: 'InProgress',
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+              {
+                study_instance_uid: '2.25.CUSTOM.003',
+                study_id: 'CUSTOM003',
+                patient_name: '自定义范围患者3',
+                patient_id: 'PT_CUSTOM_003',
+                accession_number: 'ACC_CUSTOM_003',
+                study_start_datetime:
+                  dayjs(startDate).add(5, 'day').format('YYYY-MM-DD') + 'T14:00:00Z',
+                study_status: 'Arrived',
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+            ].map((study) => ({
+              ...study,
+              specific_character_set: 'ISO_IR 192',
+              ref_physician: 'Dr. Custom',
+              patient_english_name: study.patient_name + ' EN',
+              patient_former_name: '',
+              patient_size: 'Medium',
+              other_patient_ids: '',
+              other_patient_names: '',
+              patient_age: '35Y',
+              patient_dob: '1990-01-01T00:00:00Z',
+              patient_sex: 'M',
+              sex_neutered: '',
+              pregnancy_status: '',
+              patient_state: '',
+              admitting_time: null,
+              priority: '',
+              reg_source: '',
+              study_description: '',
+              study_end_datetime: null,
+              scheduled_procedure_step_start_date: null,
+              performed_physician: '',
+              folder_path: '',
+              operator_name: 'OP001',
+              weight: 70,
+              thickness: 20,
+              length: 175,
+              study_type: 'Normal',
+              owner_name: '',
+              chip_number: '',
+              variety: '',
+              is_anaesthesia: false,
+              is_sedation: false,
+              mwl: '',
+              is_exported: false,
+              is_edited: false,
+              is_appended: false,
+              department: '',
+              mapped_status: false,
+              qc_result: false,
+              comment: '',
+              sort: 0,
+              public_study_id: '',
+            })),
+          },
+        },
+      });
+    }
+  }).as(aliasOfFetchCustomRangeStudies);
+}
+
+/**
+ * Mock: 空结果(无数据)
+ */
+export function mockFetchEmptyResult() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', {
+    statusCode: 200,
+    body: {
+      code: '0x000000',
+      description: 'Success',
+      solution: '',
+      data: {
+        '@type': 'type.googleapis.com/dr.study.StudyList',
+        count: 0,
+        studies: [],
+      },
+    },
+  }).as(aliasOfFetchEmptyResult);
+}
+
+/**
+ * Mock: 组合查询(日期范围 + 患者名称)
+ */
+export function mockFetchCombinedQuery() {
+  const today = dayjs().format('YYYY-MM-DD');
+
+  cy.intercept('GET', '/dr/api/v1/auth/study*', (req) => {
+    const url = new URL(req.url);
+    const status = url.searchParams.get('status');
+    const startTime = url.searchParams.get('start_time');
+    const patientName = url.searchParams.get('patient_name');
+
+    // 匹配包含日期范围和患者名称的请求
+    if (status && startTime?.includes(today) && patientName) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          solution: '',
+          data: {
+            '@type': 'type.googleapis.com/dr.study.StudyList',
+            count: 1,
+            studies: [
+              {
+                study_instance_uid: '2.25.COMBINED.001',
+                study_id: 'COMBINED001',
+                public_study_id: '',
+                specific_character_set: 'ISO_IR 192',
+                accession_number: 'ACC_COMBINED_001',
+                ref_physician: 'Dr. Combined',
+                patient_id: 'PT_COMBINED_001',
+                patient_name: patientName,
+                patient_english_name: patientName + ' EN',
+                patient_former_name: '',
+                patient_size: 'Medium',
+                other_patient_ids: '',
+                other_patient_names: '',
+                patient_age: '35Y',
+                patient_dob: '1990-01-01T00:00:00Z',
+                patient_sex: 'M',
+                sex_neutered: '',
+                pregnancy_status: '',
+                patient_state: '',
+                admitting_time: null,
+                priority: '',
+                reg_source: '',
+                study_description: '',
+                study_start_datetime: today + 'T09:00:00Z',
+                study_end_datetime: null,
+                scheduled_procedure_step_start_date: null,
+                performed_physician: '',
+                study_lock: 'Unlocked',
+                folder_path: '',
+                operator_name: 'OP001',
+                modality: 'DX',
+                weight: 70,
+                thickness: 20,
+                length: 175,
+                patient_type: 'Human',
+                study_type: 'Normal',
+                owner_name: '',
+                chip_number: '',
+                variety: '',
+                is_anaesthesia: false,
+                is_sedation: false,
+                mwl: '',
+                is_exported: false,
+                is_edited: false,
+                is_appended: false,
+                department: '',
+                mapped_status: false,
+                qc_result: false,
+                comment: '组合查询结果',
+                study_status: 'Arrived',
+                sort: 0,
+                product: 'DROS',
+                series: [],
+              },
+            ],
+          },
+        },
+      });
+    }
+  }).as(aliasOfFetchCombinedQuery);
+}
+
+// ============ History Mock Functions ============
+
+/**
+ * Mock: 查询今天的 study(History - Completed)
+ */
+export function mockFetchTodayStudiesHistory() {
+  const today = dayjs().format('YYYY-MM-DD');
+
+  cy.intercept('GET', '/dr/api/v1/auth/study*', (req) => {
+    const url = new URL(req.url);
+    const status = url.searchParams.get('status');
+    const startTime = url.searchParams.get('start_time');
+
+    if (status === 'Completed' && startTime?.includes(today)) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          solution: '',
+          data: {
+            '@type': 'type.googleapis.com/dr.study.StudyList',
+            count: 2,
+            studies: [
+              {
+                study_instance_uid: '2.25.HIST.TODAY.001',
+                study_id: 'HIST_TODAY001',
+                patient_name: 'History今天患者1',
+                patient_id: 'PT_HIST_TODAY_001',
+                accession_number: 'ACC_HIST_TODAY_001',
+                study_start_datetime: today + 'T08:00:00Z',
+                study_status: 'Completed',
+                is_exported: true,
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+              {
+                study_instance_uid: '2.25.HIST.TODAY.002',
+                study_id: 'HIST_TODAY002',
+                patient_name: 'History今天患者2',
+                patient_id: 'PT_HIST_TODAY_002',
+                accession_number: 'ACC_HIST_TODAY_002',
+                study_start_datetime: today + 'T10:00:00Z',
+                study_status: 'Completed',
+                is_exported: true,
+                patient_type: 'Human',
+                modality: 'DX',
+                study_lock: 'Unlocked',
+                product: 'DROS',
+                series: [],
+              },
+            ].map((study) => ({
+              ...study,
+              specific_character_set: 'ISO_IR 192',
+              ref_physician: 'Dr. History',
+              patient_english_name: study.patient_name + ' EN',
+              patient_former_name: '',
+              patient_size: 'Medium',
+              other_patient_ids: '',
+              other_patient_names: '',
+              patient_age: '35Y',
+              patient_dob: '1990-01-01T00:00:00Z',
+              patient_sex: 'M',
+              sex_neutered: '',
+              pregnancy_status: '',
+              patient_state: '',
+              admitting_time: null,
+              priority: '',
+              reg_source: '',
+              study_description: '',
+              study_end_datetime: null,
+              scheduled_procedure_step_start_date: null,
+              performed_physician: '',
+              folder_path: '',
+              operator_name: 'OP001',
+              weight: 70,
+              thickness: 20,
+              length: 175,
+              study_type: 'Normal',
+              owner_name: '',
+              chip_number: '',
+              variety: '',
+              is_anaesthesia: false,
+              is_sedation: false,
+              mwl: '',
+              is_edited: false,
+              is_appended: false,
+              department: '',
+              mapped_status: true,
+              qc_result: true,
+              comment: '',
+              sort: 0,
+              public_study_id: '',
+            })),
+          },
+        },
+      });
+    }
+  }).as(aliasOfFetchTodayStudies);
+}
+
+/**
+ * Mock: 查询最近7天的 study(History - Completed)
+ */
+export function mockFetch7DaysStudiesHistory() {
+  const today = dayjs();
+  const sevenDaysAgo = today.subtract(7, 'day');
+
+  cy.intercept('GET', '/dr/api/v1/auth/study*', (req) => {
+    const url = new URL(req.url);
+    const status = url.searchParams.get('status');
+    const startTime = url.searchParams.get('start_time');
+
+    if (
+      status === 'Completed' &&
+      startTime?.includes(sevenDaysAgo.format('YYYY-MM-DD'))
+    ) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          solution: '',
+          data: {
+            '@type': 'type.googleapis.com/dr.study.StudyList',
+            count: 4,
+            studies: Array.from({ length: 4 }, (_, index) => ({
+              study_instance_uid: `2.25.HIST.7DAYS.${String(index + 1).padStart(3, '0')}`,
+              study_id: `HIST_7DAYS${String(index + 1).padStart(3, '0')}`,
+              public_study_id: '',
+              specific_character_set: 'ISO_IR 192',
+              accession_number: `ACC_HIST_7D_${String(index + 1).padStart(3, '0')}`,
+              ref_physician: 'Dr. History7D',
+              patient_id: `PT_HIST_7D_${String(index + 1).padStart(3, '0')}`,
+              patient_name: `History7天患者${index + 1}`,
+              patient_english_name: `History 7Days Patient ${index + 1}`,
+              patient_former_name: '',
+              patient_size: 'Medium',
+              other_patient_ids: '',
+              other_patient_names: '',
+              patient_age: '35Y',
+              patient_dob: '1990-01-01T00:00:00Z',
+              patient_sex: 'M',
+              sex_neutered: '',
+              pregnancy_status: '',
+              patient_state: '',
+              admitting_time: null,
+              priority: '',
+              reg_source: '',
+              study_description: '',
+              study_start_datetime:
+                today.subtract(index + 1, 'day').format('YYYY-MM-DD') + 'T10:00:00Z',
+              study_end_datetime: null,
+              scheduled_procedure_step_start_date: null,
+              performed_physician: '',
+              study_lock: 'Unlocked',
+              folder_path: '',
+              operator_name: 'OP001',
+              modality: 'DX',
+              weight: 70,
+              thickness: 20,
+              length: 175,
+              study_type: 'Normal',
+              owner_name: '',
+              chip_number: '',
+              variety: '',
+              is_anaesthesia: false,
+              is_sedation: false,
+              mwl: '',
+              is_exported: true,
+              is_edited: false,
+              is_appended: false,
+              department: '',
+              mapped_status: true,
+              qc_result: true,
+              comment: '',
+              study_status: 'Completed',
+              sort: 0,
+              product: 'DROS',
+              series: [],
+            })),
+          },
+        },
+      });
+    }
+  }).as(aliasOfFetch7DaysStudies);
+}
+
+/**
+ * Mock: 查询所有 study(History - Completed)
+ */
+export function mockFetchAllStudiesHistory() {
+  cy.intercept('GET', '/dr/api/v1/auth/study*', (req) => {
+    const url = new URL(req.url);
+    const status = url.searchParams.get('status');
+    const startTime = url.searchParams.get('start_time');
+    const endTime = url.searchParams.get('end_time');
+
+    // 匹配不包含日期范围参数的请求
+    if (status === 'Completed' && !startTime && !endTime) {
+      req.reply({
+        statusCode: 200,
+        body: {
+          code: '0x000000',
+          description: 'Success',
+          solution: '',
+          data: {
+            '@type': 'type.googleapis.com/dr.study.StudyList',
+            count: 8,
+            studies: Array.from({ length: 8 }, (_, index) => ({
+              study_instance_uid: `2.25.HIST.ALL.${String(index + 1).padStart(3, '0')}`,
+              study_id: `HIST_ALL${String(index + 1).padStart(3, '0')}`,
+              public_study_id: '',
+              specific_character_set: 'ISO_IR 192',
+              accession_number: `ACC_HIST_ALL_${String(index + 1).padStart(3, '0')}`,
+              ref_physician: 'Dr. HistoryAll',
+              patient_id: `PT_HIST_ALL_${String(index + 1).padStart(3, '0')}`,
+              patient_name: `History所有患者${index + 1}`,
+              patient_english_name: `History All Patient ${index + 1}`,
+              patient_former_name: '',
+              patient_size: 'Medium',
+              other_patient_ids: '',
+              other_patient_names: '',
+              patient_age: '35Y',
+              patient_dob: '1990-01-01T00:00:00Z',
+              patient_sex: index % 2 === 0 ? 'M' : 'F',
+              sex_neutered: '',
+              pregnancy_status: '',
+              patient_state: '',
+              admitting_time: null,
+              priority: '',
+              reg_source: '',
+              study_description: '',
+              study_start_datetime:
+                dayjs()
+                  .subtract(index * 3, 'day')
+                  .format('YYYY-MM-DD') + 'T10:00:00Z',
+              study_end_datetime: null,
+              scheduled_procedure_step_start_date: null,
+              performed_physician: '',
+              study_lock: 'Unlocked',
+              folder_path: '',
+              operator_name: 'OP001',
+              modality: 'DX',
+              weight: 70,
+              thickness: 20,
+              length: 175,
+              patient_type: 'Human',
+              study_type: 'Normal',
+              owner_name: '',
+              chip_number: '',
+              variety: '',
+              is_anaesthesia: false,
+              is_sedation: false,
+              mwl: '',
+              is_exported: true,
+              is_edited: false,
+              is_appended: false,
+              department: '',
+              mapped_status: true,
+              qc_result: true,
+              comment: `History检查记录${index + 1}`,
+              study_status: 'Completed',
+              sort: 0,
+              product: 'DROS',
+              series: [],
+            })),
+          },
+        },
+      });
+    }
+  }).as(aliasOfFetchAllStudies);
+}

+ 133 - 0
cypress/support/pageObjects/HistoryPage.ts

@@ -111,6 +111,139 @@ class HistoryPage {
   verifyModalNotExist() {
   verifyModalNotExist() {
     cy.get('.ant-modal-confirm').should('not.exist');
     cy.get('.ant-modal-confirm').should('not.exist');
   }
   }
+
+  // ============ 日期范围验证方法 ============
+
+  /**
+   * 验证表格为空(无数据)
+   */
+  verifyTableEmpty() {
+    cy.get('table tbody').should('not.exist');
+    // 或者验证空状态提示
+    cy.contains('暂无数据').should('be.visible');
+  }
+
+  /**
+   * 验证表格中显示的 study 日期在指定范围内
+   * @param startDate 开始日期
+   * @param endDate 结束日期
+   */
+  verifyStudyDatesWithinRange(startDate: Date, endDate: Date) {
+    cy.get('table tbody tr').each(($row) => {
+      // 获取表格中的日期列(假设日期在特定列)
+      cy.wrap($row)
+        .find('td')
+        .then(($cells) => {
+          // 查找包含日期的单元格(通常是 StudyStartDatetime 列)
+          const dateText = $cells
+            .toArray()
+            .map((cell) => cell.textContent)
+            .find((text) => text && /\d{4}-\d{2}-\d{2}/.test(text || ''));
+
+          if (dateText) {
+            const studyDate = new Date(dateText);
+            expect(studyDate.getTime()).to.be.gte(startDate.getTime());
+            expect(studyDate.getTime()).to.be.lte(endDate.getTime());
+          }
+        });
+    });
+  }
+
+  /**
+   * 获取表格中第一行的检查日期
+   */
+  getFirstRowStudyDate() {
+    return cy
+      .get('table tbody tr')
+      .first()
+      .find('td')
+      .then(($cells) => {
+        // 查找包含日期的单元格
+        const dateText = $cells
+          .toArray()
+          .map((cell) => cell.textContent)
+          .find((text) => text && /\d{4}-\d{2}-\d{2}/.test(text || ''));
+
+        return dateText ? new Date(dateText) : null;
+      });
+  }
+
+  /**
+   * 验证表格中所有行的日期都是今天
+   */
+  verifyAllStudyDatesAreToday() {
+    const today = new Date();
+    today.setHours(0, 0, 0, 0);
+    const tomorrow = new Date(today);
+    tomorrow.setDate(tomorrow.getDate() + 1);
+
+    this.verifyStudyDatesWithinRange(today, tomorrow);
+  }
+
+  /**
+   * 验证表格中所有行的日期在最近7天内
+   */
+  verifyAllStudyDatesWithinLast7Days() {
+    const today = new Date();
+    today.setHours(23, 59, 59, 999);
+    const sevenDaysAgo = new Date(today);
+    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
+    sevenDaysAgo.setHours(0, 0, 0, 0);
+
+    this.verifyStudyDatesWithinRange(sevenDaysAgo, today);
+  }
+
+  /**
+   * 验证表格至少有指定数量的行
+   * @param minCount 最小行数
+   */
+  verifyTableHasMinRows(minCount: number) {
+    cy.get('table tbody tr').should('have.length.at.least', minCount);
+  }
+
+  /**
+   * 验证表格显示指定数量的行
+   * @param count 期望的行数
+   */
+  verifyTableHasExactRows(count: number) {
+    if (count === 0) {
+      this.verifyTableEmpty();
+    } else {
+      cy.get('table tbody tr').should('have.length', count);
+    }
+  }
+
+  /**
+   * 获取表格中所有 study 的 StudyID
+   */
+  getAllStudyIds() {
+    return cy.get('table tbody tr').then(($rows) => {
+      const studyIds: string[] = [];
+      $rows.each((index, row) => {
+        const studyId = Cypress.$(row).attr('data-testid')?.replace('row-', '');
+        if (studyId) {
+          studyIds.push(studyId);
+        }
+      });
+      return studyIds;
+    });
+  }
+
+  /**
+   * 验证指定的 StudyID 在表格中
+   * @param studyId StudyID
+   */
+  verifyStudyIdExists(studyId: string) {
+    cy.get('table tbody').contains(studyId).should('be.visible');
+  }
+
+  /**
+   * 验证指定的 StudyID 不在表格中
+   * @param studyId StudyID
+   */
+  verifyStudyIdNotExists(studyId: string) {
+    cy.get('table tbody').contains(studyId).should('not.exist');
+  }
 }
 }
 
 
 export default HistoryPage;
 export default HistoryPage;

+ 320 - 0
cypress/support/pageObjects/SearchPanelPage.ts

@@ -0,0 +1,320 @@
+/**
+ * SearchPanelPage - 搜索面板的 Page Object Model
+ * 用于 Worklist 和 History 页面的搜索功能
+ */
+class SearchPanelPage {
+  /**
+   * 获取患者名称输入框
+   */
+  getPatientNameInput() {
+    return cy.get('input[placeholder*="患者姓名"], input[placeholder*="Patient Name"]');
+  }
+
+  /**
+   * 获取患者ID输入框
+   */
+  getPatientIdInput() {
+    return cy.get('input[placeholder*="患者ID"], input[placeholder*="Patient ID"]');
+  }
+
+  /**
+   * 获取登记号输入框
+   */
+  getAccessionNumberInput() {
+    return cy.get('input[placeholder*="登记号"], input[placeholder*="Registration"], input[placeholder*="Accession"]');
+  }
+
+  /**
+   * 输入患者名称
+   * @param name 患者名称
+   */
+  typePatientName(name: string) {
+    this.getPatientNameInput().clear().type(name);
+  }
+
+  /**
+   * 输入患者ID
+   * @param id 患者ID
+   */
+  typePatientId(id: string) {
+    this.getPatientIdInput().clear().type(id);
+  }
+
+  /**
+   * 输入登记号
+   * @param accNo 登记号
+   */
+  typeAccessionNumber(accNo: string) {
+    this.getAccessionNumberInput().clear().type(accNo);
+  }
+
+  /**
+   * 清空患者名称
+   */
+  clearPatientName() {
+    this.getPatientNameInput().clear();
+  }
+
+  /**
+   * 清空患者ID
+   */
+  clearPatientId() {
+    this.getPatientIdInput().clear();
+  }
+
+  /**
+   * 清空登记号
+   */
+  clearAccessionNumber() {
+    this.getAccessionNumberInput().clear();
+  }
+
+  // ============ 时间范围选择器操作 ============
+
+  /**
+   * 选择"今天"时间范围
+   */
+  selectTimeRangeToday() {
+    cy.contains('label', '今天').click();
+  }
+
+  /**
+   * 选择"7天"时间范围
+   */
+  selectTimeRange7Days() {
+    cy.contains('label', '7天').click();
+  }
+
+  /**
+   * 选择"所有"时间范围
+   */
+  selectTimeRangeAll() {
+    cy.contains('label', '所有').click();
+  }
+
+  /**
+   * 验证时间范围选中状态
+   * @param type 时间范围类型
+   */
+  verifyTimeRangeSelected(type: 'today' | '7days' | 'all') {
+    const labelText = {
+      today: '今天',
+      '7days': '7天',
+      all: '所有',
+    };
+
+    cy.contains('label', labelText[type])
+      .find('input[type="radio"]')
+      .should('be.checked');
+  }
+
+  /**
+   * 获取时间范围选择器组
+   */
+  getTimeRangeSelector() {
+    return cy.get('.ant-radio-group');
+  }
+
+  // ============ 自定义日期范围操作 ============
+
+  /**
+   * 选择自定义日期范围
+   * @param startDate 开始日期,格式:YYYY-MM-DD
+   * @param endDate 结束日期,格式:YYYY-MM-DD
+   */
+  selectCustomDateRange(startDate: string, endDate: string) {
+    // 点击日期范围选择器
+    cy.get('.ant-picker-range').click();
+
+    // 选择开始日期
+    this.selectDateInPicker(startDate, 'start');
+
+    // 选择结束日期
+    this.selectDateInPicker(endDate, 'end');
+  }
+
+  /**
+   * 在日期选择器中选择日期
+   * @param date 日期字符串,格式:YYYY-MM-DD
+   * @param type 'start' 或 'end'
+   */
+  private selectDateInPicker(date: string, type: 'start' | 'end') {
+    const [year, month, day] = date.split('-').map(Number);
+
+    // 等待日期选择器面板显示
+    cy.get('.ant-picker-dropdown').should('be.visible');
+
+    // 选择年份
+    if (type === 'start') {
+      cy.get('.ant-picker-dropdown')
+        .first()
+        .find('.ant-picker-header-year-btn')
+        .click();
+      cy.get('.ant-picker-year-panel').contains('.ant-picker-cell', year.toString()).click();
+    }
+
+    // 选择月份
+    if (type === 'start') {
+      cy.get('.ant-picker-dropdown')
+        .first()
+        .find('.ant-picker-header-month-btn')
+        .click();
+      cy.get('.ant-picker-month-panel')
+        .contains('.ant-picker-cell', new RegExp(`^${month}月?$`))
+        .click();
+    }
+
+    // 选择日期
+    cy.get('.ant-picker-dropdown')
+      .find('.ant-picker-cell')
+      .not('.ant-picker-cell-disabled')
+      .contains(new RegExp(`^${day}$`))
+      .click();
+  }
+
+  /**
+   * 清空自定义日期范围
+   */
+  clearCustomDateRange() {
+    cy.get('.ant-picker-range').within(() => {
+      cy.get('.ant-picker-clear').click({ force: true });
+    });
+  }
+
+  /**
+   * 获取日期范围选择器
+   */
+  getDateRangePicker() {
+    return cy.get('.ant-picker-range');
+  }
+
+  /**
+   * 验证日期范围选择器中的值
+   * @param startDate 开始日期,格式:YYYY-MM-DD
+   * @param endDate 结束日期,格式:YYYY-MM-DD
+   */
+  verifyDateRangeValue(startDate: string, endDate: string) {
+    cy.get('.ant-picker-range input').first().should('have.value', startDate);
+    cy.get('.ant-picker-range input').last().should('have.value', endDate);
+  }
+
+  /**
+   * 验证日期范围选择器为空
+   */
+  verifyDateRangeEmpty() {
+    cy.get('.ant-picker-range input').first().should('have.value', '');
+    cy.get('.ant-picker-range input').last().should('have.value', '');
+  }
+
+  // ============ 搜索按钮 ============
+
+  /**
+   * 点击搜索按钮
+   */
+  clickSearchButton() {
+    cy.contains('button', '搜索').click();
+  }
+
+  /**
+   * 获取搜索按钮
+   */
+  getSearchButton() {
+    return cy.contains('button', '搜索');
+  }
+
+  /**
+   * 验证搜索按钮可点击
+   */
+  verifySearchButtonEnabled() {
+    this.getSearchButton().should('not.be.disabled');
+  }
+
+  /**
+   * 验证搜索按钮禁用
+   */
+  verifySearchButtonDisabled() {
+    this.getSearchButton().should('be.disabled');
+  }
+
+  // ============ 整体验证 ============
+
+  /**
+   * 验证搜索面板可见
+   */
+  verifySearchPanelVisible() {
+    this.getPatientNameInput().should('be.visible');
+    this.getPatientIdInput().should('be.visible');
+    this.getAccessionNumberInput().should('be.visible');
+    this.getTimeRangeSelector().should('be.visible');
+    this.getDateRangePicker().should('be.visible');
+    this.getSearchButton().should('be.visible');
+  }
+
+  /**
+   * 清空所有搜索条件
+   */
+  clearAllFilters() {
+    this.clearPatientName();
+    this.clearPatientId();
+    this.clearAccessionNumber();
+    this.selectTimeRangeAll();
+  }
+
+  /**
+   * 填写完整的搜索条件并搜索
+   * @param options 搜索选项
+   */
+  searchWithFilters(options: {
+    patientName?: string;
+    patientId?: string;
+    accessionNumber?: string;
+    timeRange?: 'today' | '7days' | 'all';
+    customDateRange?: { start: string; end: string };
+  }) {
+    // 清空现有条件
+    this.clearAllFilters();
+
+    // 填写患者名称
+    if (options.patientName) {
+      this.typePatientName(options.patientName);
+    }
+
+    // 填写患者ID
+    if (options.patientId) {
+      this.typePatientId(options.patientId);
+    }
+
+    // 填写登记号
+    if (options.accessionNumber) {
+      this.typeAccessionNumber(options.accessionNumber);
+    }
+
+    // 选择预设时间范围
+    if (options.timeRange) {
+      switch (options.timeRange) {
+        case 'today':
+          this.selectTimeRangeToday();
+          break;
+        case '7days':
+          this.selectTimeRange7Days();
+          break;
+        case 'all':
+          this.selectTimeRangeAll();
+          break;
+      }
+    }
+
+    // 选择自定义日期范围
+    if (options.customDateRange) {
+      this.selectCustomDateRange(
+        options.customDateRange.start,
+        options.customDateRange.end
+      );
+    }
+
+    // 点击搜索
+    this.clickSearchButton();
+  }
+}
+
+export default SearchPanelPage;

+ 133 - 0
cypress/support/pageObjects/WorklistPage.ts

@@ -169,6 +169,139 @@ class WorklistPage {
   verifyModalNotExist() {
   verifyModalNotExist() {
     cy.get('.ant-modal-confirm').should('not.exist');
     cy.get('.ant-modal-confirm').should('not.exist');
   }
   }
+
+  // ============ 日期范围验证方法 ============
+
+  /**
+   * 验证表格为空(无数据)
+   */
+  verifyTableEmpty() {
+    cy.get('table tbody').should('not.exist');
+    // 或者验证空状态提示
+    cy.contains('暂无数据').should('be.visible');
+  }
+
+  /**
+   * 验证表格中显示的 study 日期在指定范围内
+   * @param startDate 开始日期
+   * @param endDate 结束日期
+   */
+  verifyStudyDatesWithinRange(startDate: Date, endDate: Date) {
+    cy.get('table tbody tr').each(($row) => {
+      // 获取表格中的日期列(假设日期在特定列)
+      cy.wrap($row)
+        .find('td')
+        .then(($cells) => {
+          // 查找包含日期的单元格(通常是 StudyStartDatetime 列)
+          const dateText = $cells
+            .toArray()
+            .map((cell) => cell.textContent)
+            .find((text) => text && /\d{4}-\d{2}-\d{2}/.test(text || ''));
+
+          if (dateText) {
+            const studyDate = new Date(dateText);
+            expect(studyDate.getTime()).to.be.gte(startDate.getTime());
+            expect(studyDate.getTime()).to.be.lte(endDate.getTime());
+          }
+        });
+    });
+  }
+
+  /**
+   * 获取表格中第一行的检查日期
+   */
+  getFirstRowStudyDate() {
+    return cy
+      .get('table tbody tr')
+      .first()
+      .find('td')
+      .then(($cells) => {
+        // 查找包含日期的单元格
+        const dateText = $cells
+          .toArray()
+          .map((cell) => cell.textContent)
+          .find((text) => text && /\d{4}-\d{2}-\d{2}/.test(text || ''));
+
+        return dateText ? new Date(dateText) : null;
+      });
+  }
+
+  /**
+   * 验证表格中所有行的日期都是今天
+   */
+  verifyAllStudyDatesAreToday() {
+    const today = new Date();
+    today.setHours(0, 0, 0, 0);
+    const tomorrow = new Date(today);
+    tomorrow.setDate(tomorrow.getDate() + 1);
+
+    this.verifyStudyDatesWithinRange(today, tomorrow);
+  }
+
+  /**
+   * 验证表格中所有行的日期在最近7天内
+   */
+  verifyAllStudyDatesWithinLast7Days() {
+    const today = new Date();
+    today.setHours(23, 59, 59, 999);
+    const sevenDaysAgo = new Date(today);
+    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
+    sevenDaysAgo.setHours(0, 0, 0, 0);
+
+    this.verifyStudyDatesWithinRange(sevenDaysAgo, today);
+  }
+
+  /**
+   * 验证表格至少有指定数量的行
+   * @param minCount 最小行数
+   */
+  verifyTableHasMinRows(minCount: number) {
+    cy.get('table tbody tr').should('have.length.at.least', minCount);
+  }
+
+  /**
+   * 验证表格显示指定数量的行
+   * @param count 期望的行数
+   */
+  verifyTableHasExactRows(count: number) {
+    if (count === 0) {
+      this.verifyTableEmpty();
+    } else {
+      cy.get('table tbody tr').should('have.length', count);
+    }
+  }
+
+  /**
+   * 获取表格中所有 study 的 StudyID
+   */
+  getAllStudyIds() {
+    return cy.get('table tbody tr').then(($rows) => {
+      const studyIds: string[] = [];
+      $rows.each((index, row) => {
+        const studyId = Cypress.$(row).attr('data-testid')?.replace('row-', '');
+        if (studyId) {
+          studyIds.push(studyId);
+        }
+      });
+      return studyIds;
+    });
+  }
+
+  /**
+   * 验证指定的 StudyID 在表格中
+   * @param studyId StudyID
+   */
+  verifyStudyIdExists(studyId: string) {
+    cy.get('table tbody').contains(studyId).should('be.visible');
+  }
+
+  /**
+   * 验证指定的 StudyID 不在表格中
+   * @param studyId StudyID
+   */
+  verifyStudyIdNotExists(studyId: string) {
+    cy.get('table tbody').contains(studyId).should('not.exist');
+  }
 }
 }
 
 
 export default WorklistPage;
 export default WorklistPage;

+ 513 - 0
docs/测试/按日期范围查询Study功能测试方案.md

@@ -0,0 +1,513 @@
+# 按日期范围查询 Study 功能 E2E 测试方案
+
+## 功能概述
+
+在 Worklist 和 History 列表中,用户可以通过日期范围查询 study。系统提供三个预设时间范围(今天、7天、所有)和自定义日期范围选择器。
+
+## 功能描述
+
+- **页面**: Worklist、History
+- **组件**: TimeRangeSelector(预设范围)+ DatePicker.RangePicker(自定义范围)
+- **预设选项**:
+  - 今天: 查询今天的 study
+  - 7天: 查询最近 7 天的 study
+  - 所有: 不限制时间范围
+- **自定义范围**: 用户可选择任意开始和结束日期
+
+## 测试用例汇总表
+
+| 用例ID | 描述 | 输入 | 预期输出 | 测试类型 | 理由 |
+|--------|------|------|----------|---------|------|
+| TC-WL-DATE-01 | Worklist - 选择"今天" | 点击"今天"单选按钮 → 点击搜索 | API 调用包含今天的日期范围,表格显示今天的 study | 正常 | 验证预设时间范围"今天"的基本功能 |
+| TC-WL-DATE-02 | Worklist - 选择"7天" | 点击"7天"单选按钮 → 点击搜索 | API 调用包含最近7天的日期范围,表格显示最近7天的 study | 正常 | 验证预设时间范围"7天"的基本功能 |
+| TC-WL-DATE-03 | Worklist - 选择"所有" | 点击"所有"单选按钮 → 点击搜索 | API 调用不包含日期范围限制,表格显示所有 study | 正常 | 验证预设时间范围"所有"的基本功能 |
+| TC-WL-DATE-04 | Worklist - 自定义日期范围 | 选择开始日期 2025-10-01,结束日期 2025-10-10 → 点击搜索 | API 调用包含指定的日期范围,表格显示该范围内的 study | 正常 | 验证自定义日期范围查询功能 |
+| TC-WL-DATE-05 | Worklist - 日期范围组合患者名称查询 | 选择"今天" + 输入患者名称"张三" → 点击搜索 | API 调用同时包含日期范围和患者名称,表格显示符合条件的 study | 正常 | 验证多条件组合查询功能 |
+| TC-WL-DATE-06 | Worklist - 清空自定义日期范围 | 选择日期范围后清空 → 点击搜索 | API 调用不包含日期范围,表格显示所有 study | 边界 | 验证清空日期范围的行为 |
+| TC-WL-DATE-07 | Worklist - 预设和自定义日期切换 | 先选择"今天" → 再选择自定义日期范围 → 点击搜索 | 自定义日期范围优先,API 使用自定义日期 | 正常 | 验证预设和自定义日期的优先级 |
+| TC-WL-DATE-08 | Worklist - 开始日期晚于结束日期 | 选择开始日期 2025-10-10,结束日期 2025-10-01 → 点击搜索 | 系统接受查询,API 调用包含该范围(或返回空结果) | 边界 | 验证日期顺序异常情况的处理 |
+| TC-WL-DATE-09 | Worklist - 未来日期范围 | 选择未来的日期范围 → 点击搜索 | API 调用包含未来日期范围,表格显示空结果或提示 | 边界 | 验证未来日期的处理 |
+| TC-WL-DATE-10 | Worklist - 同一天作为开始和结束 | 选择开始和结束都为 2025-10-01 → 点击搜索 | API 调用包含该日期,表格显示当天的 study | 边界 | 验证单日查询 |
+| TC-HIS-DATE-01 | History - 选择"今天" | 导航到 History → 点击"今天" → 点击搜索 | API 调用 status=Completed 和今天日期范围,显示今天完成的 study | 正常 | 验证 History 页面的"今天"功能 |
+| TC-HIS-DATE-02 | History - 选择"7天" | 导航到 History → 点击"7天" → 点击搜索 | API 调用 status=Completed 和7天日期范围,显示7天内完成的 study | 正常 | 验证 History 页面的"7天"功能 |
+| TC-HIS-DATE-03 | History - 选择"所有" | 导航到 History → 点击"所有" → 点击搜索 | API 调用 status=Completed 不限日期,显示所有完成的 study | 正常 | 验证 History 页面的"所有"功能 |
+| TC-HIS-DATE-04 | History - 自定义日期范围 | 导航到 History → 选择日期范围 → 点击搜索 | API 调用 status=Completed 和指定日期范围,显示该范围内完成的 study | 正常 | 验证 History 页面的自定义日期功能 |
+| TC-DATE-STATE-01 | Redux 状态同步 - timeRangeType | 选择"今天" | Redux search.timeRangeType = "today" | 正常 | 验证预设时间范围在 Redux 中的状态管理 |
+| TC-DATE-STATE-02 | Redux 状态同步 - 自定义日期 | 选择自定义日期范围 | Redux search.start_time 和 end_time 正确存储 RFC3339Nano 格式日期 | 正常 | 验证自定义日期在 Redux 中的状态管理 |
+
+## 测试套件设计
+
+### 测试套件 1: Worklist 日期范围查询
+
+**文件**: `cypress/e2e/patient/worklist/date-range-filter.cy.ts`
+
+**测试场景**:
+
+#### 正常场景
+1. **预设时间范围查询**
+   - 今天
+   - 7天
+   - 所有
+2. **自定义日期范围查询**
+   - 指定开始和结束日期
+3. **组合查询**
+   - 日期范围 + 患者名称
+   - 日期范围 + 患者ID
+   - 日期范围 + 登记号
+
+#### 边界场景
+1. **日期范围边界**
+   - 清空日期范围
+   - 同一天作为开始和结束
+   - 开始日期晚于结束日期
+   - 未来日期范围
+2. **切换场景**
+   - 预设时间范围之间切换
+   - 预设时间范围切换到自定义日期
+   - 自定义日期切换到预设时间范围
+
+#### 异常场景
+1. **API 失败**
+   - 查询 API 返回错误
+   - 查询超时
+2. **无结果**
+   - 日期范围内无数据
+
+### 测试套件 2: History 日期范围查询
+
+**文件**: `cypress/e2e/patient/history/date-range-filter.cy.ts`
+
+**测试场景**: 与 Worklist 类似,但验证 status=Completed 参数
+
+### 测试套件 3: Redux 状态管理
+
+**文件**: 集成在上述测试文件中
+
+**测试场景**: 验证日期范围选择后 Redux 状态的正确性
+
+## 测试目标
+
+1. **功能完整性**: 验证所有预设和自定义日期范围查询功能正常工作
+2. **状态管理**: 验证 Redux 状态正确同步和持久化
+3. **API 集成**: 验证查询参数正确传递给后端 API
+4. **用户体验**: 验证日期选择器交互流畅,查询结果准确
+5. **边界处理**: 验证各种边界情况的正确处理
+6. **错误处理**: 验证 API 错误和无结果情况的处理
+
+## 验证点详细说明
+
+### 1. 预设时间范围验证点
+
+**今天**:
+- Redux `timeRangeType` = "today"
+- API 调用包含 `start_time` = 今天 00:00:00
+- API 调用包含 `end_time` = 今天 23:59:59
+- 表格显示今天的 study
+
+**7天**:
+- Redux `timeRangeType` = "7days"
+- API 调用包含 `start_time` = 7天前 00:00:00
+- API 调用包含 `end_time` = 今天 23:59:59
+- 表格显示最近7天的 study
+
+**所有**:
+- Redux `timeRangeType` = "all"
+- API 调用不包含 `start_time` 和 `end_time` 参数
+- 表格显示所有 study
+
+### 2. 自定义日期范围验证点
+
+- DatePicker.RangePicker 可以选择日期
+- Redux `start_time` 和 `end_time` 以 RFC3339Nano 格式存储
+- API 调用包含选定的日期范围
+- 表格显示指定日期范围的 study
+
+### 3. 组合查询验证点
+
+- 多个查询条件同时生效
+- API 调用包含所有指定的参数
+- 表格显示同时满足所有条件的 study
+
+### 4. API 调用验证点
+
+**Worklist**:
+- URL: `/dr/api/v1/auth/study`
+- 参数包含: `status=Arrived,InProgress`
+- 日期参数: `start_time`、`end_time`
+
+**History**:
+- URL: `/dr/api/v1/auth/study`
+- 参数包含: `status=Completed`
+- 日期参数: `start_time`、`end_time`
+
+## 需要 Mock 的请求与响应
+
+### 1. Mock: 今天的 study 数据
+
+**请求**: 
+```
+GET /dr/api/v1/auth/study?status=Arrived,InProgress&start_time=2025-10-13T00:00:00.000+08:00&end_time=2025-10-13T23:59:59.999+08:00
+```
+
+**响应**:
+```json
+{
+  "code": "0x000000",
+  "description": "Success",
+  "data": {
+    "@type": "type.googleapis.com/dr.study.StudyList",
+    "count": 2,
+    "studies": [
+      {
+        "study_id": "TODAY001",
+        "patient_name": "今天患者1",
+        "study_start_datetime": "2025-10-13T08:00:00Z",
+        "study_status": "Arrived",
+        ...
+      },
+      {
+        "study_id": "TODAY002",
+        "patient_name": "今天患者2",
+        "study_start_datetime": "2025-10-13T10:00:00Z",
+        "study_status": "InProgress",
+        ...
+      }
+    ]
+  }
+}
+```
+
+### 2. Mock: 7天的 study 数据
+
+**请求**:
+```
+GET /dr/api/v1/auth/study?status=Arrived,InProgress&start_time=2025-10-06T00:00:00.000+08:00&end_time=2025-10-13T23:59:59.999+08:00
+```
+
+**响应**: 返回 5 条记录,日期分布在最近 7 天
+
+### 3. Mock: 所有 study 数据
+
+**请求**:
+```
+GET /dr/api/v1/auth/study?status=Arrived,InProgress
+```
+
+**响应**: 返回所有 study,不限日期
+
+### 4. Mock: 自定义日期范围数据
+
+**请求**:
+```
+GET /dr/api/v1/auth/study?status=Arrived,InProgress&start_time=2025-10-01T00:00:00.000+08:00&end_time=2025-10-10T23:59:59.999+08:00
+```
+
+**响应**: 返回指定日期范围的 study
+
+### 5. Mock: 组合查询数据
+
+**请求**:
+```
+GET /dr/api/v1/auth/study?status=Arrived,InProgress&start_time=...&end_time=...&patient_name=张三
+```
+
+**响应**: 返回符合多个条件的 study
+
+### 6. Mock: 空结果
+
+**请求**: 未来日期范围或无数据的日期范围
+
+**响应**:
+```json
+{
+  "code": "0x000000",
+  "description": "Success",
+  "data": {
+    "@type": "type.googleapis.com/dr.study.StudyList",
+    "count": 0,
+    "studies": []
+  }
+}
+```
+
+### 7. Mock: API 失败
+
+**请求**: 任意查询
+
+**响应**:
+```json
+{
+  "statusCode": 500,
+  "body": {
+    "code": "0x000001",
+    "description": "Internal Server Error",
+    "solution": "请稍后重试"
+  }
+}
+```
+
+## Page Object Model (POM)
+
+### 新建 POM
+
+#### SearchPanelPage
+
+**文件**: `cypress/support/pageObjects/SearchPanelPage.ts`
+
+**方法**:
+```typescript
+class SearchPanelPage {
+  // 输入框操作
+  getPatientNameInput()
+  getPatientIdInput()
+  getAccessionNumberInput()
+  typePatientName(name: string)
+  typePatientId(id: string)
+  typeAccessionNumber(accNo: string)
+  
+  // 时间范围选择器操作
+  selectTimeRangeToday()
+  selectTimeRange7Days()
+  selectTimeRangeAll()
+  verifyTimeRangeSelected(type: 'today' | '7days' | 'all')
+  
+  // 自定义日期范围操作
+  selectCustomDateRange(startDate: string, endDate: string)
+  clearCustomDateRange()
+  getStartDateInput()
+  getEndDateInput()
+  
+  // 搜索按钮
+  clickSearchButton()
+  
+  // 验证
+  verifySearchPanelVisible()
+}
+```
+
+### 修改现有 POM
+
+#### WorklistPage
+
+**新增方法**:
+```typescript
+// 验证表格中显示的日期范围
+verifyStudyDatesWithinRange(startDate: Date, endDate: Date)
+
+// 验证表格为空
+verifyTableEmpty()
+
+// 获取表格中第一行的日期
+getFirstRowStudyDate()
+```
+
+#### HistoryPage
+
+**新增方法**: 与 WorklistPage 相同
+
+## 测试执行计划
+
+### 第一阶段: Worklist 核心功能测试(优先级:高)
+
+**时间**: 2-3 小时
+
+1. TC-WL-DATE-01: 选择"今天"
+2. TC-WL-DATE-02: 选择"7天"
+3. TC-WL-DATE-03: 选择"所有"
+4. TC-WL-DATE-04: 自定义日期范围
+
+### 第二阶段: Worklist 组合和边界测试(优先级:中)
+
+**时间**: 2-3 小时
+
+5. TC-WL-DATE-05: 组合查询
+6. TC-WL-DATE-06: 清空日期范围
+7. TC-WL-DATE-07: 预设和自定义切换
+8. TC-WL-DATE-08: 开始日期晚于结束日期
+9. TC-WL-DATE-09: 未来日期范围
+10. TC-WL-DATE-10: 同一天查询
+
+### 第三阶段: History 功能测试(优先级:中)
+
+**时间**: 1-2 小时
+
+11. TC-HIS-DATE-01: History 选择"今天"
+12. TC-HIS-DATE-02: History 选择"7天"
+13. TC-HIS-DATE-03: History 选择"所有"
+14. TC-HIS-DATE-04: History 自定义日期范围
+
+### 第四阶段: 状态管理测试(优先级:低)
+
+**时间**: 1 小时
+
+15. TC-DATE-STATE-01: Redux 状态同步 - timeRangeType
+16. TC-DATE-STATE-02: Redux 状态同步 - 自定义日期
+
+## 测试覆盖率目标
+
+- **功能覆盖**: 100% - 所有日期范围查询相关功能
+- **场景覆盖**: 
+  - 正常场景: 100%
+  - 边界场景: 90%+
+  - 异常场景: 80%+
+- **组件覆盖**: 
+  - TimeRangeSelector: 100%
+  - DatePicker.RangePicker: 90%+
+  - SearchPanel: 相关部分 100%
+
+## 潜在遗漏风险
+
+### 1. 时区问题
+**风险**: 日期时间可能因时区差异导致错误
+**缓解**: 
+- 明确使用 +08:00 时区
+- 测试跨时区边界情况(如 23:59:59)
+
+### 2. 日期格式问题
+**风险**: RFC3339Nano 格式不一致
+**缓解**: 
+- 验证 API 请求中的日期格式
+- 测试不同格式的日期输入
+
+### 3. 预设和自定义日期的优先级
+**风险**: 用户同时选择预设和自定义日期时行为不明确
+**缓解**: 
+- 明确测试 TC-WL-DATE-07
+- 文档化预期行为
+
+### 4. 分页与日期范围的交互
+**风险**: 切换日期范围时分页状态可能不正确
+**缓解**: 
+- 验证查询后分页重置为第一页
+- 测试在第二页时改变日期范围
+
+### 5. 性能问题
+**风险**: 大日期范围查询可能导致性能问题
+**缓解**: 
+- Mock 大量数据测试
+- 验证加载状态显示
+
+### 6. 并发查询
+**风险**: 快速多次点击搜索可能导致状态混乱
+**缓解**: 
+- 测试防抖/节流机制
+- 验证最后一次查询结果正确显示
+
+## 如何运行测试
+
+### 运行所有日期范围查询测试
+```bash
+npx cypress run --spec "cypress/e2e/patient/worklist/date-range-filter.cy.ts,cypress/e2e/patient/history/date-range-filter.cy.ts"
+```
+
+### 运行 Worklist 测试
+```bash
+npx cypress run --spec "cypress/e2e/patient/worklist/date-range-filter.cy.ts"
+```
+
+### 运行 History 测试
+```bash
+npx cypress run --spec "cypress/e2e/patient/history/date-range-filter.cy.ts"
+```
+
+### 在 Cypress UI 中运行
+```bash
+npx cypress open
+```
+然后选择相应的测试文件
+
+### 运行特定测试用例
+在测试文件中使用 `.only`:
+```typescript
+it.only('应该选择今天查询 study', () => {
+  // 测试代码
+});
+```
+
+## 测试环境要求
+
+### 依赖的 Mock Handlers
+1. **用户认证**: `mockLoginSuccess()`
+2. **国际化**: i18n mocks
+3. **配额**: quota mocks
+4. **协议数据**: protocol mocks(患者类型、身体部位等)
+5. **日期查询**: 新建的 dateRangeQuery mocks
+
+### 测试数据要求
+- 至少 10 条不同日期的 study 数据
+- 覆盖今天、7天内、7天外的数据
+- 包含不同状态的 study(Arrived、InProgress、Completed)
+
+### 环境配置
+- 使用固定的"当前日期"进行测试,避免时间依赖问题
+- 或使用 Cypress 的时钟功能 `cy.clock()`
+
+## 验收标准
+
+### 功能验收
+- ✅ 所有测试用例通过
+- ✅ 预设时间范围(今天、7天、所有)工作正常
+- ✅ 自定义日期范围查询功能正常
+- ✅ 日期范围与其他查询条件组合正常
+- ✅ Worklist 和 History 页面功能一致
+
+### 质量验收
+- ✅ 测试代码遵循 Given-When-Then 模式
+- ✅ 使用 POM 封装,代码可维护
+- ✅ Mock 数据完整,覆盖各种场景
+- ✅ 测试文档完整,说明清晰
+
+### 用户体验验收
+- ✅ 日期选择器交互流畅
+- ✅ 查询结果准确
+- ✅ 错误情况有适当提示
+- ✅ 状态反馈及时(加载、成功、失败)
+
+## 文档版本
+
+- **版本**: 1.0.0
+- **创建日期**: 2025-10-13
+- **最后更新**: 2025-10-13
+- **作者**: Cline AI Assistant
+- **状态**: 待审核
+
+## 附录
+
+### 时间格式说明
+
+**RFC3339Nano 格式**:
+```
+2025-10-13T08:00:00.000+08:00
+```
+- 日期: YYYY-MM-DD
+- 时间: HH:mm:ss.SSS
+- 时区: +08:00 (中国标准时间)
+
+### Redux State 结构
+
+```typescript
+{
+  search: {
+    timeRangeType: 'today' | '7days' | 'all',
+    start_time: string, // RFC3339Nano 格式
+    end_time: string,   // RFC3339Nano 格式
+    id: string,
+    name: string,
+    acc_no: string,
+    // ...其他字段
+  }
+}
+```
+
+### API 查询参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| status | string | 是 | Worklist: "Arrived,InProgress"<br/>History: "Completed" |
+| start_time | string | 否 | 开始日期,RFC3339Nano 格式 |
+| end_time | string | 否 | 结束日期,RFC3339Nano 格式 |
+| patient_id | string | 否 | 患者ID |
+| patient_name | string | 否 | 患者姓名 |
+| access_number | string | 否 | 登记号 |
+| page | number | 是 | 页码,从1开始 |
+| page_size | number | 是 | 每页记录数 |