From 08a145af813e03bade11b47a5b018408baad93a6 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Thu, 9 Apr 2026 15:02:33 +0800 Subject: [PATCH] docs: add complete login and existing stock data download workflow - Add Mermaid sequence diagram covering login to download completion - Document 5 phases: Login (4 steps), App selection (2 steps), Query scheme (2 steps), Download execution (2 steps), Data waiting (1 step) - Include element loading wait mechanism with TypeScript implementation - Add HOVER trigger warning for row operation dropdown (emphasized by user) - Add complete outerHTML reference for all UI elements - Document forced login popup handling - Add element selector reference table --- docs/现存量数据下载流程.md | 390 +++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 docs/现存量数据下载流程.md diff --git a/docs/现存量数据下载流程.md b/docs/现存量数据下载流程.md new file mode 100644 index 0000000..279173c --- /dev/null +++ b/docs/现存量数据下载流程.md @@ -0,0 +1,390 @@ +# 登录及现存量数据下载完整流程 + +本文档使用 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 秒再执行下一步点击