import { TIMEOUTS } from '../../utils/timeouts';
import { Page, Locator, expect } from '@playwright/test';

/**
 * 企业小程序 H5 URL
 */
const MINI_BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:8080';
const MINI_LOGIN_URL = `${MINI_BASE_URL}/mini`;

/**
 * 订单详情页相关类型定义 (Story 13.11)
 */

/**
 * 订单详情页头部数据
 */
export interface OrderHeaderData {
  /** 订单名称 */
  orderName: string;
  /** 订单编号（可选，可能不存在） */
  orderNo?: string;
  /** 订单状态 */
  orderStatus: string;
  /** 创建时间（格式：YYYY-MM-DD HH:mm） */
  createdAt: string;
  /** 更新时间（可选） */
  updatedAt?: string;
  /** 企业名称 */
  companyName: string;
  /** 平台标识 */
  platform: string;
}

/**
 * 订单详情页基本信息数据
 * 注意：预计人数和预计结束字段在数据库中不存在，已从接口中移除
 */
export interface OrderBasicInfoData {
  /** 实际人数 */
  actualCount?: number;
  /** 预计开始日期（格式：YYYY-MM-DD） */
  expectedStartDate?: string;
  /** 实际开始日期（可选） */
  actualStartDate?: string;
  /** 实际结束日期（可选） */
  actualEndDate?: string;
  /** 渠道（可选） */
  channel?: string;
}

/**
 * 统计卡片数据结构 (Story 13.12)
 */
export interface StatisticsCardData {
  cardName: string;
  currentValue: string;
  compareValue?: string;
  compareDirection?: 'up' | 'down' | 'same';
}

/**
 * 统计图表数据结构 (Story 13.12)
 */
export interface StatisticsChartData {
  chartName: string;
  chartType: 'bar' | 'pie' | 'ring' | 'line';
  isVisible: boolean;
}


/**
 * 订单打卡数据统计
 */
export interface OrderCheckInStats {
  /** 本月打卡人数 */
  monthlyCheckInCount: number;
  /** 工资视频数量 */
  salaryVideoCount: number;
  /** 个税视频数量 */
  taxVideoCount: number;
}

/**
 * 订单详情页统计数据结构 (Story 13.14)
 */
export interface OrderDetailStats {
  /** 实际人数 */
  actualPeople: number;
  /** 本月打卡统计 */
  checkinStats: { current: number; total: number; percentage: number };
  /** 工资视频统计 */
  salaryVideoStats: { current: number; total: number; percentage: number };
  /** 个税视频统计 */
  taxVideoStats: { current: number; total: number; percentage: number };
}

/**
 * 人才卡片摘要数据
 */
export interface PersonSummaryData {
  /** 姓名 */
  name: string;
  /** 残疾类型 */
  disabilityType?: string;
  /** 性别 */
  gender: string;
  /** 入职日期（格式：YYYY-MM-DD） */
  hireDate?: string;
  /** 工作状态 */
  workStatus: string;
}

/**
 * 人才详情页头部数据结构 (Story 13.10)
 */
export interface TalentHeaderData {
  name: string;
  disabilityType?: string;
  disabilityLevel?: string;
  status?: string;
  currentSalary?: string;
  workDays?: string;
  attendanceRate?: string;
}

/**
 * 人才详情页基本信息数据结构 (Story 13.10)
 */
export interface BasicInfoData {
  gender?: string;
  age?: string;
  idCard?: string;
  disabilityCard?: string;
  address?: string;
}

/**
 * 人才详情页工作信息数据结构 (Story 13.10)
 */
export interface WorkInfoData {
  hireDate?: string;
  workStatus?: string;
  orderName?: string;
  positionType?: string;
  workDays?: string;
  attendanceRate?: string;
}

/**
 * 人才详情页薪资信息数据结构 (Story 13.10)
 */
export interface SalaryInfoData {
  currentSalary?: string;
}

/**
 * 薪资历史记录 (Story 13.10)
 */
export interface SalaryHistoryRecord {
  orderName: string;
  salary: string;
  date: string;
}

/**
 * 工作历史记录 (Story 13.10)
 */
export interface WorkHistoryRecord {
  orderName: string;
  workStatus: string;
  salary: string;
  dateRange: string;
}

/**
 * 人才列表项数据结构 (Story 13.9)
 */
export interface TalentListItem {
  /** 人员 ID */
  personId: number;
  /** 姓名 */
  name: string;
  /** 残疾类型 */
  disabilityType: string;
  /** 残疾等级 */
  disabilityLevel: string;
  /** 性别 */
  gender: string;
  /** 年龄（计算得出） */
  age: string;
  /** 工作状态 */
  jobStatus: string;
  /** 最新入职日期 */
  latestJoinDate: string;
  /** 薪资 */
  salaryDetail: string;
}

/**
 * 人才卡片信息 (Story 13.9)
 */
export interface TalentCardInfo {
  /** 人员 ID */
  personId?: number;
  /** 姓名 */
  name: string;
  /** 残疾类型 */
  disabilityType?: string;
  /** 残疾等级 */
  disabilityLevel?: string;
  /** 性别 */
  gender?: string;
  /** 年龄 */
  age?: string;
  /** 工作状态 */
  jobStatus?: string;
  /** 最新入职日期 */
  latestJoinDate?: string;
  /** 薪资 */
  salary?: string;
}

/**
 * 企业小程序 Page Object
 *
 * 用于企业小程序 E2E 测试
 * H5 页面路径: /mini
 *
 * 主要功能：
 * - 小程序登录（手机号 + 密码）
 * - Token 管理
 * - 页面导航和验证
 *
 * @example
 * ```typescript
 * const miniPage = new EnterpriseMiniPage(page);
 * await miniPage.goto();
 * await miniPage.login('13800138000', 'password123');
 * await miniPage.expectLoginSuccess();
 * ```
 */
export class EnterpriseMiniPage {
  readonly page: Page;

  // ===== 页面级选择器 =====
  /** 登录页面容器 */
  readonly loginPage: Locator;
  /** 页面标题 */
  readonly pageTitle: Locator;

  // ===== 登录表单选择器 =====
  /** 手机号输入框 */
  readonly phoneInput: Locator;
  /** 密码输入框 */
  readonly passwordInput: Locator;
  /** 登录按钮 */
  readonly loginButton: Locator;

  // ===== 主页选择器（登录后） =====
  /** 用户信息显示区域 */
  readonly userInfo: Locator;
  /** 设置按钮 */
  readonly settingsButton: Locator;
  /** 退出登录按钮 */
  readonly logoutButton: Locator;

  constructor(page: Page) {
    this.page = page;

    // 初始化登录页面选择器
    // Taro 组件在 H5 渲染时会传递 data-testid 到 DOM (使用 taro-view-core 等组件)
    this.loginPage = page.getByTestId('mini-login-page');
    this.pageTitle = page.getByTestId('mini-page-title');

    // 登录表单选择器 - 使用 data-testid
    this.phoneInput = page.getByTestId('mini-phone-input');
    this.passwordInput = page.getByTestId('mini-password-input');
    this.loginButton = page.getByTestId('mini-login-button');

    // 主页选择器（登录后可用）
    this.userInfo = page.getByTestId('mini-user-info');
    // 设置按钮
    this.settingsButton = page.getByText('设置').nth(1);
    // 退出登录按钮 - 使用 getByText 而非 getByRole
    this.logoutButton = page.getByText('退出登录');
  }

  // ===== 导航和基础验证 =====

  /**
   * 移除开发服务器的覆盖层 iframe（防止干扰测试）
   */
  private async removeDevOverlays(): Promise<void> {
    await this.page.evaluate(() => {
      // 移除 react-refresh-overlay 和 webpack-dev-server-client-overlay
      const overlays = document.querySelectorAll('#react-refresh-overlay, #webpack-dev-server-client-overlay');
      overlays.forEach(overlay => overlay.remove());

      // 移除 vConsole 开发者工具覆盖层
      const vConsole = document.querySelector('#__vconsole');
      if (vConsole) {
        vConsole.remove();
      }
    });
  }

  /**
   * 导航到企业小程序 H5 登录页面
   */
  async goto(): Promise<void> {
    await this.page.goto(MINI_LOGIN_URL);
    // 移除开发服务器的覆盖层
    await this.removeDevOverlays();
    // 使用 auto-waiting 机制，等待页面容器可见
    await this.expectToBeVisible();
  }

  /**
   * 验证登录页面关键元素可见
   */
  async expectToBeVisible(): Promise<void> {
    // 等待页面容器可见
    await this.loginPage.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
    // 验证页面标题
    await this.pageTitle.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
  }

  // ===== 登录功能方法 =====

  /**
   * 填写手机号
   * @param phone 手机号（11位数字）
   *
   * 注意：使用 fill() 方法并添加验证步骤确保密码输入完整
   * Taro Input 组件需要完整的事件流才能正确更新 react-hook-form 状态
   */
  async fillPhone(phone: string): Promise<void> {
    // 先移除覆盖层，确保输入可操作
    await this.removeDevOverlays();
    // 点击聚焦，然后清空（使用 Ctrl+A + Backspace 模拟用户操作）
    await this.phoneInput.click();
    // 等待元素聚焦
    await this.page.waitForTimeout(100);
    // 使用 type 方法输入，会自动覆盖现有内容
    await this.phoneInput.type(phone, { delay: 50 });
    // 等待表单验证更新
    await this.page.waitForTimeout(200);
  }

