add 项目运行截图

This commit is contained in:
王鹏
2026-04-10 14:37:23 +08:00
parent a2f5875d1b
commit ef8237d8d1
15 changed files with 2943 additions and 1 deletions

View File

@@ -0,0 +1,463 @@
# 项目截图自动化功能实施计划
## 需求分析
根据用户提供的八爪鱼 RPA 流程图,需要实现以下功能:
### 项目结构
* **服务后端**: Java SpringBoot 项目
* **前台前端**: Vue3 前端项目(部分项目可能没有)
* **后台前端**: Vue3 管理后台项目
### 脚本文件
* `run_install_server.bat` - 后端 Maven 构建
* `run_install_front.bat` - 前台前端依赖安装
* `run_install_admin.bat` - 后台前端依赖安装
* `run_server.bat` - 启动服务后端
* `run_front.bat` - 启动前台前端
* `run_admin.bat` - 启动后台前端
* `run_sql.bat` - 导入 SQL 脚本SQL 脚本在后端项目文件夹下的 db 文件夹中)
### 执行流程(根据八爪鱼流程图)
```
主流程
├── 初始化变量
├── 打开网页
├── 导入 SQL
├── 更换图片
├── 后台网页截图
├── 启动后端服务
├── 启动前端服务
├── 前端依赖 install
├── 前台网页截图
├── 收尾工作
└── 准备工作
```
## 实施步骤
### 阶段 1: 创建核心模块
#### 1.1 创建 `project_screenshot.py` 模块
* 实现 `ProjectScreenshotAutomation`
* 功能包括:
* 项目路径配置管理
* 执行 bat 脚本(构建、安装依赖、启动服务)
* SQL 导入功能
* 使用 Playwright 进行浏览器控制和截图
* 菜单识别和遍历截图
#### 1.2 关键功能实现
**脚本执行功能:**
```python
def execute_bat(bat_path: str, cwd: str = None, timeout: int = 300) -> tuple[bool, str]:
"""执行 bat 脚本并返回结果"""
def wait_for_service(url: str, timeout: int = 120) -> bool:
"""等待服务启动完成"""
```
**SQL 导入功能:**
```python
def import_sql(sql_file: str, db_config: dict) -> bool:
"""导入 SQL 脚本到数据库"""
```
**更换前台轮播图片功能:**
```python
def get_random_images(bg_pic_folder: str, count: int = 3) -> list:
"""从指定文件夹随机获取指定数量的图片文件"""
def copy_images_to_target(source_images: list, target_folder: str) -> bool:
"""将源图片复制到目标文件夹并重命名为 swiperPicture1.jpg 等"""
def replace_swiper_images(project_path: str, desktop_path: str) -> str:
"""
更换前台轮播图片
1. 从桌面 bg_pic 文件夹随机选择 3 张图片
2. 复制到项目 server/src/main/resources/static/file 目录
3. 重命名为 swiperPicture1.jpg、swiperPicture2.jpg、swiperPicture3.jpg
"""
```
**更换后台登录背景图功能:**
```python
def update_login_vue_background(project_path: str) -> bool:
"""
修改 login.vue 文件中的背景图片链接
1. 从预设图片列表中随机选择一张
2. 替换 front/manage_code/src/views/login.vue 中的 background: url()
"""
# 预设后台登录背景图片列表
LOGIN_BACKGROUND_IMAGES = [
"http://img.yidaima.cn/7-dcd4fc1e8d0144aaa064bc282d1af289",
"http://img.yidaima.cn/6-3db67e2e6ff64e018d48282638900a67",
"http://img.yidaima.cn/5-74ee6fe2f0954a8db2574e8f7fbb8ef2",
"http://img.yidaima.cn/4-a926593ba8c444a3984100e99ae322bb",
"http://img.yidaima.cn/3-f91e9a0ed55d45c9aff80a08f554c625",
"http://img.yidaima.cn/2-2bd41cdd57b54b529dc3a139e66233e0",
"http://img.yidaima.cn/1-5a0b7ee2344a46bd987c1cafff19b3ba",
"http://img.yidaima.cn/8-495b9767a2f34197b963a13226d3e1d2",
"http://img.yidaima.cn/10-991483f941914f3d85bf87a4f8748fee",
"http://img.yidaima.cn/11-d274f1d695f7472a8fcd2c8c6238a79e",
"http://img.yidaima.cn/14-0e30653336d849379060ad8c436fe512",
"http://img.yidaima.cn/16-7a58235a2a4040f99e3431c3cac9ea7a",
"http://img.yidaima.cn/17-3406a546b81c412d9e3501cdb0f88b12",
"http://img.yidaima.cn/18-0a2fae1d6634480dabf5471fa86b3623",
"http://img.yidaima.cn/19-107196938e074c92a40b62c60aed18a4"
]
```
**截图功能:**
```python
def capture_screenshots(base_url: str, menu_selectors: list, output_dir: str) -> list:
"""根据菜单进行截图"""
```
### 阶段 2: 配置管理扩展
#### 2.1 更新 `config_loader.py`
添加项目截图自动化相关配置:
```yaml
project_screenshot:
# 项目路径配置
project_path: ""
has_front: true # 是否有前台前端
# 脚本路径配置(相对项目路径或绝对路径)
scripts:
install_server: "run_install_server.bat" # 后端 Maven 构建
install_front: "run_install_front.bat" # 前台前端依赖安装
install_admin: "run_install_admin.bat" # 后台前端依赖安装
run_server: "run_server.bat" # 启动服务后端
run_front: "run_front.bat" # 启动前台前端
run_admin: "run_admin.bat" # 启动后台前端
run_sql: "run_sql.bat" # 导入 SQL 脚本
# SQL 配置
sql:
script_path: "server/db/init.sql" # SQL 脚本路径(相对项目路径)
# 服务配置
services:
backend:
url: "http://localhost:8080"
health_endpoint: "/actuator/health"
startup_timeout: 120
front:
url: "http://localhost:8082" # 前台前端地址
login_url: "http://localhost:8082/#/login" # 前台登录页面
startup_timeout: 60
admin:
url: "http://localhost:8081" # 后台前端地址
login_url: "http://localhost:8081/#/login" # 后台登录页面
startup_timeout: 60
# 登录配置
login:
admin: # 后台登录配置
username: "admin"
password: "admin"
username_selector: "input[placeholder='请输入用户名'], input[name='username'], #username"
password_selector: "input[placeholder='请输入密码'], input[name='password'], #password, input[type='password']"
submit_selector: "button[type='submit'], .login-btn, .el-button--primary"
front: # 前台登录配置
username: "2"
password: "123456"
username_selector: "input[placeholder='请输入用户名'], input[name='username'], #username"
password_selector: "input[placeholder='请输入密码'], input[name='password'], #password, input[type='password']"
submit_selector: "button[type='submit'], .login-btn, .el-button--primary"
# 截图配置
screenshot:
output_dir: "./screenshots"
full_page: true
menu_selectors: # 菜单识别选择器
admin: ".el-menu-item, .el-sub-menu__title"
front: ".nav-item, .menu-item"
delay: 2000 # 截图前等待时间(ms)
# 轮播图片配置
swiper_images:
source_folder: "{desktop_path}/bg_pic" # 图片来源文件夹(桌面 bg_pic
target_subpath: "server/src/main/resources/static/file" # 目标路径(相对于项目路径)
target_names:
- "swiperPicture1.jpg"
- "swiperPicture2.jpg"
- "swiperPicture3.jpg"
count: 3 # 随机选择的图片数量
# 后台登录背景图配置
login_background:
vue_file_subpath: "front/manage_code/src/views/login.vue" # login.vue 相对项目路径
images: # 预设背景图片列表
- "http://img.yidaima.cn/7-dcd4fc1e8d0144aaa064bc282d1af289"
- "http://img.yidaima.cn/6-3db67e2e6ff64e018d48282638900a67"
- "http://img.yidaima.cn/5-74ee6fe2f0954a8db2574e8f7fbb8ef2"
- "http://img.yidaima.cn/4-a926593ba8c444a3984100e99ae322bb"
- "http://img.yidaima.cn/3-f91e9a0ed55d45c9aff80a08f554c625"
- "http://img.yidaima.cn/2-2bd41cdd57b54b529dc3a139e66233e0"
- "http://img.yidaima.cn/1-5a0b7ee2344a46bd987c1cafff19b3ba"
- "http://img.yidaima.cn/8-495b9767a2f34197b963a13226d3e1d2"
- "http://img.yidaima.cn/10-991483f941914f3d85bf87a4f8748fee"
- "http://img.yidaima.cn/11-d274f1d695f7472a8fcd2c8c6238a79e"
- "http://img.yidaima.cn/14-0e30653336d849379060ad8c436fe512"
- "http://img.yidaima.cn/16-7a58235a2a4040f99e3431c3cac9ea7a"
- "http://img.yidaima.cn/17-3406a546b81c412d9e3501cdb0f88b12"
- "http://img.yidaima.cn/18-0a2fae1d6634480dabf5471fa86b3623"
- "http://img.yidaima.cn/19-107196938e074c92a40b62c60aed18a4"
```
### 阶段 3: GUI 界面开发
#### 3.1 在 `gui.py` 中添加新的 Tab
创建 "项目截图" Tab包含以下区域
**配置区域:**
* 项目路径选择(文件夹浏览器)
* 是否有前台前端复选框
* 各脚本路径配置(可编辑,有默认值)
* SQL 脚本路径配置
**服务配置区域:**
* 后端服务 URL 和端口
* 前台前端 URL 和端口
* 后台前端 URL 和端口
**操作按钮区域:**
1. **构建/安装按钮:**
* "Maven 构建后端" - 执行 run\_install\_server.bat
* "安装前台依赖" - 执行 run\_install\_front.bat
* "安装后台依赖" - 执行 run\_install\_admin.bat
* "导入 SQL" - 执行 run\_sql.bat
2. **启动按钮:**
* "启动后端服务" - 执行 run\_server.bat
* "启动前台前端" - 执行 run\_front.bat
* "启动后台前端" - 执行 run\_admin.bat
3. **截图按钮:**
* "后台截图" - 遍历后台菜单截图
* "前台截图" - 遍历前台菜单截图(如果有)
* "一键完整流程" - 按顺序执行所有步骤
**日志输出区域:**
* 显示执行日志
* 进度条显示当前步骤
### 阶段 4: 流程实现
#### 4.1 完整流程执行顺序
根据八爪鱼流程图,实现以下执行顺序:
```
1. 初始化变量
- 加载项目配置
- 检查必要文件是否存在
2. 准备工作
- 检查项目路径
- 验证脚本文件存在
- 创建截图输出目录
3. 导入 SQL
- 执行 run_sql.bat
- 或使用 Python 直接执行 SQL 文件
4. 更换图片
- **更换前台轮播图片**
- 从桌面 bg_pic 文件夹随机选择 3 张图片
- 复制到项目 server/src/main/resources/static/file 目录
- 重命名为 swiperPicture1.jpg、swiperPicture2.jpg、swiperPicture3.jpg
- **更换后台登录背景图**
- 修改 front/manage_code/src/views/login.vue 文件
- 从预设图片列表中随机选择一张背景图
- 替换 CSS 中的 background: url() 属性
5. 前端依赖 install
- 执行 run_install_front.bat如果有前台
- 执行 run_install_admin.bat
6. 启动后端服务
- 执行 run_server.bat
- 等待服务健康检查通过
7. 启动前端服务
- 执行 run_front.bat如果有前台
- 执行 run_admin.bat
- 等待服务启动
8. 打开后台网页并登录
- 使用 Playwright 打开后台登录页面 http://localhost:8081/#/login
- 输入用户名 admin密码 admin
- 点击登录按钮
- 等待登录成功跳转
9. 后台网页截图
- 登录成功后访问 http://localhost:8081
- 识别菜单项
- 遍历每个菜单进行截图
10. 前台网页截图(如果有前台)
- 打开前台登录页面 http://localhost:8082/#/login
- 输入用户名 2密码 123456
- 点击登录按钮
- 登录成功后访问 http://localhost:8082
- 识别菜单项
- 遍历每个菜单进行截图
11. 收尾工作
- 关闭浏览器
- 停止服务(可选)
- 生成截图报告
```
### 阶段 5: 截图功能实现
#### 5.1 菜单识别和截图
```python
class ScreenshotCapture:
def __init__(self, browser, output_dir: str):
self.browser = browser
self.output_dir = output_dir
async def login(self, login_url: str, username: str, password: str,
username_selector: str, password_selector: str, submit_selector: str):
"""
执行登录操作
1. 打开登录页面
2. 输入用户名和密码
3. 点击登录按钮
4. 等待登录成功
"""
async def capture_menu_screenshots(self, base_url: str, menu_selector: str):
"""
1. 访问基础页面(已登录状态)
2. 识别所有菜单项
3. 点击每个菜单并截图
4. 保存到输出目录
"""
async def capture_full_page(self, url: str, filename: str):
"""截取整页截图"""
```
### 阶段 6: 测试和验证
#### 6.1 功能测试
* [ ] 单个 bat 脚本执行测试
* [ ] SQL 导入功能测试
* [ ] 服务启动和等待测试
* [ ] 菜单识别和截图测试
* [ ] 完整流程测试
#### 6.2 边界情况处理
* 项目没有前台前端的情况
* 服务启动超时处理
* 菜单识别失败处理
* 截图保存失败处理
## 文件结构
```
yidaima/
├── gui.py # 主 GUI添加项目截图 Tab
├── config_loader.py # 配置加载器(扩展)
├── project_screenshot.py # 项目截图自动化核心模块(新增)
├── core/
│ └── browser.py # 浏览器控制(扩展 Playwright
└── utils/
└── screenshot.py # 截图工具(扩展)
```
## 依赖项
需要添加的依赖:
```
playwright>=1.40.0
```
安装命令:
```bash
pip install playwright
playwright install chromium
```
## 实施顺序
1. **创建** **`project_screenshot.py`** - 核心自动化模块
2. **扩展** **`config_loader.py`** - 添加项目截图配置
3. **扩展** **`gui.py`** - 添加项目截图 Tab
4. **测试验证** - 完整流程测试
5. **打包发布** - PyInstaller 打包 exe
## 注意事项
1. **服务进程管理**: 启动的服务进程需要在适当的时候关闭
2. **端口占用检查**: 启动服务前检查端口是否被占用
3. **超时处理**: 每个步骤都需要有合理的超时时间
4. **错误恢复**: 某一步失败时能够优雅地停止并报告
5. **并发安全**: 避免同时执行多个自动化流程

