# 登录及现存量数据下载完整流程 本文档使用 Mermaid 流程图呈现从**登录系统**到**完成现存量数据下载**的全过程。 > **⚠️ 重要提示** > > 1. **每个操作前必须等待目标元素加载完成** —— 网页反应速度有限,不可假设点击后立即可以操作下一步 > 2. **下载步骤 9 只能用 HOVER 触发,点击无效** —— 用户特别强调 (4 个感叹号) > 3. **条件分支以用户实际观察为准** —— 不推测原因,只呈现现象 > 4. **登录流程包含强制登录弹窗处理** —— 系统可能弹出"确定"确认框 ## 流程总览 ```mermaid sequenceDiagram participant User as 用户 participant Page as 页面 participant System as 系统 participant HistoryPanel as 历史数据侧边栏 Note over User,HistoryPanel: 阶段 0: 登录系统 Page->>Page: 导航到登录 URL Page->>Page: 等待#forwardFrame iframe 加载完成 User->>Page: 1. 填写用户名 Page->>Page: 等待密码框加载完成 User->>Page: 2. 填写密码 Page->>Page: 等待登录按钮加载完成 User->>Page: 3. 点击登录按钮 alt 出现强制登录确认框 Page->>Page: 弹出"确定"确认框 User->>Page: 4. 点击"确定"按钮 Note over User,HistoryPanel: 强制登录模式 else 无确认框 Note over User,HistoryPanel: 正常登录模式 end Page->>Page: 等待登录完成 Page->>Page: 进入系统主页 Note over User,HistoryPanel: 阶段 1: 应用选择 Page->>Page: 等待应用菜单图标加载完成 User->>Page: 5. 点击应用菜单图标 Page->>Page: 展开应用列表 Page->>Page: 等待"现存量"应用项加载完成 User->>Page: 6. 点击"现存量"应用 Page->>Page: 打开新标签页 Note over User,HistoryPanel: 阶段 2: 查询方案选择 Page->>Page: 等待行操作下拉图标加载完成 User->>Page: 7. 点击行操作下拉图标 Page->>Page: 弹出查询方案列表 Page->>Page: 等待"现存量 - 总量"选项加载完成 User->>Page: 8. 点击"现存量 - 总量" alt 出现"正在加载,请耐心等待"提示 Page->>Page: 显示全屏加载提示 Page->>Page: 等待加载完成 Page->>Page: 显示查询结果 else 页面无提示,仅短暂卡顿 (1-3 秒) Page->>Page: 短暂卡顿 Page->>Page: 显示查询结果 end Note over User,HistoryPanel: 阶段 3: 执行下载 Page->>Page: 等待查询结果数据加载完成 Page->>Page: 等待行操作下拉图标 (打印图标) 加载完成 rect rgb(255, 240, 200) Note over User,Page: ⚠️ 只能使用 **HOVER**,点击无法触发! User->>Page: 9. HOVER 触发行操作下拉图标 end Page->>Page: 弹出操作菜单 Page->>Page: 等待"输出 xlsx 文件"选项加载完成 User->>Page: 10. 点击"输出 xlsx 文件" alt 弹出确认对话框 Page->>Page: 显示确认对话框 Page->>Page: 记录点击"继续"的时间戳 T User->>Page: 点击"继续"按钮 Note over User,HistoryPanel: 阶段 4: 等待数据准备 User->>HistoryPanel: 11. 点击"历史数据"图标 HistoryPanel->>HistoryPanel: 打开侧边栏 Page->>Page: 等待"实时分享"标签加载完成 User->>HistoryPanel: 点击"实时分享"标签 HistoryPanel->>HistoryPanel: 显示数据列表 loop 循环检查直到找到目标数据 HistoryPanel->>HistoryPanel: 获取所有记录的时间戳 HistoryPanel->>HistoryPanel: 检查:存在时间戳 > T 的记录? alt 存在符合条件的记录 HistoryPanel->>HistoryPanel: 找到目标数据 else 不存在符合条件的记录 User->>HistoryPanel: 关闭侧边栏 Note over User,HistoryPanel: 等待片刻 (如 5 秒) User->>HistoryPanel: 再次点击"历史数据"图标 end end Page->>Page: 等待下载图标加载完成 User->>HistoryPanel: 点击下载图标 HistoryPanel->>User: 触发下载 else 无对话框,直接下载 Page->>User: 直接触发下载 end Note over User,HistoryPanel: ✅ 下载完成 ``` ## 详细步骤说明 ### 阶段 0: 登录系统 | 步骤 | 操作 | 前置条件 | 元素选择器 / OuterHTML | |------|------|----------|----------------------| | 1 | 填写用户名 | 登录页面 #forwardFrame iframe 已加载完成 | `main_frame.get_by_role("textbox", name="用户名").fill(username)` | | 2 | 填写密码 | 用户名已填写完成 | `main_frame.get_by_role("textbox", name="密码").fill(password)` | | 3 | 点击登录按钮 | 密码已填写完成 | `main_frame.get_by_role("button", name="登录").click()` | | 4 | 处理强制登录确认框 (如出现) | 登录按钮已点击 | `main_frame.get_by_role("button", name="确定").click()` | **登录流程说明:** 1. **导航到登录页面** - 使用完整 URL: ```python url = f"{base_url.rstrip('/')}/yonbip/resources/uap/rbac/login/main/index.html" ``` 2. **定位 iframe** - 所有登录表单元素都在 `#forwardFrame` iframe 内: ```python main_frame = page.locator("#forwardFrame").content_frame ``` 3. **填写凭据** - 使用 Playwright 的 role 定位器: ```python main_frame.get_by_role("textbox", name="用户名").fill(username) main_frame.get_by_role("textbox", name="密码").fill(password) ``` 4. **点击登录** - 登录按钮: ```python main_frame.get_by_role("button", name="登录").click() ``` 5. **处理强制登录弹窗** - 系统可能弹出确认框: ```python confirm_btn = main_frame.get_by_role("button", name="确定") if confirm_btn.count() > 0: confirm_btn.click() # 强制登录模式 else: pass # 正常登录模式 ``` 6. **等待登录完成** - 登录后进入系统主页,准备进行下一步操作 --- ### 阶段 1: 应用选择 | 步骤 | 操作 | 前置条件 | 元素 OuterHTML (完整) | |------|------|----------|----------------------| | 5 | 点击应用菜单图标 | 登录后的主页已加载完成 | `
` | | 6 | 点击"现存量"应用 | 应用列表已展开完成 | `
现存量
` | ### 阶段 2: 查询方案选择 | 步骤 | 操作 | 前置条件 | 元素 OuterHTML (完整) | |------|------|----------|----------------------| | 7 | 点击行操作下拉图标 | 现存量页面已加载完成 | `` | | 8 | 点击"现存量 - 总量"方案 | 查询方案列表已弹出完成 | (列表项,通过文本"现存量 - 总量"定位) | **步骤 8 后的两种可能情况:** | 情况 | 现象 | OuterHTML | 处理 | |------|------|-----------|------| | 可能性 1 | 出现"正在加载,请耐心等待"全屏提示 | `
加载中...
` | 等待加载完成,直到该元素消失 | | 可能性 2 | 页面无提示,仅短暂卡顿 (1-3 秒) | 无额外元素出现 | 等待 1-3 秒即可 | ### 阶段 3: 执行下载 | 步骤 | 操作 | 前置条件 | 元素 OuterHTML (完整) | |------|------|----------|----------------------| | 9 | **HOVER** 触发行操作下拉图标 | 查询结果数据已显示完成 | `` | | 10 | 点击"输出 xlsx 文件" | 操作菜单已弹出完成 | `` | > **⚠️ 步骤 9 特别警告** > > - **必须使用 HOVER 动作,点击无法正确触发!** > - 用户用 4 个感叹号强调此点 > - 实现代码示例: > ```typescript > // 正确方式 > await dropdownTrigger.hover(); > await page.waitForTimeout(1000); // 等待菜单展开 > > // ❌ 错误方式:不要使用 click() > // await dropdownTrigger.click(); > ``` **步骤 10 后的两种可能情况:** | 情况 | 现象 | OuterHTML | 处理 | |------|------|-----------|------| | 可能性 1 | 弹出确认对话框 | `
当前数据较多,系统在处理完成后会发送通知消息,是否继续?
`
`` | 点击"继续"按钮,然后进入阶段 4 | | 可能性 2 | 无对话框 | 无额外元素出现 | 直接触发下载,流程结束 | ### 阶段 4: 等待数据准备 (仅当步骤 10 弹出确认对话框时) **确认对话框元素:** ```html ``` **数据等待子流程:** ```mermaid flowchart TD A[点击继续按钮] --> B[记录点击时间 T
格式:YYYY-MM-DD HH:mm:ss] B --> C[点击历史数据图标] C --> D[侧边栏打开] D --> E[点击实时分享标签] E --> F[获取数据列表] F --> G{存在时间戳 > T 的记录?} G -->|是 | H[找到目标数据
选择最新的一条] G -->|否 | I[关闭侧边栏] I --> J["等待片刻 (建议 5 秒)"] J --> C H --> K[点击下载图标] K --> L[下载完成] ``` **历史数据元素结构 (完整 OuterHTML):** ```html
  • 2026-04-09 14:16:03
    发送人:彭强强 未读
  • ``` **时间判断逻辑:** 1. 记录点击"继续"按钮的时间戳 `T` (格式:`YYYY-MM-DD HH:mm:ss`) 2. 获取历史数据列表中所有记录的时间戳 (从 `` 提取) 3. 遍历检查:`record_timestamp > T` 4. 如果存在符合条件的记录 → 选择**最新的一条** (第一条通常是最新的) 5. 如果不存在 → 关闭侧边栏,等待 5 秒后重试 --- ## 元素加载等待机制 **这是用户特别强调的核心机制** —— 每次操作前必须确认目标元素已加载完成。 ### 等待函数实现 ```typescript /** * 等待元素加载完成 * @param locator - Playwright locator * @param options - 配置选项 * - timeout: 超时时间 (毫秒),默认 30000 * - state: 等待状态 ('visible' | 'attached' | 'hidden'),默认 'visible' * - extraDelay: 额外等待时间 (毫秒),默认 500,确保元素完全可交互 */ async function waitForElement(locator: Locator, options: { timeout?: number; state?: 'visible' | 'attached' | 'hidden'; extraDelay?: number; } = {}): Promise { const { timeout = 30000, state = 'visible', extraDelay = 500 } = options; // 等待元素达到指定状态 await locator.waitFor({ state, timeout }); // 额外等待,确保元素完全可交互 if (extraDelay > 0) { await page.waitForTimeout(extraDelay); } } ``` ### 在主流程中的应用 每个步骤的标准操作模式: ```typescript // 登录阶段:填写用户名 const usernameInput = main_frame.getByRole('textbox', { name: '用户名' }); await waitForElement(usernameInput); await usernameInput.fill(username); // 登录阶段:填写密码 const passwordInput = main_frame.getByRole('textbox', { name: '密码' }); await waitForElement(passwordInput); await passwordInput.fill(password); // 登录阶段:点击登录按钮 const loginButton = main_frame.getByRole('button', { name: '登录' }); await waitForElement(loginButton); await loginButton.click(); // 登录阶段:处理强制登录确认框 (如出现) const confirmButton = main_frame.getByRole('button', { name: '确定' }); if (await confirmButton.count() > 0) { await confirmButton.click(); } // 步骤 5: 点击应用菜单 const menuIcon = page.locator('.nc-workbench-icon'); await waitForElement(menuIcon); await menuIcon.click(); // 步骤 6: 点击"现存量"应用 const currentItem = page.locator('.item-app[title="现存量"]'); await waitForElement(currentItem); await currentItem.click(); // 步骤 9: HOVER 触发 (特殊) const dropdownTrigger = page.locator('span.wui-button-text-wrap i.icon-hangcaozuoxiala1.nc-button-area-print-icon'); await waitForElement(dropdownTrigger); await dropdownTrigger.hover(); // ⚠️ 必须 HOVER,不能 click await page.waitForTimeout(1000); // 等待菜单展开 // 步骤 8 后的加载等待 const loadingBackdrop = page.locator('.wui-spin-backdrop.wui-spin-full-screen'); if (await loadingBackdrop.isVisible()) { await loadingBackdrop.waitFor({ state: 'hidden', timeout: 60000 }); } else { await page.waitForTimeout(3000); // 无提示时等待 1-3 秒 } ``` --- ## 元素选择器参考表 ### 主流程元素 | 阶段 | 步骤 | 元素 | 选择器 (精简) | 完整 OuterHTML 参考 | |------|------|------|--------------|-------------------| | 0 (登录) | 1 | 用户名输入框 | `getByRole("textbox", name="用户名")` | 见阶段 0 表格 | | 0 (登录) | 2 | 密码输入框 | `getByRole("textbox", name="密码")` | 见阶段 0 表格 | | 0 (登录) | 3 | 登录按钮 | `getByRole("button", name="登录")` | 见阶段 0 表格 | | 0 (登录) | 4 | 强制登录确认按钮 | `getByRole("button", name="确定")` | 见阶段 0 表格 | | 1 | 5 | 应用菜单图标 | `.nc-workbench-icon` | 见阶段 1 表格 | | 1 | 6 | "现存量"应用 | `.item-app[title="现存量"]` | 见阶段 1 表格 | | 2 | 7 | 查询方案下拉 | `i.icon-hangcaozuoxiala1` | 见阶段 2 表格 | | 2 | 8 | "现存量 - 总量" | 文本定位 | 见阶段 2 表格 | | 3 | 9 | 行操作下拉 (HOVER) | `span.wui-button-text-wrap i.icon-hangcaozuoxiala1.nc-button-area-print-icon` | 见阶段 3 表格 | | 3 | 10 | "输出 xlsx 文件" | `li.wui-menu-item[btn-code="xlsx"]` | 见阶段 3 表格 | | 4 | 11 | "历史数据"图标 | `button.nc-button-wrapper i.icon-fenxianglishi` | - | | 4 | 11 | "实时分享"标签 | `div[role="tab"][nodekey="current"]` | - | | 4 | 11 | 下载图标 | `span.icon-xiazai1` | 见阶段 4 历史数据元素 | ### 弹窗元素 | 元素 | 选择器 | 用途 | |------|--------|------| | 加载提示 | `.wui-spin-backdrop.wui-spin-full-screen` | 等待加载完成 | | 确认对话框 `.wui-modal-body` (含文本判断) | 识别确认对话框 | | "继续"按钮 | `button.sure-button` | 确认继续 | --- ## 实现注意事项 1. **时间戳格式必须一致** —— 点击"继续"按钮记录的时间 T 必须与页面显示的时间戳格式相同 (`YYYY-MM-DD HH:mm:ss`) 2. **循环等待需设置最大次数** —— 避免无限循环,建议设置最大重试次数 (如 10 次) 3. **侧边栏关闭后需等待** —— 关闭侧边栏后至少等待 5 秒再重新打开,给系统处理数据的时间 4. **HOVER 后需等待菜单展开** —— HOVER 动作后至少等待 1 秒再执行下一步点击