提交 ca417331 authored 作者: 陈泽健's avatar 陈泽健

feat(DocumentAutoOptimizationCalibration): 添加GUI界面和多项功能

- 新增GUI图形界面支持,使用tkinter框架实现可视化操作
- 添加PDF转换功能,通过win32com调用Word进行格式转换
- 添加网盘上传功能,支持SMB网络共享目录和UNC路径上传
- 实现GUI/CLI双模式运行,支持--gui参数启动图形界面
- 更新文档说明,添加GUI操作指南和快速开始章节
- 修改默认翻译引擎为bing以支持国内网络环境
- 重构代码结构,分离GUI、转换、上传等功能模块
- 添加打包脚本build.bat,支持PyInstaller生成独立exe文件
上级 c374deee
......@@ -2,7 +2,7 @@
## 项目简介
本工具用于自动优化Word文档的格式样式,统一文档排版规范。
本工具用于自动优化Word文档的格式样式,统一文档排版规范。支持 GUI 图形界面和 CLI 命令行两种运行方式,可打包为独立 .exe 供非技术人员使用。
### 主要功能
......@@ -19,6 +19,20 @@
- **批量翻译**:支持段落、表格、页眉页脚、文本框批量翻译
- **目录更新**:自动翻译目录内容
#### PDF 转换功能
- **格式转换**:将 Word 文档转换为 PDF 格式,格式还原度高
- **依赖检测**:启动时自动检测 Microsoft Word 是否可用,未安装时禁用并提示
#### 网盘上传功能
- **一键上传**:处理完成后可直接上传至网盘(SMB 共享目录)
- **路径兼容**:支持 UNC 路径(`\\192.168.9.9\...`)和盘符路径(`Z:\...`
- **冲突备份**:网盘存在同名文件时自动备份后再上传
#### GUI 图形界面
- **可视化操作**:tkinter 图形界面,无需命令行操作
- **实时日志**:处理过程实时显示日志,分色标注
- **独立运行**:可打包为 .exe,无需安装 Python 环境
### 设计原则
- **不修改内容**:仅调整格式样式,不改变文档内容
......@@ -27,6 +41,42 @@
---
## 快速开始
### 启动方式
```bash
# GUI 图形界面模式(推荐)
python run.py --gui
# CLI 交互式模式
python run.py
# CLI 命令行模式 - 格式优化
python run.py --input 文档.docx
# CLI 命令行模式 - 国际化翻译
python run.py --translate --to-lang ko --input 文档.docx
# 查看帮助
python run.py --help
```
### 打包为 .exe(分发给非技术人员)
```bash
# 执行打包脚本
build.bat
# 输出文件
dist/文档优化工具.exe
```
打包后的 .exe 可在任何 Windows 电脑上运行,无需安装 Python。
PDF 转换功能需要目标电脑安装 Microsoft Word,其他功能无额外依赖。
---
## 项目结构
```
......@@ -37,15 +87,19 @@ AuxiliaryTool/DocumentAutoOptimizationCalibration/
│ ├── __init__.py # 模块初始化
│ ├── config.py # 配置模块
│ ├── optimizer.py # 文档优化核心模块
│ ├── translator.py # 翻译模块(新增)
│ ├── internationalizer.py # 国际化转换模块(新增)
│ ├── translator.py # 翻译模块
│ ├── internationalizer.py # 国际化转换模块
│ ├── converter.py # PDF转换模块
│ ├── uploader.py # 网盘上传模块
│ ├── gui.py # GUI图形界面模块
│ ├── cli.py # 命令行接口
│ └── main.py # 主入口模块
│ └── main.py # 主入口模块(--gui/--cli调度)
├── testcases/ # 测试用例目录
│ └── 文档.docx # 测试文档
├── reports/ # 输出报告目录
├── logs/ # 日志目录
├── run.py # 入口脚本
├── build.bat # PyInstaller打包脚本
├── requirements.txt # 依赖包列表
└── README.md # 说明文档
```
......@@ -59,6 +113,7 @@ AuxiliaryTool/DocumentAutoOptimizationCalibration/
- Python 3.7+
- python-docx 库(Word文档处理)
- translators 库(翻译功能,免费)
- pywin32 库(PDF转换功能,需安装 Microsoft Word)
### 安装步骤
......@@ -70,21 +125,52 @@ cd AuxiliaryTool/DocumentAutoOptimizationCalibration
2. 安装依赖:
```bash
pip install -r requirements.txt
pip install pywin32
```
---
## 使用说明
本工具提供两大功能:**文档格式优化****文档国际化转换**
本工具提供两种运行模式:**GUI 图形界面****CLI 命令行**
---
## 一、文档格式优化
## 一、GUI 图形界面模式
### 功能说明
自动优化Word文档的格式样式,包括标题、正文、表格、页眉页脚等,不修改文档内容。
### 启动 GUI
```bash
python run.py --gui
```
### 界面功能说明
GUI 界面包含以下区域:
| 区域 | 说明 |
|------|------|
| 标题栏 | 显示工具名称、版本号、Word 可用状态 |
| 文件选择 | 选择待处理的 .docx 文档 |
| 处理设置 | 选择处理模式、目标语言、翻译引擎、PDF 转换、网盘上传 |
| 处理日志 | 实时显示处理过程日志(黑色=信息,绿色=成功,红色=错误,橙色=警告) |
| 进度条 | 显示当前处理进度 |
| 操作按钮 | 开始处理、打开输出文件夹、退出 |
### GUI 操作步骤
1. 点击「浏览...」选择 Word 文档
2. 在「处理设置」中选择处理模式:
- **optimize(格式优化)**:仅统一文档格式
- **translate(国际化翻译)**:翻译为其他语言
3. 如需 PDF,勾选「转换为PDF格式」(需安装 Microsoft Word)
4. 如需上传网盘,勾选「上传至网盘」并输入网盘路径
5. 点击「🚀 开始处理」
6. 在日志面板中查看处理进度
7. 处理完成后点击「📂 打开输出」查看结果文件
---
## 二、CLI 命令行模式
### 交互式模式
直接运行程序,会列出 `testcases/` 目录下的所有文档供选择:
......@@ -122,7 +208,7 @@ python run.py --input 文档.docx --footer-start-section 2
python run.py --input 文档.docx --log-level DEBUG
```
#### 5. 仅列出可用文档
#### 6. 仅列出可用文档
```bash
python run.py --list
```
......@@ -142,10 +228,7 @@ python run.py --input "testcases/文档.docx" --log-level DEBUG
---
## 二、文档国际化转换
### 功能说明
将中文Word文档自动翻译为多种语言,保留原有格式和样式。
## 三、文档国际化转换(CLI)
### 支持的目标语言
......@@ -266,10 +349,11 @@ python run.py --translate --to-lang es --input "testcases/文档.docx" --log-lev
---
## 、命令行参数速查
## 、命令行参数速查
| 参数 | 简写 | 说明 | 示例 |
|------|------|------|------|
| `--gui` | - | 启动GUI图形界面 | `--gui` |
| `--input` | `-i` | 输入文档路径(必需) | `--input 文档.docx` |
| `--output` | `-o` | 输出文档路径(可选) | `--output 输出/优化版.docx` |
| `--toc-page` | - | 目录所在页码(默认为4) | `--toc-page 3` |
......@@ -283,6 +367,46 @@ python run.py --translate --to-lang es --input "testcases/文档.docx" --log-lev
---
## 五、PDF 转换说明
### 功能说明
将 Word 文档转换为 PDF 格式,格式还原度与 Word 另存为 PDF 一致。
### 使用方式
**GUI 模式**:在处理设置中勾选「转换为PDF格式」。
**CLI 模式**:文档处理完成后,交互式询问是否转换为 PDF(输入 Y/N)。
### 注意事项
- PDF 转换依赖 Microsoft Word,目标电脑需安装 Word
- 程序启动时自动检测 Word 可用性
- 未安装 Word 时 GUI 中 PDF 复选框禁用,CLI 中会提示解决方法
---
## 六、网盘上传说明
### 功能说明
处理完成后将文档上传至网盘(SMB 网络共享目录)。
### 支持的路径格式
- UNC 路径:`\\192.168.9.9\发布版本\03服务器部署\15新统一平台`
- 盘符路径:`Z:\发布版本\03服务器部署\15新统一平台`
### 使用方式
**GUI 模式**:在处理设置中勾选「上传至网盘」,输入网盘路径。
**CLI 模式**:文档处理完成后,交互式询问是否上传(输入 Y/N),再输入目标路径。
### 注意事项
- 需当前 Windows 用户具有网盘共享目录的访问权限
- 网盘存在同名文件时自动备份(命名规则:`原文件名_备份_时间戳.docx`
- 上传成功后自动清理本地文件,失败则保留
---
## 配置说明
配置文件位于 `src/config.py`,可修改以下参数:
......@@ -321,7 +445,7 @@ TABLE_BORDER_COLOR = "000000" # 边框颜色(黑色)
### 翻译配置
```python
DEFAULT_TRANSLATE_ENGINE = "google" # 默认翻译引擎
DEFAULT_TRANSLATE_ENGINE = "bing" # 默认翻译引擎
TRANSLATE_DELAY = 0.5 # 翻译延迟(秒)
MAX_TEXT_LENGTH = 4000 # 单次翻译最大字符数
MAX_RETRY = 3 # 翻译重试次数
......@@ -385,15 +509,37 @@ python run.py --input "文档.docx" --footer-start-section 3
| 参考文件 | 用途 |
|---------|------|
| `FunctionalTestReportGeneration/src/gui.py` | tkinter GUI 界面参考 |
| `FunctionalTestReportGeneration/src/main.py` | GUI/CLI 入口调度参考 |
| `FunctionalTestReportGeneration/src/word_generator.py` | Word文档处理参考 |
| `FunctionalTestReportGeneration/src/config.py` | 配置模块参考 |
---
## 模块说明
| 模块文件 | 说明 |
|---------|------|
| `src/config.py` | 所有常量和路径配置 |
| `src/main.py` | 主入口,调度 GUI/CLI 模式 |
| `src/cli.py` | CLI 命令行接口,含交互式上传和 PDF 转换 |
| `src/gui.py` | tkinter GUI 图形界面 |
| `src/optimizer.py` | 文档格式优化核心逻辑 |
| `src/translator.py` | 翻译 API 封装(Bing/Google/Baidu) |
| `src/internationalizer.py` | 文档国际化翻译核心逻辑 |
| `src/converter.py` | Word 转 PDF(win32com) |
| `src/uploader.py` | 网盘上传(SMB/UNC 路径) |
| `run.py` | 程序入口脚本 |
| `build.bat` | PyInstaller 打包脚本 |
---
## 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| v1.5.0 | 2026-06-07 | 新增 GUI 图形界面、PDF 转换、网盘上传功能,支持 PyInstaller 打包 |
| v1.4.0 | 2026-06-07 | 新增网盘上传功能(CLI 交互式) |
| v1.3.1 | 2026-03-11 | 修复:将默认翻译引擎从google改为bing,支持国内网络环境 |
| v1.3.0 | 2026-03-11 | 新增多语言翻译支持(韩文、法文、日语、俄语、西班牙语、阿拉伯语、英文),新增--to-lang参数 |
| v1.2.0 | 2026-03-10 | 优化多节文档页眉页脚处理,新增--footer-start-section参数 |
......@@ -405,9 +551,9 @@ python run.py --input "文档.docx" --footer-start-section 3
## 优化功能回填
### 格式优化
- [x] 支持 GUI 图形界面
- [ ] 支持自定义样式配置文件
- [ ] 支持批量处理多个文档
- [ ] 支持GUI界面
- [ ] 支持自动目录更新
### 国际化翻译
......@@ -418,3 +564,13 @@ python run.py --input "文档.docx" --footer-start-section 3
- [ ] 支持人工审核模式
- [ ] 支持批量翻译多个文档
- [ ] 添加翻译质量评分
### PDF 转换
- [x] 支持 Word 转 PDF(win32com)
- [ ] 支持 WPS Office 兼容
### 网盘上传
- [x] 支持 UNC 路径和盘符路径上传
- [x] 支持同名文件自动备份
- [ ] 支持配置文件预设默认路径
- [ ] 支持上传历史记录
@echo off
chcp 65001 >nul
REM ============================================
REM 文档自动优化校准工具 - PyInstaller 打包脚本
REM 将工具打包为独立的 .exe 文件
REM ============================================
echo ========================================
echo 文档自动优化校准工具 - 打包脚本
echo ========================================
echo.
REM 检查 PyInstaller 是否安装
pip show pyinstaller >nul 2>&1
if errorlevel 1 (
echo [!] 未安装 PyInstaller,正在安装...
pip install pyinstaller
echo.
)
REM 检查项目依赖是否安装
echo [1/3] 检查依赖...
pip install python-docx translators pywin32 >nul 2>&1
echo 依赖检查完成
echo.
REM 执行打包
echo [2/3] 开始打包...
echo.
pyinstaller --onefile ^
--windowed ^
--name "文档优化工具" ^
--hidden-import=win32com.client ^
--hidden-import=win32timezone ^
--hidden-import=pythoncom ^
--hidden-import=pywintypes ^
--hidden-import=translators ^
--hidden-import=translators.apis ^
--hidden-import=docx ^
--hidden-import=docx.oxml ^
--hidden-import=docx.opc ^
--collect-all translators ^
run.py
echo.
REM 检查打包结果
if exist "dist\文档优化工具.exe" (
echo [3/3] 打包成功!
echo.
echo ========================================
echo 输出文件: dist\文档优化工具.exe
for %%A in ("dist\文档优化工具.exe") do echo 文件大小: %%~zA 字节
echo ========================================
echo.
echo 可以将 dist\文档优化工具.exe 分发给其他用户使用
) else (
echo [3/3] 打包失败,请检查错误信息
)
echo.
pause
......@@ -5,3 +5,9 @@ python-docx>=1.0.0
# 翻译库(用于文档国际化转换)
translators>=5.9.0
# Windows COM接口(用于PDF转换)
pywin32>=305
# 打包工具(仅开发时需要)
pyinstaller>=6.0.0
......@@ -4,20 +4,66 @@
使用方法:
python run.py # 交互式选择文档
python run.py --gui # 启动GUI界面
python run.py --input 文档.docx # 处理指定文档
python run.py --help # 查看帮助信息
"""
import sys
import os
from pathlib import Path
# 判断是否为 PyInstaller 打包后的环境
if getattr(sys, 'frozen', False):
# PyInstaller 打包后,使用 exe 所在目录作为项目根目录
PROJECT_ROOT = Path(sys.executable).parent
# 切换工作目录到 exe 所在目录(确保 reports/logs 等相对路径正确)
os.chdir(str(PROJECT_ROOT))
else:
# 开发环境,使用脚本所在目录
PROJECT_ROOT = Path(__file__).parent
# 将项目根目录添加到Python路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(PROJECT_ROOT))
# 导入并运行主程序
from src.main import main
if __name__ == "__main__":
exit_code = main()
def main():
"""主入口,带错误兜底"""
# 打包后的 exe 无参数启动时,自动进入 GUI 模式
# 因为 --windowed 打包没有控制台,CLI 模式无法交互
if getattr(sys, 'frozen', False) and len(sys.argv) == 1:
sys.argv.append('--gui')
try:
from src.main import main as app_main
exit_code = app_main()
sys.exit(exit_code)
except Exception as e:
# 写错误日志到文件(exe 旁边)
try:
import traceback
log_path = Path(sys.executable).parent / "error.log"
with open(str(log_path), 'w', encoding='utf-8') as f:
f.write(f"程序启动失败:\n")
f.write(f"frozen: {getattr(sys, 'frozen', False)}\n")
f.write(f"executable: {sys.executable}\n")
f.write(f"argv: {sys.argv}\n")
f.write(f"\n{traceback.format_exc()}")
except Exception:
pass
# 弹窗显示错误
try:
import tkinter as tk
from tkinter import messagebox
root = tk.Tk()
root.withdraw()
messagebox.showerror("程序错误", f"程序启动失败:\n\n{e}")
root.destroy()
except Exception:
pass
sys.exit(1)
if __name__ == "__main__":
main()
# -*- coding: utf-8 -*-
"""
文档自动优化校准工具 - GUI界面模块
本模块提供图形用户界面,支持文件选择、处理设置、进度显示和日志输出。
参考 FunctionalTestReportGeneration/src/gui.py 的设计模式。
"""
import tkinter as tk
from tkinter import filedialog, scrolledtext, ttk
import threading
import sys
import os
import logging
from pathlib import Path
from src.config import (
TESTCASES_DIR,
REPORTS_DIR,
LANGUAGE_CODES,
LANGUAGE_NAMES,
SUPPORTED_ENGINES,
DEFAULT_TRANSLATE_ENGINE,
SUPPORTED_TARGET_LANGUAGES,
TARGET_LANGUAGE,
__version__,
)
from src.optimizer import optimize_document
from src.internationalizer import internationalize_document
from src.converter import convert_to_pdf, _check_word_available
from src.uploader import upload_to_network, _cleanup_local_file
logger = logging.getLogger(__name__)
class GUILogHandler(logging.Handler):
"""
自定义日志处理器,将日志输出到GUI日志面板
Attributes:
widget: ScrolledText控件
"""
def __init__(self, widget, gui_instance):
"""
初始化GUI日志处理器
Args:
widget: ScrolledText控件
gui_instance: GUI主实例,用于线程安全的日志输出
"""
super().__init__()
self.widget = widget
self.gui = gui_instance
self.setFormatter(logging.Formatter('%(asctime)s - %(message)s', datefmt='%H:%M:%S'))
def emit(self, record):
"""
输出日志记录到GUI
Args:
record: 日志记录
"""
try:
msg = self.format(record)
# 根据日志级别设置标签
tag = "info"
if record.levelno >= logging.ERROR:
tag = "error"
elif record.levelno >= logging.WARNING:
tag = "warning"
# 使用 root.after 确保线程安全
self.gui.root.after(0, lambda m=msg, t=tag: self._append_log(m, t))
except Exception:
pass
def _append_log(self, msg, tag):
"""在主线程中追加日志"""
self.widget.insert(tk.END, msg + "\n", tag)
self.widget.see(tk.END)
self.widget.update()
class DocumentOptimizerGUI:
"""
文档自动优化校准工具GUI类
提供文件选择、处理模式切换、PDF转换、网盘上传等功能的可视化操作界面。
"""
# 颜色主题配置
COLOR_PRIMARY = "#2c3e50" # 主色调(深蓝)
COLOR_PRIMARY_LIGHT = "#34495e" # 浅主色调
COLOR_ACCENT = "#3498db" # 强调色(蓝色)
COLOR_SUCCESS = "#27ae60" # 成功色(绿色)
COLOR_DANGER = "#e74c3c" # 危险色(红色)
COLOR_WARNING = "#f39c12" # 警告色(橙色)
COLOR_GRAY = "#95a5a6" # 灰色
COLOR_BG_LIGHT = "#ecf0f1" # 浅灰背景
COLOR_BG_WHITE = "#ffffff" # 白色背景
COLOR_TEXT = "#2c3e50" # 文字颜色
COLOR_TEXT_LIGHT = "#7f8c8d" # 浅色文字
def __init__(self):
"""初始化GUI"""
self.root = tk.Tk()
self.root.title("文档自动优化校准工具")
self.root.geometry("780x700")
self.root.resizable(True, True)
self.root.minsize(680, 600)
# 设置窗口图标(尝试设置,失败不影响运行)
try:
self.root.iconbitmap(default="")
except Exception:
pass
# 是否正在处理中
self._processing = False
# Word 是否可用(启动时检测)
self._word_available, _ = _check_word_available()
# tkinter 变量
self.input_path = tk.StringVar()
self.mode_var = tk.StringVar(value="optimize") # optimize / translate
self.lang_var = tk.StringVar(value=TARGET_LANGUAGE) # 目标语言
self.engine_var = tk.StringVar(value=DEFAULT_TRANSLATE_ENGINE) # 翻译引擎
self.pdf_var = tk.BooleanVar(value=False) # 是否转PDF
self.upload_var = tk.BooleanVar(value=False) # 是否上传网盘
self.upload_path = tk.StringVar() # 网盘路径
# 翻译相关控件引用(用于显示/隐藏)
self._translate_widgets = []
# 创建界面
self._create_widgets()
# 初始化模式联动
self._on_mode_change()
# 居中显示窗口
self._center_window()
def _center_window(self):
"""将窗口居中显示"""
self.root.update_idletasks()
width = self.root.winfo_width()
height = self.root.winfo_height()
x = (self.root.winfo_screenwidth() // 2) - (width // 2)
y = (self.root.winfo_screenheight() // 2) - (height // 2)
self.root.geometry(f'{width}x{height}+{x}+{y}')
def _create_widgets(self):
"""创建界面组件"""
# 标题栏
self._create_title_bar()
# 可滚动主内容区域
canvas = tk.Canvas(self.root, highlightthickness=0)
scrollbar = ttk.Scrollbar(self.root, orient="vertical", command=canvas.yview)
main_frame = tk.Frame(canvas, padx=20, pady=15)
main_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=main_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# 文件选择区域
self._create_file_selection(main_frame)
# 处理设置区域
self._create_settings_area(main_frame)
# 日志区域
self._create_log_area(main_frame)
# 进度条
self._create_progress_bar(main_frame)
# 按钮区域
self._create_button_area(main_frame)
# 布局
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 绑定鼠标滚轮
def _on_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
canvas.bind_all("<MouseWheel>", _on_mousewheel)
def _create_title_bar(self):
"""创建标题栏"""
title_frame = tk.Frame(self.root, bg=self.COLOR_PRIMARY, height=55)
title_frame.pack(fill=tk.X)
title_frame.pack_propagate(False)
# 标题文字
title_label = tk.Label(
title_frame,
text=f"文档自动优化校准工具 v{__version__}",
font=("微软雅黑", 15, "bold"),
bg=self.COLOR_PRIMARY,
fg="white"
)
title_label.pack(side=tk.LEFT, padx=20, expand=False)
# 副标题
subtitle_label = tk.Label(
title_frame,
text="格式优化 · 国际化翻译 · PDF转换",
font=("微软雅黑", 9),
bg=self.COLOR_PRIMARY,
fg="#bdc3c7"
)
subtitle_label.pack(side=tk.LEFT, padx=5)
# Word 状态指示
if self._word_available:
status_text = "● Word 可用"
status_color = self.COLOR_SUCCESS
else:
status_text = "● Word 未安装(PDF转换不可用)"
status_color = self.COLOR_WARNING
status_label = tk.Label(
title_frame,
text=status_text,
font=("微软雅黑", 9),
bg=self.COLOR_PRIMARY,
fg=status_color
)
status_label.pack(side=tk.RIGHT, padx=20)
def _create_file_selection(self, parent):
"""创建文件选择区域"""
file_frame = tk.LabelFrame(
parent,
text=" 📄 文件选择 ",
font=("微软雅黑", 11, "bold"),
fg=self.COLOR_TEXT,
padx=12,
pady=10
)
file_frame.pack(fill=tk.X, pady=(0, 10))
# 文件路径输入框 + 浏览按钮
row = tk.Frame(file_frame)
row.pack(fill=tk.X)
tk.Label(
row,
text="Word文档:",
font=("微软雅黑", 10),
width=10,
anchor="e"
).pack(side=tk.LEFT, padx=(0, 5))
entry = tk.Entry(
row,
textvariable=self.input_path,
font=("微软雅黑", 10),
readonlybackground=self.COLOR_BG_WHITE
)
entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
tk.Button(
row,
text="浏览...",
command=self._browse_file,
font=("微软雅黑", 9),
width=8,
bg=self.COLOR_ACCENT,
fg="white",
activebackground="#2980b9",
relief=tk.FLAT,
cursor="hand2"
).pack(side=tk.LEFT)
# 提示文字
tk.Label(
file_frame,
text="支持 .docx 格式的 Word 文档",
font=("微软雅黑", 8),
fg=self.COLOR_TEXT_LIGHT
).pack(anchor=tk.W, padx=(95, 0), pady=(3, 0))
def _create_settings_area(self, parent):
"""创建处理设置区域"""
settings_frame = tk.LabelFrame(
parent,
text=" ⚙ 处理设置 ",
font=("微软雅黑", 11, "bold"),
fg=self.COLOR_TEXT,
padx=12,
pady=10
)
settings_frame.pack(fill=tk.X, pady=(0, 10))
# 处理模式选择
mode_frame = tk.Frame(settings_frame)
mode_frame.pack(fill=tk.X, pady=(0, 8))
tk.Label(
mode_frame,
text="处理模式:",
font=("微软雅黑", 10),
width=10,
anchor="e"
).pack(side=tk.LEFT, padx=(0, 5))
mode_combo = ttk.Combobox(
mode_frame,
textvariable=self.mode_var,
values=["optimize", "translate"],
state="readonly",
width=15,
font=("微软雅黑", 10)
)
mode_combo.pack(side=tk.LEFT)
# 显示友好名称
mode_label = tk.Label(
mode_frame,
text="optimize=格式优化 translate=国际化翻译",
font=("微软雅黑", 8),
fg=self.COLOR_TEXT_LIGHT
)
mode_label.pack(side=tk.LEFT, padx=(10, 0))
# 监听模式变化
self.mode_var.trace_add("write", lambda *_: self._on_mode_change())
# 目标语言选择(翻译模式可见)
lang_frame = tk.Frame(settings_frame)
lang_frame.pack(fill=tk.X, pady=(0, 8))
self._translate_widgets.append(lang_frame)
tk.Label(
lang_frame,
text="目标语言:",
font=("微软雅黑", 10),
width=10,
anchor="e"
).pack(side=tk.LEFT, padx=(0, 5))
# 语言名称友好显示
lang_display_values = [f"{code} ({name})" for code, name in LANGUAGE_NAMES.items()]
lang_combo = ttk.Combobox(
lang_frame,
values=lang_display_values,
state="readonly",
width=15,
font=("微软雅黑", 10)
)
lang_combo.set(f"{TARGET_LANGUAGE} ({LANGUAGE_NAMES.get(TARGET_LANGUAGE, '')})")
lang_combo.pack(side=tk.LEFT)
self.lang_combo = lang_combo
# 翻译引擎选择(翻译模式可见)
engine_frame = tk.Frame(settings_frame)
engine_frame.pack(fill=tk.X, pady=(0, 8))
self._translate_widgets.append(engine_frame)
tk.Label(
engine_frame,
text="翻译引擎:",
font=("微软雅黑", 10),
width=10,
anchor="e"
).pack(side=tk.LEFT, padx=(0, 5))
engine_combo = ttk.Combobox(
engine_frame,
textvariable=self.engine_var,
values=SUPPORTED_ENGINES,
state="readonly",
width=15,
font=("微软雅黑", 10)
)
engine_combo.pack(side=tk.LEFT)
# 分隔线
sep = ttk.Separator(settings_frame, orient="horizontal")
sep.pack(fill=tk.X, pady=8)
# PDF 转换选项
pdf_frame = tk.Frame(settings_frame)
pdf_frame.pack(fill=tk.X, pady=(0, 5))
pdf_cb = tk.Checkbutton(
pdf_frame,
text="转换为PDF格式",
variable=self.pdf_var,
font=("微软雅黑", 10),
state=tk.NORMAL if self._word_available else tk.DISABLED
)
pdf_cb.pack(side=tk.LEFT, padx=(95, 0))
if not self._word_available:
tk.Label(
pdf_frame,
text="(需安装 Microsoft Word)",
font=("微软雅黑", 8),
fg=self.COLOR_DANGER
).pack(side=tk.LEFT, padx=(10, 0))
# 网盘上传选项
upload_check_frame = tk.Frame(settings_frame)
upload_check_frame.pack(fill=tk.X, pady=(0, 5))
upload_cb = tk.Checkbutton(
upload_check_frame,
text="上传至网盘",
variable=self.upload_var,
font=("微软雅黑", 10),
command=self._on_upload_toggle
)
upload_cb.pack(side=tk.LEFT, padx=(95, 0))
# 网盘路径输入(勾选后显示)
self.upload_path_frame = tk.Frame(settings_frame)
self.upload_path_frame.pack(fill=tk.X, pady=(0, 5))
tk.Label(
self.upload_path_frame,
text="网盘路径:",
font=("微软雅黑", 10),
width=10,
anchor="e"
).pack(side=tk.LEFT, padx=(0, 5))
upload_entry = tk.Entry(
self.upload_path_frame,
textvariable=self.upload_path,
font=("微软雅黑", 10)
)
upload_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
tk.Button(
self.upload_path_frame,
text="浏览...",
command=self._browse_upload_path,
font=("微软雅黑", 9),
width=8,
bg=self.COLOR_ACCENT,
fg="white",
activebackground="#2980b9",
relief=tk.FLAT,
cursor="hand2"
).pack(side=tk.LEFT)
# 默认隐藏网盘路径区域
self.upload_path_frame.pack_forget()
def _create_log_area(self, parent):
"""创建日志面板"""
log_frame = tk.LabelFrame(
parent,
text=" 📋 处理日志 ",
font=("微软雅黑", 11, "bold"),
fg=self.COLOR_TEXT,
padx=10,
pady=8
)
log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 8))
# 日志文本框
self.log_text = scrolledtext.ScrolledText(
log_frame,
font=("Consolas", 9),
wrap=tk.WORD,
height=12,
bg=self.COLOR_BG_WHITE,
relief=tk.FLAT,
borderwidth=1
)
self.log_text.pack(fill=tk.BOTH, expand=True)
# 配置日志颜色标签
self.log_text.tag_configure("info", foreground="#333333")
self.log_text.tag_configure("success", foreground=self.COLOR_SUCCESS)
self.log_text.tag_configure("error", foreground=self.COLOR_DANGER)
self.log_text.tag_configure("warning", foreground=self.COLOR_WARNING)
self.log_text.tag_configure("step", foreground=self.COLOR_ACCENT, font=("Consolas", 9, "bold"))
# 初始提示
self._log("欢迎使用文档自动优化校准工具", "info")
self._log("请选择 Word 文档并配置处理参数后点击「开始处理」", "info")
def _create_progress_bar(self, parent):
"""创建进度条"""
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(
parent,
variable=self.progress_var,
maximum=100,
mode='determinate'
)
self.progress_bar.pack(fill=tk.X, pady=(0, 8))
def _create_button_area(self, parent):
"""创建操作按钮区域"""
button_frame = tk.Frame(parent)
button_frame.pack(fill=tk.X)
# 开始处理按钮
self.start_button = tk.Button(
button_frame,
text="🚀 开始处理",
command=self._on_start,
font=("微软雅黑", 11, "bold"),
width=14,
height=2,
bg=self.COLOR_SUCCESS,
fg="white",
activebackground="#219a52",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2"
)
self.start_button.pack(side=tk.LEFT, padx=(0, 8))
# 打开输出文件夹按钮
self.open_button = tk.Button(
button_frame,
text="📂 打开输出",
command=self._open_output_dir,
font=("微软雅黑", 11),
width=12,
height=2,
bg=self.COLOR_ACCENT,
fg="white",
activebackground="#2980b9",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2"
)
self.open_button.pack(side=tk.LEFT, padx=(0, 8))
# 退出按钮
exit_button = tk.Button(
button_frame,
text="退出",
command=self._on_exit,
font=("微软雅黑", 11),
width=8,
height=2,
bg=self.COLOR_GRAY,
fg="white",
activebackground="#7f8c8d",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2"
)
exit_button.pack(side=tk.RIGHT)
# ==================== 事件处理 ====================
def _on_mode_change(self):
"""处理模式切换时的联动"""
is_translate = self.mode_var.get() == "translate"
for widget in self._translate_widgets:
if is_translate:
widget.pack(fill=tk.X, pady=(0, 8))
else:
widget.pack_forget()
def _on_upload_toggle(self):
"""网盘上传复选框切换"""
if self.upload_var.get():
self.upload_path_frame.pack(fill=tk.X, pady=(0, 5))
else:
self.upload_path_frame.pack_forget()
def _browse_file(self):
"""浏览选择文件"""
file_path = filedialog.askopenfilename(
title="选择 Word 文档",
filetypes=[("Word文档", "*.docx"), ("所有文件", "*.*")],
initialdir=str(TESTCASES_DIR)
)
if file_path:
self.input_path.set(file_path)
self._log(f"已选择文件: {Path(file_path).name}", "info")
def _browse_upload_path(self):
"""浏览选择网盘目录"""
dir_path = filedialog.askdirectory(
title="选择网盘目标目录"
)
if dir_path:
self.upload_path.set(dir_path)
self._log(f"网盘路径: {dir_path}", "info")
def _on_start(self):
"""开始处理按钮点击事件"""
# 防止重复点击
if self._processing:
return
# 验证输入
input_file = self.input_path.get().strip()
if not input_file:
self._show_dialog("提示", "请先选择 Word 文档文件", "warning")
return
if not Path(input_file).exists():
self._show_dialog("错误", f"文件不存在:\n{input_file}", "error")
return
if not input_file.lower().endswith('.docx'):
self._show_dialog("提示", "仅支持 .docx 格式的 Word 文档", "warning")
return
# 验证网盘路径(如果勾选了上传)
if self.upload_var.get():
upload_dir = self.upload_path.get().strip().strip('"').strip("'")
if not upload_dir:
self._show_dialog("提示", "已勾选上传网盘,请输入网盘目标路径", "warning")
return
# 更新UI状态
self._processing = True
self.start_button.config(state=tk.DISABLED, text="处理中...", bg=self.COLOR_GRAY)
self.progress_var.set(0)
# 清空日志
self.log_text.delete(1.0, tk.END)
self._log("=" * 50, "step")
self._log("开始处理文档...", "step")
self._log("=" * 50, "step")
# 启动后台处理线程
thread = threading.Thread(
target=self._process_thread,
args=(input_file,),
daemon=True
)
thread.start()
def _process_thread(self, input_file: str):
"""
后台处理线程
根据用户配置依次执行:优化/翻译 → PDF转换 → 网盘上传
Args:
input_file: 输入文档路径
"""
result_path = None
pdf_path = None
try:
# ---- 第一步:优化或翻译 ----
mode = self.mode_var.get()
self._update_progress(10)
if mode == "optimize":
self._log("\n[步骤1] 执行文档格式优化...", "step")
result_path = optimize_document(input_path=input_file)
self._log(f"✓ 格式优化完成", "success")
else:
# 解析语言代码
lang_display = self.lang_combo.get()
lang_code = lang_display.split(" ")[0] if lang_display else TARGET_LANGUAGE
engine = self.engine_var.get()
lang_name = LANGUAGE_NAMES.get(lang_code, lang_code)
self._log(f"\n[步骤1] 执行国际化翻译 ({lang_name})...", "step")
self._log(f" 翻译引擎: {engine}", "info")
result_path = internationalize_document(
input_path=input_file,
engine=engine,
to_lang=lang_code
)
self._log(f"✓ 翻译完成", "success")
self._update_progress(50)
self._log(f" 输出文件: {result_path}", "info")
# ---- 第二步:PDF转换 ----
if self.pdf_var.get():
self._update_progress(60)
self._log("\n[步骤2] 转换PDF格式...", "step")
try:
pdf_path = convert_to_pdf(result_path)
self._log(f"✓ PDF转换成功", "success")
self._log(f" PDF文件: {pdf_path}", "info")
except Exception as e:
self._log(f"✗ PDF转换失败: {e}", "error")
pdf_path = None
else:
self._update_progress(60)
# ---- 第三步:网盘上传 ----
if self.upload_var.get():
self._update_progress(70)
upload_dir = self.upload_path.get().strip().strip('"').strip("'")
self._log(f"\n[步骤3] 上传至网盘...", "step")
self._log(f" 目标路径: {upload_dir}", "info")
# 收集待上传文件
files_to_upload = []
if result_path:
files_to_upload.append(result_path)
if pdf_path:
files_to_upload.append(pdf_path)
upload_success_files = []
for f in files_to_upload:
try:
file_name = Path(f).name
self._log(f" 正在上传: {file_name}", "info")
upload_result = upload_to_network(f, upload_dir)
self._log(f" ✓ {file_name} 上传成功", "success")
self._log(f" 地址: {upload_result}", "info")
upload_success_files.append(f)
except Exception as e:
self._log(f" ✗ 上传失败: {e}", "error")
# 清理已成功上传的本地文件
for f in upload_success_files:
_cleanup_local_file(f)
self._log(" 本地已上传文件已清理", "info")
self._update_progress(100)
# ---- 完成 ----
self._log("\n" + "=" * 50, "step")
self._log("✓ 全部处理完成!", "success")
self._log("=" * 50, "step")
# 在主线程中弹出完成提示
msg = "文档处理完成!"
if result_path:
msg += f"\n\n输出文件:\n{result_path}"
if pdf_path:
msg += f"\n{pdf_path}"
self.root.after(0, lambda: self._show_dialog("处理完成", msg, "success"))
except FileNotFoundError as e:
self._log(f"\n✗ 文件未找到: {e}", "error")
self.root.after(0, lambda: self._show_dialog("错误", f"文件未找到:\n{e}", "error"))
except PermissionError as e:
self._log(f"\n✗ 权限错误: {e}", "error")
self.root.after(0, lambda: self._show_dialog("错误", f"权限错误:\n{e}", "error"))
except Exception as e:
self._log(f"\n✗ 处理失败: {e}", "error")
logger.debug("详细错误信息", exc_info=True)
self.root.after(0, lambda: self._show_dialog("错误", f"处理失败:\n{e}", "error"))
finally:
# 恢复UI状态
self._processing = False
self.root.after(0, self._restore_start_button)
def _restore_start_button(self):
"""恢复开始按钮状态"""
self.start_button.config(
state=tk.NORMAL,
text="🚀 开始处理",
bg=self.COLOR_SUCCESS
)
def _open_output_dir(self):
"""打开输出文件夹"""
reports_dir = Path(REPORTS_DIR)
if not reports_dir.exists():
reports_dir.mkdir(parents=True, exist_ok=True)
try:
os.startfile(str(reports_dir))
except Exception as e:
self._log(f"打开文件夹失败: {e}", "error")
def _on_exit(self):
"""退出按钮点击事件"""
if self._processing:
self._show_dialog("提示", "正在处理中,请等待处理完成后再退出", "warning")
return
confirmed = self._show_confirm_dialog("确认退出", "确定要退出吗?")
if confirmed:
self.root.quit()
self.root.destroy()
# ==================== 辅助方法 ====================
def _log(self, message: str, tag: str = "info"):
"""
记录日志到日志面板
Args:
message: 日志消息
tag: 日志标签(info/success/error/warning/step)
"""
self.log_text.insert(tk.END, message + "\n", tag)
self.log_text.see(tk.END)
self.log_text.update()
def _update_progress(self, value: float):
"""
更新进度条
Args:
value: 进度值(0-100)
"""
self.root.after(0, lambda: self.progress_var.set(value))
def _show_dialog(self, title: str, message: str, dialog_type: str = "info"):
"""
显示自定义对话框
Args:
title: 对话框标题
message: 消息内容
dialog_type: 对话框类型(info/success/error/warning)
"""
dialog = tk.Toplevel(self.root)
dialog.title(title)
dialog.resizable(False, False)
dialog.transient(self.root)
dialog.grab_set()
# 颜色映射
color_map = {
"info": self.COLOR_ACCENT,
"success": self.COLOR_SUCCESS,
"error": self.COLOR_DANGER,
"warning": self.COLOR_WARNING
}
btn_color = color_map.get(dialog_type, self.COLOR_ACCENT)
# 图标映射
icon_map = {
"info": "ℹ",
"success": "✓",
"error": "✗",
"warning": "⚠"
}
icon = icon_map.get(dialog_type, "ℹ")
# 消息区域
msg_frame = tk.Frame(dialog, padx=30, pady=20)
msg_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
# 图标 + 消息
content_frame = tk.Frame(msg_frame)
content_frame.pack()
tk.Label(
content_frame,
text=icon,
font=("微软雅黑", 20),
fg=btn_color
).pack(side=tk.LEFT, padx=(0, 15))
tk.Label(
content_frame,
text=message,
font=("微软雅黑", 10),
justify=tk.LEFT,
wraplength=350
).pack(side=tk.LEFT)
# 按钮
btn_frame = tk.Frame(dialog, bg=self.COLOR_BG_LIGHT, height=55)
btn_frame.pack(side=tk.BOTTOM, fill=tk.X)
btn_frame.pack_propagate(False)
tk.Button(
btn_frame,
text="确定",
command=dialog.destroy,
width=12,
font=("微软雅黑", 10),
bg=btn_color,
fg="white",
relief=tk.FLAT,
cursor="hand2"
).pack(pady=10)
# 居中
dialog.update_idletasks()
dw = dialog.winfo_width()
dh = dialog.winfo_height()
rx = self.root.winfo_x()
ry = self.root.winfo_y()
rw = self.root.winfo_width()
rh = self.root.winfo_height()
x = rx + (rw // 2) - (dw // 2)
y = ry + (rh // 2) - (dh // 2)
dialog.geometry(f'{dw}x{dh}+{x}+{y}')
dialog.wait_window()
def _show_confirm_dialog(self, title: str, message: str) -> bool:
"""
显示确认对话框
Args:
title: 对话框标题
message: 消息内容
Returns:
True=确认,False=取消
"""
dialog = tk.Toplevel(self.root)
dialog.title(title)
dialog.resizable(False, False)
dialog.transient(self.root)
dialog.grab_set()
dialog.geometry("380x180+300+200")
# 消息
msg_frame = tk.Frame(dialog, padx=30, pady=20)
msg_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
tk.Label(
msg_frame,
text=message,
font=("微软雅黑", 11),
justify=tk.CENTER
).pack()
# 按钮
btn_frame = tk.Frame(dialog, bg=self.COLOR_BG_LIGHT, height=55)
btn_frame.pack(side=tk.BOTTOM, fill=tk.X)
btn_frame.pack_propagate(False)
result = [False]
tk.Button(
btn_frame,
text="确定",
command=lambda: [result.__setitem__(0, True), dialog.destroy()],
width=10,
font=("微软雅黑", 10),
bg=self.COLOR_ACCENT,
fg="white",
relief=tk.FLAT,
cursor="hand2"
).pack(side=tk.LEFT, padx=40, pady=10)
tk.Button(
btn_frame,
text="取消",
command=dialog.destroy,
width=10,
font=("微软雅黑", 10),
bg=self.COLOR_GRAY,
fg="white",
relief=tk.FLAT,
cursor="hand2"
).pack(side=tk.LEFT, pady=10)
# 居中
dialog.update_idletasks()
dw = dialog.winfo_width()
dh = dialog.winfo_height()
rx = self.root.winfo_x()
ry = self.root.winfo_y()
rw = self.root.winfo_width()
rh = self.root.winfo_height()
x = rx + (rw // 2) - (dw // 2)
y = ry + (rh // 2) - (dh // 2)
dialog.geometry(f'{dw}x{dh}+{x}+{y}')
dialog.wait_window()
return result[0]
def run(self):
"""运行GUI主循环"""
self.root.mainloop()
def main_gui() -> None:
"""
GUI主函数
"""
app = DocumentOptimizerGUI()
app.run()
......@@ -2,14 +2,13 @@
"""
文档自动优化校准工具 - 主入口模块
本模块是程序的主入口,负责初始化和启动应用
本模块是程序的主入口,负责初始化和启动应用。
支持 CLI 模式和 GUI 模式两种运行方式。
"""
import sys
from logging import getLogger
from src.cli import main as cli_main
logger = getLogger(__name__)
......@@ -17,16 +16,19 @@ def print_banner() -> None:
"""
打印欢迎横幅
"""
banner = """
from src.config import __version__
banner = f"""
╔════════════════════════════════════════════════════════════╗
║ ║
║ 文档自动优化校准工具 v1.0
║ 文档自动优化校准工具 v{__version__}
║ ║
║ 功能:统一Word文档格式样式 ║
║ - 标题格式统一 ║
║ - 正文格式统一 ║
║ - 表格格式统一 ║
║ - 页眉页脚设置 ║
║ - 文档国际化转换 ║
║ - PDF格式转换 ║
║ - 网盘上传 ║
║ ║
╚════════════════════════════════════════════════════════════╝
"""
......@@ -40,6 +42,7 @@ def print_usage_hint() -> None:
hint = """
使用提示:
python run.py # 交互式选择文档
python run.py --gui # 启动GUI界面
python run.py --help # 查看帮助信息
python run.py --input 文档.docx # 处理指定文档
"""
......@@ -50,15 +53,34 @@ def main() -> int:
"""
主入口函数
支持 --gui 参数启动图形界面,否则使用 CLI 命令行模式。
Returns:
退出码(0表示成功,非0表示失败)
"""
# 检测是否使用 GUI 模式
if '--gui' in sys.argv:
try:
from src.gui import main_gui
main_gui()
return 0
except ImportError as e:
print(f"错误: 无法启动GUI界面 - {str(e)}")
print("请确保已安装 tkinter 库")
return 1
except Exception as e:
logger.error("GUI异常: %s", e)
import traceback
logger.debug(traceback.format_exc())
return 1
# CLI 模式
try:
# 如果没有命令行参数或只是显示帮助,显示横幅
if len(sys.argv) == 1 or '--help' in sys.argv or '-h' in sys.argv:
print_banner()
# 调用CLI主函数
from src.cli import main as cli_main
exit_code = cli_main()
# 如果是交互模式且成功完成,打印使用提示
......
#!/bin/bash
# MySQL 深度检测脚本
# 在远程服务器上本地执行,避免 PowerShell -> plink 的引号传递问题
# 输出格式: KEY=VALUE 便于 PowerShell 解析
# 参数
CONTAINER="${1:-umysql}"
PASSWORD="$2"
if [[ -z "$PASSWORD" ]]; then
echo "ERROR: MySQL password not provided"
exit 1
fi
# 函数:安全执行SQL
mysql_exec() {
local sql="$1"
echo "$sql" | docker exec -i "$CONTAINER" mysql -u root -p"$PASSWORD" -N 2>/dev/null
}
# 输出标记
echo "===MYSQL_DEEP_START==="
# 1. 缓冲池命中率
echo "---CHECK:buffer_pool---"
BP_OUTPUT=$(mysql_exec "SHOW STATUS LIKE 'Innodb_buffer_pool_read%';")
if [[ -n "$BP_OUTPUT" ]]; then
READ_REQUESTS=$(echo "$BP_OUTPUT" | grep 'Innodb_buffer_pool_read_requests' | awk '{print $2}')
READ_DISK=$(echo "$BP_OUTPUT" | grep 'Innodb_buffer_pool_reads' | awk '{print $2}')
READ_REQUESTS=${READ_REQUESTS:-0}
READ_DISK=${READ_DISK:-0}
if [[ "$READ_REQUESTS" -gt 0 ]] 2>/dev/null; then
HIT_RATE=$(awk "BEGIN {printf \"%.2f\", ($READ_REQUESTS - $READ_DISK) / $READ_REQUESTS * 100}")
else
HIT_RATE="0.00"
fi
echo "read_requests=$READ_REQUESTS"
echo "read_disk=$READ_DISK"
echo "hit_rate=$HIT_RATE"
else
echo "result=empty"
fi
# 2. 慢查询状态
echo "---CHECK:slow_query---"
SLOW_OUTPUT=$(mysql_exec "SHOW VARIABLES LIKE 'slow_query_log'; SHOW STATUS LIKE 'Slow_queries';")
if [[ -n "$SLOW_OUTPUT" ]]; then
SLOW_ENABLED=$(echo "$SLOW_OUTPUT" | grep 'slow_query_log' | awk '{print $2}')
SLOW_COUNT=$(echo "$SLOW_OUTPUT" | grep 'Slow_queries' | awk '{print $2}')
SLOW_ENABLED=${SLOW_ENABLED:-OFF}
SLOW_COUNT=${SLOW_COUNT:-0}
echo "slow_enabled=$SLOW_ENABLED"
echo "slow_count=$SLOW_COUNT"
else
echo "result=empty"
fi
# 3. 连接使用率
echo "---CHECK:connections---"
CONN_OUTPUT=$(mysql_exec "SHOW STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';")
if [[ -n "$CONN_OUTPUT" ]]; then
CURRENT_CONN=$(echo "$CONN_OUTPUT" | grep 'Threads_connected' | awk '{print $2}')
MAX_CONN=$(echo "$CONN_OUTPUT" | grep 'max_connections' | awk '{print $2}')
CURRENT_CONN=${CURRENT_CONN:-0}
MAX_CONN=${MAX_CONN:-0}
if [[ "$MAX_CONN" -gt 0 ]] 2>/dev/null; then
CONN_RATE=$(awk "BEGIN {printf \"%.1f\", $CURRENT_CONN / $MAX_CONN * 100}")
else
CONN_RATE="0.0"
fi
echo "current_conn=$CURRENT_CONN"
echo "max_conn=$MAX_CONN"
echo "conn_rate=$CONN_RATE"
else
echo "result=empty"
fi
# 4. 主从复制状态
echo "---CHECK:replication---"
REPL_OUTPUT=$(mysql_exec "SHOW SLAVE STATUS\G")
if [[ -n "$REPL_OUTPUT" ]] && ! echo "$REPL_OUTPUT" | grep -q "Empty set"; then
IO_RUNNING=$(echo "$REPL_OUTPUT" | grep 'Slave_IO_Running:' | awk '{print $2}')
SQL_RUNNING=$(echo "$REPL_OUTPUT" | grep 'Slave_SQL_Running:' | awk '{print $2}')
SECONDS_BEHIND=$(echo "$REPL_OUTPUT" | grep 'Seconds_Behind_Master:' | awk '{print $2}')
echo "has_slave=yes"
echo "io_running=$IO_RUNNING"
echo "sql_running=$SQL_RUNNING"
echo "seconds_behind=$SECONDS_BEHIND"
else
echo "has_slave=no"
fi
# 5. TOP20 大表
echo "---CHECK:top_tables---"
TABLE_OUTPUT=$(mysql_exec "SELECT table_name, ROUND(data_length/1024/1024,2) AS data_mb, ROUND(index_length/1024/1024,2) AS idx_mb, table_rows FROM information_schema.TABLES WHERE table_schema NOT IN ('mysql','information_schema','performance_schema','sys') ORDER BY data_length DESC LIMIT 20;")
if [[ -n "$TABLE_OUTPUT" ]]; then
TABLE_COUNT=$(echo "$TABLE_OUTPUT" | grep -c '[^[:space:]]')
echo "table_count=$TABLE_COUNT"
echo "$TABLE_OUTPUT" | head -5 | while read line; do
echo "table:$line"
done
else
echo "result=empty"
fi
# 6. QPS 性能基线
echo "---CHECK:qps---"
QPS_OUTPUT=$(mysql_exec "SHOW STATUS LIKE 'Queries'; SHOW STATUS LIKE 'Uptime';")
if [[ -n "$QPS_OUTPUT" ]]; then
QUERIES=$(echo "$QPS_OUTPUT" | grep 'Queries' | awk '{print $2}')
UPTIME=$(echo "$QPS_OUTPUT" | grep 'Uptime' | awk '{print $2}')
QUERIES=${QUERIES:-0}
UPTIME=${UPTIME:-0}
if [[ "$UPTIME" -gt 0 ]] 2>/dev/null; then
QPS=$(awk "BEGIN {printf \"%.2f\", $QUERIES / $UPTIME}")
else
QPS="0.00"
fi
echo "queries=$QUERIES"
echo "uptime=$UPTIME"
echo "qps=$QPS"
else
echo "result=empty"
fi
echo "===MYSQL_DEEP_END==="
......@@ -1259,29 +1259,56 @@ function Test-MySQLDeepCheck {
$actualContainer = (@($checkResult.Output)[0].ToString().Trim() -replace "`r","")
Write-Log -Level "INFO" -Message "[MySQL深度] 容器: $actualContainer"
# 辅助:通过 docker exec mysql -e 方式执行 SQL 查询
# 密码用双引号保护特殊字符(如 & ),SQL 用单引号包裹(避免 plink 传参时引号冲突)
# SQL 内部字符串用双引号(MySQL 同时支持单引号和双引号作为字符串界定符)
# PowerShell 中: `" 产生 " , "" 产生 " , ' 是字面量
$mysqlExec = "docker exec $actualContainer mysql -uroot -p`"$mysqlPassword`" -N"
# 辅助:通过上传 check_mysql_deep.sh 脚本到远程服务器本地执行
# 避免通过 PowerShell -> plink 传递 SQL 时引号被破坏(PS 5.1 已知 bug)
$deepScriptName = 'check_mysql_deep.sh'
# 获取脚本本地路径
$localDeepScript = Join-Path $PWD $deepScriptName
if (-not (Test-Path $localDeepScript)) {
Write-Log -Level "WARN" -Message "[MySQL深度] 本地脚本不存在: $localDeepScript,跳过深度检测"
Write-Log -Level "INFO" -Message "========== MySQL 深度检测完成(共 0 项) =========="
return $results
}
# --- 1. 缓冲池命中率 ---
try {
$bpCmd = "$mysqlExec -e 'SHOW STATUS LIKE ""Innodb_buffer_pool_read%""'"
Write-Log -Level "INFO" -Message "[MySQL深度] 执行: $bpCmd"
$bpResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $bpCmd
# 上传脚本到远程服务器
Write-Log -Level "INFO" -Message "[MySQL深度] 开始上传 $deepScriptName 脚本..."
$uploadOk = Copy-File-To-Remote -LocalPath $localDeepScript -Server $Server -RemoteDir '/root'
if ($uploadOk) {
Write-Log -Level "SUCCESS" -Message "[MySQL深度] $deepScriptName 上传成功"
$fixCmd = "cd /root && dos2unix $deepScriptName 2>/dev/null || true && chmod +x $deepScriptName"
Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $fixCmd | Out-Null
} else {
Write-Log -Level "WARN" -Message "[MySQL深度] $deepScriptName 上传失败,跳过深度检测"
Write-Log -Level "INFO" -Message "========== MySQL 深度检测完成(共 0 项) =========="
return $results
}
if ($bpResult.ExitCode -eq 0 -and $bpResult.Output) {
$bpOutput = $bpResult.Output -join "`n"
$readRequests = 0; $readDisk = 0
if ($bpOutput -match 'Innodb_buffer_pool_read_requests\s+(\d+)') { $readRequests = [long]$matches[1] }
if ($bpOutput -match 'Innodb_buffer_pool_reads\s+(\d+)') { $readDisk = [long]$matches[1] }
# 执行远程脚本(参数:容器名 密码)
# 密码中的特殊字符(如 &)需要用反引号转义,避免 PowerShell 将其解释为后台运算符
$escapedPassword = $mysqlPassword -replace '&', '`&'
$execCmd = "/root/$deepScriptName $actualContainer $escapedPassword"
Write-Log -Level "INFO" -Message "[MySQL深度] 执行: $execCmd"
$deepResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $execCmd
$hitRate = 0.0
if ($readRequests -gt 0) {
$hitRate = [math]::Round(($readRequests - $readDisk) / $readRequests * 100, 2)
if ($deepResult.ExitCode -ne 0 -or -not $deepResult.Output) {
Write-Log -Level "WARN" -Message "[MySQL深度] 脚本执行失败 (ExitCode=$($deepResult.ExitCode))"
Write-Log -Level "INFO" -Message "========== MySQL 深度检测完成(共 0 项) =========="
return $results
}
# 解析脚本输出(KEY=VALUE 格式)
$deepOutput = $deepResult.Output -join "`n"
Write-Log -Level "INFO" -Message "[MySQL深度] 脚本输出:`n$deepOutput"
# --- 1. 解析缓冲池命中率 ---
try {
$hitRate = 0.0; $readRequests = 0; $readDisk = 0
if ($deepOutput -match 'read_requests=(\d+)') { $readRequests = [long]$matches[1] }
if ($deepOutput -match 'read_disk=(\d+)') { $readDisk = [long]$matches[1] }
if ($deepOutput -match 'hit_rate=([\d.]+)') { $hitRate = [double]$matches[1] }
if ($readRequests -gt 0 -or $hitRate -gt 0) {
$bpStatus = if ($hitRate -ge 95) { "正常" } elseif ($hitRate -ge 80) { "警告" } else { "异常" }
$bpColor = if ($hitRate -ge 95) { "SUCCESS" } elseif ($hitRate -ge 80) { "WARN" } else { "ERROR" }
Write-Log -Level $bpColor -Message "[MySQL深度] 缓冲池命中率: ${hitRate}% [$bpStatus]"
......@@ -1293,31 +1320,16 @@ function Test-MySQLDeepCheck {
Value = "${hitRate}%"
Success = ($hitRate -ge 80)
}
} else {
Write-Log -Level "WARN" -Message "[MySQL深度] 缓冲池命中率: 查询无结果 (ExitCode=$($bpResult.ExitCode))"
}
} catch {
Write-Log -Level "WARN" -Message "[MySQL深度] 缓冲池命中率检测异常: $($_.Exception.Message)"
Write-Log -Level "WARN" -Message "[MySQL深度] 缓冲池命中率解析异常: $($_.Exception.Message)"
}
# --- 2. 慢查询状态 ---
# --- 2. 解析慢查询状态 ---
try {
$slowCmd = "$mysqlExec -e 'SHOW VARIABLES LIKE ""slow_query_log""'"
Write-Log -Level "INFO" -Message "[MySQL深度] 执行: $slowCmd"
$slowResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $slowCmd
# 同时获取慢查询数量
$slowCountCmd = "$mysqlExec -e 'SHOW STATUS LIKE ""Slow_queries""'"
$slowCountResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $slowCountCmd
if ($slowResult.ExitCode -eq 0 -and $slowResult.Output) {
$slowOutput = $slowResult.Output -join "`n"
$slowEnabled = "OFF"; $slowCount = 0
if ($slowOutput -match 'slow_query_log\s+(ON|OFF)') { $slowEnabled = $matches[1] }
if ($slowCountResult.ExitCode -eq 0 -and $slowCountResult.Output) {
$slowCountOutput = $slowCountResult.Output -join "`n"
if ($slowCountOutput -match 'Slow_queries\s+(\d+)') { $slowCount = [long]$matches[1] }
}
if ($deepOutput -match 'slow_enabled=(ON|OFF)') { $slowEnabled = $matches[1] }
if ($deepOutput -match 'slow_count=(\d+)') { $slowCount = [long]$matches[1] }
$slowStatus = if ($slowEnabled -eq "ON") { "正常" } else { "警告" }
$slowColor = if ($slowEnabled -eq "ON") { "SUCCESS" } else { "WARN" }
......@@ -1330,35 +1342,16 @@ function Test-MySQLDeepCheck {
Value = "$slowEnabled ($slowCount)"
Success = ($slowEnabled -eq "ON")
}
} else {
Write-Log -Level "WARN" -Message "[MySQL深度] 慢查询状态: 查询无结果 (ExitCode=$($slowResult.ExitCode))"
}
} catch {
Write-Log -Level "WARN" -Message "[MySQL深度] 慢查询状态检测异常: $($_.Exception.Message)"
Write-Log -Level "WARN" -Message "[MySQL深度] 慢查询状态解析异常: $($_.Exception.Message)"
}
# --- 3. 连接使用率 ---
# --- 3. 解析连接使用率 ---
try {
$connCmd = "$mysqlExec -e 'SHOW STATUS LIKE ""Threads_connected""'"
Write-Log -Level "INFO" -Message "[MySQL深度] 执行: $connCmd"
$connResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $connCmd
$maxConnCmd = "$mysqlExec -e 'SHOW VARIABLES LIKE ""max_connections""'"
$maxConnResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $maxConnCmd
if ($connResult.ExitCode -eq 0 -and $connResult.Output) {
$connOutput = $connResult.Output -join "`n"
$currentConn = 0; $maxConn = 0
if ($connOutput -match 'Threads_connected\s+(\d+)') { $currentConn = [int]$matches[1] }
if ($maxConnResult.ExitCode -eq 0 -and $maxConnResult.Output) {
$maxConnOutput = $maxConnResult.Output -join "`n"
if ($maxConnOutput -match 'max_connections\s+(\d+)') { $maxConn = [int]$matches[1] }
}
$connRate = 0.0
if ($maxConn -gt 0) {
$connRate = [math]::Round($currentConn / $maxConn * 100, 1)
}
$currentConn = 0; $maxConn = 0; $connRate = 0.0
if ($deepOutput -match 'current_conn=(\d+)') { $currentConn = [int]$matches[1] }
if ($deepOutput -match 'max_conn=(\d+)') { $maxConn = [int]$matches[1] }
if ($deepOutput -match 'conn_rate=([\d.]+)') { $connRate = [double]$matches[1] }
$connStatus = if ($connRate -lt 80) { "正常" } else { "警告" }
$connColor = if ($connRate -lt 80) { "SUCCESS" } else { "WARN" }
......@@ -1371,27 +1364,17 @@ function Test-MySQLDeepCheck {
Value = "${connRate}%"
Success = ($connRate -lt 80)
}
} else {
Write-Log -Level "WARN" -Message "[MySQL深度] 连接使用率: 查询无结果 (ExitCode=$($connResult.ExitCode))"
}
} catch {
Write-Log -Level "WARN" -Message "[MySQL深度] 连接使用率检测异常: $($_.Exception.Message)"
Write-Log -Level "WARN" -Message "[MySQL深度] 连接使用率解析异常: $($_.Exception.Message)"
}
# --- 4. 主从复制状态 ---
# --- 4. 解析主从复制状态 ---
try {
# 使用 -E 参数获取竖向输出格式(等效于 \G)
$replCmd = "$mysqlExec -E -e 'SHOW SLAVE STATUS'"
Write-Log -Level "INFO" -Message "[MySQL深度] 执行: $replCmd"
$replResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $replCmd
if ($replResult.ExitCode -eq 0) {
$replOutput = if ($replResult.Output) { $replResult.Output -join "`n" } else { "" }
if ($replOutput -match '\S' -and $replOutput -notmatch 'Empty set') {
if ($deepOutput -match 'has_slave=yes') {
$ioRunning = ""; $sqlRunning = ""; $secondsBehind = ""
if ($replOutput -match 'Slave_IO_Running:\s*(\S+)') { $ioRunning = $matches[1] }
if ($replOutput -match 'Slave_SQL_Running:\s*(\S+)') { $sqlRunning = $matches[1] }
if ($replOutput -match 'Seconds_Behind_Master:\s*(\S+)') { $secondsBehind = $matches[1] }
if ($deepOutput -match 'io_running=(\S+)') { $ioRunning = $matches[1] }
if ($deepOutput -match 'sql_running=(\S+)') { $sqlRunning = $matches[1] }
if ($deepOutput -match 'seconds_behind=(\S+)') { $secondsBehind = $matches[1] }
$replOk = ($ioRunning -eq "Yes" -and $sqlRunning -eq "Yes")
$replStatus = if ($replOk) { "正常" } else { "异常" }
......@@ -1415,62 +1398,38 @@ function Test-MySQLDeepCheck {
Success = $true
}
}
} else {
Write-Log -Level "WARN" -Message "[MySQL深度] 主从复制: 查询无结果 (ExitCode=$($replResult.ExitCode))"
}
} catch {
Write-Log -Level "WARN" -Message "[MySQL深度] 主从复制状态检测异常: $($_.Exception.Message)"
Write-Log -Level "WARN" -Message "[MySQL深度] 主从复制状态解析异常: $($_.Exception.Message)"
}
# --- 5. TOP20 大表 ---
# --- 5. 解析 TOP20 大表 ---
try {
# SQL 内部字符串用双引号(MySQL 支持),外层用单引号包裹避免 plink 引号冲突
$topTableCmd = "$mysqlExec -e 'SELECT table_name,ROUND(data_length/1024/1024,2) AS data_mb,ROUND(index_length/1024/1024,2) AS idx_mb,table_rows FROM information_schema.TABLES WHERE table_schema NOT IN (""mysql"",""information_schema"",""performance_schema"",""sys"") ORDER BY data_length DESC LIMIT 20'"
Write-Log -Level "INFO" -Message "[MySQL深度] 执行: $topTableCmd"
$topTableResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $topTableCmd
if ($topTableResult.ExitCode -eq 0 -and $topTableResult.Output) {
$topTableOutput = $topTableResult.Output -join "`n"
$tableLines = $topTableOutput -split "`n" | Where-Object { $_ -match '\S' }
$tableSummary = ($tableLines | Select-Object -First 5 | ForEach-Object { $_.Trim() }) -join ' | '
Write-Log -Level "INFO" -Message "[MySQL深度] TOP20大表: $($tableLines.Count) 个表"
$tableCount = 0
if ($deepOutput -match 'table_count=(\d+)') { $tableCount = [int]$matches[1] }
# 提取前5个表行
$tableLines = $deepOutput -split "`n" | Where-Object { $_ -match '^table:' } | Select-Object -First 5
$tableSummary = ($tableLines | ForEach-Object { ($_ -replace '^table:\s*', '').Trim() }) -join ' | '
Write-Log -Level "INFO" -Message "[MySQL深度] TOP20大表: $tableCount 个表"
if ($tableCount -gt 0) {
$results += @{
Check = "MySQL TOP20 大表"
Status = "正常"
Details = "共 $($tableLines.Count) 个表(按数据大小排序)| Top5: $tableSummary"
Value = "$($tableLines.Count)"
Details = "共 $tableCount 个表(按数据大小排序)| Top5: $tableSummary"
Value = "$tableCount"
Success = $true
}
} else {
Write-Log -Level "WARN" -Message "[MySQL深度] TOP20大表: 查询无结果 (ExitCode=$($topTableResult.ExitCode))"
}
} catch {
Write-Log -Level "WARN" -Message "[MySQL深度] TOP20大表检测异常: $($_.Exception.Message)"
Write-Log -Level "WARN" -Message "[MySQL深度] TOP20大表解析异常: $($_.Exception.Message)"
}
# --- 6. QPS/TPS 性能基线 ---
# --- 6. 解析 QPS 性能基线 ---
try {
$qpsCmd = "$mysqlExec -e 'SHOW STATUS LIKE ""Queries""'"
Write-Log -Level "INFO" -Message "[MySQL深度] 执行: $qpsCmd"
$qpsResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $qpsCmd
$uptimeCmd = "$mysqlExec -e 'SHOW STATUS LIKE ""Uptime""'"
$uptimeResult = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $uptimeCmd
if ($qpsResult.ExitCode -eq 0 -and $qpsResult.Output) {
$qpsOutput = $qpsResult.Output -join "`n"
$queries = 0; $uptime = 0
if ($qpsOutput -match 'Queries\s+(\d+)') { $queries = [long]$matches[1] }
if ($uptimeResult.ExitCode -eq 0 -and $uptimeResult.Output) {
$uptimeOutput = $uptimeResult.Output -join "`n"
if ($uptimeOutput -match 'Uptime\s+(\d+)') { $uptime = [long]$matches[1] }
}
$qps = 0.0
if ($uptime -gt 0) {
$qps = [math]::Round($queries / $uptime, 2)
}
$queries = 0; $uptime = 0; $qps = 0.0
if ($deepOutput -match '(?m)^queries=(\d+)') { $queries = [long]$matches[1] }
if ($deepOutput -match '(?m)^uptime=(\d+)') { $uptime = [long]$matches[1] }
if ($deepOutput -match 'qps=([\d.]+)') { $qps = [double]$matches[1] }
Write-Log -Level "INFO" -Message "[MySQL深度] QPS: $qps | 总查询: $queries | 运行时间: ${uptime}s"
$results += @{
......@@ -1480,11 +1439,8 @@ function Test-MySQLDeepCheck {
Value = "$qps"
Success = $true
}
} else {
Write-Log -Level "WARN" -Message "[MySQL深度] QPS: 查询无结果 (ExitCode=$($qpsResult.ExitCode))"
}
} catch {
Write-Log -Level "WARN" -Message "[MySQL深度] QPS检测异常: $($_.Exception.Message)"
Write-Log -Level "WARN" -Message "[MySQL深度] QPS解析异常: $($_.Exception.Message)"
}
Write-Log -Level "INFO" -Message "========== MySQL 深度检测完成(共 $($results.Count) 项) =========="
......
# 工具封装
## 代码路径
- 代码路径:[AuxiliaryTool/DocumentAutoOptimizationCalibration]
## 功能需求
### 功能目标
**目标:** 将文档自动优化校准工具封装为 GUI 界面应用程序,打包为独立 .exe 文件,可在任何 Windows 电脑上运行,无需安装 Python 环境,便于非技术人员使用。
### 需求描述
#### GUI 框架
- 沿用项目内 **tkinter** 框架(与 `FunctionalTestReportGeneration` 保持统一风格)。
- 在 tkinter 基础上尽可能美化界面,提升用户体验。
#### GUI 功能模块
工具界面需支持以下所有功能模块:
| 功能模块 | 说明 |
|---------|------|
| 文件选择 | 选择待处理的 .docx 文档(文件浏览对话框,支持拖拽或手动输入路径) |
| 模式选择 | 选择"格式优化"或"国际化翻译"模式 |
| 目标语言选择 | 翻译模式时可选目标语言(英文、韩文、法文、日语、俄语、西班牙语、阿拉伯语) |
| 翻译引擎选择 | 可选翻译引擎(google / baidu / bing),默认 bing |
| PDF 转换选项 | 复选框:是否在处理后转换为 PDF 格式 |
| 网盘上传选项 | 复选框 + 路径输入框:是否上传至网盘,支持手动输入路径或浏览选择 |
| 进度显示 | 处理过程中实时显示进度状态 |
| 日志面板 | 实时显示处理日志信息(信息、警告、错误分色显示) |
| 查看输出 | 处理完成后支持打开输出文件或打开输出文件夹 |
#### 界面布局
-**易用性**为核心设计原则,操作步骤直观清晰。
- 建议采用分区布局:顶部文件选择 → 中部参数配置 → 底部操作按钮与日志面板。
#### 打包分发格式
- 使用 **PyInstaller** 打包为**单文件 .exe**`--onefile` 模式)。
- 目标用户为非技术人员,单文件便于分发和使用。
- 运行环境:Windows 系统,无需安装 Python。
#### 依赖处理
- Python 运行环境、python-docx、translators、pywin32 等依赖**全部打包进 .exe**,目标电脑无需额外安装。
- **PDF 转换功能**依赖 Microsoft Word:
- **程序启动时自动检测**本机是否安装 Microsoft Word。
- 若已安装 Word:PDF 转换功能正常使用,复选框可选。
- 若未安装 Word:PDF 转换复选框**禁用**,鼠标悬停时提示"需安装 Microsoft Word"。
- 其他功能(格式优化、翻译、上传)**不受影响**,无需 Word 即可使用。
## 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
- 文档规范: `Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
---
# 工具封装_实现计划
## 1. 背景与目标
### 背景
文档自动优化校准工具已具备格式优化、国际化翻译、PDF 转换、网盘上传四大功能,但仅支持 CLI 命令行操作,非技术人员使用门槛高。
### 目标
封装为 tkinter GUI 桌面应用,使用 PyInstaller 打包为独立 .exe,在任意 Windows 电脑上免安装 Python 直接运行。
---
## 2. 项目结构(变更部分)
```
AuxiliaryTool/DocumentAutoOptimizationCalibration/
├── src/
│ ├── config.py # 配置模块(不变)
│ ├── gui.py # ★ 新增:GUI 界面模块
│ ├── converter.py # PDF 转换模块(不变)
│ ├── uploader.py # 网盘上传模块(不变)
│ ├── cli.py # 命令行接口(不变)
│ ├── optimizer.py # 文档优化模块(不变)
│ ├── internationalizer.py # 国际化模块(不变)
│ └── main.py # 主入口(修改:添加 --gui 入口)
├── build.bat # ★ 新增:PyInstaller 打包脚本
├── run.py # 入口脚本(修改:添加 --gui 参数)
├── requirements.txt # 依赖列表(更新:添加 pywin32)
└── ...
```
---
## 3. 功能模块设计
### 3.1 新增模块:GUI 界面 (src/gui.py)
参考 `FunctionalTestReportGeneration/src/gui.py` 的设计模式,但进行美化和功能适配。
#### 界面布局
```
╔══════════════════════════════════════════════╗
║ 文档自动优化校准工具 v1.5.0 ║ ← 标题栏(深蓝色背景)
╠══════════════════════════════════════════════╣
║ 📄 文件选择 ║
║ ┌──────────────────────────┐ ┌──────┐ ║
║ │ 请选择 .docx 文件... │ │ 浏览 │ ║ ← 文件选择区
║ └──────────────────────────┘ └──────┘ ║
╠══════════════════════════════════════════════╣
║ ⚙ 处理设置 ║
║ 处理模式: [格式优化 ▼] ║
║ 目标语言: [英文 ▼] (翻译模式可见) ║ ← 参数配置区
║ 翻译引擎: [bing ▼] (翻译模式可见) ║
║ ☐ 转换为PDF格式 ║
║ ☐ 上传至网盘 路径: [________________] ║
╠══════════════════════════════════════════════╣
║ 📋 处理日志 ║
║ ┌──────────────────────────────────────┐ ║
║ │ 正在处理文档... │ ║ ← 日志面板
║ │ ✓ 文档优化完成 │ ║ (分色显示)
║ │ 正在转换为PDF... │ ║
║ └──────────────────────────────────────┘ ║
║ [████████████████████ ] 60% ║ ← 进度条
╠══════════════════════════════════════════════╣
║ [ 🚀 开始处理 ] [ 📂 打开输出 ] [退出] ║ ← 操作按钮
╚══════════════════════════════════════════════╝
```
#### GUI 类设计
| 类/函数 | 功能描述 |
|---------|---------|
| `DocumentOptimizerGUI` | 主 GUI 类 |
| `_create_title_bar()` | 标题栏 |
| `_create_file_selection()` | 文件选择区域 |
| `_create_settings_area()` | 处理设置区域(模式、语言、引擎、PDF、上传) |
| `_create_log_area()` | 日志面板(分色显示) |
| `_create_progress_bar()` | 进度条 |
| `_create_button_area()` | 操作按钮区域 |
| `_on_mode_change()` | 模式切换时显示/隐藏翻译相关选项 |
| `_on_start()` | 开始处理(后台线程) |
| `_process_thread()` | 后台处理线程 |
| `_check_word_available()` | 启动时检测 Word,控制 PDF 复选框状态 |
| `main_gui()` | GUI 入口函数 |
### 3.2 修改模块:主入口 (src/main.py)
- 添加 `--gui` 参数支持
- 检测 `--gui` 后调用 `main_gui()`
---
## 4. 详细实现步骤
### 4.1 实现 GUI 界面模块 (src/gui.py)
- [ ] 创建 `DocumentOptimizerGUI`
- [ ] 实现 `_create_title_bar()` 标题栏(深蓝背景、白色大字标题)
- [ ] 实现 `_create_file_selection()` 文件选择区域
- [ ] 文件路径输入框 + 浏览按钮
- [ ] 支持 .docx 文件过滤
- [ ] 实现 `_create_settings_area()` 处理设置区域
- [ ] 处理模式下拉框(格式优化 / 国际化翻译)
- [ ] 目标语言下拉框(翻译模式可见)
- [ ] 翻译引擎下拉框(翻译模式可见)
- [ ] PDF 转换复选框(启动时检测 Word 可用性)
- [ ] 网盘上传复选框 + 路径输入框
- [ ] 实现 `_on_mode_change()` 模式切换联动
- [ ] 选择"格式优化"时隐藏语言和引擎选项
- [ ] 选择"国际化翻译"时显示语言和引擎选项
- [ ] 实现 `_create_log_area()` 日志面板
- [ ] ScrolledText 控件
- [ ] 分色标签(info=黑色, success=绿色, error=红色, warning=橙色)
- [ ] 实现 `_create_progress_bar()` 进度条
- [ ] 实现 `_create_button_area()` 操作按钮
- [ ] 开始处理按钮
- [ ] 打开输出文件夹按钮
- [ ] 退出按钮
- [ ] 实现 `_check_word_available()` Word 检测
- [ ] 启动时调用 `converter._check_word_available()`
- [ ] 未安装 Word 时禁用 PDF 复选框 + 提示
- [ ] 实现 `_on_start()` 开始处理逻辑
- [ ] 验证文件路径
- [ ] 禁用开始按钮
- [ ] 启动后台处理线程
- [ ] 实现 `_process_thread()` 后台处理线程
- [ ] 格式优化模式:调用 `optimize_document()`
- [ ] 翻译模式:调用 `internationalize_document()`
- [ ] PDF 转换:勾选时调用 `convert_to_pdf()`
- [ ] 网盘上传:勾选时调用 `upload_to_network()`
- [ ] 实时日志输出到 GUI
- [ ] 完成后恢复按钮状态
- [ ] 实现 `main_gui()` 入口函数
### 4.2 修改主入口模块 (src/main.py)
- [ ] 添加 `--gui` 命令行参数
- [ ] 检测到 `--gui` 时调用 `main_gui()`
### 4.3 新增打包配置
- [ ] 新建 `build.bat` 打包脚本
- [ ] PyInstaller `--onefile` 模式
- [ ] 设置窗口图标和版本信息
- [ ] 处理 win32com 隐式导入
- [ ] 更新 `requirements.txt` 添加 `pywin32`
---
## 5. 关键技术要点
### 5.1 GUI 线程安全
所有耗时操作在后台线程执行,GUI 更新通过 `root.after()` 回调到主线程:
```python
thread = threading.Thread(target=self._process_thread, daemon=True)
thread.start()
# 在线程中更新GUI
self.root.after(0, lambda: self._log("处理完成", "success"))
```
### 5.2 模式联动
监听下拉框变化事件,动态显示/隐藏翻译相关选项:
```python
self.mode_var.trace_add("write", self._on_mode_change)
```
### 5.3 PyInstaller 打包
```bash
pyinstaller --onefile --windowed --name "文档优化工具" run.py
```
- `--onefile`: 打包为单个 .exe
- `--windowed`: 不显示控制台窗口
- `--hidden-import`: 处理 pywin32 等隐式依赖
### 5.4 stdout 重定向
将核心模块的 print 输出重定向到 GUI 日志面板:
```python
class TextRedirector:
def write(self, text):
self.widget.insert(tk.END, text, "info")
self.widget.see(tk.END)
sys.stdout = TextRedirector(self.log_text)
```
---
## 6. 验证测试
### 测试步骤
1. 运行 `python run.py --gui` 验证 GUI 启动正常
2. 测试格式优化模式:选择文件 → 开始处理 → 查看日志和输出
3. 测试翻译模式:切换模式 → 选语言 → 开始处理
4. 测试 PDF 转换:勾选 PDF → 验证转换结果
5. 测试网盘上传:勾选上传 → 输入路径 → 验证上传
6. 运行 `build.bat` 打包为 .exe → 在无 Python 环境的电脑上运行
### 验收标准
- [ ] GUI 启动正常,界面布局美观
- [ ] 格式优化和翻译功能在 GUI 中正常工作
- [ ] PDF 复选框根据 Word 安装状态自动启用/禁用
- [ ] 日志面板实时显示处理进度
- [ ] 网盘上传功能正常
- [ ] 打包后的 .exe 可在无 Python 环境运行
---
## 7. 参考文件
| 文件路径 | 用途 |
|---------|------|
| `FunctionalTestReportGeneration/src/gui.py` | tkinter GUI 实现参考 |
| `FunctionalTestReportGeneration/src/main.py` | GUI 入口调度参考 |
---
## 8. 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| v1.5.0 | 2026-06-07 | 新增 GUI 界面 + PyInstaller 打包 |
---
## 9. 实现状态
### 已完成功能
- [x] GUI 界面模块 (gui.py)
- [x] 主入口 --gui 参数 (main.py)
- [x] 打包脚本 (build.bat)
### 待验证功能
- [ ] GUI 功能测试验证
- [ ] .exe 打包测试
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论