Binary file not shown.

Binary file not shown.

3
bat/close_cmd.bat Normal file
View File

@@ -0,0 +1,3 @@
@echo off
taskkill /f /im cmd.exe /t

8
bat/run_admin.bat Normal file
View File

@@ -0,0 +1,8 @@
@echo off
chcp 65001 >nul
cd /d D:\code\front\manage_code
npm run serve
pause

8
bat/run_front.bat Normal file
View File

@@ -0,0 +1,8 @@
@echo off
chcp 65001 >nul
cd /d D:\code\front\client_code
npm run serve
pause

25
bat/run_install_admin.bat Normal file
View File

@@ -0,0 +1,25 @@
@echo off
:: 设置编码为 UTF-8
chcp 65001 >nul
:: 1. 增加 /d 参数切换盘符,并检查路径是否存在
cd /d D:\code\front\manage_code
if %errorlevel% neq 0 (
echo [错误] 无法找到目录: D:\code\front\manage_code
pause
exit /b
)
echo 当前目录: %cd%
:: 2. 判断 node_modules 是否存在
if not exist "node_modules\" (
echo 未检测到 node_modules 目录,开始安装依赖...
:: 使用 call 确保 npm 执行完后脚本继续运行
call npm install
) else (
echo 检测到 node_modules 目录,跳过依赖安装。
)
echo.
echo 执行完毕!

