Compare commits
10 Commits
201f65d715
...
a78afe383f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a78afe383f | ||
|
|
c176063bfa | ||
|
|
902ff9b831 | ||
|
|
1e2eed76e2 | ||
|
|
60b6fecbb3 | ||
|
|
bbf84b7f8c | ||
|
|
4b0a882b69 | ||
|
|
ebd53b168e | ||
|
|
cf88800444 | ||
|
|
acd19025d4 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -13,4 +13,8 @@ tmpclaude-*
|
|||||||
*workspace*
|
*workspace*
|
||||||
*.png
|
*.png
|
||||||
data/
|
data/
|
||||||
orderID.txt
|
orderID.txt
|
||||||
|
ProductionID.txt
|
||||||
|
|
||||||
|
# 测试脚本
|
||||||
|
tests/
|
||||||
71
CLAUDE.md
71
CLAUDE.md
@@ -12,8 +12,8 @@ This is a Python automation project using Playwright to interact with a Chinese
|
|||||||
# Activate the virtual environment (Windows)
|
# Activate the virtual environment (Windows)
|
||||||
.venv\Scripts\activate
|
.venv\Scripts\activate
|
||||||
|
|
||||||
# Install dependencies
|
# Install all dependencies
|
||||||
pip install playwright
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# Install Playwright browsers
|
# Install Playwright browsers
|
||||||
playwright install chromium
|
playwright install chromium
|
||||||
@@ -23,17 +23,53 @@ playwright install chromium
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run the main test script
|
# Run the main test script
|
||||||
python test_playwright.py
|
python tests/test_playwright.py
|
||||||
|
|
||||||
# Run the record script
|
# Run the record script
|
||||||
python record.py
|
python record.py
|
||||||
|
|
||||||
|
# Run database query test
|
||||||
|
python tests/test_db_query.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Dependencies
|
## Key Dependencies
|
||||||
|
|
||||||
- **playwright==1.57.0**: Browser automation framework
|
- **playwright==1.57.0**: Browser automation framework
|
||||||
|
- **pyodbc**: SQL Server database connectivity
|
||||||
|
- **pandas**: Data processing and Excel file handling
|
||||||
|
- **openpyxl**: Excel file operations
|
||||||
- Uses synchronous API (`playwright.sync_api`)
|
- Uses synchronous API (`playwright.sync_api`)
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
playwrite/
|
||||||
|
├── config/ # Configuration files (database, etc.)
|
||||||
|
├── db/ # Database connection components
|
||||||
|
├── data/ # Data files (excluded from git)
|
||||||
|
├── docs/ # Documentation (e.g., DEPENDENCIES.md)
|
||||||
|
├── tests/ # Test scripts (excluded from git)
|
||||||
|
├── requirements.txt # Python dependencies
|
||||||
|
├── .gitignore # Git ignore rules
|
||||||
|
└── CLAUDE.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Organization Rules
|
||||||
|
|
||||||
|
**IMPORTANT**: When creating test scripts, always place them in the `tests/` folder:
|
||||||
|
|
||||||
|
- Test scripts should be prefixed with `test_`
|
||||||
|
- All files in `tests/` are excluded from git tracking
|
||||||
|
- Examples: `tests/test_playwright.py`, `tests/test_db_query.py`
|
||||||
|
|
||||||
|
**Configuration Management**:
|
||||||
|
- Place configuration files in `config/` folder
|
||||||
|
- Database credentials and settings go in `config/database_config.py`
|
||||||
|
|
||||||
|
**Data Storage**:
|
||||||
|
- Use `data/` folder for temporary data files
|
||||||
|
- This folder is excluded from git tracking
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Nested Iframe Structure
|
### Nested Iframe Structure
|
||||||
@@ -80,6 +116,35 @@ with page.expect_popup() as popup_info:
|
|||||||
new_page = popup_info.value
|
new_page = popup_info.value
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Database Connection
|
||||||
|
|
||||||
|
The project uses `pyodbc` to connect to SQL Server for data queries:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from db.connection import get_connection
|
||||||
|
|
||||||
|
# Using context manager
|
||||||
|
with get_connection() as db:
|
||||||
|
results = db.execute_query("SELECT * FROM table")
|
||||||
|
# Connection automatically closed
|
||||||
|
|
||||||
|
# Using the query helper
|
||||||
|
from db.connection import query_production_orders
|
||||||
|
results = query_production_orders(['ID1', 'ID2', 'ID3'])
|
||||||
|
```
|
||||||
|
|
||||||
|
Database configuration is stored in `config/database_config.py`:
|
||||||
|
```python
|
||||||
|
SQL_SERVER_CONFIG = {
|
||||||
|
'driver': 'ODBC Driver 18 for SQL Server',
|
||||||
|
'server': '192.168.110.114',
|
||||||
|
'database': 'CompanyDB',
|
||||||
|
'username': 'peng',
|
||||||
|
'password': 'Cqbld123456.',
|
||||||
|
'TrustServerCertificate': 'yes'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Common Utilities
|
## Common Utilities
|
||||||
|
|
||||||
### `get_input_by_label(frame, label_text)`
|
### `get_input_by_label(frame, label_text)`
|
||||||
|
|||||||
1
config/__init__.py
Normal file
1
config/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Config package
|
||||||
9
config/database_config.py
Normal file
9
config/database_config.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# ================= SQL Server 配置 =================
|
||||||
|
SQL_SERVER_CONFIG = {
|
||||||
|
'driver': 'ODBC Driver 18 for SQL Server',
|
||||||
|
'server': '192.168.110.114',
|
||||||
|
'database': 'CompanyDB',
|
||||||
|
'username': 'peng',
|
||||||
|
'password': 'Cqbld123456.',
|
||||||
|
'TrustServerCertificate': 'yes'
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
"""
|
|
||||||
Excel 转换工具使用示例
|
|
||||||
"""
|
|
||||||
from utils.excel_converter import ExcelConverter
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# 创建转换器(verbose=True 打印详细日志)
|
|
||||||
converter = ExcelConverter(verbose=True)
|
|
||||||
|
|
||||||
# 转换 Excel 文件
|
|
||||||
input_file = "data/离散备料计划打印模版-布莱迪.xlsx"
|
|
||||||
output_file = "data/离散备料计划打印模版-布莱迪_转换.xlsx"
|
|
||||||
|
|
||||||
# 执行转换
|
|
||||||
df = converter.convert(input_file, output_file)
|
|
||||||
|
|
||||||
print(f"\n转换完成!")
|
|
||||||
print(f"数据形状: {df.shape}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
1
db/__init__.py
Normal file
1
db/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Database package
|
||||||
184
db/connection.py
Normal file
184
db/connection.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
"""
|
||||||
|
SQL Server 数据库连接组件
|
||||||
|
|
||||||
|
提供数据库连接和查询接口
|
||||||
|
"""
|
||||||
|
import pyodbc
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 添加项目根目录到 sys.path
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
if project_root not in sys.path:
|
||||||
|
sys.path.insert(0, project_root)
|
||||||
|
|
||||||
|
from config.database_config import SQL_SERVER_CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseConnection:
|
||||||
|
"""SQL Server 数据库连接类"""
|
||||||
|
|
||||||
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||||
|
"""
|
||||||
|
初始化数据库连接
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: 数据库配置字典,默认使用 SQL_SERVER_CONFIG
|
||||||
|
"""
|
||||||
|
self.config = config or SQL_SERVER_CONFIG
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
|
def connect(self) -> pyodbc.Connection:
|
||||||
|
"""
|
||||||
|
建立数据库连接
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
pyodbc.Connection: 数据库连接对象
|
||||||
|
"""
|
||||||
|
if self.connection is not None:
|
||||||
|
return self.connection
|
||||||
|
|
||||||
|
# 构建连接字符串
|
||||||
|
conn_str = (
|
||||||
|
f"DRIVER={{{self.config['driver']}}};"
|
||||||
|
f"SERVER={self.config['server']};"
|
||||||
|
f"DATABASE={self.config['database']};"
|
||||||
|
f"UID={self.config['username']};"
|
||||||
|
f"PWD={self.config['password']};"
|
||||||
|
f"TrustServerCertificate={self.config['TrustServerCertificate']};"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.connection = pyodbc.connect(conn_str)
|
||||||
|
print(f"成功连接到数据库: {self.config['server']}/{self.config['database']}")
|
||||||
|
return self.connection
|
||||||
|
except pyodbc.Error as e:
|
||||||
|
print(f"数据库连接失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
"""关闭数据库连接"""
|
||||||
|
if self.connection:
|
||||||
|
self.connection.close()
|
||||||
|
self.connection = None
|
||||||
|
print("数据库连接已关闭")
|
||||||
|
|
||||||
|
def execute_query(self, sql: str, params: Optional[tuple] = None) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
执行查询语句并返回结果
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL 查询语句
|
||||||
|
params: 查询参数(可选)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 查询结果列表,每个元素为一行数据的字典
|
||||||
|
"""
|
||||||
|
if not self.connection:
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if params:
|
||||||
|
cursor.execute(sql, params)
|
||||||
|
else:
|
||||||
|
cursor.execute(sql)
|
||||||
|
|
||||||
|
# 获取列名
|
||||||
|
columns = [column[0] for column in cursor.description]
|
||||||
|
|
||||||
|
# 将结果转换为字典列表
|
||||||
|
results = []
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
results.append(dict(zip(columns, row)))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
except pyodbc.Error as e:
|
||||||
|
print(f"查询执行失败: {e}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
def execute_update(self, sql: str, params: Optional[tuple] = None) -> int:
|
||||||
|
"""
|
||||||
|
执行更新/插入/删除语句
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sql: SQL 语句
|
||||||
|
params: 参数(可选)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 受影响的行数
|
||||||
|
"""
|
||||||
|
if not self.connection:
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if params:
|
||||||
|
cursor.execute(sql, params)
|
||||||
|
else:
|
||||||
|
cursor.execute(sql)
|
||||||
|
|
||||||
|
self.connection.commit()
|
||||||
|
return cursor.rowcount
|
||||||
|
|
||||||
|
except pyodbc.Error as e:
|
||||||
|
self.connection.rollback()
|
||||||
|
print(f"执行失败,已回滚: {e}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""支持 with 语句的上下文管理器入口"""
|
||||||
|
self.connect()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
"""支持 with 语句的上下文管理器出口"""
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数
|
||||||
|
def query_production_orders(总排号_list: List[str]) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
根据总排号列表查询生产订单号
|
||||||
|
|
||||||
|
Args:
|
||||||
|
总排号_list: 总排号列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 查询结果
|
||||||
|
"""
|
||||||
|
db = DatabaseConnection()
|
||||||
|
|
||||||
|
# 构建占位符字符串
|
||||||
|
placeholders = ','.join(['?' for _ in 总排号_list])
|
||||||
|
|
||||||
|
sql = f"""
|
||||||
|
SELECT [总排号], [生产订单号], [序号], [订单号], [客户名称], [产品型号]
|
||||||
|
FROM [productionContractData].[26年压力表合同数据]
|
||||||
|
WHERE [总排号] IN ({placeholders})
|
||||||
|
ORDER BY [序号]
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
results = db.execute_query(sql, tuple(总排号_list))
|
||||||
|
return results
|
||||||
|
finally:
|
||||||
|
db.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection() -> DatabaseConnection:
|
||||||
|
"""
|
||||||
|
获取数据库连接实例
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DatabaseConnection: 数据库连接对象
|
||||||
|
"""
|
||||||
|
return DatabaseConnection()
|
||||||
28
db/materials_to_delete.py
Normal file
28
db/materials_to_delete.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""
|
||||||
|
待删除物料查询组件
|
||||||
|
从数据库查询指定负责人需要删除的物料名称
|
||||||
|
"""
|
||||||
|
from db.connection import get_connection
|
||||||
|
|
||||||
|
|
||||||
|
def get_materials_to_delete(manager_name):
|
||||||
|
"""
|
||||||
|
根据负责人名称查询待删除物料名称列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
manager_name: 负责人姓名
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
物料名称列表(关键字)
|
||||||
|
"""
|
||||||
|
query = """
|
||||||
|
SELECT [MaterialName]
|
||||||
|
FROM [dbo].[MaterialsToBeDeleted]
|
||||||
|
WHERE [ManagerName] = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
with get_connection() as conn:
|
||||||
|
results = conn.execute_query(query, (manager_name,))
|
||||||
|
# 提取物料名称并去除空值
|
||||||
|
material_names = [row['MaterialName'] for row in results if row['MaterialName']]
|
||||||
|
return material_names
|
||||||
50
db/production_order_query.py
Normal file
50
db/production_order_query.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
"""
|
||||||
|
生产订单号查询组件
|
||||||
|
从 ProductionID.txt 读取总排号,查询数据库获取生产订单号
|
||||||
|
"""
|
||||||
|
from db.connection import get_connection
|
||||||
|
|
||||||
|
|
||||||
|
def read_production_ids(file_path):
|
||||||
|
"""
|
||||||
|
读取 ProductionID.txt 文件,获取总排号列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: ProductionID.txt 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
总排号列表
|
||||||
|
"""
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
# 去除空白行和空格
|
||||||
|
production_ids = [line.strip() for line in f if line.strip()]
|
||||||
|
return production_ids
|
||||||
|
|
||||||
|
|
||||||
|
def query_production_order_numbers(production_ids):
|
||||||
|
"""
|
||||||
|
根据总排号列表,从数据库查询生产订单号
|
||||||
|
|
||||||
|
Args:
|
||||||
|
production_ids: 总排号列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
生产订单号列表
|
||||||
|
"""
|
||||||
|
if not production_ids:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 构建 IN 子句的占位符
|
||||||
|
placeholders = ','.join(['?' for _ in production_ids])
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT [生产订单号]
|
||||||
|
FROM [productionContractData].[26年压力表合同数据]
|
||||||
|
WHERE [总排号] IN ({placeholders})
|
||||||
|
"""
|
||||||
|
|
||||||
|
with get_connection() as conn:
|
||||||
|
results = conn.execute_query(query, tuple(production_ids))
|
||||||
|
# 提取生产订单号并去除空值
|
||||||
|
production_order_numbers = [row['生产订单号'] for row in results if row['生产订单号']]
|
||||||
|
return production_order_numbers
|
||||||
169
docs/DEPENDENCIES.md
Normal file
169
docs/DEPENDENCIES.md
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# 项目依赖说明
|
||||||
|
|
||||||
|
## Python 版本要求
|
||||||
|
|
||||||
|
- Python >= 3.8
|
||||||
|
|
||||||
|
## 依赖安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 激活虚拟环境 (Windows)
|
||||||
|
.venv\Scripts\activate
|
||||||
|
|
||||||
|
# 安装所有依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 安装 Playwright 浏览器
|
||||||
|
playwright install chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心依赖
|
||||||
|
|
||||||
|
### 1. playwright==1.57.0
|
||||||
|
|
||||||
|
**用途**: 浏览器自动化框架
|
||||||
|
|
||||||
|
**功能模块**:
|
||||||
|
- `playwright.sync_api` - 同步 API
|
||||||
|
- 浏览器启动与页面操作
|
||||||
|
- 元素定位器 (Locator)
|
||||||
|
- 弹窗处理 (expect_popup)
|
||||||
|
- 嵌套 iframe 处理
|
||||||
|
|
||||||
|
**使用文件**:
|
||||||
|
- `test_playwright.py` - 主要自动化脚本
|
||||||
|
- `record.py` - 录制脚本
|
||||||
|
|
||||||
|
**重要**: 安装后需运行 `playwright install chromium` 下载浏览器
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. pyodbc>=5.0.0
|
||||||
|
|
||||||
|
**用途**: SQL Server 数据库连接
|
||||||
|
|
||||||
|
**功能模块**:
|
||||||
|
- 数据库连接管理
|
||||||
|
- SQL 查询执行
|
||||||
|
- 事务处理
|
||||||
|
|
||||||
|
**使用文件**:
|
||||||
|
- `db/connection.py` - 数据库连接组件
|
||||||
|
- `test_db_query.py` - 数据库查询测试脚本
|
||||||
|
|
||||||
|
**注意**: Windows 系统通常已预装 ODBC 驱动,如遇连接问题请安装 [ODBC Driver 18 for SQL Server](https://learn.microsoft.com/zh-cn/sql/connect/odbc/download-odbc-driver-for-sql-server)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. pandas>=2.0.0
|
||||||
|
|
||||||
|
**用途**: 数据处理与分析
|
||||||
|
|
||||||
|
**功能模块**:
|
||||||
|
- DataFrame 数据结构
|
||||||
|
- Excel 文件读写
|
||||||
|
- 数据清洗与转换
|
||||||
|
|
||||||
|
**使用文件**:
|
||||||
|
- `excel_to_markdown.py` - Excel 转 Markdown
|
||||||
|
- `analyze_excel.py` - Excel 数据分析
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. openpyxl>=3.1.0
|
||||||
|
|
||||||
|
**用途**: Excel 文件操作
|
||||||
|
|
||||||
|
**功能模块**:
|
||||||
|
- 读取 `.xlsx` 文件
|
||||||
|
- 工作表操作
|
||||||
|
- 单元格读取
|
||||||
|
|
||||||
|
**使用文件**:
|
||||||
|
- `excel_to_markdown.py`
|
||||||
|
- `analyze_excel.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. numpy>=1.24.0
|
||||||
|
|
||||||
|
**用途**: 数值计算
|
||||||
|
|
||||||
|
**说明**: pandas 的依赖包,自动安装
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. python-dateutil>=2.8.0
|
||||||
|
|
||||||
|
**用途**: 日期时间处理
|
||||||
|
|
||||||
|
**说明**: pandas 的依赖包,自动安装
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. pytz>=2023.0
|
||||||
|
|
||||||
|
**用途**: 时区处理
|
||||||
|
|
||||||
|
**说明**: pandas 的依赖包,自动安装
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 传递依赖
|
||||||
|
|
||||||
|
以下包由核心依赖自动安装,无需手动指定:
|
||||||
|
|
||||||
|
| 包名 | 版本 | 被依赖包 |
|
||||||
|
|------|------|----------|
|
||||||
|
| greenlet | 3.3.0 | playwright |
|
||||||
|
| pyee | 13.0.0 | playwright |
|
||||||
|
| typing_extensions | 4.15.0 | 多个包 |
|
||||||
|
| et_xmlfile | 2.0.0 | openpyxl |
|
||||||
|
| six | 1.17.0 | python-dateutil |
|
||||||
|
| tzdata | 2025.3 | pandas |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 按功能分类
|
||||||
|
|
||||||
|
### 浏览器自动化
|
||||||
|
```
|
||||||
|
playwright==1.57.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
```
|
||||||
|
pyodbc>=5.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Excel/数据处理
|
||||||
|
```
|
||||||
|
pandas>=2.0.0
|
||||||
|
openpyxl>=3.1.0
|
||||||
|
numpy>=1.24.0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 当前环境已安装包
|
||||||
|
|
||||||
|
```
|
||||||
|
Package Version
|
||||||
|
----------------- -----------
|
||||||
|
et_xmlfile 2.0.0
|
||||||
|
greenlet 3.3.0
|
||||||
|
numpy 2.2.6
|
||||||
|
openpyxl 3.1.5
|
||||||
|
pandas 2.3.3
|
||||||
|
pip 25.3
|
||||||
|
playwright 1.57.0
|
||||||
|
pyee 13.0.0
|
||||||
|
python-dateutil 2.9.0.post0
|
||||||
|
pytz 2025.2
|
||||||
|
setuptools 65.5.0
|
||||||
|
six 1.17.0
|
||||||
|
typing_extensions 4.15.0
|
||||||
|
tzdata 2025.3
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**: `pyodbc` 尚未安装,运行数据库相关脚本前请先安装。
|
||||||
65
login.py
65
login.py
@@ -1,65 +0,0 @@
|
|||||||
"""
|
|
||||||
登录模块 - 负责用友BIP系统的登录操作
|
|
||||||
"""
|
|
||||||
from playwright.sync_api import Playwright, Browser, BrowserContext, Page, Frame
|
|
||||||
|
|
||||||
|
|
||||||
def login(
|
|
||||||
playwright: Playwright,
|
|
||||||
username: str,
|
|
||||||
password: str,
|
|
||||||
url: str = "https://68.11.34.30:8082/yonbip/resources/uap/rbac/login/main/index.html",
|
|
||||||
headless: bool = False,
|
|
||||||
ignore_https_errors: bool = True
|
|
||||||
) -> tuple[Browser, BrowserContext, Page, Frame]:
|
|
||||||
"""
|
|
||||||
登录用友BIP系统
|
|
||||||
|
|
||||||
参数:
|
|
||||||
playwright: Playwright实例
|
|
||||||
username: 用户名
|
|
||||||
password: 密码
|
|
||||||
url: 登录页面URL
|
|
||||||
headless: 是否使用无头模式
|
|
||||||
ignore_https_errors: 是否忽略HTTPS错误
|
|
||||||
|
|
||||||
返回:
|
|
||||||
tuple: (browser, context, page, main_frame)
|
|
||||||
- browser: 浏览器实例
|
|
||||||
- context: 浏览器上下文
|
|
||||||
- page: 页面对象
|
|
||||||
- main_frame: 登录后的主iframe (forwardFrame)
|
|
||||||
"""
|
|
||||||
# 启动浏览器
|
|
||||||
browser = playwright.chromium.launch(headless=headless)
|
|
||||||
|
|
||||||
# 创建上下文
|
|
||||||
context = browser.new_context(ignore_https_errors=ignore_https_errors)
|
|
||||||
|
|
||||||
# 创建页面
|
|
||||||
page = context.new_page()
|
|
||||||
|
|
||||||
# 导航到登录页面
|
|
||||||
page.goto(url)
|
|
||||||
|
|
||||||
# 提取主 iframe (forwardFrame)
|
|
||||||
main_frame = page.locator("#forwardFrame").content_frame
|
|
||||||
|
|
||||||
# 填写用户名
|
|
||||||
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()
|
|
||||||
print("强制登录")
|
|
||||||
else:
|
|
||||||
print("正常登录")
|
|
||||||
|
|
||||||
return browser, context, page, main_frame
|
|
||||||
3
main.py
3
main.py
@@ -15,13 +15,12 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 设置文件路径
|
# 设置文件路径
|
||||||
order_id_file = os.path.join(os.path.dirname(__file__), "orderID.txt")
|
order_id_file = os.path.join(os.path.dirname(__file__), "ProductionID.txt")
|
||||||
output_file = r"D:/python/playwrite/data/离散备料计划维护_合并.xlsx"
|
output_file = r"D:/python/playwrite/data/离散备料计划维护_合并.xlsx"
|
||||||
|
|
||||||
# 执行提取
|
# 执行提取
|
||||||
extractor.extract(order_id_file, output_file)
|
extractor.extract(order_id_file, output_file)
|
||||||
|
|
||||||
input("按回车退出...")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ def main():
|
|||||||
cleaner = DiscreteMaterialPlanCleaner(
|
cleaner = DiscreteMaterialPlanCleaner(
|
||||||
username="BLDpengqiangqiang",
|
username="BLDpengqiangqiang",
|
||||||
password="Cqbld123456.",
|
password="Cqbld123456.",
|
||||||
headless=False,
|
manager_name="彭羽",
|
||||||
|
headless=True,
|
||||||
verbose=True
|
verbose=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# 设置文件路径
|
# 设置文件路径
|
||||||
order_id_file = os.path.join(os.path.dirname(__file__), "orderID.txt")
|
order_id_file = os.path.join(os.path.dirname(__file__), "ProductionID.txt")
|
||||||
|
|
||||||
# 执行清理
|
# 执行清理
|
||||||
cleaner.clean(order_id_file)
|
cleaner.clean(order_id_file)
|
||||||
|
|||||||
222
orderID.txt
222
orderID.txt
@@ -1,222 +0,0 @@
|
|||||||
SC70202510110001
|
|
||||||
SC70202510110002
|
|
||||||
SC70202510110003
|
|
||||||
SC70202510110004
|
|
||||||
SC70202510110005
|
|
||||||
SC70202510110006
|
|
||||||
SC70202510110007
|
|
||||||
SC70202510110008
|
|
||||||
SC70202510110009
|
|
||||||
SC70202510110010
|
|
||||||
SC70202510110011
|
|
||||||
SC70202510110012
|
|
||||||
SC70202510110013
|
|
||||||
SC70202510110014
|
|
||||||
SC70202510110015
|
|
||||||
SC70202510110016
|
|
||||||
SC70202510110017
|
|
||||||
SC70202510110018
|
|
||||||
SC70202510110019
|
|
||||||
SC70202510110020
|
|
||||||
SC70202510110021
|
|
||||||
SC70202510110022
|
|
||||||
SC70202510110023
|
|
||||||
SC70202510110024
|
|
||||||
SC70202510110025
|
|
||||||
SC70202510110026
|
|
||||||
SC70202510110027
|
|
||||||
SC70202510110028
|
|
||||||
SC70202510110029
|
|
||||||
SC70202510110030
|
|
||||||
SC70202510110031
|
|
||||||
SC70202510110032
|
|
||||||
SC70202510110033
|
|
||||||
SC70202510110034
|
|
||||||
SC70202510110035
|
|
||||||
SC70202510110036
|
|
||||||
SC70202510110037
|
|
||||||
SC70202510110038
|
|
||||||
SC70202510110039
|
|
||||||
SC70202510110040
|
|
||||||
SC70202510110041
|
|
||||||
SC70202510110042
|
|
||||||
SC70202510110043
|
|
||||||
SC70202510110044
|
|
||||||
SC70202510110045
|
|
||||||
SC70202510110046
|
|
||||||
SC70202510110047
|
|
||||||
SC70202510110048
|
|
||||||
SC70202510110049
|
|
||||||
SC70202510110050
|
|
||||||
SC70202510110051
|
|
||||||
SC70202510110052
|
|
||||||
SC70202510110053
|
|
||||||
SC70202510110054
|
|
||||||
SC70202510110055
|
|
||||||
SC70202510110056
|
|
||||||
SC70202510110057
|
|
||||||
SC70202510110058
|
|
||||||
SC70202510110059
|
|
||||||
SC70202510110060
|
|
||||||
SC70202510110061
|
|
||||||
SC70202510110062
|
|
||||||
SC70202510110063
|
|
||||||
SC70202510110064
|
|
||||||
SC70202510110065
|
|
||||||
SC70202510110066
|
|
||||||
SC70202510110067
|
|
||||||
SC70202510110068
|
|
||||||
SC70202510110069
|
|
||||||
SC70202510110070
|
|
||||||
SC70202510110071
|
|
||||||
SC70202510110072
|
|
||||||
SC70202510110073
|
|
||||||
SC70202510110074
|
|
||||||
SC70202510110075
|
|
||||||
SC70202510110076
|
|
||||||
SC70202510110077
|
|
||||||
SC70202510110078
|
|
||||||
SC70202510110079
|
|
||||||
SC70202510110080
|
|
||||||
SC70202510110081
|
|
||||||
SC70202510110082
|
|
||||||
SC70202510110083
|
|
||||||
SC70202510110084
|
|
||||||
SC70202510110085
|
|
||||||
SC70202510110086
|
|
||||||
SC70202510110087
|
|
||||||
SC70202510110088
|
|
||||||
SC70202510110089
|
|
||||||
SC70202510110090
|
|
||||||
SC70202510110091
|
|
||||||
SC70202510110092
|
|
||||||
SC70202510110093
|
|
||||||
SC70202510110094
|
|
||||||
SC70202510110095
|
|
||||||
SC70202510110096
|
|
||||||
SC70202510110097
|
|
||||||
SC70202510110098
|
|
||||||
SC70202510110099
|
|
||||||
SC70202510110100
|
|
||||||
SC70202510110101
|
|
||||||
SC70202510110102
|
|
||||||
SC70202510110103
|
|
||||||
SC70202510110104
|
|
||||||
SC70202510110105
|
|
||||||
SC70202510110106
|
|
||||||
SC70202510110107
|
|
||||||
SC70202510110108
|
|
||||||
SC70202510110109
|
|
||||||
SC70202510110110
|
|
||||||
SC70202510110111
|
|
||||||
SC70202510110112
|
|
||||||
SC70202510110113
|
|
||||||
SC70202510110114
|
|
||||||
SC70202510110115
|
|
||||||
SC70202510110116
|
|
||||||
SC70202510110117
|
|
||||||
SC70202510110118
|
|
||||||
SC70202510110119
|
|
||||||
SC70202510110120
|
|
||||||
SC70202510110121
|
|
||||||
SC70202510110122
|
|
||||||
SC70202510110123
|
|
||||||
SC70202510110124
|
|
||||||
SC70202510110125
|
|
||||||
SC70202510110126
|
|
||||||
SC70202510110127
|
|
||||||
SC70202510110128
|
|
||||||
SC70202510110129
|
|
||||||
SC70202510110130
|
|
||||||
SC70202510110131
|
|
||||||
SC70202510110132
|
|
||||||
SC70202510110133
|
|
||||||
SC70202510110134
|
|
||||||
SC70202510110135
|
|
||||||
SC70202510110136
|
|
||||||
SC70202510110137
|
|
||||||
SC70202510110138
|
|
||||||
SC70202510110139
|
|
||||||
SC70202510110140
|
|
||||||
SC70202510110141
|
|
||||||
SC70202510110142
|
|
||||||
SC70202510110143
|
|
||||||
SC70202510110144
|
|
||||||
SC70202510110145
|
|
||||||
SC70202510110146
|
|
||||||
SC70202510110147
|
|
||||||
SC70202510110148
|
|
||||||
SC70202510110149
|
|
||||||
SC70202510110150
|
|
||||||
SC70202510110151
|
|
||||||
SC70202510110152
|
|
||||||
SC70202510110153
|
|
||||||
SC70202510110154
|
|
||||||
SC70202510110155
|
|
||||||
SC70202510110156
|
|
||||||
SC70202510110157
|
|
||||||
SC70202510110158
|
|
||||||
SC70202510110159
|
|
||||||
SC70202510110160
|
|
||||||
SC70202510110161
|
|
||||||
SC70202510110162
|
|
||||||
SC70202510110163
|
|
||||||
SC70202510110164
|
|
||||||
SC70202510110165
|
|
||||||
SC70202510110166
|
|
||||||
SC70202510110167
|
|
||||||
SC70202510110168
|
|
||||||
SC70202510110169
|
|
||||||
SC70202510110170
|
|
||||||
SC70202510110171
|
|
||||||
SC70202510110172
|
|
||||||
SC70202510110173
|
|
||||||
SC70202510110174
|
|
||||||
SC70202510110175
|
|
||||||
SC70202510110176
|
|
||||||
SC70202510110177
|
|
||||||
SC70202510110178
|
|
||||||
SC70202510110179
|
|
||||||
SC70202510110180
|
|
||||||
SC70202510110181
|
|
||||||
SC70202510110182
|
|
||||||
SC70202510110183
|
|
||||||
SC70202510110184
|
|
||||||
SC70202510110185
|
|
||||||
SC70202510110186
|
|
||||||
SC70202510110187
|
|
||||||
SC70202510110188
|
|
||||||
SC70202510110189
|
|
||||||
SC70202510110190
|
|
||||||
SC70202510110191
|
|
||||||
SC70202510110192
|
|
||||||
SC70202510110193
|
|
||||||
SC70202510110194
|
|
||||||
SC70202510110195
|
|
||||||
SC70202510110196
|
|
||||||
SC70202510110197
|
|
||||||
SC70202510110198
|
|
||||||
SC70202510110199
|
|
||||||
SC70202510110200
|
|
||||||
SC70202510110201
|
|
||||||
SC70202510110202
|
|
||||||
SC70202510110203
|
|
||||||
SC70202510110204
|
|
||||||
SC70202510110205
|
|
||||||
SC70202510110206
|
|
||||||
SC70202510110207
|
|
||||||
SC70202510110208
|
|
||||||
SC70202510110209
|
|
||||||
SC70202510110210
|
|
||||||
SC70202510110211
|
|
||||||
SC70202510110212
|
|
||||||
SC70202510110213
|
|
||||||
SC70202510110214
|
|
||||||
SC70202510110215
|
|
||||||
SC70202510110216
|
|
||||||
SC70202510110217
|
|
||||||
SC70202510110218
|
|
||||||
SC70202510110219
|
|
||||||
SC70202510110220
|
|
||||||
SC70202510110221
|
|
||||||
SC70202510110222
|
|
||||||
19
requirements.txt
Normal file
19
requirements.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# ===========================
|
||||||
|
# Playwright Python Automation
|
||||||
|
# ===========================
|
||||||
|
# Python 3.8+ required
|
||||||
|
|
||||||
|
# --- Browser Automation ---
|
||||||
|
playwright==1.57.0
|
||||||
|
|
||||||
|
# --- Database ---
|
||||||
|
pyodbc>=5.0.0
|
||||||
|
|
||||||
|
# --- Excel/Data Processing ---
|
||||||
|
pandas>=2.0.0
|
||||||
|
openpyxl>=3.1.0
|
||||||
|
numpy>=1.24.0
|
||||||
|
|
||||||
|
# --- System Utilities (installed via pip) ---
|
||||||
|
python-dateutil>=2.8.0
|
||||||
|
pytz>=2023.0
|
||||||
@@ -1,318 +0,0 @@
|
|||||||
import re
|
|
||||||
import time
|
|
||||||
from playwright.sync_api import Playwright, sync_playwright, expect, TimeoutError as PWTimeoutError
|
|
||||||
import pdb
|
|
||||||
|
|
||||||
def click_button_until_disappear(frame, button_name="保存提交", max_attempts=None, max_duration=None):
|
|
||||||
"""
|
|
||||||
持续点击按钮直到按钮消失。
|
|
||||||
包含业务逻辑:点击前检查【生产部门】是否为空,为空则自动回填数据。
|
|
||||||
"""
|
|
||||||
click_count = 0
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
# 定义加载控件的定位器 (基于传入的 frame)
|
|
||||||
loading_locator = frame.locator("div").filter(has_text="加载中").nth(1)
|
|
||||||
|
|
||||||
print(f"开始监控'{button_name}'按钮 (含自动补全逻辑)...")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
# --- 1. 检查退出条件 ---
|
|
||||||
if max_attempts and click_count >= max_attempts:
|
|
||||||
print(f"已达到最大点击次数{max_attempts}次,停止操作")
|
|
||||||
break
|
|
||||||
|
|
||||||
if max_duration and (time.time() - start_time) >= max_duration:
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
print(f"已达到最大持续时间{max_duration}秒,停止操作")
|
|
||||||
break
|
|
||||||
|
|
||||||
# --- 2. 检查按钮状态 ---
|
|
||||||
button = frame.get_by_role("button", name=button_name)
|
|
||||||
|
|
||||||
# 如果按钮已经消失,任务完成
|
|
||||||
if button.count() == 0:
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
print(f"SUCCESS: '{button_name}'按钮已消失")
|
|
||||||
print(f"共点击{click_count}次,总耗时{elapsed:.1f}秒")
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"click_count": click_count,
|
|
||||||
"elapsed_time": elapsed
|
|
||||||
}
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# 插入业务逻辑:检查并回填数据
|
|
||||||
# ==========================================
|
|
||||||
try:
|
|
||||||
# 1. 获取生产部门输入框
|
|
||||||
dep_input = get_input_by_label(frame, "生产部门")
|
|
||||||
|
|
||||||
if dep_input:
|
|
||||||
current_val = dep_input.input_value()
|
|
||||||
# 检查是否为空 (strip去除空白字符)
|
|
||||||
if current_val != "压力表车间" and current_val != "CQ030101":
|
|
||||||
print(f" [补全] 检测到生产部门为空,开始自动填充...")
|
|
||||||
|
|
||||||
# A. 填入生产部门
|
|
||||||
dep_input.fill("压力表车间")
|
|
||||||
print(" [补全] -> 生产部门已填入: CQ030101")
|
|
||||||
|
|
||||||
# B. 填入订单类型 (仅在需要补全生产部门时才操作这个,根据你的需求逻辑)
|
|
||||||
type_input = get_input_by_label(frame, "订单类型")
|
|
||||||
if type_input:
|
|
||||||
type_input.fill("55C2-Cxx-02")
|
|
||||||
print(" [补全] -> 订单类型已填入: 55C2-Cxx-02")
|
|
||||||
else:
|
|
||||||
print(" [警告] 无法找到'订单类型'输入框")
|
|
||||||
|
|
||||||
# 稍微等待一下填入值的生效(可选)
|
|
||||||
time.sleep(0.5)
|
|
||||||
else:
|
|
||||||
print(f" [信息] 生产部门已有值: {current_val},跳过补全")
|
|
||||||
else:
|
|
||||||
print(" [警告] 无法找到'生产部门'输入框,跳过检查")
|
|
||||||
|
|
||||||
except Exception as logic_e:
|
|
||||||
print(f" [异常] 数据补全逻辑出错 (不影响继续点击): {logic_e}")
|
|
||||||
# ==========================================
|
|
||||||
|
|
||||||
|
|
||||||
# --- 3. 执行点击 ---
|
|
||||||
button.click()
|
|
||||||
click_count += 1
|
|
||||||
print(f"[{time.strftime('%H:%M:%S')}] 第{click_count}次点击'{button_name}'")
|
|
||||||
|
|
||||||
# --- 4. 智能等待加载逻辑 ---
|
|
||||||
|
|
||||||
# 4.1 缓冲等待:点击后,UI可能需要几秒钟才会弹出遮罩
|
|
||||||
# print(" - 等待遮罩层响应...")
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# 4.2 检测并等待遮罩消失
|
|
||||||
try:
|
|
||||||
# 尝试等待遮罩出现 (给它3秒的时间被检测到)
|
|
||||||
if loading_locator.is_visible(timeout=3000):
|
|
||||||
print(" - 检测到'加载中',正在等待数据载入...")
|
|
||||||
# 只要出现了,就无限等待它消失 (timeout=0)
|
|
||||||
loading_locator.wait_for(state="hidden", timeout=0)
|
|
||||||
print(" - 数据载入完毕")
|
|
||||||
else:
|
|
||||||
# 如果3秒内没检测到 visible,可能是数据极少瞬间加载完了
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
# 忽略定位超时等非致命错误
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 稍微休息一下
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
print(f"操作异常: {e}")
|
|
||||||
return {
|
|
||||||
"success": False,
|
|
||||||
"click_count": click_count,
|
|
||||||
"error": str(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_input_by_label(frame, label_text: str, label_locator=None):
|
|
||||||
"""
|
|
||||||
通过标签文本获取对应的输入框对象
|
|
||||||
|
|
||||||
参数:
|
|
||||||
frame: iframe对象
|
|
||||||
label_text: 标签文本(用于查找和日志输出)
|
|
||||||
label_locator: 可选,已经定位好的标签locator。如果为None,则函数内部查找
|
|
||||||
|
|
||||||
返回:
|
|
||||||
成功:返回输入框的locator对象
|
|
||||||
失败:返回None
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 如果没有传入label_locator,则根据label_text查找
|
|
||||||
if label_locator is None:
|
|
||||||
label_locator = frame.locator("div").filter(has_text=re.compile(f"^{label_text}$")).first
|
|
||||||
print(f"找到{label_text}标签")
|
|
||||||
|
|
||||||
# 向上找到包含标签和输入框的共同父容器
|
|
||||||
parent_container = label_locator.locator("..") # 父元素
|
|
||||||
|
|
||||||
# 在父容器中查找输入框
|
|
||||||
input_box = parent_container.locator("input").first
|
|
||||||
|
|
||||||
# 如果父元素中没有,再向上一层
|
|
||||||
if input_box.count() == 0:
|
|
||||||
print(f"{label_text}: 在父元素中未找到,向上一层查找...")
|
|
||||||
grandparent = parent_container.locator("..") # 祖父元素
|
|
||||||
input_box = grandparent.locator("input").first
|
|
||||||
|
|
||||||
# 验证是否找到输入框
|
|
||||||
if input_box.count() > 0:
|
|
||||||
input_box.wait_for(state="visible", timeout=5000)
|
|
||||||
current_value = input_box.input_value()
|
|
||||||
print(f"✓ {label_text}: {current_value}")
|
|
||||||
print(input_box.count())
|
|
||||||
return input_box
|
|
||||||
else:
|
|
||||||
print(f"✗ {label_text}: 在父容器中找不到输入框")
|
|
||||||
return None
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ {label_text}失败: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def run(playwright: Playwright) -> None:
|
|
||||||
browser = playwright.chromium.launch(headless=False)
|
|
||||||
context = browser.new_context(ignore_https_errors=True)
|
|
||||||
page = context.new_page()
|
|
||||||
|
|
||||||
# 1. 登录主页面
|
|
||||||
try:
|
|
||||||
page.goto("https://68.11.34.30:8082/yonbip/resources/uap/rbac/login/main/index.html")
|
|
||||||
|
|
||||||
# 提取主 iframe
|
|
||||||
main_frame = page.locator("#forwardFrame").content_frame
|
|
||||||
|
|
||||||
main_frame.get_by_role("textbox", name="用户名").fill("BLDpengqiangqiang")
|
|
||||||
main_frame.get_by_role("textbox", name="密码").fill("Cqbld123456.")
|
|
||||||
main_frame.get_by_role("button", name="登录").click()
|
|
||||||
|
|
||||||
# 2. 处理强制登录弹窗(判断是否存在)
|
|
||||||
confirm_btn = main_frame.get_by_role("button", name="确定")
|
|
||||||
try:
|
|
||||||
# 短暂等待检测弹窗
|
|
||||||
if confirm_btn.is_visible(timeout=3000):
|
|
||||||
confirm_btn.click()
|
|
||||||
print("强制登录")
|
|
||||||
else:
|
|
||||||
print("正常登录")
|
|
||||||
except:
|
|
||||||
print("正常登录 (未检测到弹窗)")
|
|
||||||
|
|
||||||
# 3. 点击打开“补货安排”
|
|
||||||
main_frame.locator("i").first.click() # 假设是某个新建按钮
|
|
||||||
|
|
||||||
with page.expect_popup() as page1_info:
|
|
||||||
main_frame.get_by_title("补货安排", exact=True).click()
|
|
||||||
page1 = page1_info.value
|
|
||||||
|
|
||||||
# 4. 新页面:等待页面加载完成 + 提取嵌套 iframe
|
|
||||||
print("Page1 已打开,正在等待内层 iframe 加载...")
|
|
||||||
page1.wait_for_load_state("domcontentloaded")
|
|
||||||
|
|
||||||
# 提取外层 forwardFrame
|
|
||||||
outer_frame = page1.locator("#forwardFrame").content_frame
|
|
||||||
|
|
||||||
# 关键:等待内层 #mainiframe 出现并加载
|
|
||||||
inner_frame_locator = outer_frame.locator("#mainiframe")
|
|
||||||
inner_frame_locator.wait_for(state="visible", timeout=15000)
|
|
||||||
inner_frame = inner_frame_locator.content_frame
|
|
||||||
|
|
||||||
print("Page1 内层 iframe 已加载完成")
|
|
||||||
|
|
||||||
# 填写查询条件
|
|
||||||
textbox = inner_frame.get_by_role("textbox", name="期初标识≠")
|
|
||||||
textbox.fill("")
|
|
||||||
inner_frame.locator("#rc_select_0").fill("关闭")
|
|
||||||
|
|
||||||
textbox = inner_frame.get_by_role("textbox", name="排产号")
|
|
||||||
textbox.fill("R")
|
|
||||||
|
|
||||||
inner_frame.get_by_role("textbox", name="单据日期开始日期").fill("2025-12-28")
|
|
||||||
inner_frame.get_by_role("textbox", name="单据日期结束日期").fill("2025-12-31")
|
|
||||||
# inner_frame.get_by_text("今日").click()
|
|
||||||
# inner_frame.get_by_text("今日").click()
|
|
||||||
|
|
||||||
# 点击查询按钮
|
|
||||||
inner_frame.locator(".iconfont.icon-chaxun").click()
|
|
||||||
|
|
||||||
# 勾选所有合同
|
|
||||||
inner_frame.get_by_role("row", name="序号").get_by_label("").check()
|
|
||||||
|
|
||||||
# 获取订单合计数量
|
|
||||||
summary_locator = inner_frame.get_by_text(re.compile(r"合计:\s*\d+\s*行"))
|
|
||||||
try:
|
|
||||||
summary_text = summary_locator.inner_text(timeout=5000)
|
|
||||||
match = re.search(r"\d+", summary_text)
|
|
||||||
if match:
|
|
||||||
row_count = int(match.group())
|
|
||||||
print(f"当前总行数:{row_count}")
|
|
||||||
else:
|
|
||||||
print("未匹配到行数")
|
|
||||||
except:
|
|
||||||
print("未找到合计行")
|
|
||||||
|
|
||||||
# 进入 Page2
|
|
||||||
inner_frame.get_by_role("button").filter(has_text="补货安排").hover()
|
|
||||||
inner_frame.get_by_text("生产订单").click()
|
|
||||||
inner_frame.get_by_role("textbox", name="工厂").fill("10010705")
|
|
||||||
|
|
||||||
with page1.expect_popup(timeout=60000) as page2_info:
|
|
||||||
inner_frame.get_by_role("button", name="确定(Y)").click()
|
|
||||||
page2 = page2_info.value
|
|
||||||
|
|
||||||
# --- Page2 加载逻辑优化 ---
|
|
||||||
print("Page2 已打开,正在初始化...")
|
|
||||||
page2.wait_for_load_state("domcontentloaded")
|
|
||||||
|
|
||||||
outer_frame_p2 = page2.locator("#forwardFrame").content_frame
|
|
||||||
inner_frame_locator_p2 = outer_frame_p2.locator("#mainiframe")
|
|
||||||
inner_frame_locator_p2.wait_for(state="visible", timeout=15000)
|
|
||||||
inner_frame_p2 = inner_frame_locator_p2.content_frame
|
|
||||||
print("Page2 基础框架已就绪")
|
|
||||||
|
|
||||||
# 1. 按照建议,等待3秒,让加载遮罩有机会出现
|
|
||||||
print("等待 7 秒,检测加载遮罩是否出现...")
|
|
||||||
time.sleep(7)
|
|
||||||
|
|
||||||
# 2. 定义加载控件定位器
|
|
||||||
# 注意:这里直接基于 inner_frame_p2 构建,对应你的 path: ...locator("#mainiframe").content_frame.locator("div")...
|
|
||||||
loading_locator = inner_frame_p2.locator("div").filter(has_text="加载中").nth(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 尝试等待加载控件变为 visible。
|
|
||||||
# 给它 3秒 的检测时间,如果这 3秒 内出现了,说明页面正在加载数据。
|
|
||||||
# 如果没出现,触发 TimeoutError,进入 except 块,说明数据量很少,可能瞬间加载完了。
|
|
||||||
loading_locator.wait_for(state="visible", timeout=3000)
|
|
||||||
|
|
||||||
print("检测到'加载中'控件,开始等待数据加载(时间不定)...")
|
|
||||||
|
|
||||||
# 3. 关键:等待控件消失 (state="hidden" 或 "detached")
|
|
||||||
# timeout=0 表示无限等待,防止数据量大加载时间过长导致报错
|
|
||||||
loading_locator.wait_for(state="hidden", timeout=0)
|
|
||||||
|
|
||||||
print(">>> 页面加载完毕!(加载控件已消失)")
|
|
||||||
|
|
||||||
except PWTimeoutError:
|
|
||||||
print(">>> 页面加载完毕!(未检测到'加载中'控件,可能已快速完成)")
|
|
||||||
except Exception as e:
|
|
||||||
print(f">>> 页面加载状态判断异常: {e},默认视为加载完毕")
|
|
||||||
|
|
||||||
# --- 这里可以继续 Page2 的后续操作 ---
|
|
||||||
|
|
||||||
input_box = get_input_by_label(inner_frame_p2, "生产部门")
|
|
||||||
input_box = get_input_by_label(inner_frame_p2, "订单类型")
|
|
||||||
input_box = get_input_by_label(inner_frame_p2, "数量")
|
|
||||||
|
|
||||||
print("\n>>> 开始执行连续点击操作 <<<")
|
|
||||||
|
|
||||||
# 这里的 inner_frame_p2 是你在 Page2 提取出来的内层 iframe
|
|
||||||
result = click_button_until_disappear(
|
|
||||||
frame=inner_frame_p2,
|
|
||||||
button_name="保存提交", # 修改为你实际要点击的按钮名字,比如 "下一页" 或 "加载更多"
|
|
||||||
max_attempts=500 # 防止死循环,设置一个最大上限
|
|
||||||
)
|
|
||||||
print("进入调试模式,你现在可以在终端输入 Python 代码来测试定位器...")
|
|
||||||
pdb.set_trace() # <--- 程序会在这里卡住,控制权交给终端
|
|
||||||
print("最终结果:", result)
|
|
||||||
input("操作完成,按回车关闭...")
|
|
||||||
|
|
||||||
finally:
|
|
||||||
context.close()
|
|
||||||
browser.close()
|
|
||||||
|
|
||||||
with sync_playwright() as playwright:
|
|
||||||
run(playwright)
|
|
||||||
@@ -159,7 +159,7 @@ class ExcelConverter:
|
|||||||
data_row = table_row + 1
|
data_row = table_row + 1
|
||||||
while data_row < len(all_rows) and all_rows[data_row]:
|
while data_row < len(all_rows) and all_rows[data_row]:
|
||||||
# 检查是否是页脚信息(制单人、打印人)
|
# 检查是否是页脚信息(制单人、打印人)
|
||||||
if all_rows[data_row][0] and ('制单人' in str(all_rows[data_row][0]) or '打印人' in str(all_rows[data_row][0])):
|
if all_rows[data_row+1][0] and '制单人' in str(all_rows[data_row+1][0]) :
|
||||||
# 解析页脚信息
|
# 解析页脚信息
|
||||||
self._parse_header_row(all_rows[data_row], footer_info)
|
self._parse_header_row(all_rows[data_row], footer_info)
|
||||||
# 检查下一行是否也是页脚信息
|
# 检查下一行是否也是页脚信息
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import pandas as pd
|
|||||||
from playwright.sync_api import sync_playwright
|
from playwright.sync_api import sync_playwright
|
||||||
from utils.excel_converter import ExcelConverter
|
from utils.excel_converter import ExcelConverter
|
||||||
from utils.auth import login, logout
|
from utils.auth import login, logout
|
||||||
|
from db.production_order_query import read_production_ids, query_production_order_numbers
|
||||||
|
|
||||||
|
|
||||||
class DiscreteMaterialPlanExtractor:
|
class DiscreteMaterialPlanExtractor:
|
||||||
@@ -33,11 +34,24 @@ class DiscreteMaterialPlanExtractor:
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(*args, **kwargs)
|
print(*args, **kwargs)
|
||||||
|
|
||||||
def read_order_ids(self, file_path):
|
def get_production_order_numbers(self, production_id_file):
|
||||||
"""读取订单号文件"""
|
"""
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
读取总排号文件并查询数据库获取生产订单号
|
||||||
# 去除空白行和空格
|
|
||||||
order_ids = [line.strip() for line in f if line.strip()]
|
Args:
|
||||||
|
production_id_file: ProductionID.txt 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
生产订单号列表
|
||||||
|
"""
|
||||||
|
# 读取总排号
|
||||||
|
production_ids = read_production_ids(production_id_file)
|
||||||
|
self._print(f"从文件读取到 {len(production_ids)} 个总排号")
|
||||||
|
|
||||||
|
# 查询数据库获取生产订单号
|
||||||
|
order_ids = query_production_order_numbers(production_ids)
|
||||||
|
self._print(f"查询到 {len(order_ids)} 个生产订单号")
|
||||||
|
|
||||||
return order_ids
|
return order_ids
|
||||||
|
|
||||||
def group_order_ids(self, order_ids, group_size=100):
|
def group_order_ids(self, order_ids, group_size=100):
|
||||||
@@ -171,14 +185,14 @@ class DiscreteMaterialPlanExtractor:
|
|||||||
if attempt == max_retries - 1:
|
if attempt == max_retries - 1:
|
||||||
self._print(f"警告: {max_retries} 次尝试后仍未成功填充,继续执行...")
|
self._print(f"警告: {max_retries} 次尝试后仍未成功填充,继续执行...")
|
||||||
|
|
||||||
def extract(self, order_id_file, data_dir="D:/python/playwrite/data",
|
def extract(self, production_id_file, data_dir="D:/python/playwrite/data",
|
||||||
output_file="D:/python/playwrite/data/离散备料计划维护_合并.xlsx",
|
output_file="D:/python/playwrite/data/离散备料计划维护_合并.xlsx",
|
||||||
debug_mode=False, debug_batch=None):
|
debug_mode=False, debug_batch=None):
|
||||||
"""
|
"""
|
||||||
执行完整的数据提取流程
|
执行完整的数据提取流程
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
order_id_file: 订单号文件路径
|
production_id_file: ProductionID.txt 文件路径
|
||||||
data_dir: 数据保存目录
|
data_dir: 数据保存目录
|
||||||
output_file: 最终输出文件路径
|
output_file: 最终输出文件路径
|
||||||
debug_mode: 是否启用调试模式
|
debug_mode: 是否启用调试模式
|
||||||
@@ -219,9 +233,8 @@ class DiscreteMaterialPlanExtractor:
|
|||||||
# 设置查询界面
|
# 设置查询界面
|
||||||
self.setup_query_interface(inner_frame)
|
self.setup_query_interface(inner_frame)
|
||||||
|
|
||||||
# 读取订单号文件
|
# 读取总排号并查询生产订单号
|
||||||
order_ids = self.read_order_ids(order_id_file)
|
order_ids = self.get_production_order_numbers(production_id_file)
|
||||||
self._print(f"共读取到 {len(order_ids)} 个订单号")
|
|
||||||
|
|
||||||
# 按批次下载
|
# 按批次下载
|
||||||
downloaded_files = []
|
downloaded_files = []
|
||||||
@@ -264,10 +277,10 @@ def main():
|
|||||||
verbose=True
|
verbose=True
|
||||||
)
|
)
|
||||||
|
|
||||||
order_id_file = os.path.join(os.path.dirname(__file__), "orderID.txt")
|
production_id_file = os.path.join(os.path.dirname(__file__), "productionID.txt")
|
||||||
output_file = "D:/python/playwrite/data/离散备料计划维护_合并.xlsx"
|
output_file = "D:/python/playwrite/data/离散备料计划维护_合并.xlsx"
|
||||||
|
|
||||||
extractor.extract(order_id_file, output_file)
|
extractor.extract(production_id_file, output_file)
|
||||||
|
|
||||||
input("按回车退出...")
|
input("按回车退出...")
|
||||||
|
|
||||||
|
|||||||
@@ -5,23 +5,27 @@
|
|||||||
import os
|
import os
|
||||||
from playwright.sync_api import sync_playwright
|
from playwright.sync_api import sync_playwright
|
||||||
from utils.auth import login, logout
|
from utils.auth import login, logout
|
||||||
|
from db.production_order_query import read_production_ids, query_production_order_numbers
|
||||||
|
from db.materials_to_delete import get_materials_to_delete
|
||||||
|
|
||||||
|
|
||||||
class DiscreteMaterialPlanCleaner:
|
class DiscreteMaterialPlanCleaner:
|
||||||
"""离散备料计划维护数据清理器"""
|
"""离散备料计划维护数据清理器"""
|
||||||
|
|
||||||
def __init__(self, username, password, headless=False, verbose=True):
|
def __init__(self, username, password, manager_name, headless=False, verbose=True):
|
||||||
"""
|
"""
|
||||||
初始化清理器
|
初始化清理器
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
username: 登录用户名
|
username: 登录用户名
|
||||||
password: 登录密码
|
password: 登录密码
|
||||||
|
manager_name: 负责人姓名
|
||||||
headless: 是否无头模式运行
|
headless: 是否无头模式运行
|
||||||
verbose: 是否打印详细日志
|
verbose: 是否打印详细日志
|
||||||
"""
|
"""
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
|
self.manager_name = manager_name
|
||||||
self.headless = headless
|
self.headless = headless
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
|
|
||||||
@@ -30,14 +34,27 @@ class DiscreteMaterialPlanCleaner:
|
|||||||
if self.verbose:
|
if self.verbose:
|
||||||
print(*args, **kwargs)
|
print(*args, **kwargs)
|
||||||
|
|
||||||
def read_order_ids(self, file_path):
|
def get_production_order_numbers(self, production_id_file):
|
||||||
"""读取订单号文件"""
|
"""
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
读取总排号文件并查询数据库获取生产订单号
|
||||||
# 去除空白行和空格
|
|
||||||
order_ids = [line.strip() for line in f if line.strip()]
|
Args:
|
||||||
|
production_id_file: ProductionID.txt 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
生产订单号列表
|
||||||
|
"""
|
||||||
|
# 读取总排号
|
||||||
|
production_ids = read_production_ids(production_id_file)
|
||||||
|
self._print(f"从文件读取到 {len(production_ids)} 个总排号")
|
||||||
|
|
||||||
|
# 查询数据库获取生产订单号
|
||||||
|
order_ids = query_production_order_numbers(production_ids)
|
||||||
|
self._print(f"查询到 {len(order_ids)} 个生产订单号")
|
||||||
|
|
||||||
return order_ids
|
return order_ids
|
||||||
|
|
||||||
def process_order(self, inner_frame, order_id, order_index, page1, debug_mode=False, debug_order=None):
|
def process_order(self, inner_frame, order_id, order_index, page1, materials_to_delete=None, debug_mode=False, debug_order=None):
|
||||||
"""清理单个订单的数据
|
"""清理单个订单的数据
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -45,9 +62,12 @@ class DiscreteMaterialPlanCleaner:
|
|||||||
order_id: 订单号
|
order_id: 订单号
|
||||||
order_index: 订单索引
|
order_index: 订单索引
|
||||||
page1: 页面对象
|
page1: 页面对象
|
||||||
|
materials_to_delete: 待删除物料关键字列表
|
||||||
debug_mode: 是否启用调试模式
|
debug_mode: 是否启用调试模式
|
||||||
debug_order: 调试订单号
|
debug_order: 调试订单号
|
||||||
"""
|
"""
|
||||||
|
if materials_to_delete is None:
|
||||||
|
materials_to_delete = []
|
||||||
from playwright.sync_api import TimeoutError
|
from playwright.sync_api import TimeoutError
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@@ -124,52 +144,86 @@ class DiscreteMaterialPlanCleaner:
|
|||||||
detail_count = int(match.group(1))
|
detail_count = int(match.group(1))
|
||||||
self._print(f"详细信息数量: {detail_count}")
|
self._print(f"详细信息数量: {detail_count}")
|
||||||
|
|
||||||
|
# 提取"备料状态"信息
|
||||||
if detail_count > 0:
|
detail_element = inner_frame.get_by_text(re.compile(r"^备料状态:.+$"))
|
||||||
|
detail_text = detail_element.inner_text().replace("\n", "")
|
||||||
|
# 使用正则表达式提取括号中的数字
|
||||||
|
match = re.search(r"^备料状态:(.+)$", detail_text)
|
||||||
|
if match:
|
||||||
|
detail_status = match.group(1)
|
||||||
|
self._print(f"备料状态: {detail_status}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#page2.pause()
|
||||||
|
if detail_count > 0 and detail_status == "审批通过":
|
||||||
inner_frame.get_by_role("button", name="修改").click()
|
inner_frame.get_by_role("button", name="修改").click()
|
||||||
save_button_locator = inner_frame.get_by_role("button", name="保存")
|
save_button_locator = inner_frame.get_by_role("button", name="保存")
|
||||||
save_button_locator.wait_for(state="visible", timeout=10000)
|
save_button_locator.wait_for(state="visible", timeout=10000)
|
||||||
|
|
||||||
inner_frame.get_by_text("展开").first.click()
|
inner_frame.get_by_text("展开").first.click()
|
||||||
|
|
||||||
|
|
||||||
|
# 获取展开后的父容器,基于它定位子元素更加精确
|
||||||
|
# 父元素 class="card-table-side-box undefined"
|
||||||
|
child_form = inner_frame.locator(".card-table-side-box")
|
||||||
|
# 等待父容器变为可见
|
||||||
|
child_form.wait_for(state="visible", timeout=5000)
|
||||||
|
self._print(f"父容器 .card-table-side-box 已找到")
|
||||||
|
|
||||||
|
|
||||||
page2.pause()
|
page2.pause()
|
||||||
for id in range(detail_count):
|
for id in range(detail_count):
|
||||||
id_lable_locator= inner_frame.get_by_text("序号 " + str(id + 1)).wait_for(state="visible", timeout=10000)
|
id_lable_locator = child_form.get_by_text("序号 " + str(id + 1))
|
||||||
|
id_lable_locator.wait_for(state="visible", timeout=10000)
|
||||||
|
self._print(f"处理 {id_lable_locator.inner_text()} ")
|
||||||
|
|
||||||
#page2.pause()
|
# 获取材料编码(通过文本定位,取第一个input)
|
||||||
# 提取"展开"页面中的内容
|
input_box = child_form.locator("div").filter(has_text=re.compile(r"^材料编码\d{11}$", re.MULTILINE)).locator("input").first
|
||||||
detail_element = inner_frame.get_by_text(re.compile(r"^材料编码\d+$"))
|
self._print(f"材料编码:{input_box.input_value()}")
|
||||||
#移除detail_text中的换行符
|
|
||||||
detail_text = detail_element.inner_text().replace("\n", "")
|
|
||||||
# 使用正则表达式提取括号中的数字
|
|
||||||
match = re.search(r"材料编码(\d+)", detail_text)
|
|
||||||
if match:
|
|
||||||
match_result = int(match.group(1))
|
|
||||||
self._print(f"材料编码:{match_result}")
|
|
||||||
|
|
||||||
|
# 获取材料名称
|
||||||
|
input_box = child_form.locator("div").filter(has_text=re.compile(r"^材料名称$")).locator("input[type='text']")
|
||||||
|
material_name = input_box.input_value()
|
||||||
|
self._print(f"材料名称:{material_name}")
|
||||||
|
|
||||||
|
# 获取累计待发数量
|
||||||
|
input_box = child_form.locator("div").filter(has_text=re.compile(r"^累计待发数量$")).locator("input[type='text']")
|
||||||
|
self._print(f"累计待发数量:{input_box.input_value()}")
|
||||||
|
|
||||||
input_box = inner_frame.locator("div").filter(has_text=re.compile(r"^材料名称$")).nth(2).locator("input[type='text']")
|
# 获取累计出库数量
|
||||||
self._print(f"材料名称:{input_box.input_value()}")
|
input_box = child_form.locator("div").filter(has_text=re.compile(r"^累计出库数量$")).locator("input[type='text']")
|
||||||
|
self._print(f"累计出库数量:{input_box.input_value()}")
|
||||||
|
|
||||||
|
# 检查是否需要清理该物料
|
||||||
|
should_delete = False
|
||||||
|
matched_keyword = None
|
||||||
|
for keyword in materials_to_delete:
|
||||||
|
if keyword in material_name:
|
||||||
|
should_delete = True
|
||||||
|
matched_keyword = keyword
|
||||||
|
break
|
||||||
|
|
||||||
|
if should_delete:
|
||||||
|
self._print(f">>> 需要清理:材料名称【{material_name}】匹配关键字【{matched_keyword}】")
|
||||||
|
# TODO: 执行删除操作
|
||||||
|
else:
|
||||||
|
self._print(f"保留:材料名称【{material_name}】无需清理")
|
||||||
|
|
||||||
if id != detail_count - 1:
|
if id != detail_count - 1:
|
||||||
inner_frame.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(2).click()
|
child_form.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(2).click()
|
||||||
else:
|
else:
|
||||||
inner_frame.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(4).click()
|
child_form.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(4).click()
|
||||||
#page2.pause()
|
#page2.pause()
|
||||||
|
|
||||||
|
elif detail_count == 0:
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
self._print(f"第 {order_index + 1} 个订单无数据需要清理,跳过...")
|
self._print(f"第 {order_index + 1} 个订单无数据需要清理,跳过...")
|
||||||
page2.close()
|
page2.close()
|
||||||
return
|
return
|
||||||
|
elif detail_status != "审批通过":
|
||||||
|
self._print(f"第 {order_index + 1} 个订单备料状态: {detail_status}")
|
||||||
|
page2.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -205,15 +259,20 @@ class DiscreteMaterialPlanCleaner:
|
|||||||
if attempt == max_retries - 1:
|
if attempt == max_retries - 1:
|
||||||
self._print(f"警告: {max_retries} 次尝试后仍未成功填充,继续执行...")
|
self._print(f"警告: {max_retries} 次尝试后仍未成功填充,继续执行...")
|
||||||
|
|
||||||
def clean(self, order_id_file, debug_mode=False, debug_order=None):
|
def clean(self, production_id_file, debug_mode=False, debug_order=None):
|
||||||
"""
|
"""
|
||||||
执行完整的数据清理流程
|
执行完整的数据清理流程
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
order_id_file: 订单号文件路径
|
production_id_file: ProductionID.txt 文件路径
|
||||||
debug_mode: 是否启用调试模式
|
debug_mode: 是否启用调试模式
|
||||||
debug_order: 调试订单索引
|
debug_order: 调试订单索引
|
||||||
"""
|
"""
|
||||||
|
# 获取待删除物料列表
|
||||||
|
self._print(f"正在查询负责人 [{self.manager_name}] 的待删除物料列表...")
|
||||||
|
materials_to_delete = get_materials_to_delete(self.manager_name)
|
||||||
|
self._print(f"查询到 {len(materials_to_delete)} 个待删除物料关键字")
|
||||||
|
|
||||||
with sync_playwright() as playwright:
|
with sync_playwright() as playwright:
|
||||||
# 调用登录模块
|
# 调用登录模块
|
||||||
browser, context, page, main_frame = login(
|
browser, context, page, main_frame = login(
|
||||||
@@ -246,15 +305,14 @@ class DiscreteMaterialPlanCleaner:
|
|||||||
# 设置查询界面
|
# 设置查询界面
|
||||||
self.setup_query_interface(inner_frame)
|
self.setup_query_interface(inner_frame)
|
||||||
|
|
||||||
# 读取订单号文件
|
# 读取总排号并查询生产订单号
|
||||||
order_ids = self.read_order_ids(order_id_file)
|
order_ids = self.get_production_order_numbers(production_id_file)
|
||||||
self._print(f"共读取到 {len(order_ids)} 个订单号")
|
|
||||||
|
|
||||||
# 按订单清理
|
# 按订单清理
|
||||||
for order_index, order_id in enumerate(order_ids):
|
for order_index, order_id in enumerate(order_ids):
|
||||||
self._print(f"\n=== 开始处理第 {order_index + 1} 个订单,订单号: {order_id} ===")
|
self._print(f"\n=== 开始处理第 {order_index + 1} 个订单,订单号: {order_id} ===")
|
||||||
self.process_order(
|
self.process_order(
|
||||||
inner_frame, order_id, order_index, page1,
|
inner_frame, order_id, order_index, page1, materials_to_delete,
|
||||||
debug_mode=debug_mode, debug_order=debug_order
|
debug_mode=debug_mode, debug_order=debug_order
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -274,13 +332,14 @@ def main():
|
|||||||
cleaner = DiscreteMaterialPlanCleaner(
|
cleaner = DiscreteMaterialPlanCleaner(
|
||||||
username="BLDpengqiangqiang",
|
username="BLDpengqiangqiang",
|
||||||
password="Cqbld123456.",
|
password="Cqbld123456.",
|
||||||
|
manager_name="彭羽",
|
||||||
headless=False,
|
headless=False,
|
||||||
verbose=True
|
verbose=True
|
||||||
)
|
)
|
||||||
|
|
||||||
order_id_file = os.path.join(os.path.dirname(__file__), "orderID.txt")
|
production_id_file = os.path.join(os.path.dirname(__file__), "productionID.txt")
|
||||||
|
|
||||||
cleaner.clean(order_id_file)
|
cleaner.clean(production_id_file)
|
||||||
|
|
||||||
input("按回车退出...")
|
input("按回车退出...")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user