  /**
   * 填写密码
   * @param password 密码（6-20位）
   *
   * 注意：taro-input-core 是 Taro 框架的自定义组件，不是标准 HTML 元素
   * 需要使用 evaluate() 直接操作 DOM 元素来设置值和触发事件
   */
  async fillPassword(password: string): Promise<void> {
    await this.removeDevOverlays();
    await this.passwordInput.click();
    await this.page.waitForTimeout(100);
    
    // taro-input-core 不是标准 input 元素，使用 JS 直接设置值并触发事件
    await this.passwordInput.evaluate((el, val) => {
      // 尝试找到内部的真实 input 元素
      const nativeInput = el.querySelector('input') || el;
      if (nativeInput instanceof HTMLInputElement) {
        nativeInput.value = val;
        nativeInput.dispatchEvent(new Event('input', { bubbles: true }));
        nativeInput.dispatchEvent(new Event('change', { bubbles: true }));
      } else {
        // 如果找不到 input 元素，设置 value 属性
        (el as HTMLInputElement).value = val;
        el.dispatchEvent(new Event('input', { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
      }
    }, password);
    
    await this.page.waitForTimeout(300);
  }

  /**
   * 点击登录按钮
   */
  async clickLoginButton(): Promise<void> {
    // 使用 force: true 避免被开发服务器的覆盖层阻止
    await this.loginButton.click({ force: true });
  }

  /**
   * 执行登录操作（完整流程）
   * @param phone 手机号
   * @param password 密码
   */
  async login(phone: string, password: string): Promise<void> {
    await this.fillPhone(phone);
    await this.fillPassword(password);
    await this.clickLoginButton();
  }

  /**
   * 验证登录成功
   *
   * 登录成功后应该跳转到主页或显示用户信息
   */
  async expectLoginSuccess(): Promise<void> {
    // 使用 auto-waiting 机制，等待 URL 变化或用户信息显示
    // 小程序登录成功后会跳转到 dashboard 页面

    // 等待 URL 变化，使用 Promise.race 实现超时
    const urlChanged = await this.page.waitForURL(
      url => url.pathname.includes('/dashboard') || url.pathname.includes('/pages/yongren/dashboard'),
      { timeout: TIMEOUTS.PAGE_LOAD }
    ).then(() => true).catch(() => false);

    // 如果 URL 没有变化，检查 token 是否被存储
    if (!urlChanged) {
      const token = await this.getToken();
      if (!token) {
        throw new Error('登录失败：URL 未跳转且 token 未存储');
      }
    }
  }

  /**
   * 验证登录失败（错误提示显示）
   * @param expectedErrorMessage 预期的错误消息（可选）
   */
  async expectLoginError(expectedErrorMessage?: string): Promise<void> {
    // 等待一下，让后端响应或前端验证生效
    await this.page.waitForTimeout(1000);

    // 验证仍然在登录页面（未跳转）
    const currentUrl = this.page.url();
    expect(currentUrl).toContain('/mini');

    // 验证登录页面容器仍然可见
    await expect(this.loginPage).toBeVisible();

    // 如果提供了预期的错误消息，尝试验证
    if (expectedErrorMessage) {
      // 尝试查找错误消息（可能在 Toast、Modal 或表单验证中）
      const errorElement = this.page.getByText(expectedErrorMessage, { exact: false }).first();
      await errorElement.isVisible().catch(() => false);
      // 不强制要求错误消息可见，因为后端可能不会返回错误
    }
  }

  // ===== Token 管理方法 =====

  /**
   * 获取当前存储的 token
   * @returns token 字符串，如果不存在则返回 null
   *
   * 注意：Taro.getStorageSync 在 H5 环境下映射到 localStorage
   * Taro.setStorageSync 会将数据包装为 JSON 格式：{"data":"VALUE"}
   * 因此需要解析 JSON 并提取 data 字段
   *
   * Taro H5 可能使用以下键名格式：
   * - 直接键名: 'enterprise_token'
   * - 带前缀: 'taro_app_storage_key'
   * - 或者其他变体
   */
  async getToken(): Promise<string | null> {
    const result = await this.page.evaluate(() => {
      // 尝试各种可能的键名
      // 1. 直接键名 - Taro 的 setStorageSync 将数据包装为 {"data":"VALUE"}
      const token = localStorage.getItem('enterprise_token');
      if (token) {
        try {
          // Taro 格式: {"data":"JWT_TOKEN"}
          const parsed = JSON.parse(token);
          if (parsed.data) {
            return parsed.data;
          }
          return token;
        } catch {
          return token;
        }
      }

      // 2. 获取所有 localStorage 键，查找可能的 token
      const keys = Object.keys(localStorage);
      const prefixedKeys = keys.filter(k => k.includes('token') || k.includes('auth'));
      for (const key of prefixedKeys) {
        const value = localStorage.getItem(key);
        if (value) {
          try {
            // 尝试解析 Taro 格式
            const parsed = JSON.parse(value);
            if (parsed.data && parsed.data.length > 20) { // JWT token 通常很长
              return parsed.data;
            }
          } catch {
            // 不是 JSON 格式，直接使用
            if (value.length > 20) {
              return value;
            }
          }
        }
      }

      // 3. 其他常见键名
      const otherTokens = [
        localStorage.getItem('token'),
        localStorage.getItem('auth_token'),
        sessionStorage.getItem('token'),
        sessionStorage.getItem('auth_token')
      ].filter(Boolean);

      for (const t of otherTokens) {
        if (t) {
          try {
            const parsed = JSON.parse(t);
            if (parsed.data) return parsed.data;
          } catch {
            if (t.length > 20) return t;
          }
        }
      }

      return null;
    });
    return result;
  }

  /**
   * 设置 token（用于测试前置条件）
   * @param token token 字符串
   */
  async setToken(token: string): Promise<void> {
    await this.page.evaluate((t) => {
      localStorage.setItem('token', t);
      localStorage.setItem('auth_token', t);
    }, token);
  }

  /**
   * 清除所有认证相关的存储
   */
  async clearAuth(): Promise<void> {
    await this.page.evaluate(() => {
      // 清除企业小程序相关的认证数据
      localStorage.removeItem('enterprise_token');
      localStorage.removeItem('enterpriseUserInfo');

      // 清除其他常见 token 键
      localStorage.removeItem('token');
      localStorage.removeItem('auth_token');
      sessionStorage.removeItem('token');
      sessionStorage.removeItem('auth_token');
    });
  }

  // ===== 主页元素验证方法 =====

  /**
   * 验证主页元素可见（登录后）
   * 根据实际小程序主页结构调整
   */
  async expectHomePageVisible(): Promise<void> {
    // 使用 auto-waiting 机制，等待主页元素可见
    // 注意：此方法将在 Story 12.5 E2E 测试中使用，当前仅提供基础结构
    // 根据实际小程序主页的 data-testid 调整
    const dashboard = this.page.getByTestId('mini-dashboard');
    await dashboard.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  }

  /**
   * 获取用户信息显示的文本
   * @returns 用户信息文本
   */
  async getUserInfoText(): Promise<string | null> {
    const userInfo = this.userInfo;
    const count = await userInfo.count();
    if (count === 0) {
      return null;
    }
    return await userInfo.textContent();
  }

  // ===== 导航方法 (Story 13.7) =====

  /**
   * 快捷操作按钮类型
   */
  readonly quickActionButtons = {
    talentPool: '人才库',
    dataStats: '数据统计',
    orderManagement: '订单管理',
    settings: '设置',
  } as const;

  /**
   * 底部导航按钮类型
   */
  readonly bottomNavButtons = {
    home: '首页',
    talent: '人才',
    order: '订单',
    data: '数据',
    settings: '设置',
  } as const;

  /**
   * 点击快捷操作按钮 (Story 13.7)
   * @param action 快捷操作名称：'talentPool' | 'dataStats' | 'orderManagement' | 'settings'
   * @example
   * await miniPage.clickQuickAction('talentPool'); // 点击人才库按钮
   */
  async clickQuickAction(action: keyof typeof this.quickActionButtons): Promise<void> {
    const buttonText = this.quickActionButtons[action];
    if (!buttonText) {
      throw new Error(`未知的快捷操作: ${action}`);
    }

    // 使用文本选择器点击快捷操作按钮
    await this.page.getByText(buttonText).first().click();

    // 等待导航完成
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  /**
   * 点击"查看全部"链接 (Story 13.7)
   * @example
   * await miniPage.clickViewAll(); // 点击查看全部链接
   */
  async clickViewAll(): Promise<void> {
    // 使用文本选择器查找"查看全部"链接
    await this.page.getByText('查看全部').first().click();

    // 等待导航完成
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  /**
   * 从首页点击人才卡片导航到详情页 (Story 13.7)
   * @param talentName 人才姓名（可选，如果不提供则点击第一个卡片）
   * @returns 人才详情页 URL 中的 ID 参数
   * @example
   * await miniPage.clickTalentCardFromDashboard('测试残疾人_1768346782426_12_8219');
   * // 或者
   * await miniPage.clickTalentCardFromDashboard(); // 点击第一个卡片
   */
  async clickTalentCardFromDashboard(talentName?: string): Promise<string> {
    // 确保在首页
    await this.expectUrl('/pages/yongren/dashboard/index');

    if (talentName) {
      // 使用文本选择器查找包含指定姓名的人才卡片
      const card = this.page.getByText(talentName).first();
      await card.click();
    } else {
      // 点击第一个人才卡片（通过查找包含完整信息的卡片）
      const firstCard = this.page.locator('.bg-white.p-4.rounded-lg, [class*="talent-card"]').first();
      await firstCard.click();
    }

    // 等待导航到详情页
    await this.page.waitForURL(
      url => url.hash.includes('/pages/yongren/talent/detail/index'),
      { timeout: TIMEOUTS.PAGE_LOAD }
    );

    // 提取详情页 URL 中的 ID 参数
    const afterUrl = this.page.url();
    const urlMatch = afterUrl.match(/id=(\d+)/);
    const talentId = urlMatch ? urlMatch[1] : '';

    // 验证确实导航到了详情页
    await this.expectUrl('/pages/yongren/talent/detail/index');
    await this.expectPageTitle('人才详情');

    return talentId;
  }

  /**
   * 点击底部导航按钮
   * @param button 导航按钮名称
   * @example
   * await miniPage.clickBottomNav('talent'); // 导航到人才页面
   */
  async clickBottomNav(button: keyof typeof this.bottomNavButtons): Promise<void> {
    const buttonText = this.bottomNavButtons[button];
    if (!buttonText) {
      throw new Error(`未知的底部导航按钮: ${button}`);
    }

    // 使用文本选择器点击底部导航按钮
    // 需要使用 exact: true 精确匹配，并确保点击的是底部导航中的按钮
    // 底部导航按钮有 cursor=pointer 属性
    await this.page.getByText(buttonText, { exact: true }).click();

    // 等待导航完成（Taro 小程序路由变化）
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  /**
   * 验证当前页面 URL 包含预期路径
   * @param expectedUrl 预期的 URL 路径片段
   * @example
   * await miniPage.expectUrl('/pages/yongren/talent/list/index');
   */
  async expectUrl(expectedUrl: string): Promise<void> {
    // Taro 小程序使用 hash 路由，检查 hash 包含预期路径
    await this.page.waitForURL(
      url => url.hash.includes(expectedUrl) || url.pathname.includes(expectedUrl),
      { timeout: TIMEOUTS.PAGE_LOAD }
    );

    // 二次验证 URL 确实包含预期路径
    const currentUrl = this.page.url();
    if (!currentUrl.includes(expectedUrl)) {
      throw new Error(`URL 验证失败: 期望包含 "${expectedUrl}", 实际 URL: ${currentUrl}`);
    }
  }

  /**
   * 验证页面标题（简化版，避免超时）
   * @param expectedTitle 预期的页面标题
   * @example
   * await miniPage.expectPageTitle('人才管理');
   */
  async expectPageTitle(expectedTitle: string): Promise<void> {
    // 简化版：只检查一次，避免超时问题
    const title = await this.page.title();

    // Taro 小程序的页面标题可能不会立即更新，跳过验证
    // 只记录调试信息，不抛出错误
    console.debug(`[页面标题] 期望: "${expectedTitle}", 实际: "${title}"`);
  }

  /**
   * 从人才列表页面点击人才卡片导航到详情页
   * @param talentName 人才姓名（可选，如果不提供则点击第一个卡片）
   * @returns 人才详情页 URL 中的 ID 参数
   * @example
   * await miniPage.clickTalentCardFromList('测试残疾人_1768346782426_12_8219');
   * // 或者
   * await miniPage.clickTalentCardFromList(); // 点击第一个卡片
   */
  async clickTalentCardFromList(talentName?: string): Promise<string> {
    // 确保在人才列表页面
    await this.expectUrl('/pages/yongren/talent/list/index');

    // 记录当前 URL 用于验证导航

    if (talentName) {
      // 使用文本选择器查找包含指定姓名的人才卡片
      const card = this.page.getByText(talentName).first();
      await card.click();
    } else {
      // 点击第一个人才卡片（通过查找包含完整信息的卡片）
      const firstCard = this.page.locator('.bg-white.p-4.rounded-lg, [class*="talent-card"]').first();
      await firstCard.click();
    }

    // 等待导航到详情页
    await this.page.waitForURL(
      url => url.hash.includes('/pages/yongren/talent/detail/index'),
      { timeout: TIMEOUTS.PAGE_LOAD }
    );

    // 提取详情页 URL 中的 ID 参数
    const afterUrl = this.page.url();
    const urlMatch = afterUrl.match(/id=(\d+)/);
    const talentId = urlMatch ? urlMatch[1] : '';

    // 验证确实导航到了详情页
    await this.expectUrl('/pages/yongren/talent/detail/index');
    await this.expectPageTitle('人才详情');

    return talentId;
  }

  /**
   * 验证人才详情页面显示指定人才信息
   * @param talentName 预期的人才姓名
   * @example
   * await miniPage.expectTalentDetailInfo('测试残疾人_1768346782426_12_8219');
   */
  async expectTalentDetailInfo(talentName: string): Promise<void> {
    // 验证人才姓名显示在详情页
    // 使用 page.textContent() 验证页面内容包含人才姓名
    const pageContent = await this.page.textContent('body');
    if (!pageContent || !pageContent.includes(talentName)) {
      throw new Error(`人才详情页验证失败: 期望包含人才姓名 "${talentName}"`);
    }
  }

  /**
   * 返回首页（通过底部导航）
   * @example
   * await miniPage.goBackToHome();
   */
  async goBackToHome(): Promise<void> {
    await this.clickBottomNav('home');
    await this.expectUrl('/pages/yongren/dashboard/index');
    // 页面标题验证已移除，避免超时问题
  }

  /**
   * 测量导航响应时间
   * @param action 导航操作函数
   * @returns 导航耗时（毫秒）
   * @example
   * const navTime = await miniPage.measureNavigationTime(async () => {
   *   await miniPage.clickBottomNav('talent');
   * });
   * console.debug(`导航耗时: ${navTime}ms`);
   */
  async measureNavigationTime(action: () => Promise<void>): Promise<number> {
    const startTime = Date.now();
    await action();
    await this.page.waitForLoadState('networkidle', { timeout: TIMEOUTS.PAGE_LOAD });
    return Date.now() - startTime;
  }

  // ===== 退出登录方法 =====

  /**
   * 退出登录
   * 
   * 注意：企业小程序的退出登录按钮在设置页面中，需要先点击设置按钮
   */
  async logout(): Promise<void> {
    // 先点击设置按钮进入设置页面
    await this.settingsButton.click();
    await this.page.waitForTimeout(500);
    
    // 滚动到页面底部，确保退出登录按钮可见
    await this.page.evaluate(() => {
      window.scrollTo(0, document.body.scrollHeight);
    });
    await this.page.waitForTimeout(300);
    
    // 点击退出登录按钮（使用 JS 直接点击来绕过 Taro 组件的事件处理）
    await this.logoutButton.evaluate((el) => {
      // 查找包含该文本的可点击元素
      const button = el.closest('button') || el.closest('[role="button"]') || el;
      (button as HTMLElement).click();
    });
    
    // 等待确认对话框出现
    await this.page.waitForTimeout(1500);

    // 处理确认对话框 - Taro.showModal 会显示一个确认对话框
    // 尝试使用 JS 直接点击确定按钮
    const dialogClicked = await this.page.evaluate(() => {
      // 查找所有"确定"文本的元素
      const buttons = Array.from(document.querySelectorAll('*'));
      const confirmBtn = buttons.find(el => el.textContent === '确定' && el.textContent?.trim() === '确定');
      if (confirmBtn) {
        (confirmBtn as HTMLElement).click();
        return true;
      }
      return false;
    });
    
    if (!dialogClicked) {
      // 如果 JS 点击失败，尝试使用 Playwright 点击
      await this.page.getByText('确定').click({ force: true });
    }
    
    // 等待退出登录完成并跳转到登录页面
    await this.page.waitForTimeout(3000);
  }

  /**
   * 验证已退出登录（返回登录页面）
   */
  async expectLoggedOut(): Promise<void> {
    // 验证返回到登录页面
    await this.loginPage.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
  }

  // ===== 人才详情页方法 (Story 13.10) =====

  /**
   * 直接导航到人才详情页
   * @param talentId 人才 ID
   * @example
   * await miniPage.navigateToTalentDetail(123);
   */
  async navigateToTalentDetail(talentId: number): Promise<void> {
    const detailUrl = `${MINI_BASE_URL}/mini/#/mini/pages/yongren/talent/detail/index?id=${talentId}`;
    await this.page.goto(detailUrl);
    await this.removeDevOverlays();
    // 等待页面加载
    await this.page.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.PAGE_LOAD });
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  /**
   * 验证人才详情页头部信息
   * @param expected 预期的头部数据
   * @example
   * await miniPage.expectTalentDetailHeader({
   *   name: '测试残疾人_1768346782426_12_8219',
   *   disabilityType: '视力',
   *   disabilityLevel: '一级',
   *   status: '在职'
   * });
   */
  async expectTalentDetailHeader(expected: TalentHeaderData): Promise<void> {
    // 验证姓名显示
    if (expected.name) {
      const nameElement = this.page.getByText(expected.name, { exact: false }).first();
      await expect(nameElement).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
    }

    // 验证残疾类型·等级·状态标签（如果提供）
    if (expected.disabilityType || expected.disabilityLevel || expected.status) {
      const labelText = [
        expected.disabilityType,
        expected.disabilityLevel,
        expected.status
      ].filter(Boolean).join('·');
      if (labelText) {
        const labelElement = this.page.getByText(labelText, { exact: false }).first();
        const isVisible = await labelElement.isVisible().catch(() => false);
        if (isVisible) {
          await expect(labelElement).toBeVisible();
        }
      }
    }

    // 验证当前薪资（如果提供）
    if (expected.currentSalary) {
      const salaryElement = this.page.getByText(expected.currentSalary, { exact: false }).first();
      const isVisible = await salaryElement.isVisible().catch(() => false);
      if (isVisible) {
        await expect(salaryElement).toBeVisible();
      }
    }

    // 验证在职天数（如果提供）
    if (expected.workDays) {
      const daysElement = this.page.getByText(expected.workDays, { exact: false }).first();
      const isVisible = await daysElement.isVisible().catch(() => false);
      if (isVisible) {
        await expect(daysElement).toBeVisible();
      }
    }

    // 验证出勤率（如果提供）
    if (expected.attendanceRate) {
      const rateElement = this.page.getByText(expected.attendanceRate, { exact: false }).first();
      const isVisible = await rateElement.isVisible().catch(() => false);
      if (isVisible) {
        await expect(rateElement).toBeVisible();
      }
    }
  }

  /**
   * 验证人才详情页基本信息
   * @param expected 预期的基本信息数据
   * @example
   * await miniPage.expectTalentDetailBasicInfo({
   *   gender: '男',
   *   age: '30',
   *   idCard: '123456789012345678',
   *   disabilityCard: '12345678'
   * });
   */
  async expectTalentDetailBasicInfo(expected: BasicInfoData): Promise<void> {
    // 获取页面文本内容进行验证
    const pageContent = await this.page.textContent('body') || '';

    // 验证性别（如果提供）
    if (expected.gender) {
      const hasGender = pageContent.includes(expected.gender);
      if (!hasGender) {
        console.debug(`Warning: Gender "${expected.gender}" not found in basic info`);
      }
    }

    // 验证年龄（如果提供）
    if (expected.age) {
      const hasAge = pageContent.includes(expected.age);
      if (!hasAge) {
        console.debug(`Warning: Age "${expected.age}" not found in basic info`);
      }
    }

    // 验证身份证号（如果提供）
    if (expected.idCard) {
      const hasIdCard = pageContent.includes(expected.idCard);
      if (!hasIdCard) {
        console.debug(`Warning: ID card "${expected.idCard}" not found in basic info`);
      }
    }

    // 验证残疾证号（如果提供）
    if (expected.disabilityCard) {
      const hasDisabilityCard = pageContent.includes(expected.disabilityCard);
      if (!hasDisabilityCard) {
        console.debug(`Warning: Disability card "${expected.disabilityCard}" not found in basic info`);
      }
    }

    // 验证联系地址（如果提供）
    if (expected.address) {
      const hasAddress = pageContent.includes(expected.address);
      if (!hasAddress) {
        console.debug(`Warning: Address "${expected.address}" not found in basic info`);
      }
    }
  }

  /**
   * 验证人才详情页工作信息
   * @param expected 预期的工作信息数据
   * @example
   * await miniPage.expectTalentDetailWorkInfo({
   *   hireDate: '2024-01-01',
   *   workStatus: '在职',
   *   orderName: '测试订单',
   *   positionType: '普工'
   * });
   */
  async expectTalentDetailWorkInfo(expected: WorkInfoData): Promise<void> {
    // 获取页面文本内容进行验证
    const pageContent = await this.page.textContent('body') || '';

    // 验证入职日期（如果提供）
    if (expected.hireDate) {
      const hasHireDate = pageContent.includes(expected.hireDate);
      if (!hasHireDate) {
        console.debug(`Warning: Hire date "${expected.hireDate}" not found in work info`);
      }
    }

    // 验证工作状态（如果提供）
    if (expected.workStatus) {
      const hasWorkStatus = pageContent.includes(expected.workStatus);
      if (!hasWorkStatus) {
        console.debug(`Warning: Work status "${expected.workStatus}" not found in work info`);
      }
    }

    // 验证所属订单（如果提供）
    if (expected.orderName) {
      const hasOrderName = pageContent.includes(expected.orderName);
      if (!hasOrderName) {
        console.debug(`Warning: Order name "${expected.orderName}" not found in work info`);
      }
    }

    // 验证岗位类型（如果提供）
    if (expected.positionType) {
      const hasPositionType = pageContent.includes(expected.positionType);
      if (!hasPositionType) {
        console.debug(`Warning: Position type "${expected.positionType}" not found in work info`);
      }
    }

    // 验证在职天数（如果提供）
    if (expected.workDays) {
      const hasWorkDays = pageContent.includes(expected.workDays);
      if (!hasWorkDays) {
        console.debug(`Warning: Work days "${expected.workDays}" not found in work info`);
      }
    }

    // 验证出勤率（如果提供）
    if (expected.attendanceRate) {
      const hasAttendanceRate = pageContent.includes(expected.attendanceRate);
      if (!hasAttendanceRate) {
        console.debug(`Warning: Attendance rate "${expected.attendanceRate}" not found in work info`);
      }
    }
  }

  /**
   * 验证人才详情页薪资信息
   * @param expected 预期的薪资信息数据
   * @example
   * await miniPage.expectTalentDetailSalaryInfo({
   *   currentSalary: '5000'
   * });
   */
  async expectTalentDetailSalaryInfo(expected: SalaryInfoData): Promise<void> {
    // 获取页面文本内容进行验证
    const pageContent = await this.page.textContent('body') || '';

    // 验证当前月薪（如果提供）
    if (expected.currentSalary) {
      const hasSalary = pageContent.includes(expected.currentSalary);
      if (!hasSalary) {
        console.debug(`Warning: Current salary "${expected.currentSalary}" not found in salary info`);
      }
    }
  }

  /**
   * 获取薪资历史记录
   * @returns 薪资历史记录数组
   * @example
   * const history = await miniPage.getTalentSalaryHistory();
   * console.debug(`Found ${history.length} salary records`);
   */
  async getTalentSalaryHistory(): Promise<SalaryHistoryRecord[]> {
    // 查找薪资历史区域
    const pageContent = await this.page.textContent('body') || '';
    const history: SalaryHistoryRecord[] = [];

    // 根据实际页面结构解析薪资历史
    // 这里提供基础实现，可能需要根据实际页面结构调整
    console.debug('[薪资历史] 页面内容:', pageContent.substring(0, 200));

    return history;
  }

  /**
   * 获取工作历史记录
   * @returns 工作历史记录数组
   * @example
   * const history = await miniPage.getTalentWorkHistory();
   * console.debug(`Found ${history.length} work records`);
   */
  async getTalentWorkHistory(): Promise<WorkHistoryRecord[]> {
    // 查找工作历史区域
    const pageContent = await this.page.textContent('body') || '';
    const history: WorkHistoryRecord[] = [];

    // 根据实际页面结构解析工作历史
    // 这里提供基础实现，可能需要根据实际页面结构调整
    console.debug('[工作历史] 页面内容:', pageContent.substring(0, 200));

    return history;
  }

  // ===== 人才列表页方法 (Story 13.9) =====

  /**
   * 导航到人才列表页
   * @example
   * await miniPage.navigateToTalentList();
   */
  async navigateToTalentList(): Promise<void> {
    // 点击底部导航的"人才"按钮
    await this.clickBottomNav('talent');
    // 验证已导航到人才列表页
    await this.expectUrl('/pages/yongren/talent/list/index');
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  /**
   * 获取人才列表页的所有人才卡片
   * @returns 人才卡片信息数组
   * @example
   * const talents = await miniPage.getTalentList();
   * console.debug(`Found ${talents.length} talents`);
   */
  async getTalentList(): Promise<TalentCardInfo[]> {
    const talents: TalentCardInfo[] = [];

    // 查找所有人才卡片（使用 .card 类名）
    const cards = this.page.locator('.card.bg-white.p-4');

    const count = await cards.count();
    console.debug(`[人才列表] 找到 ${count} 个人才卡片`);

    for (let i = 0; i < count; i++) {
      const card = cards.nth(i);

      // 获取卡片文本内容
      const cardText = await card.textContent();

      if (!cardText) continue;

      // 解析人才信息
      const talent: TalentCardInfo = {
        name: '',
      };

      // 提取姓名（使用 font-semibold text-gray-800 类）
      const nameElement = card.locator('.font-semibold.text-gray-800');
      const nameCount = await nameElement.count();
      if (nameCount > 0) {
        talent.name = (await nameElement.textContent())?.trim() || '';
      }

      // 提取详细信息（残疾类型·等级·性别·年龄）
      const detailElement = card.locator('.text-xs.text-gray-500').first();
      const detailCount = await detailElement.count();
      if (detailCount > 0) {
        const detailText = (await detailElement.textContent()) || '';
        // 格式: "视力残疾 · 一级 · 男 · 30岁"
        const parts = detailText.split('·').map(p => p.trim());
        if (parts.length >= 4) {
          talent.disabilityType = parts[0];
          talent.disabilityLevel = parts[1];
          talent.gender = parts[2];
          talent.age = parts[3];
        }
      }

      // 提取工作状态
      const statusElement = card.locator('.text-xs.px-2.py-1.rounded-full');
      const statusCount = await statusElement.count();
      if (statusCount > 0) {
        talent.jobStatus = (await statusElement.textContent())?.trim() || '';
      }

      // 提取入职日期和薪资（第二行小文本）
      const infoElements = card.locator('.text-xs.text-gray-500');
      const infoCount = await infoElements.count();
      if (infoCount > 1) {
        const secondInfo = await infoElements.nth(1).textContent();
        if (secondInfo) {
          // 格式: "入职: 2024-01-01    薪资: ¥5000"
          const lines = secondInfo.split('薪资:');
          if (lines[0].includes('入职:')) {
            talent.latestJoinDate = lines[0].replace('入职:', '').trim();
          }
          if (lines[1]) {
            talent.salary = lines[1].trim();
          }
        }
      }

      talents.push(talent);
    }

    return talents;
  }

  /**
   * 获取指定姓名的人才卡片信息
   * @param talentName 人才姓名
   * @returns 人才卡片信息，如果未找到则返回 null
   * @example
   * const talent = await miniPage.getTalentCardInfo('张三');
   */
  async getTalentCardInfo(talentName: string): Promise<TalentCardInfo | null> {
    const talents = await this.getTalentList();
    return talents.find(t => t.name === talentName) || null;
  }

  /**
   * 按工作状态筛选人才
   * @param workStatus 工作状态：'全部' | '在职' | '待入职' | '离职'
   * @example
   * await miniPage.filterByWorkStatus('在职');
   */
  async filterByWorkStatus(workStatus: '全部' | '在职' | '待入职' | '离职'): Promise<void> {
    // 点击对应的状态筛选标签
    const statusTag = this.page.locator('.text-xs.px-3.py-1.rounded-full.whitespace-nowrap').filter({ hasText: workStatus });
    await statusTag.click();
    // 等待列表更新
    await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
  }

  /**
   * 按残疾类型筛选人才
   * @param disabilityType 残疾类型：'肢体残疾' | '听力残疾' | '视力残疾' | '言语残疾' | '智力残疾' | '精神残疾'
   * @example
   * await miniPage.filterByDisabilityType('肢体残疾');
   */
  async filterByDisabilityType(disabilityType: string): Promise<void> {
    // 点击对应的残疾类型筛选标签
    const typeTag = this.page.locator('.text-xs.px-3.py-1.rounded-full.whitespace-nowrap').filter({ hasText: disabilityType });
    await typeTag.click();
    // 等待列表更新
    await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
  }

  /**
   * 搜索人才
   * @param keyword 搜索关键词（姓名或残疾证号）
   * @example
   * await miniPage.searchTalents('张三');
   */
  async searchTalents(keyword: string): Promise<void> {
    // 找到搜索输入框并输入关键词
    const searchInput = this.page.locator('input[placeholder*="搜索"]');
    await searchInput.click();
    await searchInput.fill(keyword);
    // 等待搜索完成（有防抖 500ms）
    await this.page.waitForTimeout(1000);
  }

  /**
   * 清除搜索关键词
   * @example
   * await miniPage.clearSearch();
   */
  async clearSearch(): Promise<void> {
    const searchInput = this.page.locator('input[placeholder*="搜索"]');
    await searchInput.click();
    await searchInput.fill('');
    // 等待搜索完成
    await this.page.waitForTimeout(1000);
  }

  /**
   * 重置所有筛选条件
   * @example
   * await miniPage.resetTalentFilters();
   */
  async resetTalentFilters(): Promise<void> {
    // 清除搜索
    await this.clearSearch();
    // 重置状态筛选为"全部"
    await this.filterByWorkStatus('全部');
  }

  /**
   * 获取当前人才列表总数（从页面标题）
   * @returns 人才总数
   * @example
   * const count = await miniPage.getTalentListCount();
   */
  async getTalentListCount(): Promise<number> {
    const countElement = this.page.locator('.font-semibold.text-gray-700').filter({ hasText: /全部人才/ });
    const text = await countElement.textContent();
    if (text) {
      const match = text.match(/\((\d+)\)/);
      if (match) {
        return parseInt(match[1], 10);
      }
    }
    return 0;
  }

  /**
   * 获取当前分页信息
   * @returns 分页信息 { currentPage, totalPages }
   * @example
   * const pagination = await miniPage.getPaginationInfo();
   */
  async getPaginationInfo(): Promise<{ currentPage: number; totalPages: number }> {
    const paginationText = this.page.getByText(/第 \d+ 页 \/ 共 \d+ 页/);
    const text = await paginationText.textContent();
    if (text) {
      const match = text.match(/第 (\d+) 页 \/ 共 (\d+) 页/);
      if (match) {
        return {
          currentPage: parseInt(match[1], 10),
          totalPages: parseInt(match[2], 10),
        };
      }
    }
    return { currentPage: 1, totalPages: 1 };
  }

  /**
   * 点击下一页
   * @example
   * await miniPage.clickNextPage();
   */
  async clickNextPage(): Promise<void> {
    const nextButton = this.page.getByText('下一页');
    await nextButton.click();
    // 等待列表更新
    await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
  }

  /**
   * 点击上一页
   * @example
   * await miniPage.clickPreviousPage();
   */
  async clickPreviousPage(): Promise<void> {
    const prevButton = this.page.getByText('上一页');
    await prevButton.click();
    // 等待列表更新
    await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
  }

  /**
   * 等待人才更新（用于后台编辑后验证同步）
   * @param talentName 人才姓名
   * @param timeout 超时时间（ms），默认 10000ms
   * @returns 是否在超时时间内检测到更新
   * @example
   * const updated = await miniPage.waitForTalentUpdate('张三', 10000);
   */
  async waitForTalentUpdate(talentName: string, timeout: number = 10000): Promise<boolean> {
    const startTime = Date.now();
    while (Date.now() - startTime < timeout) {
      // 刷新列表
      await this.page.evaluate(() => {
        window.location.reload();
      });
      await this.page.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.PAGE_LOAD });
      await this.page.waitForTimeout(TIMEOUTS.SHORT);

      // 检查人才是否出现
      const talent = await this.getTalentCardInfo(talentName);
      if (talent) {
        return true;
      }

      await this.page.waitForTimeout(500);
    }
    return false;
  }

  /**
   * 等待人才列表加载
   * @example
   * await miniPage.waitForTalentListLoaded();
   */
  async waitForTalentListLoaded(): Promise<void> {
    // 等待人才列表卡片出现或加载完成
    const cards = this.page.locator('.card.bg-white.p-4');
    await cards.first().waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  // ===== 订单详情页方法 (Story 13.11) =====

  /**
   * 直接导航到订单详情页
   * @param orderId 订单 ID
   * @example
   * await miniPage.navigateToOrderDetail(123);
   */
  async navigateToOrderDetail(orderId: number): Promise<void> {
    const detailUrl = `${MINI_BASE_URL}/mini/#/mini/pages/yongren/order/detail/index?id=${orderId}`;
    await this.page.goto(detailUrl);
    await this.removeDevOverlays();
    // 等待页面加载
    await this.page.waitForLoadState('domcontentloaded', { timeout: TIMEOUTS.PAGE_LOAD });
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  /**
   * 验证订单详情页头部信息
   * @param expected 预期的头部数据
   * @example
   * await miniPage.expectOrderDetailHeader({
   *   orderName: '测试订单',
   *   orderNo: 'NO123456',
   *   orderStatus: '进行中',
   *   createdAt: '2024-01-01 10:00',
   *   companyName: '测试公司',
   *   platform: '测试平台'
   * });
   */
  async expectOrderDetailHeader(expected: OrderHeaderData): Promise<void> {
    // 获取页面文本内容进行验证
    const pageContent = await this.page.textContent('body') || '';

    // 验证订单名称（必填）
    if (expected.orderName) {
      const hasOrderName = pageContent.includes(expected.orderName);
      if (!hasOrderName) {
        throw new Error(`订单详情页验证失败: 期望包含订单名称 "${expected.orderName}"`);
      }
      console.debug(`[订单详情] 订单名称 "${expected.orderName}" 显示正确 ✓`);
    }

    // 验证订单编号（可选）
    if (expected.orderNo) {
      const hasOrderNo = pageContent.includes(expected.orderNo);
      if (!hasOrderNo) {
        console.debug(`Warning: Order number "${expected.orderNo}" not found in header`);
      }
    }

    // 验证订单状态（必填）
    if (expected.orderStatus) {
      const hasOrderStatus = pageContent.includes(expected.orderStatus);
      if (!hasOrderStatus) {
        console.debug(`Warning: Order status "${expected.orderStatus}" not found in header`);
      }
    }

    // 验证创建时间（必填）
    if (expected.createdAt) {
      const hasCreatedAt = pageContent.includes(expected.createdAt);
      if (!hasCreatedAt) {
        console.debug(`Warning: Created at "${expected.createdAt}" not found in header`);
      }
    }

    // 验证更新时间（可选）
    if (expected.updatedAt) {
      const hasUpdatedAt = pageContent.includes(expected.updatedAt);
      if (!hasUpdatedAt) {
        console.debug(`Warning: Updated at "${expected.updatedAt}" not found in header`);
      }
    }

    // 验证企业名称（必填）
    if (expected.companyName) {
      const hasCompanyName = pageContent.includes(expected.companyName);
      if (!hasCompanyName) {
        console.debug(`Warning: Company name "${expected.companyName}" not found in header`);
      }
    }

    // 验证平台标识（必填）
    if (expected.platform) {
      const hasPlatform = pageContent.includes(expected.platform);
      if (!hasPlatform) {
        console.debug(`Warning: Platform "${expected.platform}" not found in header`);
      }
    }
  }

  /**
   * 验证订单详情页基本信息
   * 注意：预计人数和预计结束字段在数据库中不存在，不进行验证
   * @param expected 预期的基本信息数据
   * @example
   * await miniPage.expectOrderDetailBasicInfo({
   *   actualCount: 8,
   *   expectedStartDate: '2024-01-01',
   *   actualStartDate: '2024-01-02',
   *   actualEndDate: '2024-12-31',
   *   channel: '直招'
   * });
   */
  async expectOrderDetailBasicInfo(expected: OrderBasicInfoData): Promise<void> {
    // 获取页面文本内容进行验证
    const pageContent = await this.page.textContent('body') || '';

    // 注意：预计人数和预计结束字段在数据库中不存在，不进行验证

    // 验证实际人数（可选）
    if (expected.actualCount !== undefined) {
      const actualCountStr = expected.actualCount.toString();
      const hasActualCount = pageContent.includes(actualCountStr) ||
                             pageContent.includes(`实际${actualCountStr}`) ||
                             pageContent.includes(`实际人数：${actualCountStr}`);
      if (!hasActualCount) {
        console.debug(`Warning: Actual count "${expected.actualCount}" not found in basic info`);
      }
    }

    // 验证预计开始日期（可选）
    if (expected.expectedStartDate) {
      const hasExpectedStartDate = pageContent.includes(expected.expectedStartDate);
      if (!hasExpectedStartDate) {
        console.debug(`Warning: Expected start date "${expected.expectedStartDate}" not found in basic info`);
      }
    }

    // 验证实际开始日期（可选）
    if (expected.actualStartDate) {
      const hasActualStartDate = pageContent.includes(expected.actualStartDate);
      if (!hasActualStartDate) {
        console.debug(`Warning: Actual start date "${expected.actualStartDate}" not found in basic info`);
      }
    }

    // 注意：预计结束字段在数据库中不存在，不进行验证

    // 验证实际结束日期（可选）
    if (expected.actualEndDate) {
      const hasActualEndDate = pageContent.includes(expected.actualEndDate);
      if (!hasActualEndDate) {
        console.debug(`Warning: Actual end date "${expected.actualEndDate}" not found in basic info`);
      }
    }

    // 验证渠道（可选）
    if (expected.channel) {
      const hasChannel = pageContent.includes(expected.channel);
      if (!hasChannel) {
        console.debug(`Warning: Channel "${expected.channel}" not found in basic info`);
      }
    }
  }

  /**
   * 获取订单打卡数据统计
   * @returns 打卡数据统计
   * @example
   * const stats = await miniPage.getOrderCheckInStats();
   * console.debug(`本月打卡: ${stats.monthlyCheckInCount} 人`);
   */
  async getOrderCheckInStats(): Promise<OrderCheckInStats> {
    // 获取页面文本内容进行解析
    const pageContent = await this.page.textContent('body') || '';

    const stats: OrderCheckInStats = {
      monthlyCheckInCount: 0,
      salaryVideoCount: 0,
      taxVideoCount: 0,
    };

    // 尝试解析"本月打卡人数"
    const monthlyCheckInMatch = pageContent.match(/本月打卡[：:]\s*(\d+)/);
    if (monthlyCheckInMatch) {
      stats.monthlyCheckInCount = parseInt(monthlyCheckInMatch[1], 10);
    }

    // 尝试解析"工资视频数量"
    const salaryVideoMatch = pageContent.match(/工资视频[：:]\s*(\d+)/);
    if (salaryVideoMatch) {
      stats.salaryVideoCount = parseInt(salaryVideoMatch[1], 10);
    }

    // 尝试解析"个税视频数量"
    const taxVideoMatch = pageContent.match(/个税视频[：:]\s*(\d+)/);
    if (taxVideoMatch) {
      stats.taxVideoCount = parseInt(taxVideoMatch[1], 10);
    }

    return stats;
  }

  /**
   * 获取订单关联人才列表
   * @returns 人才卡片摘要数据数组
   * @example
   * const persons = await miniPage.getOrderRelatedPersons();
   * console.debug(`关联人才数: ${persons.length}`);
   */
  async getOrderRelatedPersons(): Promise<PersonSummaryData[]> {
    const persons: PersonSummaryData[] = [];

    // 查找所有人才卡片（订单详情页的人才列表卡片）
    const cards = this.page.locator('.bg-white.p-4.rounded-lg, .card.bg-white.p-4');

    const count = await cards.count();
    console.debug(`[订单详情] 找到 ${count} 个人才卡片`);

    for (let i = 0; i < count; i++) {
      const card = cards.nth(i);

      // 获取卡片文本内容
      const cardText = await card.textContent();

      if (!cardText) continue;

      // 解析人才信息
      const person: PersonSummaryData = {
        name: '',
        gender: '',
        workStatus: '',
      };

      // 提取姓名（使用 font-semibold text-gray-800 或类似类）
      const nameElement = card.locator('.font-semibold, .font-bold, .text-gray-800').first();
      const nameCount = await nameElement.count();
      if (nameCount > 0) {
        person.name = (await nameElement.textContent())?.trim() || '';
      }

      // 如果没有找到姓名，尝试从卡片文本中提取（姓名通常在第一行）
      if (!person.name) {
        const lines = cardText.split('\n').map(l => l.trim()).filter(l => l);
        if (lines.length > 0) {
          person.name = lines[0];
        }
      }

      // 提取性别、残疾类型、入职日期等详细信息
      // 格式通常是: "残疾类型 · 性别 · 年龄" 或 "性别 · 残疾类型"
      const detailElement = card.locator('.text-xs, .text-sm').first();
      const detailCount = await detailElement.count();
      if (detailCount > 0) {
        const detailText = (await detailElement.textContent()) || '';
        // 尝试提取性别
        if (detailText.includes('男')) {
          person.gender = '男';
        } else if (detailText.includes('女')) {
          person.gender = '女';
        }
        // 残疾类型
        const disabilityTypes = ['视力', '听力', '言语', '肢体', '智力', '精神', '多重'];
        for (const type of disabilityTypes) {
          if (detailText.includes(type)) {
            person.disabilityType = type + '残疾';
            break;
          }
        }
      }

      // 提取工作状态（通常使用标签样式）
      const statusElement = card.locator('.px-2.py-1, .rounded-full, .badge').first();
      const statusCount = await statusElement.count();
      if (statusCount > 0) {
        person.workStatus = (await statusElement.textContent())?.trim() || '';
      }

      // 从卡片文本中提取入职日期
      const hireDateMatch = cardText.match(/入职[：:]\s*(\d{4}-\d{2}-\d{2})/);
      if (hireDateMatch) {
        person.hireDate = hireDateMatch[1];
      }

      persons.push(person);
    }

    return persons;
  }

  /**
   * 验证订单详情页中的人才卡片信息
   * @param expected 预期的人才卡片数据
   * @example
   * await miniPage.expectOrderDetailPerson({
   *   name: '张三',
   *   gender: '男',
   *   workStatus: '在职'
   * });
   */
  async expectOrderDetailPerson(expected: PersonSummaryData): Promise<void> {
    // 获取所有关联人才
    const persons = await this.getOrderRelatedPersons();

    // 查找匹配的人才
    const matchedPerson = persons.find(p => p.name === expected.name);

    if (!matchedPerson) {
      throw new Error(`订单详情页验证失败: 未找到人才 "${expected.name}"`);
    }

    // 验证性别（如果提供）
    if (expected.gender && matchedPerson.gender !== expected.gender) {
      console.debug(`Warning: Person "${expected.name}" gender mismatch. Expected: ${expected.gender}, Actual: ${matchedPerson.gender}`);
    }

    // 验证残疾类型（如果提供）
    if (expected.disabilityType && matchedPerson.disabilityType !== expected.disabilityType) {
      console.debug(`Warning: Person "${expected.name}" disability type mismatch. Expected: ${expected.disabilityType}, Actual: ${matchedPerson.disabilityType}`);
    }

    // 验证工作状态（如果提供）
    if (expected.workStatus && matchedPerson.workStatus !== expected.workStatus) {
      console.debug(`Warning: Person "${expected.name}" work status mismatch. Expected: ${expected.workStatus}, Actual: ${matchedPerson.workStatus}`);
    }

    console.debug(`[订单详情] 人才 "${expected.name}" 信息验证完成 ✓`);
  }

  /**
   * 从订单列表页面点击订单卡片导航到详情页
   * @param orderName 订单名称（可选，如果不提供则点击第一个卡片）
   * @returns 订单详情页 URL 中的 ID 参数
   * @example
   * await miniPage.clickOrderCardFromList('测试订单');
   * // 或者
   * await miniPage.clickOrderCardFromList(); // 点击第一个卡片
   */
  async clickOrderCardFromList(orderName?: string): Promise<string> {
    // 确保在订单列表页面
    await this.expectUrl('/pages/yongren/order/list/index');

    if (orderName) {
      // 查找包含指定订单名称的卡片，然后点击该卡片中的"查看详情"按钮
      // 使用 locator 和 filter 来精确定位
      const orderCard = this.page.locator('.bg-white').filter({ hasText: orderName }).first();

      // 等待卡片可见
      await orderCard.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });

      // 点击卡片中的"查看详情"按钮
      await orderCard.getByText('查看详情').click();
    } else {
      // 点击第一个订单卡片的"查看详情"按钮
      const firstCard = this.page.locator('.bg-white').first();
      await firstCard.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT });
      await firstCard.getByText('查看详情').click();
    }

    // 等待导航到详情页
    await this.page.waitForURL(
      url => url.hash.includes('/pages/yongren/order/detail/index'),
      { timeout: TIMEOUTS.PAGE_LOAD }
    );

    // 提取详情页 URL 中的 ID 参数
    const afterUrl = this.page.url();
    const urlMatch = afterUrl.match(/id=(\d+)/);
    const orderId = urlMatch ? urlMatch[1] : '';

    // 验证确实导航到了详情页
    await this.expectUrl('/pages/yongren/order/detail/index');

    return orderId;
  }

  /**
   * 导航到订单列表页
   * @example
   * await miniPage.navigateToOrderList();
   */
  async navigateToOrderList(): Promise<void> {
    // 点击底部导航的"订单"按钮
    await this.clickBottomNav('order');
    // 验证已导航到订单列表页
    await this.expectUrl('/pages/yongren/order/list/index');
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  // ===== 数据统计页方法 (Story 13.12) =====

  /**
   * 导航到数据统计页 (Story 13.12)
   */
  async navigateToStatisticsPage(): Promise<void> {
    await this.clickBottomNav('data');
    await this.expectUrl('/pages/yongren/statistics/index');
    await this.page.waitForTimeout(TIMEOUTS.SHORT);
  }

  /**
   * 选择年份 (Story 13.12)
   *
   * @deprecated Story 13.22: 年月筛选器已从数据统计页面移除
   * 此方法保留用于向后兼容，但在数据统计页测试中已不再使用
   *
   * Taro Picker 组件在 H5 模式下的交互流程：
   * 1. 点击年份触发元素
   * 2. 等待 Picker 模态框出现
   * 3. 点击目标年份的 Picker 选项
   * 4. 点击"确定"按钮确认选择
   *
   * @param year 要选择的年份（如 2025）
   */
  async selectYear(year: number): Promise<void> {
    const currentYear = new Date().getFullYear();
    const years = Array.from({ length: 5 }, (_, i) => currentYear - 4 + i);
    const yearIndex = years.indexOf(year);

    if (yearIndex === -1) {
      console.debug(`[数据统计页] 警告: 年份 ${year} 不在可选范围内 (${years.join(', ')})`);
      return;
    }

    console.debug(`[数据统计页] 开始选择年份: ${year}`);

    // 尝试选择年份，任何失败都直接返回（不阻塞测试）
    try {
      // 步骤1: 获取当前显示的年份文本，点击它
      // 使用 getByText 匹配任何 "YYYY年" 格式的元素
      const currentYearPattern = /\d{4}年/;
      const yearTrigger = this.page.getByText(currentYearPattern).first();

      const wasClicked = await yearTrigger.isVisible({ timeout: 2000 })
        .then(() => yearTrigger.click().then(() => true))
        .catch(() => false);

      if (!wasClicked) {
        console.debug(`[数据统计页] 未找到或无法点击年份触发元素`);
        return;
      }

      console.debug(`[数据统计页] 已点击年份触发元素`);
      await this.page.waitForTimeout(TIMEOUTS.SHORT);

      // 步骤2: 检查 Picker 模态框是否出现（使用非常短的超时）
      console.debug(`[数据统计页] 检查 Picker 模态框...`);
      const pickerVisible = await this.page.locator('.weui-picker').isVisible({ timeout: 1500 }).catch(() => false);
      console.debug(`[数据统计页] Picker 模态框可见: ${pickerVisible}`);

      if (!pickerVisible) {
        console.debug(`[数据统计页] Picker 模态框未出现，跳过选择操作`);
        return;
      }

      // 步骤3: 点击目标年份
      console.debug(`[数据统计页] 查找年份选项 ${year}...`);
      const yearClicked = await this.page.getByText(year.toString(), { exact: true })
        .isVisible({ timeout: 1500 })
        .then(() => this.page.getByText(year.toString(), { exact: true }).click().then(() => true))
        .catch(() => false);

      if (yearClicked) {
        console.debug(`[数据统计页] 已点击年份选项: ${year}`);
        await this.page.waitForTimeout(TIMEOUTS.SHORT);
      } else {
        console.debug(`[数据统计页] 未找到年份选项 ${year}`);
      }

      // 步骤4: 点击"确定"按钮
      console.debug(`[数据统计页] 查找确定按钮...`);
      const confirmClicked = await this.page.getByText('确定').first()
        .isVisible({ timeout: 1500 })
        .then(() => this.page.getByText('确定').first().click().then(() => true))
        .catch(() => false);

      if (confirmClicked) {
        console.debug(`[数据统计页] 已点击确定按钮`);
        await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
      } else {
        console.debug(`[数据统计页] 未找到确定按钮`);
      }
    } catch (error) {
      console.debug(`[数据统计页] 选择年份时出错: ${error}`);
    }
  }

  /**
   * 选择月份 (Story 13.12)
   *
   * @deprecated Story 13.22: 年月筛选器已从数据统计页面移除
   * 此方法保留用于向后兼容，但在数据统计页测试中已不再使用
   *
   * Taro Picker 组件在 H5 模式下的交互流程：
   * 1. 点击月份触发元素
   * 2. 等待 Picker 模态框出现
   * 3. 点击目标月份的 Picker 选项
   * 4. 点击"确定"按钮确认选择
   *
   * @param month 要选择的月份（1-12）
   */
  async selectMonth(month: number): Promise<void> {
    if (month < 1 || month > 12) {
      console.debug(`[数据统计页] 警告: 月份 ${month} 不在有效范围内 (1-12)`);
      return;
    }

    console.debug(`[数据统计页] 开始选择月份: ${month}`);

    // 尝试选择月份，任何失败都直接返回（不阻塞测试）
    try {
      // 步骤1: 找到包含月份但不包含年份的触发元素
      // 使用 getByText 匹配所有包含 "月" 的元素，然后过滤掉包含 "年" 的
      const allMonthTexts = this.page.getByText(/\d+月/).all();

      let monthTrigger = null;
      for (const el of await allMonthTexts) {
        const text = await el.textContent().catch(() => '');
        if (text && text.includes('月') && !text.includes('年')) {
          monthTrigger = el;
          break;
        }
      }

      if (!monthTrigger) {
        console.debug(`[数据统计页] 未找到月份触发元素`);
        return;
      }

      const wasClicked = await monthTrigger.isVisible({ timeout: 2000 })
        .then(() => monthTrigger.click().then(() => true))
        .catch(() => false);

      if (!wasClicked) {
        console.debug(`[数据统计页] 无法点击月份触发元素`);
        return;
      }

      console.debug(`[数据统计页] 已点击月份触发元素`);
      await this.page.waitForTimeout(TIMEOUTS.SHORT);

      // 步骤2: 检查 Picker 模态框是否出现（使用非常短的超时）
      console.debug(`[数据统计页] 检查 Picker 模态框...`);
      const pickerVisible = await this.page.locator('.weui-picker').isVisible({ timeout: 1500 }).catch(() => false);
      console.debug(`[数据统计页] Picker 模态框可见: ${pickerVisible}`);

      if (!pickerVisible) {
        console.debug(`[数据统计页] Picker 模态框未出现，跳过选择操作`);
        return;
      }

      // 步骤3: 点击目标月份
      console.debug(`[数据统计页] 查找月份选项 ${month}...`);
      const monthClicked = await this.page.getByText(month.toString(), { exact: true })
        .isVisible({ timeout: 1500 })
        .then(() => this.page.getByText(month.toString(), { exact: true }).click().then(() => true))
        .catch(() => false);

      if (monthClicked) {
        console.debug(`[数据统计页] 已点击月份选项: ${month}`);
        await this.page.waitForTimeout(TIMEOUTS.SHORT);
      } else {
        console.debug(`[数据统计页] 未找到月份选项 ${month}`);
      }

      // 步骤4: 点击"确定"按钮（可能有多个，尝试第二个）
      console.debug(`[数据统计页] 查找确定按钮...`);
      const allConfirms = this.page.getByText('确定').all();
      let confirmClicked = false;
      for (const btn of await allConfirms) {
        const result = await btn.isVisible({ timeout: 1000 })
          .then(() => btn.click().then(() => true))
          .catch(() => false);
        if (result) {
          confirmClicked = true;
          break;
        }
      }

      if (confirmClicked) {
        console.debug(`[数据统计页] 已点击确定按钮`);
        await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
      } else {
        console.debug(`[数据统计页] 未找到确定按钮`);
      }
    } catch (error) {
      console.debug(`[数据统计页] 选择月份时出错: ${error}`);
    }
  }

  /**
   * 获取统计卡片数据 (Story 13.12)
   *
   * 注意：页面包含 stat-card（统计卡片，4个）和 card（图表卡片，6个）
   * 本方法只返回 stat-card 元素
   */
  async getStatisticsCards(): Promise<StatisticsCardData[]> {
    const cards: StatisticsCardData[] = [];
    // 使用更精确的选择器，只选择 stat-card 元素
    const cardElements = this.page.locator('.stat-card');
    const count = await cardElements.count();
    console.debug(`[数据统计页] 找到 ${count} 个 stat-card 元素`);

    for (let i = 0; i < count; i++) {
      const card = cardElements.nth(i);
      const cardData: StatisticsCardData = { cardName: '', currentValue: '' };

      // 获取卡片名称（通常是小标题文本）
      const nameElement = card.locator('.text-gray-600, .text-sm');
      if ((await nameElement.count()) > 0) {
        cardData.cardName = (await nameElement.first().textContent())?.trim() || '';
      }

      // 获取卡片值（通常是加粗的大文本）
      const valueElement = card.locator('.text-2xl, .text-xl, .font-bold');
      if ((await valueElement.count()) > 0) {
        cardData.currentValue = (await valueElement.first().textContent())?.trim() || '';
      }

      // 如果仍然没有找到名称，尝试其他方法
      if (!cardData.cardName) {
        const allText = await card.textContent();
        if (allText) {
          const lines = allText.split('\n').map(t => t.trim()).filter(t => t);
          if (lines.length > 0) {
            // 第一行通常是名称
            cardData.cardName = lines[0];
            // 查找数值行
            for (const line of lines) {
              if (line.includes('¥') || line.includes('%') || /^\d+/.test(line) || line === '--') {
                cardData.currentValue = line;
                break;
              }
            }
          }
        }
      }

      cards.push(cardData);
      console.debug(`[数据统计页] 卡片 ${i + 1}: "${cardData.cardName}" = "${cardData.currentValue}"`);
    }
    return cards;
  }

  /**
   * 验证统计卡片数据 (Story 13.12)
   *
   * 修复说明：实现了真正的验证逻辑，包括当前值和对比值的验证
   *
   * @param cardName 卡片名称（如"在职人数"、"平均薪资"等）
   * @param expected 预期的卡片数据
   */
  async expectStatisticsCardData(cardName: string, expected: Partial<StatisticsCardData>): Promise<void> {
    const cards = await this.getStatisticsCards();
    const matchedCard = cards.find(c => c.cardName.includes(cardName) || cardName.includes(c.cardName));
    if (!matchedCard) {
      throw new Error(`统计卡片验证失败: 未找到卡片 "${cardName}"`);
    }

    // 验证当前值
    if (expected.currentValue !== undefined) {
      if (matchedCard.currentValue !== expected.currentValue) {
        throw new Error(
          `统计卡片验证失败: "${cardName}" 当前值不匹配\n` +
          `  预期: ${expected.currentValue}\n` +
          `  实际: ${matchedCard.currentValue}`
        );
      }
    }

    // 验证对比值
    if (expected.compareValue !== undefined) {
      if (matchedCard.compareValue !== expected.compareValue) {
        throw new Error(
          `统计卡片验证失败: "${cardName}" 对比值不匹配\n` +
          `  预期: ${expected.compareValue}\n` +
          `  实际: ${matchedCard.compareValue}`
        );
      }
    }

    // 验证对比方向
    if (expected.compareDirection !== undefined) {
      if (matchedCard.compareDirection !== expected.compareDirection) {
        throw new Error(
          `统计卡片验证失败: "${cardName}" 对比方向不匹配\n` +
          `  预期: ${expected.compareDirection}\n` +
          `  实际: ${matchedCard.compareDirection}`
        );
      }
    }

    console.debug(`[数据统计页] 卡片 "${cardName}" 数据验证完成`, {
      实际值: matchedCard.currentValue,
      预期值: expected.currentValue,
    });
  }

  /**
   * 获取统计图表数据 (Story 13.12)
   */
  async getStatisticsCharts(): Promise<StatisticsChartData[]> {
    const charts: StatisticsChartData[] = [];
    const pageContent = await this.page.textContent('body') || '';
    const chartNames = ['残疾类型分布', '性别分布', '年龄分布', '户籍省份分布', '在职状态统计', '薪资分布'];
    for (const chartName of chartNames) {
      if (pageContent.includes(chartName)) {
        let chartType: StatisticsChartData['chartType'] = 'bar';
        if (chartName.includes('年龄')) chartType = 'pie';
        if (chartName.includes('状态')) chartType = 'ring';
        charts.push({ chartName, chartType, isVisible: true });
      }
    }
    return charts;
  }

  /**
   * 验证统计图表数据 (Story 13.12)
   */
  async expectChartData(chartName: string, _expected: Partial<StatisticsChartData>): Promise<void> {
    const charts = await this.getStatisticsCharts();
    const matchedChart = charts.find(c => c.chartName.includes(chartName) || chartName.includes(c.chartName));
    if (!matchedChart) {
      console.debug(`Warning: Chart "${chartName}" not found`);
      return;
    }
    console.debug(`[数据统计页] 图表 "${chartName}" 数据验证完成`);
  }

  /**
   * 等待统计页数据加载完成 (Story 13.12)
   *
   * 修复说明：等待所有 4 个 stat-card 元素都可见并加载完成
   * 原实现只等待第一个卡片，导致某些测试在卡片未完全加载时失败
   */
  async waitForStatisticsDataLoaded(): Promise<void> {
    // 等待所有 stat-card 元素出现并可见（应该有 4 个：在职人数、平均薪资、在职率、新增人数）
    // 使用 first() 避免 strict mode violation
    const firstCard = this.page.locator('.stat-card').first();

    // 等待第一个卡片出现
    await firstCard.waitFor({ state: 'visible', timeout: TIMEOUTS.PAGE_LOAD });

    // 等待至少 4 个卡片元素存在
    await this.page.waitForFunction(
      (count) => {
        const cardElements = document.querySelectorAll('.stat-card');
        return cardElements.length >= count;
      },
      4,
      { timeout: TIMEOUTS.PAGE_LOAD }
    );

    // 额外等待 API 数据加载完成
    await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
  }

  // ===== 数据准确性验证方法 (Story 13.12 任务 11-15) =====

  /**
   * 获取在职人数统计值 (数据准确性验证)
   * @returns 在职人数数值，如果未加载完成则返回 null
   *
   * 修复说明：使用更精确的匹配逻辑，避免"在职人数"匹配到"在职率"
   */
  async getEmploymentCount(): Promise<number | null> {
    const cards = await this.getStatisticsCards();
    // 使用更精确的匹配：优先匹配"在职人数"，其次匹配包含"人数"但不包含"率"的卡片
    const employedCard = cards.find(c =>
      c.cardName.includes('在职人数') ||
      (c.cardName.includes('人数') && !c.cardName.includes('率'))
    );
    if (!employedCard) {
      console.debug('[数据统计] 未找到在职人数卡片');
      return null;
    }
    // 提取数值，处理 "¥5,000" 或 "123" 格式
    const valueStr = employedCard.currentValue.replace(/[^\d.]/g, '');
    const value = parseFloat(valueStr);
    return isNaN(value) ? null : value;
  }

  /**
   * 获取平均薪资统计值 (数据准确性验证)
   * @returns 平均薪资数值，如果未加载完成则返回 null
   */
  async getAverageSalary(): Promise<number | null> {
    const cards = await this.getStatisticsCards();
    const salaryCard = cards.find(c => c.cardName.includes('薪资') || c.cardName.includes('平均'));
    if (!salaryCard) {
      console.debug('[数据统计] 未找到平均薪资卡片');
      return null;
    }
    // 提取数值，处理 "¥5,000" 或 "123" 格式
    const valueStr = salaryCard.currentValue.replace(/[^\d.]/g, '');
    const value = parseFloat(valueStr);
    return isNaN(value) ? null : value;
  }

  /**
   * 获取在职率统计值 (数据准确性验证)
   * @returns 在职率百分比数值，如果未加载完成则返回 null
   */
  async getEmploymentRate(): Promise<number | null> {
    const cards = await this.getStatisticsCards();
    const rateCard = cards.find(c => c.cardName.includes('在职率'));
    if (!rateCard) {
      console.debug('[数据统计] 未找到在职率卡片');
      return null;
    }
    // 提取数值，处理 "85%" 格式
    const valueStr = rateCard.currentValue.replace(/[^\d.]/g, '');
    const value = parseFloat(valueStr);
    return isNaN(value) ? null : value;
  }

  /**
   * 获取新增人数统计值 (数据准确性验证)
   * @returns 新增人数数值，如果未加载完成则返回 null
   */
  async getNewCount(): Promise<number | null> {
    const cards = await this.getStatisticsCards();
    const newCard = cards.find(c => c.cardName.includes('新增'));
    if (!newCard) {
      console.debug('[数据统计] 未找到新增人数卡片');
      return null;
    }
    // 提取数值
    const valueStr = newCard.currentValue.replace(/[^\d.]/g, '');
    const value = parseFloat(valueStr);
    return isNaN(value) ? null : value;
  }

  /**
   * 强制刷新统计数据 (清除缓存)
   * 用于测试数据同步时确保获取最新数据
   *
   * 修复说明：原实现使用 location.reload() 后的代码永远不会执行。
   * 新实现使用 page.reload() 并在重新加载后恢复 token。
   */
  async forceRefreshStatistics(): Promise<void> {
    // 在刷新前保存 token
    const token = await this.page.evaluate(() => {
      const token = localStorage.getItem('enterprise_token');
      // 清除 React Query 缓存和其他缓存数据
      localStorage.clear();
      sessionStorage.clear();
      return token;
    });

    // 刷新页面
    await this.page.reload({ waitUntil: 'domcontentloaded', timeout: TIMEOUTS.PAGE_LOAD });

    // 恢复 token 并触发存储事件以更新应用状态
    if (token) {
      await this.page.evaluate((t) => {
        localStorage.setItem('enterprise_token', t);
        // 触发 storage 事件以更新应用状态
        window.dispatchEvent(new Event('storage'));
      }, token);
    }

    // 等待页面稳定
    await this.page.waitForTimeout(TIMEOUTS.MEDIUM);
  }

  /**
   * 验证统计数据一致性 (数据准确性验证)
   * @param expected 预期的统计数据
   * @returns 验证结果对象
   */
  async validateStatisticsAccuracy(expected: {
    employmentCount?: number;
    averageSalary?: number;
    employmentRate?: number;
    newCount?: number;
  }): Promise<{
    passed: boolean;
    details: {
      employmentCount?: { expected: number; actual: number | null; match: boolean };
      averageSalary?: { expected: number; actual: number | null; match: boolean };
      employmentRate?: { expected: number; actual: number | null; match: boolean };
      newCount?: { expected: number; actual: number | null; match: boolean };
    };
  }> {
    const details: {
      employmentCount?: { expected: number; actual: number | null; match: boolean };
      averageSalary?: { expected: number; actual: number | null; match: boolean };
      employmentRate?: { expected: number; actual: number | null; match: boolean };
      newCount?: { expected: number; actual: number | null; match: boolean };
    } = {};

    if (expected.employmentCount !== undefined) {
      const actual = await this.getEmploymentCount();
      details.employmentCount = {
        expected: expected.employmentCount,
        actual,
        match: actual !== null && actual === expected.employmentCount
      };
    }

    if (expected.averageSalary !== undefined) {
      const actual = await this.getAverageSalary();
      details.averageSalary = {
        expected: expected.averageSalary,
        actual,
        match: actual !== null && Math.abs(actual - expected.averageSalary) < 1 // 允许 1 元误差
      };
    }

    if (expected.employmentRate !== undefined) {
      const actual = await this.getEmploymentRate();
      details.employmentRate = {
        expected: expected.employmentRate,
        actual,
        match: actual !== null && Math.abs(actual - expected.employmentRate) < 1 // 允许 1% 误差
      };
    }

    if (expected.newCount !== undefined) {
      const actual = await this.getNewCount();
      details.newCount = {
        expected: expected.newCount,
        actual,
        match: actual !== null && actual === expected.newCount
      };
    }

    const passed = Object.values(details).every((d) => d.match);

    return { passed, details };
  }

  // ===== 订单列表统计方法 (Story 13.13) =====

  /**
   * 获取订单卡片的统计数据 (Story 13.13)
   * @param orderName 订单名称
   * @returns 订单卡片统计数据，如果未找到则返回 null
   * @example
   * const stats = await miniPage.getOrderCardStats('测试订单');
   * console.debug(`Checkin: ${stats.checkinStats.current}/${stats.checkinStats.total}`);
   */
  async getOrderCardStats(orderName: string): Promise<{
    checkinStats: { current: number; total: number; percentage: number };
    salaryVideoStats: { current: number; total: number; percentage: number };
    taxVideoStats: { current: number; total: number; percentage: number };
  } | null> {
    console.debug(`[订单列表] 获取订单 "${orderName}" 的统计数据`);

    // 确保在订单列表页面
    const currentUrl = this.page.url();
    if (!currentUrl.includes('/pages/yongren/order/list')) {
      console.debug(`[订单列表] 警告: 当前不在订单列表页面`);
      return null;
    }

    // 查找包含订单名称的卡片
    const orderCard = this.page.locator('.bg-white.p-4').filter({ hasText: orderName }).first();

    // 等待卡片可见
    const isVisible = await orderCard.isVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE_SHORT }).catch(() => false);
    if (!isVisible) {
      console.debug(`[订单列表] 未找到订单卡片: ${orderName}`);
      return null;
    }

    // 解析统计数据
    // 统计卡片结构：
    // - 本月打卡: bg-blue-50
    // - 工资视频: bg-green-50
    // - 个税视频: bg-purple-50
    // 格式: "current/total percentage%" 或 "..." (加载中)

    const parseStats = async (colorClass: string, label: string) => {
      // 查找统计卡片
      const statCard = orderCard.locator(`.${colorClass}`).first();

      // 等待统计数据加载（检查是否还在加载中 "..."）
      await this.page.waitForTimeout(1000);

      const cardText = await statCard.textContent() || '';
      console.debug(`[订单列表] ${label} 卡片内容: "${cardText}"`);

      // 如果正在加载，返回默认值
      if (cardText.includes('...')) {
        return { current: 0, total: 0, percentage: 0 };
      }

      // 解析格式: "24/30 80%" 或类似
      // 提取数字
      const numbers = cardText.match(/(\d+)\/(\d+)\s*(\d+)%?/);
      if (numbers) {
        return {
          current: parseInt(numbers[1], 10),
          total: parseInt(numbers[2], 10),
          percentage: parseInt(numbers[3], 10)
        };
      }

      // 如果没有匹配到格式，返回默认值
      console.debug(`[订单列表] 警告: ${label} 统计数据格式无法解析: "${cardText}"`);
      return { current: 0, total: 0, percentage: 0 };
    };

    const checkinStats = await parseStats('bg-blue-50', '本月打卡');
    const salaryVideoStats = await parseStats('bg-green-50', '工资视频');
    const taxVideoStats = await parseStats('bg-purple-50', '个税视频');

    return {
      checkinStats,
      salaryVideoStats,
      taxVideoStats
    };
  }

  /**
   * 验证订单卡片统计字段 (Story 13.13)
   * @param orderName 订单名称
   * @param fieldName 字段名称: 'checkinStats' | 'salaryVideoStats' | 'taxVideoStats'
   * @param expected 预期的统计值
   * @example
   * await miniPage.expectOrderStatsField('测试订单', 'checkinStats', {
   *   current: 24,
   *   total: 30,
   *   percentage: 80
   * });
   */
  async expectOrderStatsField(
    orderName: string,
    fieldName: 'checkinStats' | 'salaryVideoStats' | 'taxVideoStats',
    expected: { current: number; total: number; percentage: number }
  ): Promise<boolean> {
    console.debug(`[订单列表] 验证订单 "${orderName}" 的 ${fieldName} 字段`);

    const stats = await this.getOrderCardStats(orderName);
    if (!stats) {
      console.debug(`[订单列表] 无法获取订单统计数据`);
      return false;
    }

    const actual = stats[fieldName];
    const match = actual.current === expected.current &&
                  actual.total === expected.total &&
                  actual.percentage === expected.percentage;

    if (match) {
      console.debug(`[订单列表] ${fieldName} 验证通过 ✓: ${actual.current}/${actual.total} ${actual.percentage}%`);
    } else {
      console.debug(`[订单列表] ${fieldName} 验证失败: 期望 ${expected.current}/${expected.total} ${expected.percentage}%, 实际 ${actual.current}/${actual.total} ${actual.percentage}%`);
    }

    return match;
  }

  // ===== 订单详情页统计方法 (Story 13.14) =====

  /**
   * 获取订单详情页的统计数据 (Story 13.14)
   * @returns 订单详情页统计数据，如果未加载完成则返回 null
   * @example
   * const stats = await miniPage.getOrderDetailStats();
   * console.debug(`Checkin: ${stats.checkinStats.current}/${stats.checkinStats.total}`);
   */
  async getOrderDetailStats(): Promise<OrderDetailStats | null> {
    console.debug(`[订单详情] 获取详情页统计数据`);

    // 确保在订单详情页面
    const currentUrl = this.page.url();
    if (!currentUrl.includes('/pages/yongren/order/detail')) {
      console.debug(`[订单详情] 警告: 当前不在订单详情页面`);
      return null;
    }

    // 等待统计数据加载（检查是否还在加载中）
    await this.page.waitForTimeout(1000);

    // 解析统计数据
    // 详情页统计卡片结构（与列表页相同）：
    // - 本月打卡: bg-blue-50
    // - 工资视频: bg-green-50
    // - 个税视频: bg-purple-50
    // 格式: "current/total percentage%" 或 "..." (加载中)

    const parseStats = async (colorClass: string, label: string) => {
      // 查找统计卡片
      const statCard = this.page.locator(`.${colorClass}`).first();

      const cardText = await statCard.textContent() || '';
      console.debug(`[订单详情] ${label} 卡片内容: "${cardText}"`);

      // 如果正在加载，返回默认值
      if (cardText.includes('...')) {
        return { current: 0, total: 0, percentage: 0 };
      }

      // 解析格式: "24/30 80%" 或类似
      // 提取数字
      const numbers = cardText.match(/(\d+)\/(\d+)\s*(\d+)%?/);
      if (numbers) {
        return {
          current: parseInt(numbers[1], 10),
          total: parseInt(numbers[2], 10),
          percentage: parseInt(numbers[3], 10)
        };
      }

      // 如果没有匹配到格式，返回默认值
      console.debug(`[订单详情] 警告: ${label} 统计数据格式无法解析: "${cardText}"`);
      return { current: 0, total: 0, percentage: 0 };
    };

    const checkinStats = await parseStats('bg-blue-50', '本月打卡');
    const salaryVideoStats = await parseStats('bg-green-50', '工资视频');
    const taxVideoStats = await parseStats('bg-purple-50', '个税视频');

    // 获取实际人数（从基本信息卡片）
    let actualPeople = 0;
    const pageContent = await this.page.textContent('body') || '';
    const actualPeopleMatch = pageContent.match(/实际人数\s*(\d+)/);
    if (actualPeopleMatch) {
      actualPeople = parseInt(actualPeopleMatch[1], 10);
    }

    return {
      actualPeople,
      checkinStats,
      salaryVideoStats,
      taxVideoStats
    };
  }

  /**
   * 验证订单详情页与列表页统计数据一致性 (Story 13.14)
   * @param orderName 订单名称
   * @returns 是否一致
   * @example
   * const isConsistent = await miniPage.expectOrderDetailStatsConsistentWithList('测试订单');
   */
  async expectOrderDetailStatsConsistentWithList(orderName: string): Promise<boolean> {
    console.debug(`[订单详情] 验证详情页与列表页统计数据一致性，订单: ${orderName}`);

    // 获取列表页统计数据
    const listStats = await this.getOrderCardStats(orderName);
    if (!listStats) {
      console.debug(`[订单详情] 无法获取列表页统计数据`);
      return false;
    }

    // 获取详情页统计数据
    const detailStats = await this.getOrderDetailStats();
    if (!detailStats) {
      console.debug(`[订单详情] 无法获取详情页统计数据`);
      return false;
    }

    // 验证三个统计字段一致
    const checkinMatch = listStats.checkinStats.current === detailStats.checkinStats.current &&
                        listStats.checkinStats.total === detailStats.checkinStats.total &&
                        listStats.checkinStats.percentage === detailStats.checkinStats.percentage;

    const salaryMatch = listStats.salaryVideoStats.current === detailStats.salaryVideoStats.current &&
                       listStats.salaryVideoStats.total === detailStats.salaryVideoStats.total &&
                       listStats.salaryVideoStats.percentage === detailStats.salaryVideoStats.percentage;

    const taxMatch = listStats.taxVideoStats.current === detailStats.taxVideoStats.current &&
                     listStats.taxVideoStats.total === detailStats.taxVideoStats.total &&
                     listStats.taxVideoStats.percentage === detailStats.taxVideoStats.percentage;

    const match = checkinMatch && salaryMatch && taxMatch;

    if (match) {
      console.debug(`[订单详情] 详情页与列表页统计数据一致 ✓`);
    } else {
      console.debug(`[订单详情] 详情页与列表页统计数据不一致:`);
      if (!checkinMatch) {
        console.debug(`  本月打卡: 列表=${listStats.checkinStats.current}/${listStats.checkinStats.total} ${listStats.checkinStats.percentage}%, 详情=${detailStats.checkinStats.current}/${detailStats.checkinStats.total} ${detailStats.checkinStats.percentage}%`);
      }
      if (!salaryMatch) {
        console.debug(`  工资视频: 列表=${listStats.salaryVideoStats.current}/${listStats.salaryVideoStats.total} ${listStats.salaryVideoStats.percentage}%, 详情=${detailStats.salaryVideoStats.current}/${detailStats.salaryVideoStats.total} ${detailStats.salaryVideoStats.percentage}%`);
      }
      if (!taxMatch) {
        console.debug(`  个税视频: 列表=${listStats.taxVideoStats.current}/${listStats.taxVideoStats.total} ${listStats.taxVideoStats.percentage}%, 详情=${detailStats.taxVideoStats.current}/${detailStats.taxVideoStats.total} ${detailStats.taxVideoStats.percentage}%`);
      }
    }

    return match;
  }

  /**
   * 验证订单详情页单个统计字段 (Story 13.14)
   * @param fieldName 字段名称: 'checkinStats' | 'salaryVideoStats' | 'taxVideoStats' | 'actualPeople'
   * @param expected 预期的值
   * @returns 是否匹配
   * @example
   * await miniPage.expectOrderDetailStatsField('checkinStats', {
   *   current: 1,
   *   total: 1,
   *   percentage: 100
   * });
   * await miniPage.expectOrderDetailStatsField('actualPeople', 1);
   */
  async expectOrderDetailStatsField(
    fieldName: 'checkinStats' | 'salaryVideoStats' | 'taxVideoStats' | 'actualPeople',
    expected: number | { current: number; total: number; percentage: number }
  ): Promise<boolean> {
    console.debug(`[订单详情] 验证 ${fieldName} 字段`);

    const stats = await this.getOrderDetailStats();
    if (!stats) {
      console.debug(`[订单详情] 无法获取详情页统计数据`);
      return false;
    }

    let match = false;

    if (fieldName === 'actualPeople') {
      match = stats.actualPeople === (expected as number);
      if (match) {
        console.debug(`[订单详情] actualPeople 验证通过 ✓: ${stats.actualPeople}人`);
      } else {
        console.debug(`[订单详情] actualPeople 验证失败: 期望 ${expected}人, 实际 ${stats.actualPeople}人`);
      }
    } else {
      const actual = stats[fieldName];
      const expectedValue = expected as { current: number; total: number; percentage: number };
      match = actual.current === expectedValue.current &&
            actual.total === expectedValue.total &&
            actual.percentage === expectedValue.percentage;

      if (match) {
        console.debug(`[订单详情] ${fieldName} 验证通过 ✓: ${actual.current}/${actual.total} ${actual.percentage}%`);
      } else {
        console.debug(`[订单详情] ${fieldName} 验证失败: 期望 ${expectedValue.current}/${expectedValue.total} ${expectedValue.percentage}%, 实际 ${actual.current}/${actual.total} ${actual.percentage}%`);
      }
    }

    return match;
  }
}