25
bat/run_install_front.bat Normal file
View File

@@ -0,0 +1,25 @@
@echo off
:: 设置编码为 UTF-8
chcp 65001 >nul
:: 1. 增加 /d 参数切换盘符,并检查路径是否存在
cd /d D:\code\front\client_code
if %errorlevel% neq 0 (
echo [错误] 无法找到目录: D:\code\front\client_code
pause
exit /b
)
echo 当前目录: %cd%
:: 2. 判断 node_modules 是否存在
if not exist "node_modules\" (
echo 未检测到 node_modules 目录,开始安装依赖...
:: 使用 call 确保 npm 执行完后脚本继续运行
call npm install
) else (
echo 检测到 node_modules 目录,跳过依赖安装。
)
echo.
echo 执行完毕!

View File

@@ -0,0 +1,47 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
:: 强制切换到脚本所在目录解决管理员模式启动默认在System32的问题
cd /d "%~dp0"
echo 正确盘符: %~d0
echo 正确路径: %cd%
:: 使用/d参数强制切换驱动器+目录
cd /d "D:\code\server" 2>nul
if %errorlevel% neq 0 (
echo [ERROR] Can not enter D:\code\server
echo 请检查:
echo 1. D盘是否存在
echo 2. code目录是否存在
echo 3. 是否有权限访问
exit /b
)
echo 扫描子目录中...
dir /ad /b
set "first_dir="
for /f "delims=" %%d in ('dir /ad /b 2^>nul') do (
set "first_dir=%%d"
goto :dir_found
)
:dir_found
if not defined first_dir (
echo [ERROR] No subdirectory found
exit /b
)
echo 进入子目录: !first_dir!
cd /d "!first_dir!"
echo 当前路径: %cd%
if not exist "target\" (
echo 执行Maven构建...
call mvn clean package
) else (
pause
echo 检测到 target 目录跳过Maven构建。
)

