Files
BIPAuto/docs/现存量数据下载流程.md
Misaka_Company 08a145af81 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
2026-04-09 15:02:33 +08:00

391 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 登录及现存量数据下载完整流程
本文档使用 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 | 点击应用菜单图标 | 登录后的主页已加载完成 | `<div class="nc-workbench-icon" data-step="1" data-intro=" &lt;img src=images/novice-guide-allapps.gif alt=&quot;&quot; class='novice-guide-allapps' /&gt; &lt;p&gt;应用菜单&lt;/p&gt; &lt;div class='span-box'&gt; &lt;span&gt;展示全部有权限的应用。在"我的应用"中,可查看"我的收藏"和"最近访问"的应用。&lt;/span&gt; &lt;/div&gt; &lt;i class='iconfont icon-guanbi2 guide-close' onclick=&quot;handleWorkbenchIntro('close')&quot;&gt;&lt;/i&gt; "><i style="background-image: url(&quot;images/logo2.svg&quot;);"></i></div>` |
| 6 | 点击"现存量"应用 | 应用列表已展开完成 | `<div class="item-app" open-type="tab" grp-index="0" item-index="5" title="现存量" style="min-width: 255px;">现存量<div class="icon-content"><i class="iconfont icon-xinyeqiandakai app-open" grp-index="0" item-index="5" open-type="newtab" date-for-wui-tooltip="wui-tooltip-8makk4g2uj"></i><i class="iconfont icon-shoucangdianliang" date-for-wui-tooltip="wui-tooltip-79heoa4zzh" style="color: rgb(240, 158, 68); font-size: 16px; visibility: visible;"></i></div></div>` |
### 阶段 2: 查询方案选择
| 步骤 | 操作 | 前置条件 | 元素 OuterHTML (完整) |
|------|------|----------|----------------------|
| 7 | 点击行操作下拉图标 | 现存量页面已加载完成 | `<i class="iconfont icon-hangcaozuoxiala1"></i>` |
| 8 | 点击"现存量 - 总量"方案 | 查询方案列表已弹出完成 | (列表项,通过文本"现存量 - 总量"定位) |
**步骤 8 后的两种可能情况:**
| 情况 | 现象 | OuterHTML | 处理 |
|------|------|-----------|------|
| 可能性 1 | 出现"正在加载,请耐心等待"全屏提示 | `<div class="wui-spin-backdrop wui-spin-full-screen base-loading-back-drop" style="z-index: 1900;"><div class="wui-spin-default-container "><div class="wui-spin wui-spin-default wui-spin-show-text base-loading base-loading-spin-show"><div class="wui-spin-spin wui-spin-dot wui-spin-dot-spin"><i></i><i></i><i></i><i></i></div></div><div class="wui-spin-desc">加载中...</div></div></div>` | 等待加载完成,直到该元素消失 |
| 可能性 2 | 页面无提示,仅短暂卡顿 (1-3 秒) | 无额外元素出现 | 等待 1-3 秒即可 |
### 阶段 3: 执行下载
| 步骤 | 操作 | 前置条件 | 元素 OuterHTML (完整) |
|------|------|----------|----------------------|
| 9 | **HOVER** 触发行操作下拉图标 | 查询结果数据已显示完成 | `<span class="wui-button-text-wrap"><i class="arrow iconfont icon-hangcaozuoxiala1 nc-button-area-print-icon "></i></span>` |
| 10 | 点击"输出 xlsx 文件" | 操作菜单已弹出完成 | `<li class="wui-menu-item wui-menu-item-only-child dropdown-btn-item" role="menuitem" tabindex="-1" btn-code="xlsx" date-for-wui-tooltip="wui-tooltip-cj1jzq1434" aria-disabled="false" data-menu-id="rc-menu-uuid-36646-2-xlsx"><span date-for-wui-tooltip="wui-tooltip-vvedhdkxwj"><div class="dropdown-btn-item-box"><div class="btn-item-left-wrapper"><div class="btn-item-left" btn-code="xlsx">输出 xlsx 文件</div></div></div></span></li>` |
> **⚠️ 步骤 9 特别警告**
>
> - **必须使用 HOVER 动作,点击无法正确触发!**
> - 用户用 4 个感叹号强调此点
> - 实现代码示例:
> ```typescript
> // 正确方式
> await dropdownTrigger.hover();
> await page.waitForTimeout(1000); // 等待菜单展开
>
> // ❌ 错误方式:不要使用 click()
> // await dropdownTrigger.click();
> ```
**步骤 10 后的两种可能情况:**
| 情况 | 现象 | OuterHTML | 处理 |
|------|------|-----------|------|
| 可能性 1 | 弹出确认对话框 | `<div class="wui-modal-body">当前数据较多,系统在处理完成后会发送通知消息,是否继续?</div>`<br/>`<button type="button" class="wui-button sure-button nc-button-wrapper button-primary " tabindex="-1"><span class="wui-button-text-wrap">继续</span></button>` | 点击"继续"按钮,然后进入阶段 4 |
| 可能性 2 | 无对话框 | 无额外元素出现 | 直接触发下载,流程结束 |
### 阶段 4: 等待数据准备 (仅当步骤 10 弹出确认对话框时)
**确认对话框元素:**
```html
<div class="wui-modal-resizbox modal-content-resizeWrap react-draggable">
<div class="wui-modal-content">
<div class="wui-modal-body">当前数据较多,系统在处理完成后会发送通知消息,是否继续?</div>
<div class="wui-modal-footer">
<button class="wui-button sure-button">继续</button>
<button class="wui-button cancel-button">取消</button>
</div>
</div>
</div>
```
**数据等待子流程:**
```mermaid
flowchart TD
A[点击继续按钮] --> B[记录点击时间 T<br/>格式YYYY-MM-DD HH:mm:ss]
B --> C[点击历史数据图标]
C --> D[侧边栏打开]
D --> E[点击实时分享标签]
E --> F[获取数据列表]
F --> G{存在时间戳 > T 的记录?}
G -->|是 | H[找到目标数据<br/>选择最新的一条]
G -->|否 | I[关闭侧边栏]
I --> J["等待片刻 (建议 5 秒)"]
J --> C
H --> K[点击下载图标]
K --> L[下载完成]
```
**历史数据元素结构 (完整 OuterHTML)**
```html
<li class="history nc-theme-xrow-bgc">
<p class="title sidebox-title-class" date-for-wui-tooltip="wui-tooltip-d2n8imkyvl">现存量</p>
<span class="ts sidebox-ts-class">2026-04-09 14:16:03</span>
<br>
<span class="ts sidebox-ts-class">发送人:彭强强</span>
<i class="read-icon not-read">未读</i>
<span class="icon iconfont icon-xiazai1" date-for-wui-tooltip="wui-tooltip-h6v3886qti"></span>
</li>
```
**时间判断逻辑:**
1. 记录点击"继续"按钮的时间戳 `T` (格式:`YYYY-MM-DD HH:mm:ss`)
2. 获取历史数据列表中所有记录的时间戳 (从 `<span class="ts sidebox-ts-class">` 提取)
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<void> {
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 秒再执行下一步点击