mirror of
https://devops.liangqichi.top/qichi.liang/Orbitin.git
synced 2026-02-10 07:41:29 +08:00
feat: 新增月底/月初数据调整和Confluence月份页面映射功能
- 新增月底最后一天自动剔除12点后数据功能 - 实现月底剔除数据自动转移到次月1号 - 新增Confluence月份页面ID映射功能,解决每月页面ID变化问题 - 修复1月份页面解析问题,支持'2026.1.1'日期格式 - 优化GUI界面,增加页面ID配置管理 - 精简README文档,增加详细功能说明 - 修复月度统计计算包含调整数据的问题
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,6 +23,7 @@ debug/
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
AGENTS.md
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
|
||||
467
README.md
467
README.md
@@ -4,28 +4,144 @@
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 从 Confluence 获取交接班日志 HTML
|
||||
- 提取保留布局的文本内容
|
||||
- SQLite3 数据库存储
|
||||
- 生成日报和月度统计
|
||||
- 支持未统计数据手动录入
|
||||
- 支持去除多余统计数据(对称功能)
|
||||
- 支持二次靠泊记录合并
|
||||
- GUI 图形界面(可选)
|
||||
- 飞书排班表集成(自动获取班次人员)
|
||||
- **数据获取**:从 Confluence API 获取交接班日志 HTML
|
||||
- **文本提取**:提取保留布局的文本内容,支持表格格式化
|
||||
- **数据库存储**:SQLite3 数据库存储,支持二次靠泊记录合并
|
||||
- **报表生成**:生成日报和月度统计,包含完成率计算
|
||||
- **数据调整**:支持手动添加/剔除统计数据,月底数据自动转移
|
||||
- **智能调整**:月底最后一天自动询问剔除12点后数据,月初自动添加上月数据
|
||||
- **GUI界面**:tkinter 图形界面,支持一键操作
|
||||
- **飞书集成**:自动获取排班人员信息,支持应用凭证自动刷新token
|
||||
- **月份页面映射**:支持配置各月份的Confluence页面ID,解决每月页面ID变化问题
|
||||
|
||||
## 项目结构
|
||||
## 快速开始
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
pip install requests beautifulsoup4 python-dotenv
|
||||
```
|
||||
|
||||
### 配置
|
||||
|
||||
```bash
|
||||
# 复制配置文件模板
|
||||
cp .env.example .env
|
||||
|
||||
# 编辑 .env 文件,填入以下配置
|
||||
```
|
||||
|
||||
### 配置文件 (.env)
|
||||
|
||||
```bash
|
||||
# Confluence 配置
|
||||
CONFLUENCE_BASE_URL=https://confluence.westwell-lab.com/rest/api
|
||||
CONFLUENCE_TOKEN=your-api-token
|
||||
CONFLUENCE_CONTENT_ID=155764524
|
||||
|
||||
# 飞书表格配置(用于获取排班人员信息)
|
||||
FEISHU_BASE_URL=https://open.feishu.cn/open-apis/sheets/v3
|
||||
FEISHU_SPREADSHEET_TOKEN=EgNPssi2ghZ7BLtGiTxcIBUmnVh
|
||||
|
||||
# 飞书应用凭证(推荐方式,自动获取tenant_access_token)
|
||||
FEISHU_APP_ID=your-feishu-app-id
|
||||
FEISHU_APP_SECRET=your-feishu-app-secret
|
||||
|
||||
# 业务配置
|
||||
DAILY_TARGET_TEU=300 # 每日目标TEU数量,用于计算完成率
|
||||
DUTY_PHONE=13107662315 # 值班电话,显示在日报中
|
||||
```
|
||||
|
||||
### 使用方法
|
||||
|
||||
#### 命令行方式
|
||||
|
||||
```bash
|
||||
# 获取并保存数据到数据库
|
||||
python3 main.py fetch-save
|
||||
|
||||
# 仅获取HTML并提取文本(保存到debug目录)
|
||||
python3 main.py fetch
|
||||
|
||||
# 生成日报(指定日期)
|
||||
python3 main.py report 2025-12-28
|
||||
|
||||
# 生成今日日报
|
||||
python3 main.py report-today
|
||||
|
||||
# 添加未统计数据
|
||||
python3 main.py --unaccounted 118 --month 2025-12
|
||||
|
||||
# 去除未统计数据
|
||||
python3 main.py --remove-unaccounted --month 2025-12
|
||||
|
||||
# 配置测试(验证所有连接)
|
||||
python3 main.py config-test
|
||||
```
|
||||
|
||||
#### GUI 方式
|
||||
|
||||
```bash
|
||||
python3 src/gui.py
|
||||
```
|
||||
|
||||
## GUI功能详解
|
||||
|
||||
### 核心功能
|
||||
- **获取并处理数据**:从Confluence获取数据并保存到数据库
|
||||
- **生成日报**:生成指定日期的日报,支持复制内容
|
||||
- **今日日报**:自动获取前一天数据并生成日报
|
||||
- **重置数据库**:清空数据库并重新获取数据
|
||||
|
||||
### 数据调整功能
|
||||
- **添加未统计数据**:用于补全缺失的箱量
|
||||
- **去除多余统计数据**:用于删除多余统计的箱量(对称功能)
|
||||
- **月底智能调整**:月底最后一天自动弹出剔除对话框
|
||||
- **数据自动转移**:月底剔除的数据自动转移到次月1号
|
||||
|
||||
### 配置管理
|
||||
- **管理月份页面ID映射**:配置各月份的Confluence页面ID
|
||||
- **数据库统计**:显示当月每艘船的作业量总计
|
||||
- **自动刷新排班信息**:从飞书获取最新的排班人员信息
|
||||
|
||||
## 高级功能说明
|
||||
|
||||
### 月份页面ID映射
|
||||
由于每月Confluence页面ID不同,系统支持配置月份到页面ID的映射:
|
||||
- 在GUI中点击"管理月份页面ID映射"按钮
|
||||
- 添加、编辑、删除各月份的页面ID配置
|
||||
- 获取数据时自动使用当前月份对应的页面ID
|
||||
|
||||
### 月底/月初数据调整
|
||||
系统支持智能化的月底/月初数据调整:
|
||||
|
||||
1. **月底最后一天**:
|
||||
- 获取数据后自动询问是否需要剔除12点后的数据
|
||||
- 用户可以输入需要剔除的船名、TEU以及具体尺寸(20尺/40尺)
|
||||
- 剔除后的数据会自动转移到次月1号
|
||||
|
||||
2. **月初1号**:
|
||||
- 系统自动添加上月剔除的数据,无需用户手动操作
|
||||
- 确保月度数据的准确性和连续性
|
||||
|
||||
3. **其他日期**:
|
||||
- 默认不弹出调整对话框
|
||||
- 但GUI侧边栏保留了手动添加/剔除TEU的功能入口
|
||||
|
||||
### 二次靠泊合并
|
||||
解析时会自动合并同一天的二次靠泊记录:
|
||||
- 夜班 学友洋山: 273TEU
|
||||
- 夜班 学友洋山(二次靠泊): 14TEU
|
||||
- 合并后: 夜班 学友洋山: 287TEU
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
OrbitIn/
|
||||
├── main.py # CLI 入口
|
||||
├── README.md # 项目说明
|
||||
├── AGENTS.md # AI助手开发文档
|
||||
├── .env # 环境配置(敏感信息)
|
||||
├── .env.example # 环境配置示例
|
||||
├── layout_output.txt # 缓存的布局文本
|
||||
├── debug/ # 调试输出目录
|
||||
│ └── layout_output_*.txt # 带时间戳的调试文件
|
||||
├── data/ # 数据目录
|
||||
│ ├── daily_logs.db # SQLite3 数据库
|
||||
│ └── schedule_cache.json # 排班数据缓存
|
||||
@@ -46,342 +162,43 @@ OrbitIn/
|
||||
│ ├── parser.py # HTML 内容解析器
|
||||
│ ├── text.py # HTML 文本提取器
|
||||
│ ├── log_parser.py # 日志解析器
|
||||
│ ├── manager.py # 内容管理器
|
||||
│ └── __init__.py # 模块导出
|
||||
│ └── manager.py # 内容管理器
|
||||
└── feishu/ # 飞书 API 模块
|
||||
├── client.py # 飞书 API 客户端
|
||||
├── parser.py # 排班数据解析器
|
||||
├── manager.py # 飞书排班管理器
|
||||
└── __init__.py # 模块导出
|
||||
└── manager.py # 飞书排班管理器
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
pip install requests beautifulsoup4 python-dotenv
|
||||
```
|
||||
|
||||
### 配置
|
||||
|
||||
在 `.env` 文件中配置:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
# Confluence 配置
|
||||
CONFLUENCE_BASE_URL=https://your-confluence.atlassian.net/rest/api
|
||||
CONFLUENCE_TOKEN=your-api-token
|
||||
CONFLUENCE_CONTENT_ID=155764524
|
||||
|
||||
# 飞书表格配置(用于获取排班人员信息)
|
||||
FEISHU_BASE_URL=https://open.feishu.cn/open-apis/sheets/v3
|
||||
FEISHU_SPREADSHEET_TOKEN=EgNPssi2ghZ7BLtGiTxcIBUmnVh
|
||||
|
||||
# 飞书应用凭证(推荐方式,自动获取tenant_access_token)
|
||||
# 创建飞书自建应用后获取app_id和app_secret
|
||||
FEISHU_APP_ID=your-feishu-app-id
|
||||
FEISHU_APP_SECRET=your-feishu-app-secret
|
||||
|
||||
# 备选:手动配置token(不推荐,token会过期)
|
||||
# FEISHU_TOKEN=your-feishu-api-token
|
||||
|
||||
# 数据库配置
|
||||
DATABASE_PATH=data/daily_logs.db
|
||||
|
||||
# 业务配置
|
||||
DAILY_TARGET_TEU=300 # 每日目标TEU数量,用于计算完成率
|
||||
DUTY_PHONE=13107662315 # 值班电话,显示在日报中
|
||||
SEPARATOR_CHAR=─ # 分隔线字符,用于格式化输出
|
||||
SEPARATOR_LENGTH=50 # 分隔线长度
|
||||
SCHEDULE_REFRESH_DAYS=30 # 排班数据刷新间隔(天)
|
||||
```
|
||||
|
||||
参考 `.env.example` 文件创建 `.env` 文件。
|
||||
|
||||
### 使用方法
|
||||
|
||||
#### 命令行方式
|
||||
|
||||
```bash
|
||||
# 默认:获取、提取、解析并保存到数据库
|
||||
python3 main.py fetch-save
|
||||
|
||||
# 仅获取HTML并提取文本(保存到debug目录)
|
||||
python3 main.py fetch
|
||||
|
||||
# 获取并保存带时间戳的debug文件
|
||||
python3 main.py fetch-debug
|
||||
|
||||
# 生成日报(指定日期)
|
||||
python3 main.py report 2025-12-28
|
||||
|
||||
# 生成今日日报
|
||||
python3 main.py report-today
|
||||
|
||||
# 配置测试(验证所有连接)
|
||||
python3 main.py config-test
|
||||
|
||||
# 添加未统计数据
|
||||
python3 main.py --unaccounted 118 --month 2025-12
|
||||
|
||||
# 去除未统计数据
|
||||
python3 main.py --remove-unaccounted --month 2025-12
|
||||
|
||||
# 显示帮助
|
||||
python3 main.py --help
|
||||
```
|
||||
|
||||
#### GUI 方式
|
||||
|
||||
```bash
|
||||
python3 src/gui.py
|
||||
```
|
||||
|
||||
GUI 功能:
|
||||
- 获取并处理数据
|
||||
- 重置数据库(删除并重新获取)
|
||||
- 生成日报
|
||||
- 今日日报(自动获取前一天数据)
|
||||
- 添加未统计数据
|
||||
- 去除多余统计数据(对称功能)
|
||||
- 月底/月初智能调整(自动弹出对话框)
|
||||
- 数据库统计(显示当月每艘船的作业量)
|
||||
- 日报内容可复制
|
||||
- 自动刷新排班信息
|
||||
|
||||
#### 智能调整功能
|
||||
- **月初1号**:自动询问是否添加上月数据
|
||||
- **月底最后一天**:自动询问是否剔除12点后数据
|
||||
- **其他日期**:保留手动调整入口
|
||||
- **调整数据**:在日报中清晰显示,确保数据准确性
|
||||
|
||||
## 数据格式
|
||||
|
||||
### 日报表 (daily_handover_logs)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | INTEGER | 主键 |
|
||||
| date | TEXT | 日期 YYYY-MM-DD |
|
||||
| shift | TEXT | 班次 (白班/夜班) |
|
||||
| ship_name | TEXT | 船名(不含船号前缀) |
|
||||
| teu | INTEGER | 作业量 TEU |
|
||||
| efficiency | REAL | 效率 |
|
||||
| vehicles | INTEGER | 上场车辆数 |
|
||||
| created_at | TEXT | 创建时间 |
|
||||
|
||||
### 未统计表 (monthly_unaccounted)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | INTEGER | 主键 |
|
||||
| year_month | TEXT | 年月 YYYY-MM |
|
||||
| teu | INTEGER | 未统计的 TEU |
|
||||
| note | TEXT | 备注 |
|
||||
| created_at | TEXT | 创建时间 |
|
||||
|
||||
### 手动调整表 (manual_adjustments)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | INTEGER | 主键 |
|
||||
| date | TEXT | 调整适用的日期 YYYY-MM-DD |
|
||||
| ship_name | TEXT | 船名 |
|
||||
| teu | INTEGER | TEU数量 |
|
||||
| twenty_feet | INTEGER | 20尺箱量 |
|
||||
| forty_feet | INTEGER | 40尺箱量 |
|
||||
| adjustment_type | TEXT | 调整类型 'add' 或 'exclude' |
|
||||
| note | TEXT | 备注 |
|
||||
| created_at | TEXT | 创建时间 |
|
||||
|
||||
## 特性说明
|
||||
|
||||
### 二次靠泊合并
|
||||
|
||||
解析时会自动合并同一天的二次靠泊记录:
|
||||
|
||||
- 夜班 学友洋山: 273TEU
|
||||
- 夜班 学友洋山(二次靠泊): 14TEU
|
||||
- 合并后: 夜班 学友洋山: 287TEU
|
||||
|
||||
### 未统计数据
|
||||
|
||||
可以在数据库统计中查看当月每艘船的作业量总计,便于跟踪船舶运营情况。
|
||||
|
||||
### 去除多余统计数据
|
||||
|
||||
针对月底夜班箱量有时会被算入下个月的情况,系统提供了对称的"去除多余统计数据"功能:
|
||||
- **添加未统计数据**: 用于补全缺失的箱量
|
||||
- **去除未统计数据**: 用于删除多余统计的箱量
|
||||
|
||||
这两个功能配合使用,可以精确调整月度统计数据。
|
||||
|
||||
### 月底/月初数据调整功能
|
||||
|
||||
系统支持智能化的月底/月初数据调整:
|
||||
|
||||
#### 1. 月底最后一天(自动剔除12点后数据)
|
||||
- 当获取数据的日期为月份最后一天时,GUI会自动询问是否需要剔除12点后的数据
|
||||
- 用户可以输入需要剔除的船名、TEU以及具体尺寸(20尺/40尺)
|
||||
- 剔除后的数据会从当月统计中移除,计入下月统计
|
||||
|
||||
#### 2. 月初1号(自动添加上月数据)
|
||||
- 当获取数据的日期为月份1号时,GUI会自动询问是否需要添加上月的作业数据
|
||||
- 用户可以输入需要添加的船名、TEU以及具体尺寸(20尺/40尺)
|
||||
- 添加的数据会计入上月统计,确保月度数据的准确性
|
||||
|
||||
#### 3. 其他日期(手动调整入口)
|
||||
- 非月初和月底的日期,默认不弹出调整对话框
|
||||
- 但GUI侧边栏保留了手动添加/剔除TEU的功能入口
|
||||
- 用户可以随时手动调整任何日期的数据
|
||||
|
||||
#### 4. 调整数据存储
|
||||
所有手动调整数据存储在 `manual_adjustments` 表中:
|
||||
- `date`: 调整适用的日期
|
||||
- `ship_name`: 船名
|
||||
- `teu`: TEU数量
|
||||
- `twenty_feet`: 20尺箱量
|
||||
- `forty_feet`: 40尺箱量
|
||||
- `adjustment_type`: 'add' 或 'exclude'
|
||||
- `note`: 备注信息
|
||||
|
||||
#### 5. 日报显示
|
||||
调整数据会在日报中清晰显示:
|
||||
- 每艘船下方显示具体的添加/剔除记录
|
||||
- 日报末尾显示调整汇总信息
|
||||
- 净调整量计算,确保数据准确性
|
||||
|
||||
## 示例输出
|
||||
|
||||
```
|
||||
日期:12/28
|
||||
|
||||
船名:学友洋山
|
||||
作业量:246TEU
|
||||
|
||||
当日实际作业量:246TEU
|
||||
|
||||
当月计划作业量:8400TEU (用天数*300TEU)
|
||||
当月实际作业量:12632TEU
|
||||
当月完成比例:150.38%
|
||||
|
||||
12/29 白班人员:
|
||||
12/29 夜班人员:
|
||||
24小时值班手机:13107662315
|
||||
```
|
||||
|
||||
## 核心模块说明
|
||||
|
||||
### Confluence 模块 (`src/confluence/`)
|
||||
- **`client.py`** - Confluence API 客户端,负责 HTTP 请求和连接管理
|
||||
- **`text.py`** - HTML 文本提取器,保留布局结构
|
||||
- **`log_parser.py`** - 日志解析器,解析船次作业数据
|
||||
- **`parser.py`** - HTML 内容解析器,提取链接、图片、表格
|
||||
- **`manager.py`** - 内容管理器,提供高级内容管理功能
|
||||
|
||||
### 飞书模块 (`src/feishu/`)
|
||||
- **`client.py`** - 飞书 API 客户端,支持自动获取和刷新tenant_access_token
|
||||
- **`parser.py`** - 排班数据解析器
|
||||
- **`manager.py`** - 飞书排班管理器,缓存和刷新排班信息
|
||||
|
||||
#### Token 自动获取机制
|
||||
飞书模块现在支持两种认证方式:
|
||||
1. **推荐方式**:使用应用凭证(FEISHU_APP_ID + FEISHU_APP_SECRET)
|
||||
- 系统会自动调用飞书API获取tenant_access_token
|
||||
- token有效期2小时,系统会在过期前30分钟自动刷新
|
||||
- 无需手动管理token过期问题
|
||||
|
||||
2. **备选方式**:使用手动配置的FEISHU_TOKEN
|
||||
- 兼容旧配置方式
|
||||
- token过期后需要手动更新
|
||||
- 不推荐长期使用
|
||||
|
||||
#### 如何获取应用凭证
|
||||
1. 登录飞书开放平台:https://open.feishu.cn/
|
||||
2. 创建自建应用
|
||||
3. 在"凭证与基础信息"中获取App ID和App Secret
|
||||
4. 为应用添加"获取tenant_access_token"权限
|
||||
5. 将应用发布到企业(仅自建应用需要)
|
||||
|
||||
### 数据库模块 (`src/database/`)
|
||||
- **`base.py`** - 数据库基类,提供统一的连接管理
|
||||
- **`daily_logs.py`** - 每日交接班日志数据库
|
||||
- **`schedules.py`** - 排班数据库
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Python 3.7+
|
||||
- SQLite3
|
||||
- Requests (HTTP 客户端)
|
||||
- BeautifulSoup4 (HTML 解析)
|
||||
- tkinter (GUI,可选)
|
||||
- tkinter (GUI)
|
||||
- 类型提示 (Python 3.5+)
|
||||
|
||||
## 架构特点
|
||||
|
||||
1. **模块化设计** - 每个模块职责单一,便于测试和维护
|
||||
2. **统一配置** - 集中管理所有环境变量和业务配置
|
||||
3. **统一日志** - 标准化的日志配置和文件轮转
|
||||
4. **异常处理** - 详细的错误处理和日志记录
|
||||
5. **类型安全** - 全面的 Python 类型提示
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新功能
|
||||
|
||||
1. **配置管理**: 所有配置项应在 `src/config.py` 中定义
|
||||
2. **日志记录**: 使用 `from src.logging_config import get_logger` 获取日志器
|
||||
3. **异常处理**: 为每个模块创建自定义异常类
|
||||
4. **类型提示**: 所有函数和方法都应包含完整的类型提示
|
||||
5. **数据库操作**: 使用 `src/database/base.py` 中的基类确保连接管理
|
||||
|
||||
### 测试
|
||||
|
||||
```bash
|
||||
# 运行配置测试
|
||||
python3 main.py config-test
|
||||
|
||||
# 测试特定功能
|
||||
python3 main.py fetch
|
||||
python3 main.py report-today
|
||||
```
|
||||
|
||||
### 调试
|
||||
|
||||
1. **日志查看**: 查看 `logs/app.log` 获取详细运行信息
|
||||
2. **调试文件**: 使用 `python3 main.py fetch-debug` 生成带时间戳的调试文件
|
||||
|
||||
### 代码规范
|
||||
|
||||
- 遵循 PEP 8 编码规范
|
||||
- 使用 Black 格式化代码(可选)
|
||||
- 使用 isort 排序导入
|
||||
- 所有公开 API 应有文档字符串
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **连接失败**: 检查 `.env` 文件中的 API 令牌和 URL
|
||||
2. **数据库错误**: 确保 `data/` 目录存在且有写入权限
|
||||
3. **解析错误**: 检查 Confluence 页面结构是否发生变化
|
||||
4. **飞书数据获取失败**:
|
||||
1. **连接失败**:检查 `.env` 文件中的 API 令牌和 URL
|
||||
2. **数据库错误**:确保 `data/` 目录存在且有写入权限
|
||||
3. **解析错误**:检查 Confluence 页面结构是否发生变化
|
||||
4. **飞书数据获取失败**:
|
||||
- 验证飞书表格权限
|
||||
- 检查应用凭证是否正确(FEISHU_APP_ID + FEISHU_APP_SECRET)
|
||||
- 确认应用已发布到企业(自建应用需要)
|
||||
- 检查网络连接是否能访问飞书API
|
||||
5. **飞书token获取失败**:
|
||||
- 确认应用有"获取tenant_access_token"权限
|
||||
- 检查app_id和app_secret是否正确
|
||||
- 查看日志文件获取详细错误信息
|
||||
5. **月份页面ID问题**:
|
||||
- 在GUI中配置正确的月份页面ID映射
|
||||
- 确保当前月份的页面ID已正确配置
|
||||
|
||||
### 日志级别
|
||||
### 日志查看
|
||||
|
||||
- 默认日志级别: INFO
|
||||
- 调试日志级别: DEBUG (设置环境变量 `LOG_LEVEL=DEBUG`)
|
||||
- 日志文件: `logs/app.log`,自动轮转
|
||||
|
||||
## License
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
|
||||
@@ -48,7 +48,7 @@ class Config:
|
||||
# GUI 配置
|
||||
GUI_FONT_FAMILY = os.getenv('GUI_FONT_FAMILY', 'SimHei')
|
||||
GUI_FONT_SIZE = int(os.getenv('GUI_FONT_SIZE', '10'))
|
||||
GUI_WINDOW_SIZE = os.getenv('GUI_WINDOW_SIZE', '900x700')
|
||||
GUI_WINDOW_SIZE = os.getenv('GUI_WINDOW_SIZE', '1600x900')
|
||||
|
||||
# 排班刷新配置
|
||||
SCHEDULE_REFRESH_DAYS = int(os.getenv('SCHEDULE_REFRESH_DAYS', '30'))
|
||||
|
||||
@@ -110,7 +110,26 @@ class HandoverLogParser:
|
||||
try:
|
||||
logs: List[ShipLog] = []
|
||||
|
||||
# 预处理:移除单行分隔符(前后都是空行的分隔符)
|
||||
# 预处理:修复日期格式和特殊字符
|
||||
# 1. 修复日期格式:将 "2026.1.1" 转换为 "2026.01.01"
|
||||
def fix_date_format(match):
|
||||
date_str = match.group(1)
|
||||
parts = date_str.split('.')
|
||||
if len(parts) == 3:
|
||||
year, month, day = parts
|
||||
# 补零
|
||||
month = month.zfill(2)
|
||||
day = day.zfill(2)
|
||||
return f"日期:{year}.{month}.{day}"
|
||||
return match.group(0)
|
||||
|
||||
# 修复日期格式
|
||||
text = re.sub(r'日期:(\d{4}\.\d{1,2}\.\d{1,2})', fix_date_format, text)
|
||||
|
||||
# 2. 修复特殊空格字符(\xa0 转换为普通空格)
|
||||
text = text.replace('\xa0', ' ')
|
||||
|
||||
# 3. 移除单行分隔符(前后都是空行的分隔符)
|
||||
# 保留真正的内容分隔符(前后有内容的)
|
||||
lines = text.split('\n')
|
||||
processed_lines: List[str] = []
|
||||
@@ -136,7 +155,7 @@ class HandoverLogParser:
|
||||
if not block.strip():
|
||||
continue
|
||||
|
||||
# 检查块中是否包含日期
|
||||
# 检查块中是否包含日期(使用改进后的正则表达式)
|
||||
date_match = re.search(r'日期:(\d{4}\.\d{2}\.\d{2})', block)
|
||||
if date_match:
|
||||
current_date = self.parse_date(date_match.group(1))
|
||||
|
||||
@@ -119,6 +119,21 @@ class DailyLogsDatabase(DatabaseBase):
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_manual_date ON manual_adjustments(date)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_manual_type ON manual_adjustments(adjustment_type)')
|
||||
|
||||
# 创建Confluence页面ID映射表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS confluence_pages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
month_key TEXT NOT NULL UNIQUE, -- 月份键,格式:'2025-12', '2026-01'
|
||||
page_id TEXT NOT NULL, -- Confluence页面ID
|
||||
page_title TEXT, -- 页面标题(可选)
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建索引
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_confluence_month ON confluence_pages(month_key)')
|
||||
|
||||
conn.commit()
|
||||
logger.debug("数据库表结构初始化完成")
|
||||
|
||||
@@ -582,6 +597,124 @@ class DailyLogsDatabase(DatabaseBase):
|
||||
'total_adjustment_teu': 0
|
||||
}
|
||||
|
||||
def insert_confluence_page(self, month_key: str, page_id: str, page_title: str = '') -> bool:
|
||||
"""
|
||||
插入或更新Confluence页面ID映射
|
||||
|
||||
参数:
|
||||
month_key: 月份键,格式:'2025-12', '2026-01'
|
||||
page_id: Confluence页面ID
|
||||
page_title: 页面标题(可选)
|
||||
|
||||
返回:
|
||||
是否成功
|
||||
"""
|
||||
try:
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO confluence_pages
|
||||
(month_key, page_id, page_title, updated_at)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||
'''
|
||||
params = (month_key, page_id, page_title)
|
||||
self.execute_update(query, params)
|
||||
logger.info(f"插入Confluence页面映射: {month_key} -> {page_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"插入Confluence页面映射失败: {e}")
|
||||
return False
|
||||
|
||||
def get_confluence_page(self, month_key: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
获取指定月份的Confluence页面ID映射
|
||||
|
||||
参数:
|
||||
month_key: 月份键,格式:'2025-12', '2026-01'
|
||||
|
||||
返回:
|
||||
页面映射字典,如果不存在则返回None
|
||||
"""
|
||||
query = 'SELECT * FROM confluence_pages WHERE month_key = ?'
|
||||
result = self.execute_query(query, (month_key,))
|
||||
return result[0] if result else None
|
||||
|
||||
def get_all_confluence_pages(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
获取所有Confluence页面ID映射
|
||||
|
||||
返回:
|
||||
页面映射列表
|
||||
"""
|
||||
query = 'SELECT * FROM confluence_pages ORDER BY month_key DESC'
|
||||
return self.execute_query(query)
|
||||
|
||||
def delete_confluence_page(self, month_key: str) -> bool:
|
||||
"""
|
||||
删除指定月份的Confluence页面ID映射
|
||||
|
||||
参数:
|
||||
month_key: 月份键,格式:'2025-12', '2026-01'
|
||||
|
||||
返回:
|
||||
是否成功删除
|
||||
"""
|
||||
try:
|
||||
query = 'DELETE FROM confluence_pages WHERE month_key = ?'
|
||||
result = self.execute_update(query, (month_key,))
|
||||
if result > 0:
|
||||
logger.info(f"删除Confluence页面映射: {month_key}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"未找到Confluence页面映射: {month_key}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"删除Confluence页面映射失败: {e}")
|
||||
return False
|
||||
|
||||
def get_confluence_page_for_date(self, date: str) -> Optional[str]:
|
||||
"""
|
||||
根据日期获取对应的Confluence页面ID
|
||||
|
||||
参数:
|
||||
date: 日期字符串,格式:'2025-12-31'
|
||||
|
||||
返回:
|
||||
Confluence页面ID,如果不存在则返回None
|
||||
"""
|
||||
try:
|
||||
# 从日期中提取年月
|
||||
year_month = date[:7] # '2025-12-31' -> '2025-12'
|
||||
|
||||
# 查询数据库
|
||||
page_info = self.get_confluence_page(year_month)
|
||||
if page_info:
|
||||
return page_info['page_id']
|
||||
|
||||
# 如果没有找到,尝试从环境变量中获取
|
||||
import os
|
||||
from src.config import config
|
||||
|
||||
# 检查环境变量中的配置
|
||||
env_key = f"CONFLUENCE_PAGE_{year_month.replace('-', '_')}"
|
||||
page_id = os.getenv(env_key)
|
||||
if page_id:
|
||||
# 保存到数据库以便下次使用
|
||||
self.insert_confluence_page(year_month, page_id, f"从环境变量获取: {env_key}")
|
||||
return page_id
|
||||
|
||||
# 使用默认配置
|
||||
default_page_id = config.CONFLUENCE_CONTENT_ID
|
||||
if default_page_id:
|
||||
logger.warning(f"未找到 {year_month} 的Confluence页面映射,使用默认页面ID: {default_page_id}")
|
||||
return default_page_id
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取Confluence页面ID失败: {date}, 错误: {e}")
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试代码
|
||||
|
||||
340
src/gui.py
340
src/gui.py
@@ -174,6 +174,20 @@ class OrbitInGUI:
|
||||
)
|
||||
btn_clear.pack(pady=5)
|
||||
|
||||
# 分隔线
|
||||
ttk.Separator(left_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
||||
|
||||
# Confluence页面ID管理
|
||||
ttk.Label(left_frame, text="Confluence页面ID:").pack(anchor=tk.W, pady=(10, 5))
|
||||
|
||||
btn_confluence_pages = ttk.Button(
|
||||
left_frame,
|
||||
text="管理页面ID映射",
|
||||
command=self.manage_confluence_pages,
|
||||
width=20
|
||||
)
|
||||
btn_confluence_pages.pack(pady=5)
|
||||
|
||||
# === 右侧主区域 ===
|
||||
|
||||
# 状态标签
|
||||
@@ -181,6 +195,11 @@ class OrbitInGUI:
|
||||
status_label = ttk.Label(right_frame, textvariable=self.status_var)
|
||||
status_label.pack(anchor=tk.W)
|
||||
|
||||
# Confluence页面ID显示
|
||||
self.confluence_id_var = tk.StringVar(value="Confluence页面ID: 未获取")
|
||||
confluence_id_label = ttk.Label(right_frame, textvariable=self.confluence_id_var, font=('', 9))
|
||||
confluence_id_label.pack(anchor=tk.W, pady=(5, 0))
|
||||
|
||||
# 日报完整内容(可复制)
|
||||
ttk.Label(right_frame, text="日报内容 (可复制):").pack(anchor=tk.W, pady=(5, 0))
|
||||
|
||||
@@ -274,16 +293,39 @@ class OrbitInGUI:
|
||||
|
||||
try:
|
||||
# 检查配置
|
||||
if not config.CONFLUENCE_BASE_URL or not config.CONFLUENCE_TOKEN or not config.CONFLUENCE_CONTENT_ID:
|
||||
if not config.CONFLUENCE_BASE_URL or not config.CONFLUENCE_TOKEN:
|
||||
self.log_message("错误: 未配置 Confluence 信息,请检查 .env 文件", is_error=True)
|
||||
self.logger.error("Confluence 配置不完整")
|
||||
return
|
||||
|
||||
# 获取当前月份对应的页面ID
|
||||
# 程序是在第二天打开获取昨天的数据,所以使用昨天的日期
|
||||
yesterday = datetime.now() - timedelta(days=1)
|
||||
yesterday_str = yesterday.strftime('%Y-%m-%d')
|
||||
|
||||
db = DailyLogsDatabase()
|
||||
page_id = db.get_confluence_page_for_date(yesterday_str)
|
||||
|
||||
if not page_id:
|
||||
# 如果没有找到映射,使用默认配置
|
||||
if not config.CONFLUENCE_CONTENT_ID:
|
||||
self.log_message("错误: 未配置 Confluence 页面ID,请检查 .env 文件或配置页面ID映射", is_error=True)
|
||||
self.logger.error("Confluence 页面ID未配置")
|
||||
return
|
||||
page_id = config.CONFLUENCE_CONTENT_ID
|
||||
self.log_message(f"警告: 未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}")
|
||||
self.logger.warning(f"未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}")
|
||||
self.confluence_id_var.set(f"Confluence页面ID: {page_id} (默认)")
|
||||
else:
|
||||
self.log_message(f"使用页面ID映射: {yesterday_str} -> {page_id}")
|
||||
self.logger.info(f"使用页面ID映射: {yesterday_str} -> {page_id}")
|
||||
self.confluence_id_var.set(f"Confluence页面ID: {page_id} ({yesterday_str})")
|
||||
|
||||
# 获取 HTML
|
||||
self.log_message("正在从 Confluence 获取 HTML...")
|
||||
self.logger.info("正在从 Confluence 获取 HTML...")
|
||||
client = ConfluenceClient(config.CONFLUENCE_BASE_URL, config.CONFLUENCE_TOKEN)
|
||||
html = client.get_html(config.CONFLUENCE_CONTENT_ID)
|
||||
html = client.get_html(page_id)
|
||||
|
||||
if not html:
|
||||
self.log_message("错误: 未获取到 HTML 内容", is_error=True)
|
||||
@@ -761,13 +803,32 @@ class OrbitInGUI:
|
||||
self.log_message("正在尝试获取最新作业数据...")
|
||||
self.logger.info("正在尝试获取最新作业数据...")
|
||||
|
||||
if config.CONFLUENCE_BASE_URL and config.CONFLUENCE_TOKEN and config.CONFLUENCE_CONTENT_ID:
|
||||
if config.CONFLUENCE_BASE_URL and config.CONFLUENCE_TOKEN:
|
||||
try:
|
||||
# 获取当前月份对应的页面ID
|
||||
# 程序是在第二天打开获取昨天的数据,所以使用昨天的日期
|
||||
yesterday = datetime.now() - timedelta(days=1)
|
||||
yesterday_str = yesterday.strftime('%Y-%m-%d')
|
||||
|
||||
db = DailyLogsDatabase()
|
||||
page_id = db.get_confluence_page_for_date(yesterday_str)
|
||||
|
||||
if not page_id:
|
||||
# 如果没有找到映射,使用默认配置
|
||||
if not config.CONFLUENCE_CONTENT_ID:
|
||||
self.log_message("Confluence 页面ID未配置,跳过数据获取")
|
||||
self.logger.warning("Confluence 页面ID未配置,跳过数据获取")
|
||||
return
|
||||
page_id = config.CONFLUENCE_CONTENT_ID
|
||||
self.log_message(f"警告: 未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}")
|
||||
self.logger.warning(f"未找到 {yesterday_str} 的页面ID映射,使用默认页面ID: {page_id}")
|
||||
self.confluence_id_var.set(f"Confluence页面ID: {page_id} (默认)")
|
||||
|
||||
# 获取 HTML
|
||||
self.log_message("正在从 Confluence 获取 HTML...")
|
||||
self.logger.info("正在从 Confluence 获取 HTML...")
|
||||
client = ConfluenceClient(config.CONFLUENCE_BASE_URL, config.CONFLUENCE_TOKEN)
|
||||
html = client.get_html(config.CONFLUENCE_CONTENT_ID)
|
||||
html = client.get_html(page_id)
|
||||
|
||||
if html:
|
||||
self.log_message(f"获取成功,共 {len(html)} 字符")
|
||||
@@ -877,6 +938,11 @@ class OrbitInGUI:
|
||||
self.logger.error(f"未知错误: {e}", exc_info=True)
|
||||
self.set_status("错误")
|
||||
|
||||
def manage_confluence_pages(self):
|
||||
"""管理Confluence页面ID映射"""
|
||||
dialog = ConfluencePagesDialog(self.root, self)
|
||||
self.root.wait_window(dialog)
|
||||
|
||||
|
||||
class AddDataDialog(tk.Toplevel):
|
||||
"""添加数据对话框"""
|
||||
@@ -1137,6 +1203,272 @@ class ExcludeDataDialog(tk.Toplevel):
|
||||
self.destroy()
|
||||
|
||||
|
||||
class ConfluencePagesDialog(tk.Toplevel):
|
||||
"""Confluence页面ID映射管理对话框"""
|
||||
|
||||
def __init__(self, parent, gui):
|
||||
super().__init__(parent)
|
||||
self.title("Confluence页面ID映射管理")
|
||||
self.gui = gui
|
||||
|
||||
# 设置对话框大小和位置
|
||||
self.geometry("600x500")
|
||||
self.resizable(True, True)
|
||||
|
||||
# 使对话框模态
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
|
||||
# 创建主框架
|
||||
main_frame = ttk.Frame(self, padding="10")
|
||||
main_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 标题
|
||||
ttk.Label(main_frame, text="Confluence页面ID映射管理", font=('', 12, 'bold')).pack(anchor=tk.W, pady=(0, 10))
|
||||
|
||||
# 说明文本
|
||||
ttk.Label(main_frame, text="每月Confluence页面ID不同,请在此配置各月份的页面ID映射。",
|
||||
wraplength=550).pack(anchor=tk.W, pady=(0, 10))
|
||||
|
||||
# 列表框架
|
||||
list_frame = ttk.LabelFrame(main_frame, text="现有页面ID映射", padding="10")
|
||||
list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
||||
|
||||
# 创建Treeview
|
||||
columns = ('month_key', 'page_id', 'page_title', 'updated_at')
|
||||
self.tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=8)
|
||||
|
||||
# 设置列标题
|
||||
self.tree.heading('month_key', text='月份')
|
||||
self.tree.heading('page_id', text='页面ID')
|
||||
self.tree.heading('page_title', text='页面标题')
|
||||
self.tree.heading('updated_at', text='更新时间')
|
||||
|
||||
# 设置列宽度
|
||||
self.tree.column('month_key', width=80)
|
||||
self.tree.column('page_id', width=120)
|
||||
self.tree.column('page_title', width=200)
|
||||
self.tree.column('updated_at', width=120)
|
||||
|
||||
# 添加滚动条
|
||||
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
|
||||
self.tree.configure(yscroll=scrollbar.set)
|
||||
|
||||
# 布局
|
||||
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
# 按钮框架
|
||||
button_frame = ttk.Frame(main_frame)
|
||||
button_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
ttk.Button(button_frame, text="添加新映射", command=self.add_mapping).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(button_frame, text="编辑选中", command=self.edit_mapping).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(button_frame, text="删除选中", command=self.delete_mapping).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(button_frame, text="刷新列表", command=self.refresh_list).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 关闭按钮
|
||||
ttk.Button(main_frame, text="关闭", command=self.destroy).pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# 绑定双击事件
|
||||
self.tree.bind('<Double-Button-1>', lambda e: self.edit_mapping())
|
||||
|
||||
# 加载数据
|
||||
self.refresh_list()
|
||||
|
||||
def refresh_list(self):
|
||||
"""刷新列表"""
|
||||
# 清空现有数据
|
||||
for item in self.tree.get_children():
|
||||
self.tree.delete(item)
|
||||
|
||||
try:
|
||||
db = DailyLogsDatabase()
|
||||
pages = db.get_all_confluence_pages()
|
||||
|
||||
for page in pages:
|
||||
self.tree.insert('', tk.END, values=(
|
||||
page['month_key'],
|
||||
page['page_id'],
|
||||
page['page_title'] or '',
|
||||
page['updated_at']
|
||||
))
|
||||
|
||||
self.gui.log_message(f"加载了 {len(pages)} 个页面ID映射")
|
||||
|
||||
except Exception as e:
|
||||
self.gui.log_message(f"加载页面ID映射失败: {e}", is_error=True)
|
||||
|
||||
def add_mapping(self):
|
||||
"""添加新映射"""
|
||||
dialog = ConfluencePageEditDialog(self, self.gui, None)
|
||||
self.wait_window(dialog)
|
||||
if dialog.result:
|
||||
self.refresh_list()
|
||||
|
||||
def edit_mapping(self):
|
||||
"""编辑选中映射"""
|
||||
selection = self.tree.selection()
|
||||
if not selection:
|
||||
messagebox.showwarning("警告", "请先选择一个映射")
|
||||
return
|
||||
|
||||
item = self.tree.item(selection[0])
|
||||
month_key = item['values'][0]
|
||||
|
||||
try:
|
||||
db = DailyLogsDatabase()
|
||||
page_info = db.get_confluence_page(month_key)
|
||||
if page_info:
|
||||
dialog = ConfluencePageEditDialog(self, self.gui, page_info)
|
||||
self.wait_window(dialog)
|
||||
if dialog.result:
|
||||
self.refresh_list()
|
||||
else:
|
||||
messagebox.showerror("错误", f"未找到月份 {month_key} 的映射")
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"获取映射信息失败: {e}")
|
||||
|
||||
def delete_mapping(self):
|
||||
"""删除选中映射"""
|
||||
selection = self.tree.selection()
|
||||
if not selection:
|
||||
messagebox.showwarning("警告", "请先选择一个映射")
|
||||
return
|
||||
|
||||
item = self.tree.item(selection[0])
|
||||
month_key = item['values'][0]
|
||||
page_id = item['values'][1]
|
||||
|
||||
if not messagebox.askyesno("确认删除", f"确定要删除月份 {month_key} 的页面ID映射吗?\n页面ID: {page_id}"):
|
||||
return
|
||||
|
||||
try:
|
||||
db = DailyLogsDatabase()
|
||||
success = db.delete_confluence_page(month_key)
|
||||
if success:
|
||||
self.gui.log_message(f"已删除页面ID映射: {month_key} -> {page_id}")
|
||||
self.refresh_list()
|
||||
else:
|
||||
messagebox.showerror("错误", "删除失败")
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"删除失败: {e}")
|
||||
|
||||
|
||||
class ConfluencePageEditDialog(tk.Toplevel):
|
||||
"""Confluence页面ID映射编辑对话框"""
|
||||
|
||||
def __init__(self, parent, gui, page_info):
|
||||
super().__init__(parent)
|
||||
self.title("编辑Confluence页面ID映射" if page_info else "添加Confluence页面ID映射")
|
||||
self.gui = gui
|
||||
self.page_info = page_info
|
||||
self.result = None
|
||||
|
||||
# 设置对话框大小和位置
|
||||
self.geometry("400x250")
|
||||
self.resizable(False, False)
|
||||
|
||||
# 使对话框模态
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
|
||||
# 创建输入字段
|
||||
frame = ttk.Frame(self, padding="20")
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 月份键
|
||||
ttk.Label(frame, text="月份键 (YYYY-MM):").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
self.month_key_var = tk.StringVar(value=page_info['month_key'] if page_info else '')
|
||||
month_key_entry = ttk.Entry(frame, textvariable=self.month_key_var, width=15)
|
||||
month_key_entry.grid(row=0, column=1, sticky=tk.W, pady=5)
|
||||
ttk.Label(frame, text="例如: 2025-12, 2026-01").grid(row=0, column=2, sticky=tk.W, pady=5)
|
||||
|
||||
# 页面ID
|
||||
ttk.Label(frame, text="页面ID:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
self.page_id_var = tk.StringVar(value=page_info['page_id'] if page_info else '')
|
||||
page_id_entry = ttk.Entry(frame, textvariable=self.page_id_var, width=20)
|
||||
page_id_entry.grid(row=1, column=1, sticky=tk.W, pady=5)
|
||||
|
||||
# 页面标题
|
||||
ttk.Label(frame, text="页面标题 (可选):").grid(row=2, column=0, sticky=tk.W, pady=5)
|
||||
self.page_title_var = tk.StringVar(value=page_info['page_title'] if page_info else '')
|
||||
page_title_entry = ttk.Entry(frame, textvariable=self.page_title_var, width=30)
|
||||
page_title_entry.grid(row=2, column=1, sticky=tk.W, pady=5)
|
||||
|
||||
# 按钮
|
||||
button_frame = ttk.Frame(frame)
|
||||
button_frame.grid(row=3, column=0, columnspan=2, pady=20)
|
||||
|
||||
ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=10)
|
||||
ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=10)
|
||||
|
||||
# 绑定回车键
|
||||
self.bind('<Return>', lambda e: self.on_ok())
|
||||
self.bind('<Escape>', lambda e: self.on_cancel())
|
||||
|
||||
# 焦点设置
|
||||
if page_info:
|
||||
page_id_entry.focus_set()
|
||||
else:
|
||||
month_key_entry.focus_set()
|
||||
|
||||
def on_ok(self):
|
||||
"""确定按钮处理"""
|
||||
try:
|
||||
# 验证输入
|
||||
month_key = self.month_key_var.get().strip()
|
||||
page_id = self.page_id_var.get().strip()
|
||||
page_title = self.page_title_var.get().strip()
|
||||
|
||||
if not month_key:
|
||||
messagebox.showerror("错误", "请输入月份键")
|
||||
return
|
||||
|
||||
# 验证月份键格式
|
||||
try:
|
||||
year, month = month_key.split('-')
|
||||
if len(year) != 4 or len(month) != 2:
|
||||
raise ValueError
|
||||
int(year)
|
||||
int(month)
|
||||
if int(month) < 1 or int(month) > 12:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
messagebox.showerror("错误", "月份键格式无效,请使用 YYYY-MM 格式")
|
||||
return
|
||||
|
||||
if not page_id:
|
||||
messagebox.showerror("错误", "请输入页面ID")
|
||||
return
|
||||
|
||||
# 验证页面ID是否为数字
|
||||
try:
|
||||
int(page_id)
|
||||
except ValueError:
|
||||
if not messagebox.askyesno("确认", f"页面ID '{page_id}' 不是纯数字,确定要继续吗?"):
|
||||
return
|
||||
|
||||
# 保存到数据库
|
||||
db = DailyLogsDatabase()
|
||||
success = db.insert_confluence_page(month_key, page_id, page_title)
|
||||
|
||||
if success:
|
||||
self.gui.log_message(f"保存页面ID映射: {month_key} -> {page_id}")
|
||||
self.result = True
|
||||
self.destroy()
|
||||
else:
|
||||
messagebox.showerror("错误", "保存失败")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"保存失败: {e}")
|
||||
|
||||
def on_cancel(self):
|
||||
"""取消按钮处理"""
|
||||
self.result = None
|
||||
self.destroy()
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
root = tk.Tk()
|
||||
|
||||
Reference in New Issue
Block a user