99
bat/run_server.bat Normal file
View File

@@ -0,0 +1,99 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
:: 强制切换到脚本所在目录解决管理员模式启动默认在System32的问题
cd /d "%~dp0"
echo 正确盘符: %~d0
echo 正确路径: %cd%
:: 使用/d参数强制切换驱动器+目录
cd /d "D:\code\server" 2>nul
if %errorlevel% neq 0 (
echo [ERROR] Can not enter D:\code\server
echo 请检查:
echo 1. D盘是否存在
echo 2. code目录是否存在
echo 3. 是否有权限访问
exit /b
)
echo 扫描子目录中...
dir /ad /b
set "first_dir="
for /f "delims=" %%d in ('dir /ad /b 2^>nul') do (
set "first_dir=%%d"
goto :dir_found
)
:dir_found
if not defined first_dir (
echo [ERROR] No subdirectory found
exit /b
)
echo 进入子目录: !first_dir!
cd /d "!first_dir!"
echo 当前路径: %cd%
if not exist "target\" (
echo 执行Maven构建...
call mvn clean package
) else (
echo 检测到 target 目录跳过Maven构建。
)
echo 进入target目录...
cd target
echo 目录内容:
dir /b
:: ========== 新增端口检查功能 ==========
echo 正在检查8080端口占用...
set "port_pid="
for /f "tokens=5" %%p in ('netstat -ano -p tcp ^| findstr ":8080" ^| findstr "LISTENING"') do (
set "port_pid=%%p"
echo 检测到端口占用PID: !port_pid!
taskkill /F /PID !port_pid! >nul 2>&1 && (
echo 成功终止进程: !port_pid!
) || (
echo [警告] 无法终止进程: !port_pid!
echo 可能需要管理员权限,请右键用管理员身份运行
)
)
if not defined port_pid (
echo 8080端口未被占用
)
:: ========== 端口检查结束 ==========
set "jar_file="
for /f "delims=" %%j in ('dir /b /a-d *.jar ^| findstr /v "original.jar"') do (
set "jar_file=%%j"
goto :jar_found
)
:jar_found
if not defined jar_file (
echo [ERROR] No valid jar found
echo 建议检查:
echo 1. Maven构建是否成功
echo 2. target目录内容
dir /b *.jar
exit /b
)
echo 启动Jar包: !jar_file!
java -jar "!jar_file!"
if %errorlevel% neq 0 (
echo [ERROR] Java启动失败 (code: %errorlevel%)
echo 可能原因:
echo 1. 缺少依赖
echo 2. 端口占用
echo 3. 配置错误
pause
)

34
bat/run_sql.bat Normal file
View File

@@ -0,0 +1,34 @@
@echo off
chcp 65001 >nul
cd /d D:\code\db
set "MYSQL_PATH=C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe"
for %%F in (*.sql) do (
echo 正在处理文件: %%F
"%MYSQL_PATH%" -u root -p123456 -e "CREATE DATABASE IF NOT EXISTS %%~nF;"
:: 第一次导入尝试
"%MYSQL_PATH%" -u root -p123456 %%~nF < %%F
:: 检查数据库是否存在
"%MYSQL_PATH%" -u root -p123456 -e "USE %%~nF;" 2>nul
if errorlevel 1 (
echo 数据库 %%~nF 第一次导入失败,尝试重新导入...
:: 第二次导入尝试
"%MYSQL_PATH%" -u root -p123456 -e "DROP DATABASE IF EXISTS %%~nF; CREATE DATABASE %%~nF;"
"%MYSQL_PATH%" -u root -p123456 %%~nF < %%F
:: 再次检查
"%MYSQL_PATH%" -u root -p123456 -e "USE %%~nF;" 2>nul
if errorlevel 1 (
echo 错误: 数据库 %%~nF 导入失败!
) else (
echo 数据库 %%~nF 重新导入成功
)
) else (
echo 数据库 %%~nF 导入完成
)
)

