- 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
17 KiB
17 KiB
登录及现存量数据下载完整流程
本文档使用 Mermaid 流程图呈现从登录系统到完成现存量数据下载的全过程。
⚠️ 重要提示
- 每个操作前必须等待目标元素加载完成 —— 网页反应速度有限,不可假设点击后立即可以操作下一步
- 下载步骤 9 只能用 HOVER 触发,点击无效 —— 用户特别强调 (4 个感叹号)
- 条件分支以用户实际观察为准 —— 不推测原因,只呈现现象
- 登录流程包含强制登录弹窗处理 —— 系统可能弹出"确定"确认框
流程总览
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() |
登录流程说明:
-
导航到登录页面 - 使用完整 URL:
url = f"{base_url.rstrip('/')}/yonbip/resources/uap/rbac/login/main/index.html" -
定位 iframe - 所有登录表单元素都在
#forwardFrameiframe 内:main_frame = page.locator("#forwardFrame").content_frame -
填写凭据 - 使用 Playwright 的 role 定位器:
main_frame.get_by_role("textbox", name="用户名").fill(username) main_frame.get_by_role("textbox", name="密码").fill(password) -
点击登录 - 登录按钮:
main_frame.get_by_role("button", name="登录").click() -
处理强制登录弹窗 - 系统可能弹出确认框:
confirm_btn = main_frame.get_by_role("button", name="确定") if confirm_btn.count() > 0: confirm_btn.click() # 强制登录模式 else: pass # 正常登录模式 -
等待登录完成 - 登录后进入系统主页,准备进行下一步操作
阶段 1: 应用选择
| 步骤 | 操作 | 前置条件 | 元素 OuterHTML (完整) |
|---|---|---|---|
| 5 | 点击应用菜单图标 | 登录后的主页已加载完成 | <div class="nc-workbench-icon" data-step="1" data-intro=" <img src=images/novice-guide-allapps.gif alt="" class='novice-guide-allapps' /> <p>应用菜单</p> <div class='span-box'> <span>展示全部有权限的应用。在"我的应用"中,可查看"我的收藏"和"最近访问"的应用。</span> </div> <i class='iconfont icon-guanbi2 guide-close' onclick="handleWorkbenchIntro('close')"></i> "><i style="background-image: url("images/logo2.svg");"></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 个感叹号强调此点
- 实现代码示例:
// 正确方式 await dropdownTrigger.hover(); await page.waitForTimeout(1000); // 等待菜单展开 // ❌ 错误方式:不要使用 click() // await dropdownTrigger.click();
步骤 10 后的两种可能情况:
| 情况 | 现象 | OuterHTML | 处理 |
|---|---|---|---|
| 可能性 1 | 弹出确认对话框 | <div class="wui-modal-body">当前数据较多,系统在处理完成后会发送通知消息,是否继续?</div><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 弹出确认对话框时)
确认对话框元素:
<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>
数据等待子流程:
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):
<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>
时间判断逻辑:
- 记录点击"继续"按钮的时间戳
T(格式:YYYY-MM-DD HH:mm:ss) - 获取历史数据列表中所有记录的时间戳 (从
<span class="ts sidebox-ts-class">提取) - 遍历检查:
record_timestamp > T - 如果存在符合条件的记录 → 选择最新的一条 (第一条通常是最新的)
- 如果不存在 → 关闭侧边栏,等待 5 秒后重试
元素加载等待机制
这是用户特别强调的核心机制 —— 每次操作前必须确认目标元素已加载完成。
等待函数实现
/**
* 等待元素加载完成
* @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);
}
}
在主流程中的应用
每个步骤的标准操作模式:
// 登录阶段:填写用户名
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 |
确认继续 |
实现注意事项
-
时间戳格式必须一致 —— 点击"继续"按钮记录的时间 T 必须与页面显示的时间戳格式相同 (
YYYY-MM-DD HH:mm:ss) -
循环等待需设置最大次数 —— 避免无限循环,建议设置最大重试次数 (如 10 次)
-
侧边栏关闭后需等待 —— 关闭侧边栏后至少等待 5 秒再重新打开,给系统处理数据的时间
-
HOVER 后需等待菜单展开 —— HOVER 动作后至少等待 1 秒再执行下一步点击