Merge pull request 'new file: Compare-PBI-Data.spec' (#1) from 打包程序 into main

Reviewed-on: https://git.chenwuzhu.cn/chenwu/Compare-PBI-Data/pulls/1
This commit is contained in:
chenwu 2025-03-08 16:54:01 +08:00
commit 1fdac9b8eb
35 changed files with 118277 additions and 157 deletions

39
Compare-PBI-Data.spec Normal file
View File

@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['app.py'],
pathex=[],
binaries=[],
datas=[('templates', 'templates'), ('MS.NET_Package', 'MS.NET_Package'), ('Export_Json', 'Export_Json'), ('Evaluate_Result', 'Evaluate_Result')],
hiddenimports=['pythonnet', 'clr', 'pandas', 'openpyxl', 'flask', 'signal', 'werkzeug.serving'],
hookspath=[],
hooksconfig={},
runtime_hooks=['runtime_hook.py'],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='Compare-PBI-Data',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['icon.jpg'],
)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

128
app.py
View File

@ -1,14 +1,18 @@
from flask import Flask, request, render_template, redirect, url_for
from flask import Flask, request, render_template, redirect, url_for, Response
import os
import threading
import config
from getDataFromAS import ASDataFetcher
from getQueries import getQueries_From_json
from compareData import Comparator
import time
import sys
import webbrowser
app = Flask(__name__)
result = ""
result_buffer = []
@app.route('/')
def index():
@ -22,10 +26,38 @@ def update_config():
setattr(config, key, value)
return redirect(url_for('index'))
@app.route('/stream')
def stream():
def generate():
global result_buffer
last_index = 0
while True:
if len(result_buffer) > last_index:
# 发送新的消息,一次只发送一条,确保前端有足够时间处理
import json
json_data = json.dumps({"message": result_buffer[last_index]})
yield f"data: {json_data}\n\n"
last_index += 1
time.sleep(0.2) # 每发送一条消息后短暂休眠,给前端处理时间
else:
time.sleep(0.1) # 如果没有新消息短暂休眠避免过度占用CPU
return Response(generate(), mimetype='text/event-stream')
@app.route('/exit', methods=['POST'])
def exit_app():
# 关闭应用程序的函数
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
return 'Server shutting down...'
@app.route('/run', methods=['POST'])
def run():
global result
global result, result_buffer
result = ""
result_buffer = []
# 拼接导出页面查询json文件夹路径
json_folder_path = os.path.join(config.CURRENT_DIR_PATH, "Export_Json")
@ -37,12 +69,29 @@ def run():
if not os.path.exists(result_folder_path):
os.makedirs(result_folder_path)
try:
message = "---*********************************************************---\n"
message += " 开始读取json\n"
message += "---*********************************************************---\n"
result += message
result_buffer.append(message)
# 拼接导出页面查询json文件路径
export_page_json_path_1 = os.path.join(json_folder_path, config.EXPORT_PAGE_JSON_NAME_1)
export_page_json_path_2 = os.path.join(json_folder_path, config.EXPORT_PAGE_JSON_NAME_2)
export_page_json_path_1 = os.path.join(json_folder_path, config.EXPORT_PAGE_JSON_NAME_1)
export_page_json_path_2 = os.path.join(json_folder_path, config.EXPORT_PAGE_JSON_NAME_2)
# 读取json文件中的查询
queries_1 = getQueries_From_json(export_page_json_path_1)
queries_2 = getQueries_From_json(export_page_json_path_2)
queries_1 = getQueries_From_json(export_page_json_path_1)
queries_2 = getQueries_From_json(export_page_json_path_2)
message = "\n---*********************************************************---\n"
message += " 成功读取json\n"
message += "---*********************************************************---\n"
result += message
result_buffer.append(message)
except Exception as e:
message = f"错误提示: {e}\n"
result += message
result_buffer.append(message)
return redirect(url_for('index'))
# 创建 ASDataFetcher 实例
fetcher_1 = ASDataFetcher(
@ -78,16 +127,20 @@ def run():
# 定义线程任务
def run_fetcher(fetcher, print_lock):
global result
global result, result_buffer
try:
fetcher.writeToExcel(print_lock)
with print_lock:
result += f"\n-------------------------\n{fetcher.json_name} 查询完成\n"
result += "\n".join(fetcher.log_messages) + "\n"
message = f"\n-------------------------\n{fetcher.json_name} 查询完成\n"
message += "\n".join(fetcher.log_messages) + "\n"
result += message
result_buffer.append(message)
except Exception as e:
with print_lock:
result += f"错误提示: {e}\n"
result += "\n".join(fetcher.log_messages) + "\n"
message = f"错误提示: {e}\n"
message += "\n".join(fetcher.log_messages) + "\n"
result += message
result_buffer.append(message)
return
# 创建锁
@ -98,9 +151,11 @@ def run():
thread_2 = threading.Thread(target=run_fetcher, args=(fetcher_2, print_lock))
try:
result += "---*********************************************************---\n"
result += " 开始查询\n"
result += "---*********************************************************---\n"
message = "---*********************************************************---\n"
message += " 开始查询\n"
message += "---*********************************************************---\n"
result += message
result_buffer.append(message)
# 启动线程
thread_1.start()
@ -110,29 +165,48 @@ def run():
thread_1.join()
thread_2.join()
result += "\n---*********************************************************---\n"
result += " 查询完成\n"
result += "---*********************************************************---\n"
message = "\n---*********************************************************---\n"
message += " 查询完成\n"
message += "---*********************************************************---\n"
result += message
result_buffer.append(message)
except Exception as e:
result += f"错误提示: {e}\n"
message = f"错误提示: {e}\n"
result += message
result_buffer.append(message)
return redirect(url_for('index'))
try:
result += "\n---------------------------------------------------------------\n"
result += " 开始比较\n"
result += "---------------------------------------------------------------\n"
message = "\n---------------------------------------------------------------\n"
message += " 开始比较\n"
message += "---------------------------------------------------------------\n"
result += message
result_buffer.append(message)
comparator = Comparator(fetcher_1.result_full_excel_name, fetcher_2.result_full_excel_name)
comparator.compare_ExcelFiles()
result += "\n".join(comparator.log_messages) + "\n"
result += "\n---------------------------------------------------------------\n"
result += " 比较完成\n"
result += "---------------------------------------------------------------\n"
message = "\n".join(comparator.log_messages) + "\n"
result += message
result_buffer.append(message)
message = "\n---------------------------------------------------------------\n"
message += " 比较完成\n"
message += "---------------------------------------------------------------\n"
result += message
result_buffer.append(message)
except Exception as e:
result += f"错误提示: {e}\n"
message = f"错误提示: {e}\n"
result += message
result_buffer.append(message)
return redirect(url_for('index'))
def open_browser():
"""在启动 Flask 服务器后自动打开浏览器。"""
webbrowser.open_new("http://127.0.0.1:5555/")
if __name__ == '__main__':
app.run(debug=True)
# 启动自动打开浏览器的线程
threading.Timer(1, open_browser).start()
app.run(debug=True, threaded=True,port=5555)

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

85
build_exe.py Normal file
View File

@ -0,0 +1,85 @@
import PyInstaller.__main__
import os
import shutil
# 获取当前脚本所在的目录路径
CURRENT_DIR_PATH = os.path.dirname(os.path.abspath(__file__))
# 定义需要包含的文件夹
folders_to_include = [
'Export_Json',
'Evaluate_Result',
'MS.NET_Package',
'templates'
]
# 确保文件夹存在
for folder in folders_to_include:
folder_path = os.path.join(CURRENT_DIR_PATH, folder)
if not os.path.exists(folder_path):
os.makedirs(folder_path)
# 定义PyInstaller参数
pyinstaller_args = [
'app.py', # 主脚本
'--name=Compare-PBI-Data', # 可执行文件名称
'--onefile', # 打包成单个可执行文件
'--windowed', # 不显示控制台窗口
'--icon=icon.jpg', # 使用图标
'--add-data=templates;templates', # 添加模板文件夹
'--add-data=MS.NET_Package;MS.NET_Package', # 添加.NET包
'--add-data=Export_Json;Export_Json', # 添加导出JSON文件夹
'--add-data=Evaluate_Result;Evaluate_Result', # 添加评估结果文件夹
'--hidden-import=pythonnet', # 添加隐藏导入
'--hidden-import=clr', # 添加隐藏导入
'--hidden-import=pandas', # 添加隐藏导入
'--hidden-import=openpyxl', # 添加隐藏导入
'--hidden-import=flask', # 添加隐藏导入
'--hidden-import=signal', # 添加信号处理模块
'--hidden-import=werkzeug.serving', # 添加werkzeug服务模块
'--clean', # 清理临时文件
'--runtime-hook=runtime_hook.py', # 添加运行时钩子
]
# 创建运行时钩子文件
with open('runtime_hook.py', 'w', encoding='utf-8') as f:
f.write("""
# 运行时钩子,用于确保应用程序可以正确关闭并创建必要的文件夹
import os
import signal
import sys
import config
# 确保正确处理SIGTERM信号
def handle_sigterm(*args):
sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)
# 获取应用程序所在的目录路径
app_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
# 重新设置config模块中的CURRENT_DIR_PATH变量确保使用正确的运行时路径
config.CURRENT_DIR_PATH = app_dir
# 定义需要确保存在的文件夹
folders_to_ensure = ['Export_Json', 'Evaluate_Result']
# 检查并创建必要的文件夹
for folder in folders_to_ensure:
folder_path = os.path.join(app_dir, folder)
if not os.path.exists(folder_path):
os.makedirs(folder_path)
print(f'已创建文件夹: {folder_path}')
""")
# 运行PyInstaller
PyInstaller.__main__.run(pyinstaller_args)
# 删除临时的运行时钩子文件
if os.path.exists('runtime_hook.py'):
os.remove('runtime_hook.py')
print("打包完成!可执行文件位于 dist 文件夹中。")
print("注意运行可执行文件后用户可以在Export_Json文件夹中放入文件程序会将结果输出到Evaluate_Result文件夹。")
print("程序可以通过界面上的'退出程序'按钮正常关闭。")

View File

@ -2,6 +2,7 @@ import os
# 获取当前文件所在的文件夹路径
CURRENT_DIR_PATH = os.path.dirname(os.path.abspath(__file__))
CURRENT_DIR_PATH_NET = os.path.dirname(os.path.abspath(__file__))
# MS.NET包的路径
NET_FOLDER = "MS.NET_Package"
@ -47,11 +48,11 @@ CUSTOMER_DATA_2 = "2"
# --------------------------------------------------------------
desc_WORKSPACE = "Analysis Services的连接信息如果是powerbi则连接字符串中包含catalog"
desc_USERNAME = "用户名"
desc_USERNAME = "用户名 (如果是powerbi则为邮箱如果是AS则为用户名或不填写)"
desc_PASSWORD = "密码"
desc_CATALOG = "如果是powerbicatalog为语义模型的名称默认为None"
desc_CATALOG = "如果是powerbi必填且catalog为语义模型的名称默认为None"
desc_EXPORT_PAGE_JSON_NAME = "导出页面查询的json文件的名称"
desc_IS_ADMIN = "是否是管理员方式运行如果是则为True否则为False"
desc_ROLE = "角色名称"
desc_ROLE = "角色名称。如果ADMIN为True则不填否则必填"
desc_EFFECTIVE_USERNAME = "username()获取字段"
desc_CUSTOMER_DATA = "customerdata()获取字段"

BIN
dist/Compare-PBI-Data.exe vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
dist/Export_Json/example.json vendored Normal file
View File

@ -0,0 +1 @@
{"version":"1.1.0","events":[{"name":"User Action","component":"Report Canvas","start":"2024-12-21T17:14:40.987Z","id":"515c279a49002a814996","metrics":{"sourceLabel":"UserAction_StartedMonitoring"}},{"name":"User Action","component":"Report Canvas","start":"2024-12-21T17:14:42.623Z","id":"79b174c0d02ca69ada00","metrics":{"sourceLabel":"UserAction_Refresh"}},{"name":"Visual Container Lifecycle","start":"2024-12-21T17:14:42.629Z","end":"2024-12-21T17:14:42.682Z","id":"24c30acf43d0ab30dd40-b004a31d5ccd6a4a5385","metrics":{"status":1,"visualTitle":"Table","visualId":"24c30acf43d0ab30dd40","visualType":"tableEx","initialLoad":false}},{"name":"Resolve Parameters","start":"2024-12-21T17:14:42.630Z","end":"2024-12-21T17:14:42.631Z","id":"865f086f-a553-4057-9963-f386c6ea310f","parentId":"24c30acf43d0ab30dd40-b004a31d5ccd6a4a5385"},{"name":"Query","start":"2024-12-21T17:14:42.631Z","end":"2024-12-21T17:14:42.662Z","id":"f374c5d4-aeae-49e4-a63f-a086480fbad1","parentId":"24c30acf43d0ab30dd40-b004a31d5ccd6a4a5385"},{"name":"Render","start":"2024-12-21T17:14:42.661Z","end":"2024-12-21T17:14:42.682Z","id":"bf797551-b336-43c1-8a29-eda4f83f224e","parentId":"24c30acf43d0ab30dd40-b004a31d5ccd6a4a5385"},{"name":"Data View Transform","start":"2024-12-21T17:14:42.670Z","end":"2024-12-21T17:14:42.673Z","id":"2b79f57f-9353-48e9-afe8-0c1f446f8636","parentId":"bf797551-b336-43c1-8a29-eda4f83f224e"},{"name":"Visual Update Async","start":"2024-12-21T17:14:42.673Z","end":"2024-12-21T17:14:42.682Z","id":"ca7f9db7-b9c5-4739-aced-6a8a5560f0a8","parentId":"bf797551-b336-43c1-8a29-eda4f83f224e"},{"name":"Visual Update","start":"2024-12-21T17:14:42.673Z","end":"2024-12-21T17:14:42.673Z","id":"29d7c7a6-ef0d-416a-973f-cf4ddacedc8a","parentId":"bf797551-b336-43c1-8a29-eda4f83f224e"},{"name":"Execute Semantic Query","component":"DSE","start":"2024-12-21T17:14:42.649Z","end":"2024-12-21T17:14:42.656Z","id":"47aeb947-80d7-4096-9f2d-3b16c8b5b5e9","parentId":"f374c5d4-aeae-49e4-a63f-a086480fbad1"},{"name":"Query Generation","start":"2024-12-21T17:14:42.633Z","end":"2024-12-21T17:14:42.634Z","id":"4fa46d04-aa1d-46ea-933b-49002a24c309","parentId":"f374c5d4-aeae-49e4-a63f-a086480fbad1"},{"name":"Query Pending","start":"2024-12-21T17:14:42.648Z","end":"2024-12-21T17:14:42.649Z","id":"178f1281-20eb-4c78-a3d1-bdc9f6735a7c","parentId":"f374c5d4-aeae-49e4-a63f-a086480fbad1"},{"name":"Parse Query Result","start":"2024-12-21T17:14:42.658Z","end":"2024-12-21T17:14:42.660Z","id":"33e79f0c-bc13-4951-a18e-6fdbd767e4f8","parentId":"f374c5d4-aeae-49e4-a63f-a086480fbad1"},{"name":"Execute DAX Query","component":"DSE","start":"2024-12-21T17:14:42.651Z","end":"2024-12-21T17:14:42.655Z","id":"e458b6a8-0b2b-474a-a8f1-294856878497","parentId":"47aeb947-80d7-4096-9f2d-3b16c8b5b5e9","metrics":{"QueryText":"DEFINE\r\n\tVAR __DS0Core = \r\n\t\tSUMMARIZECOLUMNS(\r\n\t\t\tROLLUPADDISSUBTOTAL('financials'[Country], \"IsGrandTotalRowTotal\"),\r\n\t\t\t\"Sumv_Sales\", CALCULATE(SUM('financials'[ Sales]))\r\n\t\t)\r\n\r\n\tVAR __DS0PrimaryWindowed = \r\n\t\tTOPN(502, __DS0Core, [IsGrandTotalRowTotal], 0, 'financials'[Country], 1)\r\n\r\nEVALUATE\r\n\t__DS0PrimaryWindowed\r\n\r\nORDER BY\r\n\t[IsGrandTotalRowTotal] DESC, 'financials'[Country]","RowCount":6}},{"name":"Execute Query","component":"AS","start":"2024-12-21T17:14:42.653Z","end":"2024-12-21T17:14:42.657Z","id":"3BC9FA84-D491-49ED-B2A1-7DC0D4F32A4C","parentId":"e458b6a8-0b2b-474a-a8f1-294856878497"},{"name":"Serialize Rowset","component":"AS","start":"2024-12-21T17:14:42.657Z","end":"2024-12-21T17:14:42.657Z","id":"57BE528D-04A5-4696-A0AF-3327CD0CE702","parentId":"3BC9FA84-D491-49ED-B2A1-7DC0D4F32A4C"}],"sessionId":"14d9da6f-0492-428d-bcff-ece9e2ac26f6"}

View File

@ -2,9 +2,9 @@ import re
import pandas as pd
import clr
import os
from config import CURRENT_DIR_PATH, NET_FOLDER
from config import CURRENT_DIR_PATH_NET, NET_FOLDER
folder = os.path.join(CURRENT_DIR_PATH, NET_FOLDER)
folder = os.path.join(CURRENT_DIR_PATH_NET, NET_FOLDER)
clr.AddReference(
folder
+ r"\Microsoft.AnalysisServices.AdomdClient\v4.0_15.0.0.0__89845dcd8080cc91\Microsoft.AnalysisServices.AdomdClient.DLL"

View File

@ -4,15 +4,19 @@ import pandas as pd
def getQueries_From_json(json_name):
# 打开并读取JSON文件
with open(json_name + ".json", "r", encoding="utf-8-sig") as file:
data = json.load(file)
# 从JSON数据中提取所有名为"Execute DAX Query"的事件中的QueryText
queries = [
event["metrics"]["QueryText"]
for event in data["events"]
if event["name"] == "Execute DAX Query"
]
return queries
try:
with open(json_name + ".json", "r", encoding="utf-8-sig") as file:
data = json.load(file)
# 从JSON数据中提取所有名为"Execute DAX Query"的事件中的QueryText
queries = [
event["metrics"]["QueryText"]
for event in data["events"]
if event["name"] == "Execute DAX Query"
]
return queries
except Exception as e:
print(f"读取JSON文件错误: {e},文件路径: {json_name}.json")
raise
def getQueries_From_excel(excel_name):

BIN
icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

112
run.py
View File

@ -1,112 +0,0 @@
import os
import threading
from compareData import Comparator
from getDataFromAS import ASDataFetcher
from getQueries import getQueries_From_json
import config
# 拼接导出页面查询json文件夹路径
json_folder_path = os.path.join(config.CURRENT_DIR_PATH, "Export_Json")
# 拼接查询结果excel文件夹路径
result_folder_path = os.path.join(config.CURRENT_DIR_PATH, "Evaluate_Result")
# 检查文件夹是否存在,如果不存在则创建它
if not os.path.exists(json_folder_path):
os.makedirs(json_folder_path)
if not os.path.exists(result_folder_path):
os.makedirs(result_folder_path)
# 拼接导出页面查询json文件路径
export_page_json_path_1 = os.path.join(json_folder_path, config.EXPORT_PAGE_JSON_NAME_1)
export_page_json_path_2 = os.path.join(json_folder_path, config.EXPORT_PAGE_JSON_NAME_2)
# 读取json文件中的查询
queries_1 = getQueries_From_json(export_page_json_path_1)
queries_2 = getQueries_From_json(export_page_json_path_2)
# 创建 ASDataFetcher 实例
fetcher_1 = ASDataFetcher(
workspace=config.WORKSPACE_1,
username=config.USERNAME_1,
password=config.PASSWORD_1,
queries=queries_1,
json_name=config.EXPORT_PAGE_JSON_NAME_1,
result_folder_path=result_folder_path,
isAdmin=config.IS_ADMIN_1,
catalog=config.CATALOG_1,
effective_username=config.EFFECTIVE_USERNAME_1,
customdata=config.CUSTOMER_DATA_1,
role=config.ROLE_1,
model_number=1,
)
# 创建 ASDataFetcher 实例
fetcher_2 = ASDataFetcher(
workspace=config.WORKSPACE_2,
username=config.USERNAME_2,
password=config.PASSWORD_2,
queries=queries_2,
json_name=config.EXPORT_PAGE_JSON_NAME_2,
result_folder_path=result_folder_path,
isAdmin=config.IS_ADMIN_2,
catalog=config.CATALOG_2,
effective_username=config.EFFECTIVE_USERNAME_2,
customdata=config.CUSTOMER_DATA_2,
role=config.ROLE_2,
model_number=2,
)
# 定义线程任务
def run_fetcher(fetcher, print_lock):
fetcher.writeToExcel(print_lock)
# 创建锁
print_lock = threading.Lock()
# 创建线程
thread_1 = threading.Thread(target=run_fetcher, args=(fetcher_1, print_lock))
thread_2 = threading.Thread(target=run_fetcher, args=(fetcher_2, print_lock))
try:
print(
f"""
---*********************************************************---
开始查询
"""
)
# 启动线程
thread_1.start()
thread_2.start()
# 等待线程完成
thread_1.join()
thread_2.join()
print(
f"""
查询完成
---*********************************************************---
"""
)
except Exception as e:
print(f"错误提示: {e}")
try:
print(
"""
---------------------------------------------------------------
开始比较
---------------------------------------------------------------
"""
)
comparator = Comparator(
fetcher_1.result_full_excel_name, fetcher_2.result_full_excel_name
)
comparator.compare_ExcelFiles()
print(
"""
----------------------------------------------------------------
比较完成
----------------------------------------------------------------
"""
)
except Exception as e:
print(f"错误提示: {e}")

View File

@ -3,9 +3,11 @@
<head>
<meta charset="UTF-8">
<title>数据对比</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="shortcut icon" href="https:/chenwuzhu.cn/upload/IMG_0495.JPG">
<style>
.container {
height: 100vh;
height: 98vh;
display: flex;
}
.left {
@ -16,10 +18,13 @@
padding: 10px;
box-sizing: border-box;
overflow-y: auto;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
margin-right: 10px;
}
.config {
flex: 1; /* 上半部分 */
display: inline-table;
display: unset;
flex-direction: row;
}
.config-content {
@ -40,6 +45,9 @@
padding: 10px;
box-sizing: border-box;
overflow-y: auto;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
position: relative;
}
.description {
color: gray;
@ -54,6 +62,10 @@
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 15px;
background-color: white;
padding: 2px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.config-keys {
display: grid;
@ -68,6 +80,55 @@
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
width: 98%;
}
input[type="text"] {
padding: 8px;
border: 1px solid #ddd;
border-radius: 6px;
width: 100%;
}
button {
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #45a049;
}
h1, h2 {
color: #333;
border-bottom: 2px solid #eee;
padding-bottom: 8px;
border-radius: 0 0 4px 4px;
}
.social-icons {
position: absolute;
top: 15px;
right: 15px;
display: flex;
gap: 15px;
}
.social-icons a {
color: #333;
font-size: 24px;
transition: color 0.3s;
}
.social-icons a:hover {
color: #4CAF50;
}
pre {
background-color: #fff;
padding: 10px;
border-radius: 8px;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 80vh;
overflow-y: auto;
}
</style>
</head>
@ -97,15 +158,110 @@
{% endfor %}
<div class="buttons">
<button type="submit">保存配置</button>
<button type="submit" formaction="{{ url_for('run') }}">运行</button>
<button type="button" id="runButton">运行</button>
<button type="button" id="exitButton" style="background-color: #f44336;">退出程序</button>
</div>
</form>
</div>
</div>
<div class="result">
<div class="social-icons">
<a href="https://chenwuzhu.cn" target="_blank" title="微信"><i class="fab fa-weixin"></i></a>
<a href="https://git.chenwuzhu.cn/chenwu/Compare-PBI-Data" target="_blank" title="Git"><i class="fab fa-github"></i></a>
</div>
<h1>运行结果</h1>
<pre>{{ result }}</pre>
<pre id="resultOutput">{{ result }}</pre>
</div>
</div>
<script>
document.getElementById('runButton').addEventListener('click', function() {
// 清空结果区域
document.getElementById('resultOutput').textContent = '';
// 获取表单数据
const form = document.querySelector('form');
const formData = new FormData(form);
// 创建EventSource连接
const eventSource = new EventSource('/stream');
// 监听消息事件
eventSource.onmessage = function(event) {
const resultOutput = document.getElementById('resultOutput');
// 解析JSON数据并显示消息内容
try {
const jsonData = JSON.parse(event.data);
if (jsonData.message) {
resultOutput.textContent += jsonData.message + '\n\n';
resultOutput.scrollTop = resultOutput.scrollHeight; // 自动滚动到底部
}
} catch (error) {
console.error('解析JSON数据失败:', error);
// 如果解析失败,则直接显示原始数据
resultOutput.textContent += event.data + '\n\n';
resultOutput.scrollTop = resultOutput.scrollHeight;
}
};
// 监听错误事件
eventSource.onerror = function() {
eventSource.close();
};
// 发送运行请求
fetch('{{ url_for("run") }}', {
method: 'POST',
body: formData
}).catch(function(error) {
console.error('Error:', error);
eventSource.close();
});
// 添加一个特殊的消息监听器,用于检测处理完成
const checkCompletion = function() {
try {
// 尝试解析JSON数据
const jsonData = JSON.parse(event.data);
// 检测到结束分隔符时关闭连接
if (jsonData.message && jsonData.message.includes('---*********************************************************---')) {
setTimeout(function() {
eventSource.close();
}, 500);
} else {
setTimeout(checkCompletion, 500);
}
} catch (error) {
// 如果解析失败,检查原始数据
if (event.data.includes('---*********************************************************---')) {
setTimeout(function() {
eventSource.close();
}, 500);
} else {
setTimeout(checkCompletion, 500);
}
}
};
setTimeout(checkCompletion, 1000);
});
// 添加退出按钮的事件监听器
document.getElementById('exitButton').addEventListener('click', function() {
if (confirm('确定要退出程序吗?')) {
fetch('{{ url_for("exit_app") }}', {
method: 'POST'
}).then(function(response) {
window.close();
// 如果window.close()不起作用(在某些浏览器中可能被阻止),显示一条消息
setTimeout(function() {
alert('程序已关闭,请手动关闭此窗口。');
}, 1000);
}).catch(function(error) {
console.error('Error:', error);
alert('关闭程序时出错,请手动关闭窗口。');
});
}
});
</script>
</body>
</html>