View File

@@ -51,6 +51,89 @@ class Config:
"database": "test",
"user": "root",
"password": "123456"
},
"project_screenshot": {
"project_path": "",
"desktop_path": "C:\\Users\\南音\\Desktop",
"has_front": True,
"bat_folder": "C:\\Users\\南音\\Desktop\\yidaima\\bat",
"show_cmd_window": True,
"code_source_folder": "code",
"code_target_path": "D:\\code",
"scripts": {
"install_server": "run_install_server.bat",
"install_front": "run_install_front.bat",
"install_admin": "run_install_admin.bat",
"run_server": "run_server.bat",
"run_front": "run_front.bat",
"run_admin": "run_admin.bat",
"run_sql": "run_sql.bat"
},
"sql": {
"script_path": "server/db/init.sql"
},
"services": {
"backend": {
"url": "http://localhost:8080",
"health_endpoint": "/actuator/health",
"startup_timeout": 120
},
"front": {
"url": "http://localhost:8082",
"login_url": "http://localhost:8082/#/login",
"startup_timeout": 60
},
"admin": {
"url": "http://localhost:8081",
"login_url": "http://localhost:8081/#/login",
"startup_timeout": 60
}
},
"login": {
"admin": {
"username": "admin",
"password": "admin"
},
"front": {
"username": "2",
"password": "123456"
}
},
"screenshot": {
"output_dir": "./screenshots",
"full_page": True,
"menu_selectors": {
"admin": ".el-menu-item, .el-sub-menu__title",
"front": ".nav-item, .menu-item"
},
"delay": 2000
},
"swiper_images": {
"source_folder": "bg_pic",
"target_subpath": "server/src/main/resources/static/file",
"target_names": ["swiperPicture1.jpg", "swiperPicture2.jpg", "swiperPicture3.jpg"],
"count": 3
},
"login_background": {
"vue_file_subpath": "front/manage_code/src/views/login.vue",
"images": [
"http://img.yidaima.cn/7-dcd4fc1e8d0144aaa064bc282d1af289",
"http://img.yidaima.cn/6-3db67e2e6ff64e018d48282638900a67",
"http://img.yidaima.cn/5-74ee6fe2f0954a8db2574e8f7fbb8ef2",
"http://img.yidaima.cn/4-a926593ba8c444a3984100e99ae322bb",
"http://img.yidaima.cn/3-f91e9a0ed55d45c9aff80a08f554c625",
"http://img.yidaima.cn/2-2bd41cdd57b54b529dc3a139e66233e0",
"http://img.yidaima.cn/1-5a0b7ee2344a46bd987c1cafff19b3ba",
"http://img.yidaima.cn/8-495b9767a2f34197b963a13226d3e1d2",
"http://img.yidaima.cn/10-991483f941914f3d85bf87a4f8748fee",
"http://img.yidaima.cn/11-d274f1d695f7472a8fcd2c8c6238a79e",
"http://img.yidaima.cn/14-0e30653336d849379060ad8c436fe512",
"http://img.yidaima.cn/16-7a58235a2a4040f99e3431c3cac9ea7a",
"http://img.yidaima.cn/17-3406a546b81c412d9e3501cdb0f88b12",
"http://img.yidaima.cn/18-0a2fae1d6634480dabf5471fa86b3623",
"http://img.yidaima.cn/19-107196938e074c92a40b62c60aed18a4"
]
}
}
}
@@ -148,6 +231,10 @@ class Config:
def database_config(self) -> dict:
return self.get("database", {})
@property
def project_screenshot_config(self) -> dict:
return self.get("project_screenshot", {})
# 全局配置实例
_config_instance = None

534
gui.py
View File

@@ -15,6 +15,7 @@ from step2 import Step2Converter
from editor_gui import open_editor
from markdown_editor import ThemeManager
from db_manager import get_db_manager, reset_db_manager, ProjectOrder
from project_screenshot import ProjectScreenshotAutomation, ProjectConfig
class YidaimaGUI:
@@ -86,7 +87,12 @@ class YidaimaGUI:
self.tab_manage = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.tab_manage, text="文章发布管理")
self._create_manage_tab()
# === Tab 3: 项目运行截图 ===
self.tab_screenshot = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.tab_screenshot, text="项目运行截图")
self._create_screenshot_tab()
# === Tab 4: 参数设置 ===
self.tab_settings = ttk.Frame(self.notebook, padding="10")
self.notebook.add(self.tab_settings, text="参数设置")
@@ -756,6 +762,532 @@ class YidaimaGUI:
self.setting_db_user_var.set(self.config.get("database.user", "root"))
self.setting_db_pass_var.set(self.config.get("database.password", "123456"))
# ==================== 项目运行截图 Tab 方法 ====================
def _create_screenshot_tab(self):
"""创建项目运行截图 Tab"""
# 配置网格
self.tab_screenshot.columnconfigure(0, weight=1)
self.tab_screenshot.rowconfigure(3, weight=1)
# 获取项目截图配置
ps_config = self.config.get("project_screenshot", {})
# === 项目路径配置区域 ===
path_frame = ttk.LabelFrame(self.tab_screenshot, text="项目路径配置", padding="10")
path_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
path_frame.columnconfigure(1, weight=1)
# 项目路径
ttk.Label(path_frame, text="项目路径:").grid(row=0, column=0, sticky=tk.W)
self.ps_project_path_var = tk.StringVar(value=ps_config.get("project_path", ""))
ps_project_path_entry = ttk.Entry(path_frame, textvariable=self.ps_project_path_var, width=60)
ps_project_path_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0))
ttk.Button(path_frame, text="浏览...", command=self._browse_project_path, width=10).grid(row=0, column=2, padx=(10, 0))
# 桌面路径
ttk.Label(path_frame, text="桌面路径:").grid(row=1, column=0, sticky=tk.W, pady=(10, 0))
self.ps_desktop_path_var = tk.StringVar(value=ps_config.get("desktop_path", r"C:\Users\南音\Desktop"))
ps_desktop_path_entry = ttk.Entry(path_frame, textvariable=self.ps_desktop_path_var, width=60)
ps_desktop_path_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
# bat 文件夹名称
ttk.Label(path_frame, text="bat文件夹:").grid(row=2, column=0, sticky=tk.W, pady=(10, 0))
self.ps_bat_folder_var = tk.StringVar(value=ps_config.get("bat_folder", r"C:\Users\南音\Desktop\yidaima\bat"))
ps_bat_folder_entry = ttk.Entry(path_frame, textvariable=self.ps_bat_folder_var, width=60)
ps_bat_folder_entry.grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
# 代码目标路径
ttk.Label(path_frame, text="代码目标路径:").grid(row=3, column=0, sticky=tk.W, pady=(10, 0))
self.ps_code_target_var = tk.StringVar(value=ps_config.get("code_target_path", r"D:\code"))
ps_code_target_entry = ttk.Entry(path_frame, textvariable=self.ps_code_target_var, width=60)
ps_code_target_entry.grid(row=3, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=(10, 0))
# 是否有前台前端
self.ps_has_front_var = tk.BooleanVar(value=ps_config.get("has_front", True))
ttk.Checkbutton(path_frame, text="有前台前端", variable=self.ps_has_front_var).grid(row=4, column=1, sticky=tk.W, padx=(10, 0), pady=(10, 0))
# 是否显示 CMD 窗口
self.ps_show_cmd_var = tk.BooleanVar(value=ps_config.get("show_cmd_window", True))
ttk.Checkbutton(path_frame, text="显示CMD窗口", variable=self.ps_show_cmd_var).grid(row=5, column=1, sticky=tk.W, padx=(10, 0), pady=(10, 0))
# === 操作按钮区域 ===
button_frame = ttk.LabelFrame(self.tab_screenshot, text="操作按钮", padding="10")
button_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
# 构建/安装按钮
install_frame = ttk.Frame(button_frame)
install_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Button(install_frame, text="整理代码", command=self._ps_organize_code, width=15).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(install_frame, text="Maven构建后端", command=self._ps_install_server, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(install_frame, text="安装前台依赖", command=self._ps_install_front, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(install_frame, text="安装后台依赖", command=self._ps_install_admin, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(install_frame, text="导入SQL", command=self._ps_run_sql, width=15).pack(side=tk.LEFT, padx=(5, 0))
# 启动按钮
start_frame = ttk.Frame(button_frame)
start_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Button(start_frame, text="启动后端服务", command=self._ps_start_server, width=15).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(start_frame, text="启动前台前端", command=self._ps_start_front, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(start_frame, text="启动后台前端", command=self._ps_start_admin, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(start_frame, text="更换图片", command=self._ps_replace_images, width=15).pack(side=tk.LEFT, padx=(5, 0))
# 截图按钮
screenshot_frame = ttk.Frame(button_frame)
screenshot_frame.pack(fill=tk.X)
ttk.Button(screenshot_frame, text="后台截图", command=self._ps_capture_admin, width=15).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(screenshot_frame, text="前台截图", command=self._ps_capture_front, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(screenshot_frame, text="收尾工作", command=self._ps_finalize_screenshots, width=15).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(screenshot_frame, text="一键完整流程", command=self._ps_run_full_flow, width=20).pack(side=tk.LEFT, padx=(5, 5))
ttk.Button(screenshot_frame, text="关闭所有CMD", command=self._ps_close_all_cmd, width=15).pack(side=tk.LEFT, padx=(5, 0))
# === 日志输出区域 ===
log_frame = ttk.LabelFrame(self.tab_screenshot, text="运行日志", padding="10")
log_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(10, 0))
log_frame.columnconfigure(0, weight=1)
log_frame.rowconfigure(0, weight=1)
self.ps_log_text = scrolledtext.ScrolledText(
log_frame,
wrap=tk.WORD,
width=80,
height=15,
font=("Consolas", 9)
)
self.ps_log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 进度条
self.ps_progress = ttk.Progressbar(
self.tab_screenshot,
mode='indeterminate',
length=400
)
self.ps_progress.grid(row=3, column=0, pady=(10, 0), sticky=(tk.W, tk.E))
# 状态标签
self.ps_status_var = tk.StringVar(value="就绪")
self.ps_status_label = ttk.Label(
self.tab_screenshot,
textvariable=self.ps_status_var,
foreground="#666"
)
self.ps_status_label.grid(row=4, column=0, pady=(10, 0))
def _browse_project_path(self):
"""浏览项目路径"""
from tkinter import filedialog
path = filedialog.askdirectory(title="选择项目文件夹")
if path:
self.ps_project_path_var.set(path)
def _ps_log(self, message: str):
"""添加项目截图日志"""
self.ps_log_text.insert(tk.END, f"{message}\n")
self.ps_log_text.see(tk.END)
self.root.update_idletasks()
def _get_ps_config(self) -> ProjectConfig:
"""获取项目截图配置"""
ps_config = self.config.get("project_screenshot", {})
return ProjectConfig(
project_path=self.ps_project_path_var.get(),
desktop_path=self.ps_desktop_path_var.get(),
has_front=self.ps_has_front_var.get(),
install_server=ps_config.get("scripts", {}).get("install_server", "run_install_server.bat"),
install_front=ps_config.get("scripts", {}).get("install_front", "run_install_front.bat"),
install_admin=ps_config.get("scripts", {}).get("install_admin", "run_install_admin.bat"),
run_server=ps_config.get("scripts", {}).get("run_server", "run_server.bat"),
run_front=ps_config.get("scripts", {}).get("run_front", "run_front.bat"),
run_admin=ps_config.get("scripts", {}).get("run_admin", "run_admin.bat"),
run_sql=ps_config.get("scripts", {}).get("run_sql", "run_sql.bat"),
bat_folder=self.ps_bat_folder_var.get(),
sql_script_path=ps_config.get("sql", {}).get("script_path", "server/db/init.sql"),
backend_url=ps_config.get("services", {}).get("backend", {}).get("url", "http://localhost:8080"),
backend_startup_timeout=ps_config.get("services", {}).get("backend", {}).get("startup_timeout", 120),
front_url=ps_config.get("services", {}).get("front", {}).get("url", "http://localhost:8082"),
front_login_url=ps_config.get("services", {}).get("front", {}).get("login_url", "http://localhost:8082/#/login"),
front_startup_timeout=ps_config.get("services", {}).get("front", {}).get("startup_timeout", 60),
admin_url=ps_config.get("services", {}).get("admin", {}).get("url", "http://localhost:8081"),
admin_login_url=ps_config.get("services", {}).get("admin", {}).get("login_url", "http://localhost:8081/#/login"),
admin_startup_timeout=ps_config.get("services", {}).get("admin", {}).get("startup_timeout", 60),
admin_username=ps_config.get("login", {}).get("admin", {}).get("username", "admin"),
admin_password=ps_config.get("login", {}).get("admin", {}).get("password", "admin"),
front_username=ps_config.get("login", {}).get("front", {}).get("username", "2"),
front_password=ps_config.get("login", {}).get("front", {}).get("password", "123456"),
screenshot_output_dir=ps_config.get("screenshot", {}).get("output_dir", "./screenshots"),
screenshot_delay=ps_config.get("screenshot", {}).get("delay", 2000),
admin_menu_selector=ps_config.get("screenshot", {}).get("menu_selectors", {}).get("admin", ".el-menu-item, .el-sub-menu__title"),
front_menu_selector=ps_config.get("screenshot", {}).get("menu_selectors", {}).get("front", ".nav-item, .menu-item"),
swiper_source_folder=ps_config.get("swiper_images", {}).get("source_folder", "bg_pic"),
swiper_target_subpath=ps_config.get("swiper_images", {}).get("target_subpath", "server/src/main/resources/static/file"),
swiper_target_names=ps_config.get("swiper_images", {}).get("target_names", ["swiperPicture1.jpg", "swiperPicture2.jpg", "swiperPicture3.jpg"]),
swiper_count=ps_config.get("swiper_images", {}).get("count", 3),
login_vue_subpath=ps_config.get("login_background", {}).get("vue_file_subpath", "front/manage_code/src/views/login.vue"),
login_background_images=ps_config.get("login_background", {}).get("images", []),
code_source_folder=ps_config.get("code_source_folder", "code"),
code_target_path=self.ps_code_target_var.get(),
show_cmd_window=self.ps_show_cmd_var.get()
)
def _ps_set_running(self, running: bool):
"""设置项目截图运行状态"""
if running:
self.ps_progress.start()
self.ps_status_var.set("运行中...")
else:
self.ps_progress.stop()
self.ps_status_var.set("就绪")
def _ps_install_server(self):
"""Maven 构建后端"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始 Maven 构建后端...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success, msg = automation.install_server()
if success:
self._ps_log("Maven 构建完成!")
else:
self._ps_log(f"构建失败: {msg}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_install_front(self):
"""安装前台前端依赖"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始安装前台前端依赖...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success, msg = automation.install_front()
if success:
self._ps_log("前台依赖安装完成!")
else:
self._ps_log(f"安装失败: {msg}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_install_admin(self):
"""安装后台前端依赖"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始安装后台前端依赖...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success, msg = automation.install_admin()
if success:
self._ps_log("后台依赖安装完成!")
else:
self._ps_log(f"安装失败: {msg}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_run_sql(self):
"""导入 SQL 脚本"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始导入 SQL...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success, msg = automation.run_sql_script()
if success:
self._ps_log("SQL 导入完成!")
else:
self._ps_log(f"导入失败: {msg}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_start_server(self):
"""启动后端服务"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("启动后端服务...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
automation.start_backend()
if automation.wait_for_service(config.backend_url, config.backend_startup_timeout):
self._ps_log("后端服务启动成功!")
else:
self._ps_log("后端服务启动超时,请手动检查")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_start_front(self):
"""启动前台前端"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("启动前台前端...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
automation.start_front()
if automation.wait_for_service(config.front_url, config.front_startup_timeout):
self._ps_log("前台前端启动成功!")
else:
self._ps_log("前台前端启动超时,请手动检查")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_organize_code(self):
"""整理项目代码"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始整理项目代码...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
sql_filename = automation.organize_project_code()
if sql_filename:
self._ps_log(f"代码整理完成SQL文件名: {sql_filename}")
else:
self._ps_log("代码整理完成但未获取到SQL文件名")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_start_admin(self):
"""启动后台前端"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("启动后台前端...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
automation.start_admin()
if automation.wait_for_service(config.admin_url, config.admin_startup_timeout):
self._ps_log("后台前端启动成功!")
else:
self._ps_log("后台前端启动超时,请手动检查")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_replace_images(self):
"""更换图片"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("更换图片...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
# 更换前台轮播图片
self._ps_log("更换前台轮播图片...")
result = automation.replace_swiper_images()
self._ps_log(result)
# 更换后台登录背景图
self._ps_log("更换后台登录背景图...")
if automation.update_login_vue_background():
self._ps_log("后台登录背景图更换成功!")
else:
self._ps_log("后台登录背景图更换失败")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_capture_admin(self):
"""后台截图"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始后台截图...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
import asyncio
# 截图保存到桌面路径下的"截图"文件夹
output_dir = os.path.join(config.desktop_path, "截图")
os.makedirs(output_dir, exist_ok=True)
screenshots = asyncio.run(automation.capture_admin_screenshots_only(output_dir))
self._ps_log(f"后台截图完成!共生成 {len(screenshots)} 张截图,保存到: {output_dir}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_capture_front(self):
"""前台截图"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始前台截图...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
import asyncio
# 截图保存到桌面路径下的"截图"文件夹
output_dir = os.path.join(config.desktop_path, "截图")
os.makedirs(output_dir, exist_ok=True)
screenshots = asyncio.run(automation.capture_front_screenshots_only(output_dir))
self._ps_log(f"前台截图完成!共生成 {len(screenshots)} 张截图,保存到: {output_dir}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_finalize_screenshots(self):
"""截图收尾工作"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始执行截图收尾工作...")
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
# 截图保存到桌面路径下的"截图"文件夹
output_dir = os.path.join(config.desktop_path, "截图")
automation.finalize_screenshots(output_dir)
self._ps_log(f"截图收尾工作完成!")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_run_full_flow(self):
"""一键完整流程"""
def run():
try:
self._ps_set_running(True)
config = self._get_ps_config()
automation = ProjectScreenshotAutomation(config, log_callback=self._ps_log)
success = automation.run_full_flow()
if success:
self.root.after(0, lambda: messagebox.showinfo("成功", "完整流程执行成功!"))
else:
self.root.after(0, lambda: messagebox.showerror("错误", "流程执行失败,请查看日志"))
except Exception as e:
self._ps_log(f"错误: {str(e)}")
self.root.after(0, lambda: messagebox.showerror("错误", f"执行失败: {str(e)}"))
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def _ps_close_all_cmd(self):
"""关闭所有CMD窗口"""
def run():
try:
self._ps_set_running(True)
self._ps_log("=" * 50)
self._ps_log("开始关闭所有CMD窗口...")
config = self._get_ps_config()
# 构建 close_cmd.bat 路径
close_cmd_bat = os.path.join(config.desktop_path, config.bat_folder, "close_cmd.bat")
if not os.path.exists(close_cmd_bat):
self._ps_log(f"错误close_cmd.bat 不存在: {close_cmd_bat}")
return
self._ps_log(f"执行: {close_cmd_bat}")
# 执行 bat 文件弹出CMD窗口
import subprocess
if config.show_cmd_window:
# 显示CMD窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = 1 # SW_SHOWNORMAL
subprocess.Popen(
['cmd', '/c', close_cmd_bat],
creationflags=subprocess.CREATE_NEW_CONSOLE,
startupinfo=startupinfo,
cwd=config.desktop_path
)
self._ps_log("CMD窗口已弹出执行关闭命令")
else:
# 不显示CMD窗口
result = subprocess.run(
['cmd', '/c', close_cmd_bat],
capture_output=True,
text=True,
encoding='utf-8',
errors='ignore'
)
if result.returncode == 0:
self._ps_log("CMD窗口关闭成功")
if result.stdout:
self._ps_log(result.stdout)
else:
self._ps_log(f"关闭失败: {result.stderr}")
except Exception as e:
self._ps_log(f"错误: {str(e)}")
finally:
self._ps_set_running(False)
threading.Thread(target=run, daemon=True).start()
def main():
"""启动 GUI"""

1611
project_screenshot.py Normal file

File diff suppressed because it is too large Load Diff