活动介绍

> result <- left_join(file1, file2, by = "ID") Error in `left_join()`: ! Input columns in `x` must be unique. ✖ Problem with `ID`. Run `rlang::last_trace()` to see where the error occurred. >

时间: 2024-01-26 21:03:19 浏览: 186
这个错误提示说明在进行 left_join 操作时,数据框中的某些列在名称上存在冲突。在这种情况下,需要检查两个数据框中是否存在相同名称的列,并对列名进行修改以消除名称冲突。 你可以按照以下步骤进行修改: 1. 读取第一个 CSV 文件,并将第一列的列名修改为 ID: ``` file1 <- read.csv("【1】output_summary.csv") colnames(file1)[1] <- "ID" ``` 2. 读取第二个 CSV 文件,并将其中与第一个 CSV 文件中列名相同的列重命名: ``` file2 <- read.csv("fullnamelineage.csv") colnames(file2)[1] <- "ID2" # 将第一列的列名修改为 ID2 ``` 3. 进行 left_join 操作并将结果保存为名为 blastx 的 CSV 文件: ``` library(dplyr) # 将 file2 的数据根据 ID 列与 file1 进行左连接 result <- left_join(file1, file2, by = c("ID" = "ID2")) # 将结果保存为名为 blastx 的 CSV 文件 write.csv(result, "blastx.csv", row.names = FALSE) ``` 在这个示例中,我们首先读取了第一个 CSV 文件,然后使用 `colnames()` 函数修改了第一列的列名为 ID。然后,我们读取了第二个 CSV 文件,并使用 `colnames()` 函数将其中与第一个 CSV 文件中列名相同的列重命名为 ID2。最后,我们使用 `left_join()` 函数将两个数据框根据 ID 列进行左连接操作后得到了结果,并使用 `write.csv()` 函数将结果保存为名为 blastx.csv 的文件。
阅读全文

相关推荐

look54.php: <?php session_start(); require "../db54.php"; if(!isset($_SESSION["username"])) { header("Location: ../login54.php"); exit; } $user = $_SESSION["username"]; ?> <!DOCTYPE html> <html> <head> <title>我的购物车</title> <style> table { width: 100%; border-collapse: collapse; } th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; } img { max-width: 100px; } </style> <script> function openme(row) { row.style.backgroundColor = "#FFFFCC"; } function closeme(row) { row.style.backgroundColor = (row.rowIndex % 2 == 1 ? "#F0F0F0" : ""); } function op(button, id) { // Implement quantity increase/decrease logic } function select_all() { // Implement select all checkboxes } function reset_all() { // Implement reset all checkboxes } function delete_all() { // Implement delete selected items } function na() { return confirm("确定要移除该商品吗?"); } function op(t,id) { value=t.value; //按钮上显示的文字 if(value=='+') { //带参数跳转,兼容Google.ie parent.top.window.location="look54.php?op=add&id="+id; } if(value=='-') { parent.top.window.location="look54.php?op=sub&id="+id; } } </script> </head> <body> 我的购物车 <?php // 显示当前用户信息 - 统一使用username if(isset($_SESSION['username'])) { echo "查看购物车 | 当前用户:" . htmlspecialchars($user) . ""; ?> <form name="cartForm" method="post"> 选择 商品名称 商品图片 订阅数量 单价 操作 <?php // 处理"拿掉该商品"超链接删除 if(isset($_GET['id']) && $_GET["op"]=="移除") { $id = $_GET['id']; $sql = "DELETE FROM dingdan54 WHERE id = $id"; if($db54->write($sql)) { echo "<script>alert('拿掉商品成功!')</script>"; } else { echo "<script>alert('拿掉商品失败!')</script>"; echo "<script>location.href('look54.php')</script>"; } } // 处理增减按钮+-操作 if(isset($_GET["op"])) { $op = $_GET["op"]; $id = $_GET['id']; // 获取当前数量 $sql = "SELECT * FROM dingdan54 WHERE id = $id"; $result = $db54->read($sql); if(count($result) > 0) { $current_num = $result[0]['num']; if($op == "add") { $new_num = $current_num + 1; } elseif($op == "sub" && $current_num > 1) { $new_num = $current_num - 1; } else { $new_num = $current_num; } // 更新数量 $update_sql = "UPDATE dingdan54 SET num = $new_num WHERE id = $id"; $db54->write($update_sql); } } // 处理"拿掉选中商品"按钮 if(isset($_GET["str"])) { $ids = explode("|", trim($_GET["str"], "|")); foreach($ids as $id) { if(is_numeric($id)) { $sql = "DELETE FROM dingdan54 WHERE id = $id"; $db54->write($sql); } } echo "<script>alert('拿掉商品成功!')</script>"; echo "<script>location.href('look54.php')</script>"; } // 显示购物车 $cart_sql = "SELECT dingdan54.id AS id, sp54.name AS name, sp54.photo AS photo, SUM(dingdan54.num) AS total_num, sp54.money AS money FROM sp54 JOIN dingdan54 ON sp54.id = dingdan54.sp_id WHERE sp54.id = dingdan54.sp_id AND dingdan54.user = '$user' AND dingdan54.flag = 0 GROUP BY sp54.id, sp54.name, sp54.photo, sp54.money"; $cart_result = $db54->read($cart_sql); $n = count($cart_result); if ($n == 0) { echo "购物车是空的!"; }else{ for ($i = 0; $i < $n; $i++) { $item = $cart_result[$i]; echo ""; echo "<input type='checkbox' name='flag' value='{$item['id']}'>"; echo "{$item['name']}"; echo ""; echo "<input type='button' name='enter' value='-' onclick='op(this, {$item['id']})'> <input type='text' name='num{$item['id']}' readonly size='3' style='text-align:center' value='{$item['total_num']}'> <input type='button' name='enter' value='+' onclick='op(this, {$item['id']})'>"; echo "{$item['money']}元"; echo " 移除该商品"; echo ""; } } ?> <input type="button" name="enter" value="全选" onclick="select_all()"> <input type="button" name="enter" value="全部取消" onclick="reset_all()"> <input type="button" name="enter" value="删除选中商品" onclick="delete_all()"> <?php $total_sql = "SELECT sp54.money AS money, dingdan54.num AS num FROM sp54, dingdan54 WHERE sp54.id = dingdan54.sp_id AND dingdan54.user = '$user' AND dingdan54.flag = 0"; $total_result = $db54->read($total_sql); $sum = 0; foreach ($total_result as $item) { $sum += $item["money"] * $item["num"]; } echo "应付金额:¥"; echo $sum . " 元"; echo " | 去付款"; ?> </form> 继续购物 <?php } ?> </body> </html> l54.php: <?php include('../db54.php'); session_start(); $user_id=$_SESSION["id"]?? 0; $sql="select * from user54 where id='$user_id'"; $result=$db54->read($sql); $username=$result[0]["username"]; ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>信安28班54何雨霏PHP实训</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="robots" content="all,follow"> </head> <body> person Nathan AndrewsWeb Developer BD Main 何雨霏的留言板 实训页面1 实训页面2 实训页面3 Example dropdown admin 查看购物车 后台管理 Login page Demo 6 New Second menu admin 查看购物车 后台管理
<?php ?> 信安28班54何雨霏PHP实训 <?php echo $username; ?> 12 You have 6 new messages 4 minutes ago You have 2 followers 4 minutes ago Server Rebooted 4 minutes ago You have 2 followers 10 minutes ago view all notifications 10 ... Jason Doesent you a direct message3 days ago at 7:58 pm - 10.06.2019 ... Frank Williamssent you a direct message3 days ago at 7:58 pm - 10.06.2019 ... Ashley Woodsent you a direct message3 days ago at 7:58 pm - 10.06.2019 Read all messages EnglishEnglish EnglishGerman EnglishFrench Logout
<?php require "daohang54.php"; ?> <?php require "look54.php"; ?>
Copyright © 信安28班54何雨霏《Web应用与安全管理实训》 All rights reserved.
<script src="https://www.jq22.com/jquery/jquery-1.10.2.js"></script> <script src="vendor/popper.js/umd/popper.min.js"> </script> <script src="https://www.jq22.com/jquery/bootstrap-4.2.1.js"></script> <script src="js/grasp_mobile_progress_circle-1.0.0.min.js"></script> <script src="vendor/jquery.cookie/jquery.cookie.js"> </script> <script src="vendor/chart.js/Chart.min.js"></script> <script src="vendor/jquery-validation/jquery.validate.min.js"></script> <script src="vendor/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js"></script> <script src="js/charts-home.js"></script> <script src="js/front.js"></script> </body> </html> 页面能显示look54的内容,不能显示l54的框架

<template> <view class="detail-container"> <view class="index-card"> <u-form :model="formData" ref="indexForm" class="mobile-form"> <u-form-item label="问卷标题:" prop="dcWjTitle" label-width="150rpx"> <u-input v-model="formData.dcWjTitle" placeholder="请输入问卷标题" clearable border="bottom" prefixIcon="file-text" /> </u-form-item> <u-form-item label="被测评人:" prop="dcId" label-width="150rpx"> <u-select v-model="formData.dcId" :list="bdrOptions" multiple value-name="dcId" label-name="dcName" @confirm="handleBdrSelect" placeholder="请选择被测评人" ></u-select> </u-form-item> <u-form-item label="人员部门:" prop="dcDept" label-width="150rpx"> <u-input v-model="formData.dcDept" placeholder="请输入人员部门" clearable border="bottom" prefixIcon="home" /> </u-form-item> <u-form-item label="人员状态:" prop="state" label-width="150rpx"> <u-select v-model="formData.state" :list="stateOptions" value-name="value" label-name="label" placeholder="请选择提交状态" ></u-select> </u-form-item> <view class="button-group"> <u-button type="primary" @click="handleSearch" class="action-button" icon="search" > 搜索 </u-button> <u-button @click="handleReset" class="action-button" icon="reload" > 重置 </u-button> </view> </u-form> </view> <view class="data-card"> <view class="card-header"> <u-button type="primary" size="small" icon="reload" @click="fetchData" > 刷新数据 </u-button> </view> <view class="card-container"> <u-loading-page :loading="loading" loadingText="加载中..." /> <view v-for="(item, index) in tableData" :key="item.dcWjId" class="data-card-item" > <view class="card-header-section"> <view class="card-title">{{ item.dcWjTitle }}</view> </view> <view class="card-body-section"> <view class="card-row"> <text class="card-label">被测评人:</text> <text class="card-value">{{ item.dcName }}</text> </view> <view class="card-row"> <text class="card-label">部门:</text> <text class="card-value">{{ item.dcDept }}</text> </view> <view class="card-row"> <text class="card-label">创建时间:</text> <text class="card-value">{{ item.createTime }}</text> </view> <view class="card-row"> <text class="card-label">提交时间:</text> <text class="card-value">{{ item.updateTime || '-' }}</text> </view> </view> <view class="card-footer-section"> <view class="status-container"> <u-tag :text="item.state === '1' ? '已提交' : '未提交'" :type="item.state === '1' ? 'success' : 'info'" /> <view class="score">总分: {{ item.score || '0' }}</view> </view> <u-button size="small" type="primary" @click="handleView(item)" class="action-btn" > 编辑/查看 </u-button> </view> </view> <u-empty v-if="tableData.length === 0" mode="data" /> </view> <view class="pagination-container"> <u-pagination v-model="pagination.current" :itemsPerPage="pagination.size" :total="pagination.total" :showTotal="true" @change="handlePageChange" /> </view> </view> </view> </template> <script> import { ref, reactive, onMounted } from 'vue'; import { onLoad } from '@dcloudio/uni-app'; export default { setup() { // 环境变量管理API地址 - 使用uni-app兼容方式 const API_BASE = 'http://172.26.26.43/dev-api'; // 替换为实际API地址 const API_URL = ${API_BASE}/wjdc/wj/listTx; const BDR_API_URL = ${API_BASE}/wjdc/wj/getBdrList; // 状态选项 const stateOptions = ref([ { label: '已提交', value: 1 }, { label: '未提交', value: 0 } ]); // 被测评人相关数据 const bdrOptions = ref([]); // 被测评人选项列表 const bdrLoading = ref(false); // 加载状态 const bdrCache = ref([]); // 缓存所有被测评人数据 // 表单数据 const formData = reactive({ dcWjTitle: '', dcId: [], dcDept: '', state: null }); // 表格数据 const tableData = ref([]); const loading = ref(false); // 分页配置 const pagination = reactive({ current: 1, size: 10, total: 0 }); // 表单引用 const indexForm = ref(null); // 处理被测评人选择 const handleBdrSelect = (selected) => { formData.dcId = selected.map(item => item.value); }; // 获取被测评人列表 const fetchBdrList = async () => { const token = getAuthToken(); if (!token) return; bdrLoading.value = true; try { const [err, res] = await uni.request({ url: BDR_API_URL, method: 'GET', header: { 'Authorization': Bearer ${token} } }); if (err) { throw new Error(err.errMsg || '请求失败'); } const data = res.data; if (data && data.code === 200) { bdrCache.value = data.data || []; bdrOptions.value = bdrCache.value.map(item => ({ value: item.dcId, label: item.dcName })); } else { const msg = data?.msg || '返回数据格式不正确'; uni.showToast({ title: '获取被测评人列表失败: ' + msg, icon: 'none' }); } } catch (error) { console.error('获取被测评人列表失败:', error); uni.showToast({ title: '获取被测评人列表失败: ' + error.message, icon: 'none' }); } finally { bdrLoading.value = false; } }; const token = getAuthToken(); if (!token) return; bdrLoading.value = true; try { const response = await uni.request({ url: BDR_API_URL, method: 'GET', params: { pageNum: pagination.current, pageSize: pagination.size, ...formData, dcId: formData.dcId.join(',') }, header: { 'Authorization': Bearer ${token} } }); const data = response[1].data; if (data && data.code === 200) { bdrCache.value = data.data || []; bdrOptions.value = bdrCache.value.map(item => ({ value: item.dcId, label: item.dcName })); } else { const msg = data?.msg || '返回数据格式不正确'; uni.showToast({ title: '获取被测评人列表失败: ' + msg, icon: 'none' }); } } catch (error) { console.error('获取被测评人列表失败:', error); uni.showToast({ title: '获取被测评人列表失败', icon: 'none' }); } finally { bdrLoading.value = false; } }; // 获取认证令牌 const getAuthToken = () => { const token = uni.getStorageSync('token'); if (!token) { uni.showToast({ title: '请先登录', icon: 'none' }); uni.navigateTo({ url: '/pages/login/login' }); return null; } return token; }; // 搜索按钮处理函数 const handleSearch = () => { // 检查被测评人选择数量 if (formData.dcId.length > 1) { uni.showToast({ title: '当前只能搜索一个被测人员', icon: 'none', duration: 3000 }); return; } pagination.current = 1; fetchData(); }; // 重置按钮处理函数 const handleReset = () => { formData.dcWjTitle = ''; formData.dcId = []; formData.dcDept = ''; formData.state = null; handleSearch(); }; // 编辑/查看 const handleView = (row) => { uni.navigateTo({ url: /pages/operation/operation?id=${row.dcWjId} }); }; // 页码改变 const handlePageChange = (page) => { pagination.current = page; fetchData(); }; // 获取数据 const fetchData = async () => { const token = getAuthToken(); if (!token) return; loading.value = true; try { const params = { pageNum: pagination.current, pageSize: pagination.size, ...formData, dcId: formData.dcId.join(',') }; const [err, res] = await uni.request({ url: API_URL, method: 'GET', data: params, header: { 'Content-Type': 'application/json', 'Authorization': Bearer ${token} } }); if (err) { throw new Error(err.errMsg || '请求失败'); } const data = res.data; if (data && data.code === 200) { tableData.value = data.rows || []; pagination.total = data.total || 0; if (tableData.value.length === 0) { uni.showToast({ title: '没有找到匹配的数据', icon: 'none' }); } } else { const errorMsg = data?.msg || '未知错误'; console.error('API返回错误:', errorMsg); uni.showToast({ title: 请求失败: ${errorMsg}, icon: 'none' }); tableData.value = []; pagination.total = 0; } } catch (error) { // ... 错误处理 ... } finally { loading.value = false; } }; const token = getAuthToken(); if (!token) return; loading.value = true; try { const params = { pageNum: pagination.current, pageSize: pagination.size, ...formData, dcId: formData.dcId.join(',') }; const response = await uni.request({ url: API_URL, method: 'GET', data: params, header: { 'Content-Type': 'application/json', 'Authorization': Bearer ${token} } }); const data = response[1].data; if (data && data.code === 200) { tableData.value = data.rows || []; pagination.total = data.total || 0; if (tableData.value.length === 0) { uni.showToast({ title: '没有找到匹配的数据', icon: 'none' }); } } else { const errorMsg = data?.msg || '未知错误'; console.error('API返回错误:', errorMsg); uni.showToast({ title: 请求失败: ${errorMsg}, icon: 'none' }); tableData.value = []; pagination.total = 0; } } catch (error) { // 安全地访问错误属性 const statusCode = error.statusCode || error.errMsg?.match(/status code (\d+)/)?.[1]; if (statusCode === '401') { uni.showToast({ title: '认证过期,请重新登录', icon: 'none' }); uni.removeStorageSync('token'); uni.navigateTo({ url: '/pages/login/login' }); return; } console.error('获取数据失败:', error); // 安全地获取错误信息 let errorMsg = '网络请求失败'; if (error.errMsg) { errorMsg = error.errMsg; } else if (error.message) { errorMsg = error.message; } else if (typeof error === 'string') { errorMsg = error; } uni.showToast({ title: 请求失败: ${errorMsg}, icon: 'none' }); tableData.value = []; pagination.total = 0; } finally { loading.value = false; } }; onMounted(() => { fetchBdrList(); fetchData(); }); return { formData, bdrOptions, stateOptions, tableData, loading, pagination, indexForm, handleBdrSelect, handleSearch, handleReset, handleView, handlePageChange, fetchData }; } }; </script> <style> .detail-container { padding: 20rpx; background-color: #f8f8f8; min-height: 100vh; } .index-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.05); } .mobile-form .u-form-item { margin-bottom: 32rpx; } .mobile-form .u-input, .mobile-form .u-select { background: #f8f8f8; border-radius: 12rpx; padding: 20rpx; margin-top: 12rpx; } .button-group { display: flex; justify-content: space-between; margin-top: 24rpx; } .button-group .action-button { flex: 1; margin: 0 12rpx; height: 80rpx; border-radius: 12rpx; font-size: 32rpx; } .data-card { background: #fff; border-radius: 16rpx; padding: 24rpx; box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.05); } .data-card .card-header { margin-bottom: 24rpx; display: flex; justify-content: flex-end; } .card-container .data-card-item { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; border: 1rpx solid #eee; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); } .card-container .card-header-section { padding-bottom: 20rpx; border-bottom: 1rpx solid #f0f2f5; margin-bottom: 20rpx; } .card-container .card-header-section .card-title { font-size: 34rpx; font-weight: bold; color: #333; } .card-container .card-body-section { margin-bottom: 20rpx; } .card-container .card-row { display: flex; margin-bottom: 16rpx; font-size: 30rpx; line-height: 1.6; } .card-container .card-label { color: #666; min-width: 150rpx; } .card-container .card-value { color: #333; flex: 1; } .card-container .card-footer-section { display: flex; justify-content: space-between; align-items: center; padding-top: 20rpx; border-top: 1rpx solid #f0f2f5; } .card-container .status-container { display: flex; align-items: center; } .card-container .status-container .score { margin-left: 24rpx; font-size: 30rpx; color: #e6a23c; font-weight: 500; } .card-container .action-btn { min-width: 180rpx; } .pagination-container { margin-top: 40rpx; display: flex; justify-content: center; } /* 响应式调整 - 移动端 */ @media (max-width: 768px) { .button-group { flex-direction: column; } .button-group .action-button { margin: 12rpx 0; width: 100%; } .card-container .card-footer-section { flex-direction: column; align-items: flex-start; } .card-container .status-container { margin-bottom: 20rpx; } .card-container .action-btn { width: 100%; } } </style> 报错:18:15:29.275 [plugin:vite:vue] [vue/compiler-sfc] Unexpected reserved word 'await'. (257:25) 18:15:29.275 D:/民意测评/vue3-uni-mycp/pages/detail/detail.vue 18:15:29.275 255| 18:15:29.275 256| try { 18:15:29.275 257| const response = await uni.request({ 18:15:29.275 | ^ 18:15:29.275 258| url: BDR_API_URL, 18:15:29.275 259| method: 'GET', 18:15:29.275 at pages/detail/detail.vue:257:25

修改问题,子类模块界面(1.输入分析)里就是没有用户指定的标签及其它,而其它模块界面里,只有模块界面(3.跟随分析)里出现重复的四个按钮,其它的都是正确的。问题2:Exception in Tkinter callback Traceback (most recent call last): File "F:\python\Lib\tkinter\__init__.py", line 1967, in __call__ return self.func(*args) ^^^^^^^^^^^^^^^^ File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 983, in <lambda> command=lambda m=module: self._on_module_button_click(m) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 1097, in _on_module_button_click self._create_input_analysis_content(content_frame) File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 1285, in _create_input_analysis_content exclude_frame = LabelFrame(main_frame, text=" 排除号码 ", font=('微软雅黑', 12, 'bold')) ^^^^^^^^^^ NameError: name 'LabelFrame' is not defined 检查原代码class MainInterface: def __init__(self, root: Tk, pool: 'NumberPool'): self.root = root self.pool = pool self.left_panel = None self.center_paned = None # 修正变量名 self.right_panel = None self.core_vars = {} self.pool_vars = {} self.status_var = StringVar() self.dynamic_text = None self.current_module = None self._setup_ui() self._setup_event_handlers() self.module_instances = {} self.exclude_front_entries = [] self.exclude_back_entries = [] self.front_dan_entries = [] self.back_dan_entries = [] # 初始化结果文本控件 self.result_text = None # 初始化排除号码变量 self.exclude_front_var = StringVar() self.exclude_back_var = StringVar() self.recommend_front_var = StringVar() self.recommend_back_var = StringVar() # 初始化模块内容框架 self.dynamic_content = None self.module_content_frame = None # 模块标签定义 self.labels = { 'input_analysis': [ # 修正为小写 "排除号码:", "前区:", "后区:", "推荐号码:", "前区:", "后区:", ], 'combination_analysis': [ "前数字频:", "前数字缺:", "后数字频:", "后数字缺:", "前频繁推:", "后低频推:", "生组合数:", "未组合码:" ], 'follow_analysis': [ "前推荐多:", "前推荐少:", "后推荐多:", "后推荐少:" ], 'trend_analysis': [ "和值:", "质合比:", "奇偶比:", "断区推荐:", "连号推荐:", "冷热推荐:", "后区热号:", "后区冷号:", "趋势号:" ], 'number_generation': [ # 修正为小写 "胆码:", "前区:", "后区:", "推荐5注号码:", "1:", "", "2:", "", "3:", "", "4:", "", "5:", "" ], } # 初始化所有模块的条目引用 self.front_dan_entry = None self.back_dan_entry = None self.result_text = None self.exclude_front_entry = None self.exclude_back_entry = None self.front_entry = None self.back_entry = None def _setup_event_handlers(self): """初始化事件处理器""" event_center.subscribe(EventType.MODULE_COMPLETE, self._handle_module_complete) event_center.subscribe(EventType.UI_UPDATE, self._handle_ui_update) event_center.subscribe(EventType.EXCLUDE_NUMBERS, self._handle_exclude_numbers) def _setup_ui(self): self.root.title(f"大乐透智能分析平台 - {GlobalConfig.VERSION}") self.root.geometry("1400x800") # 添加主标题 title_frame = Frame(self.root) title_frame.pack(fill='x', pady=5) Label(title_frame, text="大乐透智能分析平台", font=('微软雅黑', 16, 'bold')).pack(expand=True) # 主容器 - 三栏布局 main_container = PanedWindow(self.root, orient=HORIZONTAL, sashrelief=RAISED, sashwidth=5) main_container.pack(fill='both', expand=True, padx=5, pady=(0, 5)) # 左侧面板 self.left_panel = Frame(main_container, width=200, bg="#eaeaea") main_container.add(self.left_panel, minsize=150, stretch="never") # 中间内容区 self.center_paned = PanedWindow(main_container, orient=VERTICAL, sashrelief=RAISED, sashwidth=5) main_container.add(self.center_paned, minsize=500, stretch="always") # 右侧面板 self.right_panel = Frame(main_container, width=700, bg="#f5f5f5") main_container.add(self.right_panel, minsize=250, stretch="never") # 初始化各区域 self._setup_left_panel() self._setup_center_area() self._setup_right_panel() def _setup_left_panel(self): """初始化左侧模块按钮区""" module_names = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } for module in GlobalConfig.MODULES: Button( self.left_panel, text=module_names[module], width=18, command=lambda m=module: self._on_module_button_click(m) ).pack(pady=3, padx=5, ipady=3) def _setup_center_area(self): """设置中间区域布局,分为上下两部分""" # 上半部分 - 核心区 (固定高度) self.core_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.core_frame, minsize=150, stretch="never") # 核心区内容 Label(self.core_frame, text="核心区", font=('微软雅黑', 12, 'bold')).pack(anchor='w', padx=5, pady=2) # 核心数据展示 self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } for label, var_name in [ ("前区:", 'front_area'), ("后区:", 'back_area'), ("前区热号:", 'front_hot'), ("前区冷号:", 'front_cold'), ("后区热号:", 'back_hot'), ("后区冷号:", 'back_cold') ]: frame = Frame(self.core_frame) frame.pack(fill='x', padx=5, pady=2) Label(frame, text=label, width=10, anchor='w').pack(side='left') entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) entry = Entry(entry_container, textvariable=self.core_vars[var_name], font=('微软雅黑', 10), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) # 下半部分 - 动态区 self.dynamic_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.dynamic_frame, minsize=200, stretch="always") # 主容器使用Grid布局 self.dynamic_container = Frame(self.dynamic_frame) self.dynamic_container.pack(fill='both', expand=True) self.dynamic_container.grid_rowconfigure(0, weight=1) self.dynamic_container.grid_columnconfigure(0, weight=1) # 操作按钮放在右下角 self.btn_frame = Frame(self.dynamic_container) self.btn_frame.grid(row=1, column=0, sticky='se', pady=5, padx=5) Button(self.btn_frame, text="运行", width=8, command=self._run_current_module).pack(side='left', padx=2) Button(self.btn_frame, text="清除", width=8, command=self._clear_dynamic_content).pack(side='left', padx=2) Button(self.btn_frame, text="保存", width=8, command=self._save_dynamic_content).pack(side='left', padx=2) Button(self.btn_frame, text="刷新", width=8, command=self._refresh_dynamic).pack(side='left', padx=2) # 模块内容容器 - 确保先创建 self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') # 初始化默认内容 self._init_default_dynamic_content() def _init_default_dynamic_content(self): """初始化默认动态区内容""" # 清除现有内容 for widget in self.module_content_frame.winfo_children(): widget.destroy() # 创建新内容 self.dynamic_content = Frame(self.module_content_frame) self.dynamic_content.pack(fill='both', expand=True) Label(self.dynamic_content, text="请从左侧选择分析模块", font=('微软雅黑', 12)).pack(expand=True, pady=50) def _on_module_button_click(self, module: str): """模块按钮点击事件处理""" self.status_var.set(f"打开 {module} 模块...") self.current_module = module if not hasattr(self, 'module_content_frame') or self.module_content_frame is None: # 紧急初始化内容框架 self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') logging.error("紧急初始化 module_content_frame") # 同时初始化默认内容 self._init_default_dynamic_content() # 清除现有内容 try: for widget in self.module_content_frame.winfo_children(): widget.destroy() except AttributeError as e: logging.critical(f"清除内容失败: {str(e)}") # 尝试恢复UI self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') self._init_default_dynamic_content() # 清除之前的动态内容 for widget in self.module_content_frame.winfo_children(): widget.destroy() # 创建新的内容容器 - 使用pack布局填充整个空间 content_frame = Frame(self.module_content_frame) content_frame.pack(fill='both', expand=True) # 根据模块类型创建具体内容 if module == "input_analysis": self._create_input_analysis_content(content_frame) elif module == "combination_analysis": self._create_combination_analysis_content(content_frame) elif module == "follow_analysis": self._create_follow_analysis_content(content_frame) elif module == "trend_analysis": self._create_trend_analysis_content(content_frame) elif module == "number_generation": self._create_number_generation_content(content_frame) # 底部按钮区 - 使用pack放在底部 btn_frame = Frame(self.module_content_frame) btn_frame.pack(side='bottom', fill='x', pady=5) # 按钮容器靠右 btn_container = Frame(btn_frame) btn_container.pack(side='right') Button(btn_container, text="运行", width=8, command=lambda: self._run_module(module)).pack(side='left', padx=2) Button(btn_container, text="清除", width=8, command=lambda: self._clear_module_data(module)).pack(side='left', padx=2) Button(btn_container, text="保存", width=8, command=lambda: self._save_module_data(module)).pack(side='left', padx=2) Button(btn_container, text="刷新", width=8, command=lambda: self._on_module_button_click(module)).pack(side='left', padx=2) # 强制刷新界面 self.root.update_idletasks() def _run_current_module(self): """运行当前模块""" if self.current_module: self._run_module(self.current_module) def _clear_dynamic_content(self): """清除动态区内容""" if self.current_module: self._clear_module_data(self.current_module) # 额外确保清除结果文本框(如果存在) if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') def _save_dynamic_content(self): """保存动态区内容""" if self.current_module: self._save_module_data(self.current_module) else: messagebox.showinfo("提示", "请先选择并运行一个模块") def _refresh_dynamic(self): """刷新动态区内容""" if self.current_module: self._on_module_button_click(self.current_module) else: messagebox.showinfo("提示", "请先选择一个模块") def _organize_data(self): """整理号码池数据""" try: # 发布整理事件 event = Event( event_id=int(time.time()), type=EventType.ORGANIZE_DATA, source='main_ui', target='pool' ) event_center.publish(event) self.status_var.set("号码池数据已整理") except Exception as e: messagebox.showerror("整理失败", str(e)) logging.error(f"整理数据失败: {str(e)}", exc_info=True) def _freeze_data(self): """冻结号码池数据""" try: # 发布冻结事件 event = Event( event_id=int(time.time()), type=EventType.FREEZE_DATA, source='main_ui', target='pool' ) event_center.publish(event) self.status_var.set("号码池数据已冻结") except Exception as e: messagebox.showerror("冻结失败", str(e)) logging.error(f"冻结数据失败: {str(e)}", exc_info=True) def _run_module(self, module: str): """运行指定模块""" if module == "input_analysis": # 获取排除号码 exclude_front = self.exclude_front_entry.get() exclude_back = self.exclude_back_entry.get() # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _create_ui_element(self, parent, label_text): """创建统一的UI元素(与核心区对齐)""" frame = Frame(parent) frame.pack(fill='x', pady=2) # 标签固定宽度与核心区对齐 Label(frame, text=label_text, width=10, anchor='w').pack(side='left') # 条目容器 - 宽度与核心区对齐 entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) return entry_container def _setup_right_panel(self): """设置右侧号码池布局""" # 号码池标题 pool_title_frame = Frame(self.right_panel) pool_title_frame.pack(fill='x', pady=5) Label(pool_title_frame, text="号码池", font=('微软雅黑', 12, 'bold')).pack(anchor='w') # 号码池内容区(添加边框和2px内边距) pool_content = Frame(self.right_panel, bd=1, relief='solid', padx=2, pady=2) pool_content.pack(fill='both', expand=True, padx=5, pady=5) # 创建Canvas和Scrollbar canvas = Canvas(pool_content, highlightthickness=0) scrollbar = Scrollbar(pool_content, orient="vertical", command=canvas.yview) scrollable_frame = Frame(canvas) scrollable_frame.bind( "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) # 号码池项目 - 优化布局和样式(带2px右边距) for label, var_name, row_id in GlobalConfig.UI_CONFIG: frame = Frame(scrollable_frame) frame.grid(row=row_id, column=0, sticky='ew', padx=0, pady=1) # 移除水平padding # 左侧标签(固定宽度8字符) lbl = Label(frame, text=label, width=8, anchor='w') lbl.pack(side='left', padx=(0, 5)) # 标签右侧留5px间距 # 右侧输入框容器(带2px右边距) entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True, padx=(0, 2)) var = StringVar() self.pool_vars[var_name] = var entry = Entry(entry_container, textvariable=var, font=('微软雅黑', 9), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 底部按钮区 btn_frame = Frame(self.right_panel) btn_frame.pack(fill='x', pady=5) Button(btn_frame, text="整理", width=10, command=self._organize_data).pack(side='left', padx=5) Button(btn_frame, text="冻结", width=10, command=self._freeze_data).pack(side='left', padx=5) Button(btn_frame, text="导出", width=10).pack(side='left', padx=5) def _create_input_analysis_content(self, parent: Frame): """创建输入分析模块内容 - 修复版""" # 主容器使用Grid布局 main_frame = Frame(parent) main_frame.pack(fill='both', expand=True) # === 排除号码区 === exclude_frame = LabelFrame(main_frame, text=" 排除号码 ", font=('微软雅黑', 12, 'bold')) exclude_frame.pack(fill='x', padx=20, pady=10, ipady=5) # 使用预定义的标签文本 labels = self.labels['input_analysis'] # 排除号码标签 Label(exclude_frame, text=labels[0], font=('微软雅黑', 10, 'bold')).pack(anchor='w', padx=10, pady=(5, 0)) # 前区排除 Label(exclude_frame, text=labels[1], font=('微软雅黑', 10)).pack(anchor='w', padx=10, pady=5) front_entries_frame = Frame(exclude_frame) front_entries_frame.pack(fill='x', padx=10, pady=5) self.exclude_front_entries = [] for i in range(10): # 10个前区输入框 entry_frame = Frame(front_entries_frame) entry_frame.pack(side='left', padx=2) Label(entry_frame, text=f"{i + 1}:").pack(side='left') entry = Entry(entry_frame, width=3, font=('微软雅黑', 10)) entry.pack(side='left') # 绑定事件处理 entry.bind("<KeyRelease>", self._auto_format_entry) entry.bind("<Left>", lambda e, d=-1: self._navigate_entry(e, d)) entry.bind("<Right>", lambda e, d=1: self._navigate_entry(e, d)) self.exclude_front_entries.append(entry) # 后区排除 Label(exclude_frame, text=labels[2], font=('微软雅黑', 10)).pack(anchor='w', padx=10, pady=(10, 5)) back_entries_frame = Frame(exclude_frame) back_entries_frame.pack(fill='x', padx=10, pady=5) self.exclude_back_entries = [] for i in range(10): # 10个后区输入框 entry_frame = Frame(back_entries_frame) entry_frame.pack(side='left', padx=2) Label(entry_frame, text=f"{i + 1}:").pack(side='left') entry = Entry(entry_frame, width=3, font=('微软雅黑', 10)) entry.pack(side='left') # 绑定事件处理 entry.bind("<KeyRelease>", self._auto_format_entry) entry.bind("<Left>", lambda e, d=-1: self._navigate_entry(e, d)) entry.bind("<Right>", lambda e, d=1: self._navigate_entry(e, d)) self.exclude_back_entries.append(entry) # === 推荐号码区 === recommend_frame = LabelFrame(main_frame, text=" 推荐号码 ", font=('微软雅黑', 12, 'bold')) recommend_frame.pack(fill='x', padx=20, pady=10, ipady=5) # 推荐号码标签 Label(recommend_frame, text=labels[3], font=('微软雅黑', 10, 'bold')).pack(anchor='w', padx=10, pady=(5, 0)) # 前区推荐 front_rec_frame = Frame(recommend_frame) front_rec_frame.pack(fill='x', padx=10, pady=5) Label(front_rec_frame, text=labels[4], font=('微软雅黑', 10)).pack(side='left') self.recommend_front_var = StringVar() Entry(front_rec_frame, textvariable=self.recommend_front_var, state='readonly', font=('微软雅黑', 10), readonlybackground='#f0f5f0').pack(side='left', fill='x', expand=True, padx=5) # 后区推荐 back_rec_frame = Frame(recommend_frame) back_rec_frame.pack(fill='x', padx=10, pady=5) Label(back_rec_frame, text=labels[5], font=('微软雅黑', 10)).pack(side='left') self.recommend_back_var = StringVar() Entry(back_rec_frame, textvariable=self.recommend_back_var, state='readonly', font=('微软雅黑', 10), readonlybackground='#f0f5f0').pack(side='left', fill='x', expand=True, padx=5) # === 结果区 === result_frame = LabelFrame(main_frame, text=" 分析结果 ", font=('微软雅黑', 12, 'bold')) result_frame.pack(fill='both', expand=True, padx=20, pady=10, ipady=5) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word', font=('微软雅黑', 10)) self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) # 添加初始提示 self.result_text.insert('end', "请设置排除号码后点击'运行'按钮开始分析\n") # 强制刷新界面 self.root.update_idletasks() def _handle_entry_input(self, event): """处理输入框相关事件的总入口""" if event.keysym in ('Left', 'Right'): # 处理方向键导航 self._navigate_entry(event, 1 if event.keysym == 'Right' else -1) else: # 处理输入自动格式化 self._auto_format_entry(event) def _auto_format_entry(self, event): """ 自动格式化输入框内容 功能: 1. 自动将1-9的数字补零显示为01-09 2. 输入满2位后自动跳到下一个输入框 """ entry = event.widget current = entry.get().strip() if current.isdigit(): # 只处理数字输入 # 自动补零处理 formatted = self._format_number(current) if formatted != current: entry.delete(0, 'end') entry.insert(0, formatted) # 输入满2位后自动跳转 if len(current) == 2: self._focus_adjacent_entry(event.widget, 1) # 正向跳转 def _focus_adjacent_entry(self, current_entry, direction): """ 焦点跳转到相邻输入框 :param current_entry: 当前输入框控件 :param direction: 跳转方向 (1:下一个, -1:上一个) """ all_entries = self.exclude_front_entries + self.exclude_back_entries try: current_index = all_entries.index(current_entry) target_index = current_index + direction if 0 <= target_index < len(all_entries): all_entries[target_index].focus() except ValueError: pass def _navigate_entry(self, event, direction): """使用方向键在输入框间导航""" self._focus_adjacent_entry(event.widget, direction) def _format_number(self, num_str: str) -> str: """ 格式化数字为两位数 :param num_str: 输入的数字字符串 :return: 格式化后的两位数字符串 """ if not num_str.isdigit(): return num_str # 非数字不处理 num = int(num_str) if 1 <= num <= 9: # 1-9的数字补零 return f"0{num}" return str(num) if num > 0 else num_str # 保留0和负数原样 def _create_combination_analysis_content(self, parent: Frame): """创建组合分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 使用预定义的labels for label in self.labels['combination_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) # 保存对控件的引用 var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前区热号": self.front_hot_entry = entry elif var_name == "前数字频": self.front_freq_entry = entry elif var_name == "前频繁推": self.front_freq_rec_entry = entry elif var_name == "后区热号": self.back_hot_entry = entry elif var_name == "后数字频": self.back_freq_entry = entry elif var_name == "后低频推": self.back_infreq_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_follow_analysis_content(self, parent: Frame): """创建跟随分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['follow_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前推荐多": self.front_more_entry = entry elif var_name == "前推荐少": self.front_less_entry = entry elif var_name == "后推荐多": self.back_more_entry = entry elif var_name == "后推荐少": self.back_less_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_trend_analysis_content(self, parent: Frame): """创建趋势分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['trend_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "和值": self.sum_value_entry = entry elif var_name == "质合比": self.prime_ratio_entry = entry elif var_name == "奇偶比": self.odd_even_ratio_entry = entry elif var_name == "断区推荐": self.zone_rec_entry = entry elif var_name == "连号推荐": self.consec_rec_entry = entry elif var_name == "冷热推荐": self.hot_cold_rec_entry = entry elif var_name == "后区热号": self.hot_rec_entry = entry elif var_name == "后区冷号": self.cold_rec_entry = entry elif var_name == "趋势号": self.trend_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_number_generation_content(self, parent: Frame): """创建数字生成模块的动态内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 胆码输入区 dan_frame = Frame(content_frame) dan_frame.pack(fill='x', pady=5) # 前区胆码 front_dan_frame = Frame(dan_frame) front_dan_frame.pack(fill='x') Label(front_dan_frame, text="前区胆码:").pack(side='left') self.front_dan_entries = [] for i in range(5): entry = Entry(front_dan_frame, width=3) entry.pack(side='left', padx=2) self.front_dan_entries.append(entry) self.front_dan_entry = self.front_dan_entries[0] # 保存第一个条目引用 # 后区胆码 back_dan_frame = Frame(dan_frame) back_dan_frame.pack(fill='x') Label(back_dan_frame, text="后区胆码:").pack(side='left') self.back_dan_entries = [] for i in range(5): entry = Entry(back_dan_frame, width=3) entry.pack(side='left', padx=2) self.back_dan_entries.append(entry) self.back_dan_entry = self.back_dan_entries[0] # 保存第一个条目引用 # 生成的号码显示区 generated_frame = Frame(content_frame) generated_frame.pack(fill='x', pady=5) Label(generated_frame, text="生成号码:").pack(anchor='w') self.generated_labels = [] for i in range(1, 6): frame = Frame(generated_frame) frame.pack(fill='x') Label(frame, text=f"{i}. ").pack(side='left') label = Label(frame, text="", width=30, anchor='w') label.pack(side='left') self.generated_labels.append(label) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _run_module(self, module: str): """运行指定模块""" if module == "input_analysis": # 获取并格式化排除号码 exclude_front_list = [] for entry in self.exclude_front_entries: num = entry.get() if num: # 只处理非空输入 formatted = self._format_number(num) exclude_front_list.append(formatted) exclude_back_list = [] for entry in self.exclude_back_entries: num = entry.get() if num: # 只处理非空输入 formatted = self._format_number(num) exclude_back_list.append(formatted) exclude_front = ' '.join(exclude_front_list) exclude_back = ' '.join(exclude_back_list) # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _generate_recommend_numbers(self, exclude_front: str, exclude_back: str): """生成推荐号码(示例逻辑)""" # 实际应用中应调用分析模块生成推荐号码 # 这里简化为生成随机推荐号码 import random # 前区号码范围1-35 all_front = [str(idx) for idx in range(1, 36)] exclude_front_list = exclude_front.split() if exclude_front else [] available_front = [num for num in all_front if num not in exclude_front_list] # 后区号码范围1-12 all_back = [str(idx) for idx in range(1, 13)] exclude_back_list = exclude_back.split() if exclude_back else [] available_back = [num for num in all_back if num not in exclude_back_list] # 随机选择5个前区号码 if len(available_front) >= 5: recommend_front = random.sample(available_front, 5) else: recommend_front = random.sample(all_front, 5) # 随机选择2个后区号码 if len(available_back) >= 2: recommend_back = random.sample(available_back, 2) else: recommend_back = random.sample(all_back, 2) # 更新推荐号码显示 self.recommend_front_var.set(' '.join(sorted(recommend_front, key=int))) self.recommend_back_var.set(' '.join(sorted(recommend_back, key=int))) # 在结果文本中记录 self.result_text.insert('end', f"生成推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 更新号码池 self._update_pool_with_recommendations(self.recommend_front_var.get(), self.recommend_back_var.get()) def _update_pool_with_recommendations(self, front: str, back: str): """用推荐号码更新号码池""" # 发布事件更新号码池 update_event = Event( event_id=int(time.time()), type=EventType.POOL_UPDATE, source='input_analysis', target='pool', data={ 'front_numbers': front, 'back_numbers': back } ) event_center.publish(update_event) # 在结果文本中记录 self.result_text.insert('end', "号码池已更新\n") def _clear_module_data(self, module: str): """清除模块数据""" if module == "input_analysis": if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') if hasattr(self, 'recommend_front_var'): self.recommend_front_var.set('') if hasattr(self, 'recommend_back_var'): self.recommend_back_var.set('') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') elif module == "combination_analysis": if hasattr(self, 'front_hot_entry') and self.front_hot_entry: self.front_hot_entry.delete(0, 'end') if hasattr(self, 'front_freq_entry') and self.front_freq_entry: self.front_freq_entry.delete(0, 'end') if hasattr(self, 'front_freq_rec_entry') and self.front_freq_rec_entry: self.front_freq_rec_entry.delete(0, 'end') if hasattr(self, 'back_hot_entry') and self.back_hot_entry: self.back_hot_entry.delete(0, 'end') if hasattr(self, 'back_freq_entry') and self.back_freq_entry: self.back_freq_entry.delete(0, 'end') if hasattr(self, 'back_infreq_rec_entry') and self.back_infreq_rec_entry: self.back_infreq_rec_entry.delete(0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') def _save_module_data(self, module: str): """保存模块数据""" try: data = {} if module == "input_analysis": data['front'] = self.front_entry.get() data['back'] = self.back_entry.get() data['exclude_front'] = self.exclude_front_entry.get() data['exclude_back'] = self.exclude_back_entry.get() data['recommend_front'] = self.recommend_front_var.get() data['recommend_back'] = self.recommend_back_var.get() data['result'] = self.result_text.get(1.0, 'end') elif module == "combination_analysis": data['front_hot'] = self.front_hot_entry.get() data['front_freq'] = self.front_freq_entry.get() data['front_freq_rec'] = self.front_freq_rec_entry.get() data['back_hot'] = self.back_hot_entry.get() data['back_freq'] = self.back_freq_entry.get() data['back_infreq_rec'] = self.back_infreq_rec_entry.get() data['result'] = self.result_text.get(1.0, 'end') # 其他模块数据收集... filename = f"{module}_data.json" with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) messagebox.showinfo("保存成功", f"数据已保存到{filename}") except Exception as e: messagebox.showerror("保存失败", str(e)) logging.error(f"保存数据失败: {str(e)}", exc_info=True) def _handle_exclude_numbers(self, event: Event): """处理排除号码事件""" if event.data: exclude_front = event.data.get('exclude_front', '') exclude_back = event.data.get('exclude_back', '') # 更新排除号码显示 self.exclude_front_entry.delete(0, 'end') self.exclude_front_entry.insert(0, exclude_front) self.exclude_back_entry.delete(0, 'end') self.exclude_back_entry.insert(0, exclude_back) # 在结果文本中记录 self.result_text.insert('end', f"收到排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") def _handle_module_complete(self, event: Event): self.status_var.set(f"{event.source} 模块运行完成") if event.source == "input_analysis" and hasattr(self, 'result_text') and self.result_text: # 更新推荐号码显示 if 'recommend_front' in event.data: self.recommend_front_var.set(event.data['recommend_front']) if 'recommend_back' in event.data: self.recommend_back_var.set(event.data['recommend_back']) # 在结果文本中记录 self.result_text.insert('end', f"\n{event.source} 模块已完成分析\n") self.result_text.insert('end', f"推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 同步更新号码池 update_event = Event( event_id=int(time.time()), type=EventType.POOL_UPDATE, source='input_analysis', target='pool', data={ 'front_numbers': self.recommend_front_var.get(), 'back_numbers': self.recommend_back_var.get() } ) event_center.publish(update_event) self.result_text.insert('end', "号码池已同步更新\n") def _on_module_renovate(self, module: str): """刷新模块""" if module == self.current_module: self._on_module_button_click(module) def _handle_ui_update(self, event: Event): """处理UI更新事件""" if not event.data or 'update_type' not in event.data: return update_type = event.data['update_type'] data = event.data.get('data', {}) # 处理核心变量更新 if update_type == 'organized_data': # 确保所有核心变量已初始化 if not hasattr(self, 'core_vars'): self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } # 更新界面变量 self.core_vars['front_area'].set(str(data.get('front_numbers', []))) self.core_vars['back_area'].set(str(data.get('back_numbers', []))) self.core_vars['front_hot'].set(str(data.get('front_hot', []))) self.core_vars['front_cold'].set(str(data.get('front_cold', []))) self.core_vars['back_hot'].set(str(data.get('back_hot', []))) self.core_vars['back_cold'].set(str(data.get('back_cold', [])))

下列代码由于“代码中尝试查找的文件名格式为"001.docx"(使用3位数字编号)”出现了下述问题 if not os.path.exists(target_path): print(f"错误:未找到编号为 {number} 的文档")修改代码接触必须检索3位数字编号的限制,改为正常数字即可: import os import jieba import tkinter as tk import pandas as pd import numpy as np from tkinter import filedialog from tkinter import scrolledtext, messagebox from sklearn.naive_bayes import BernoulliNB from sklearn.metrics.pairwise import cosine_similarity from sklearn.feature_extraction.text import TfidfVectorizer from docx import Document import warnings warnings.filterwarnings("ignore", category=UserWarning, message="pkg_resources is deprecated") sample_dict = None custom_dict = None stop_dict = None freq_table = None sample_eval = None qualified_samples = [] def sample_lib_action(): global sample_dict root = tk.Tk() root.withdraw() desktop_path = os.path.join(os.path.expanduser("~"), "Desktop") if not os.path.exists(desktop_path): desktop_path = os.path.expanduser("~") folder_path = filedialog.askdirectory( title="请选择样本库地址", initialdir=desktop_path ) root.destroy() sample_dict = folder_path sample_lib_result_var.set(sample_dict) def custom_dict_action(): global custom_dict root = tk.Tk() root.withdraw() desktop_path = os.path.join(os.path.expanduser("~"), "Desktop") if not os.path.exists(desktop_path): desktop_path = os.path.expanduser("~") file_path = filedialog.askopenfilename( title="请选择自定义词库文本文件", initialdir=desktop_path, filetypes=[("所有文件", "*.*")] ) root.destroy() custom_dict = file_path custom_result_var.set(custom_dict) def stop_dict_action(): global stop_dict root = tk.Tk() root.withdraw() desktop_path = os.path.join(os.path.expanduser("~"), "Desktop") if not os.path.exists(desktop_path): desktop_path = os.path.expanduser("~") file_path = filedialog.askopenfilename( title="请选择停止词库文本文件", initialdir=desktop_path, filetypes=[("所有文件", "*.*")] ) root.destroy() stop_dict = file_path stop_result_var.set(stop_dict) def freq_table_action(): global freq_table root = tk.Tk() root.withdraw() desktop_path = os.path.join(os.path.expanduser("~"), "Desktop") if not os.path.exists(desktop_path): desktop_path = os.path.expanduser("~") file_path = filedialog.askopenfilename( title="请选择词频表文件", initialdir=desktop_path, filetypes=[("所有文件", "*.*")] ) root.destroy() freq_table = file_path freq_result_var.set(freq_table) def sample_eval_action(): global sample_eval, qualified_samples root = tk.Tk() root.withdraw() desktop_path = os.path.join(os.path.expanduser("~"), "Desktop") if not os.path.exists(desktop_path): desktop_path = os.path.expanduser("~") file_path = filedialog.askopenfilename( title="请选择样本评估文件", initialdir=desktop_path, filetypes=[("所有文件", "*.*")] ) root.destroy() sample_eval = file_path sample_result_var.set(sample_eval) qualified_samples = [] try: encodings = ['utf-8', 'gbk', 'gb18030', 'latin1'] for encoding in encodings: try: with open(sample_eval, 'r', encoding=encoding) as f: for line_num, line in enumerate(f): file_id = f"{line_num + 1:03d}" qualified_samples.append((file_id, line.strip())) break except UnicodeDecodeError: continue else: messagebox.showerror("错误", "无法解码样本文件,请检查文件编码") qualified_samples = [] if not qualified_samples: messagebox.showwarning("警告", "样本文件中没有找到任何句子") except Exception as e: messagebox.showerror("错误", f"读取样本文件失败: {str(e)}") qualified_samples = [] def preprocess_text(text, stop_words): words = jieba.lcut(text) return " ".join([word for word in words if word not in stop_words]) def find_most_similar_sample(input_text, samples, stop_words): if not samples: return None, "没有可用的合格样本句子", 0.0 sample_texts = [sentence for _, sentence in samples] all_texts = [input_text] + sample_texts preprocessed_texts = [preprocess_text(text, stop_words) for text in all_texts] vectorizer = TfidfVectorizer() tfidf_matrix = vectorizer.fit_transform(preprocessed_texts) similarities = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:]) max_index = np.argmax(similarities) max_similarity = similarities[0, max_index] file_id, most_similar = samples[max_index] return file_id, most_similar, max_similarity def run_action(): global freq_table global stop_dict global custom_dict global sample_eval global qualified_samples input_text = input_textbox.get("1.0", "end-1c") if not input_text.strip(): messagebox.showwarning("警告", "请输入内容") return if not all([freq_table, stop_dict, custom_dict, sample_eval]): messagebox.showwarning("警告", "请先选择所有必需的文件") return try: with open(stop_dict, 'r', encoding='utf-8') as f: stop_words = set(line.strip() for line in f) with open(custom_dict, 'r', encoding='utf-8') as f: user_words = [line.strip() for line in f] for word in user_words: jieba.add_word(word) xingzhi_df = pd.read_excel(sample_eval, sheet_name=0) file_to_label = dict(zip(xingzhi_df['文件'], xingzhi_df['性质'])) cipin_df = pd.read_excel(freq_table, sheet_name=0) cipin_df.set_index('词语', inplace=True) cipin_transposed = cipin_df.transpose() X_train = [] y_train = [] for file_name, word_counts in cipin_transposed.iterrows(): label = file_to_label.get(file_name) if label is None: continue X_train.append(word_counts.values.astype(int)) y_train.append(1 if label == "不合格" else 0) model = BernoulliNB() model.fit(X_train, y_train) feature_words = cipin_df.index.tolist() input_features = np.zeros(len(feature_words)) words = jieba.lcut(input_text) filtered_words = [word for word in words if word not in stop_words] for word in filtered_words: if word in feature_words: idx = feature_words.index(word) input_features[idx] = 1 prediction = model.predict([input_features])[0] if prediction == 1: run_status = "判定结果:不合格" file_id, most_similar, similarity_score = find_most_similar_sample(input_text, qualified_samples,stop_words) if file_id: target_filename = f"{file_id}.docx" target_path = os.path.join(sample_dict, target_filename) if not os.path.exists(target_path): print(f"错误:未找到编号为 {number} 的文档") try: doc = Document(target_path) full_text = [] for paragraph in doc.paragraphs: full_text.append(paragraph.text) for table in doc.tables: for row in table.rows: for cell in row.cells: for paragraph in cell.paragraphs: full_text.append(paragraph.text) except Exception as e: print(f"读取文档时出错: {str(e)}") output_data = '\n'.join(full_text) else: output_data = "未找到相似合格样本" else: run_status = "判定结果:合格" output_data = "合格,暂无改进意见,可直接使用" run_result_var.set(run_status) output_textbox.delete("1.0", tk.END) output_textbox.insert(tk.END, output_data) except Exception as e: messagebox.showerror("错误", f"处理过程中发生错误: {str(e)}") root = tk.Tk() root.title("自我介绍评价与优化程序") root.geometry("700x750") root.columnconfigure(1, weight=1) root.rowconfigure(6, weight=1) root.rowconfigure(8, weight=1) sample_lib_frame = tk.Frame(root) sample_lib_frame.grid(row=0, column=0, columnspan=2, sticky="ew", padx=10, pady=5) tk.Button(sample_lib_frame, text="样本库", command=sample_lib_action, width=10).pack(side=tk.LEFT, padx=(0, 10)) sample_lib_result_var = tk.StringVar(value="等待操作") tk.Entry(sample_lib_frame, textvariable=sample_lib_result_var, state="readonly").pack(side=tk.LEFT, fill=tk.X, expand=True) custom_frame = tk.Frame(root) custom_frame.grid(row=1, column=0, columnspan=2, sticky="ew", padx=10, pady=5) tk.Button(custom_frame, text="自定义词库", command=custom_dict_action, width=10).pack(side=tk.LEFT, padx=(0, 10)) custom_result_var = tk.StringVar(value="等待操作") tk.Entry(custom_frame, textvariable=custom_result_var, state="readonly").pack(side=tk.LEFT, fill=tk.X, expand=True) stop_frame = tk.Frame(root) stop_frame.grid(row=2, column=0, columnspan=2, sticky="ew", padx=10, pady=5) tk.Button(stop_frame, text="停止词库", command=stop_dict_action, width=10).pack(side=tk.LEFT, padx=(0, 10)) stop_result_var = tk.StringVar(value="等待操作") tk.Entry(stop_frame, textvariable=stop_result_var, state="readonly").pack(side=tk.LEFT, fill=tk.X, expand=True) freq_frame = tk.Frame(root) freq_frame.grid(row=3, column=0, columnspan=2, sticky="ew", padx=10, pady=5) tk.Button(freq_frame, text="词频表", command=freq_table_action, width=10).pack(side=tk.LEFT, padx=(0, 10)) freq_result_var = tk.StringVar(value="等待操作") tk.Entry(freq_frame, textvariable=freq_result_var, state="readonly").pack(side=tk.LEFT, fill=tk.X, expand=True) sample_frame = tk.Frame(root) sample_frame.grid(row=4, column=0, columnspan=2, sticky="ew", padx=10, pady=5) tk.Button(sample_frame, text="样本评估", command=sample_eval_action, width=10).pack(side=tk.LEFT, padx=(0, 10)) sample_result_var = tk.StringVar(value="等待操作") tk.Entry(sample_frame, textvariable=sample_result_var, state="readonly").pack(side=tk.LEFT, fill=tk.X, expand=True) input_label = tk.Label(root, text="自定义自我介绍:", anchor="w") input_label.grid(row=5, column=0, sticky="w", padx=10, pady=(10, 0)) input_textbox = scrolledtext.ScrolledText(root, height=10) input_textbox.grid(row=6, column=0, columnspan=2, sticky="nsew", padx=10, pady=5) run_frame = tk.Frame(root) run_frame.grid(row=7, column=0, columnspan=2, sticky="ew", padx=10, pady=5) tk.Button(run_frame, text="运行", command=run_action, width=10).pack(side=tk.LEFT, padx=(0, 10)) run_result_var = tk.StringVar(value="等待运行") tk.Entry(run_frame, textvariable=run_result_var, state="readonly").pack(side=tk.LEFT, fill=tk.X, expand=True) output_label = tk.Label(root, text="参考介绍:", anchor="w") output_label.grid(row=8, column=0, sticky="w", padx=10, pady=(10, 0)) output_textbox = scrolledtext.ScrolledText(root, height=10) output_textbox.grid(row=9, column=0, columnspan=2, sticky="nsew", padx=10, pady=(0, 10)) root.mainloop()

认真阅读修改问题的地方,修改问题:强调:下面的代码是能够运行,现在修改,只是局部修改问题,不要胡乱修改。。。修改时一定要说明在什么类的什么地方,这样不要用户进行修改。找准位置,认真修改,本着用户至上的原则。问题:2025-07-16 13:40:56 - root - ERROR - 系统启动失败: 'MainInterface' object has no attribute '_clear_dynamic_content' Traceback (most recent call last): File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 1604, in main app.main_ui = MainInterface(root, pool) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 892, in __init__ self._setup_ui() File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 983, in _setup_ui self._setup_center_area() File "C:\Users\Administrator\Desktop\数字模型生成器.py", line 1056, in _setup_center_area Button(btn_frame, text="清除", width=8, command=self._clear_dynamic_content).pack(side='left', padx=2) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'MainInterface' object has no attribute '_clear_dynamic_content'原代码class MainInterface: def __init__(self, root: Tk, pool: 'NumberPool'): self.root = root self.pool = pool self.left_panel = None self.center_paned = None # 修正变量名 self.right_panel = None self.core_vars = {} self.pool_vars = {} self.status_var = StringVar() self.dynamic_text = None self.current_module = None self._setup_ui() self._setup_event_handlers() self.module_instances = {} self._init_modules() # 初始化排除号码相关控件 self.exclude_front_entries = [] self.exclude_back_entries = [] self.front_dan_entries = [] self.back_dan_entries = [] # 初始化结果文本控件 self.result_text = None # 初始化排除号码变量 self.exclude_front_var = StringVar() self.exclude_back_var = StringVar() self.recommend_front_var = StringVar() self.recommend_back_var = StringVar() # 初始化模块内容框架 self.dynamic_content = None self.module_content_frame = None # 模块标签定义 self.labels = { 'InputAnalysis_analysis': [ "输入号码:", "前区:", "后区:", "推荐号码:", "前区:", "后区:", ], 'combination_analysis': [ "前区热号:", "前数字频:", "前频繁推:", "后区热号:", "后数字频:", "后低频推:" ], 'follow_analysis': [ "前推荐多:", "前推荐少:", "后推荐多:", "后推荐少:" ], 'trend_analysis': [ "和值:", "质合比:", "奇偶比:", "断区推荐:", "连号推荐:", "冷热推荐:", "后区热号:", "后区冷号:", "趋势号:" ], 'NumberGeneration_analysis': [ "胆码:", "前区:", "后区:", "推荐5注号码:", "1:", "", "2:", "", "3:", "", "4:", "", "5:", "" ], } # 初始化所有模块的条目引用 self.front_dan_entry = None self.back_dan_entry = None self.result_text = None self.exclude_front_entry = None self.exclude_back_entry = None self.front_entry = None self.back_entry = None def _focus_adjacent_entry(self, event, current_idx, offset, area): """移动焦点到相邻的输入框""" entries = self.exclude_front_entries if area == 'front' else self.exclude_back_entries new_idx = current_idx + offset if 0 <= new_idx < len(entries): entries[new_idx].focus_set() def _init_modules(self): """初始化所有分析模块""" modules = { 'input_analysis': InputAnalysisModule, 'combination_analysis': CombinationAnalysisModule, 'follow_analysis': FollowAnalysisModule, 'trend_analysis': TrendAnalysisModule, 'number_generation': NumberGenerationModule } for name, cls in modules.items(): self.module_instances[name] = cls(name) def _setup_event_handlers(self): """初始化事件处理器""" event_center.subscribe(EventType.MODULE_COMPLETE, self._handle_module_complete) event_center.subscribe(EventType.UI_UPDATE, self._handle_ui_update) event_center.subscribe(EventType.EXCLUDE_NUMBERS, self._handle_exclude_numbers) def _setup_ui(self): self.root.title(f"大乐透智能分析平台 - {GlobalConfig.VERSION}") self.root.geometry("1400x800") # 添加主标题 title_frame = Frame(self.root) title_frame.pack(fill='x', pady=5) Label(title_frame, text="大乐透智能分析平台", font=('微软雅黑', 16, 'bold')).pack(expand=True) # 主容器 - 三栏布局 main_container = PanedWindow(self.root, orient=HORIZONTAL, sashrelief=RAISED, sashwidth=5) main_container.pack(fill='both', expand=True, padx=5, pady=(0, 5)) # 左侧面板 self.left_panel = Frame(main_container, width=200, bg="#eaeaea") main_container.add(self.left_panel, minsize=150, stretch="never") # 中间内容区 self.center_paned = PanedWindow(main_container, orient=VERTICAL, sashrelief=RAISED, sashwidth=5) main_container.add(self.center_paned, minsize=500, stretch="always") # 右侧面板 self.right_panel = Frame(main_container, width=700, bg="#f5f5f5") main_container.add(self.right_panel, minsize=250, stretch="never") # 初始化各区域 self._setup_left_panel() self._setup_center_area() self._setup_right_panel() def _setup_left_panel(self): """初始化左侧模块按钮区""" module_names = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } for module in GlobalConfig.MODULES: Button( self.left_panel, text=module_names[module], width=18, command=lambda m=module: self._on_module_button_click(m) ).pack(pady=3, padx=5, ipady=3) def _setup_center_area(self): """设置中间区域布局,分为上下两部分""" # 上半部分 - 核心区 (固定高度) self.core_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.core_frame, minsize=150, stretch="never") # 核心区内容 Label(self.core_frame, text="核心区", font=('微软雅黑', 12, 'bold')).pack(anchor='w', padx=5, pady=2) # 核心数据展示 self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } for label, var_name in [ ("前区:", 'front_area'), ("后区:", 'back_area'), ("前区热号:", 'front_hot'), ("前区冷号:", 'front_cold'), ("后区热号:", 'back_hot'), ("后区冷号:", 'back_cold') ]: frame = Frame(self.core_frame) frame.pack(fill='x', padx=5, pady=2) Label(frame, text=label, width=10, anchor='w').pack(side='left') entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) entry = Entry(entry_container, textvariable=self.core_vars[var_name], font=('微软雅黑', 10), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) # 下半部分 - 动态区 (可扩展) self.dynamic_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.dynamic_frame, minsize=200, stretch="always") # 动态区内部容器 dynamic_container = Frame(self.dynamic_frame) dynamic_container.pack(fill='both', expand=True, padx=5, pady=5) # 动态区标题 dynamic_header = Frame(dynamic_container) dynamic_header.pack(fill='x', pady=5) Label(dynamic_header, text="动态区", font=('微软雅黑', 12, 'bold')).pack(side='left') # 动态区操作按钮 btn_frame = Frame(dynamic_header) btn_frame.pack(side='right') Button(btn_frame, text="运行", width=8, command=self._run_current_module).pack(side='left', padx=2) Button(btn_frame, text="清除", width=8, command=self._clear_dynamic_content).pack(side='left', padx=2) Button(btn_frame, text="保存", width=8, command=self._save_dynamic_content).pack(side='left', padx=2) Button(btn_frame, text="刷新", width=8, command=self._refresh_dynamic).pack(side='left', padx=2) # 模块内容容器 self.module_content_frame = Frame(dynamic_container) self.module_content_frame.pack(fill='both', expand=True) # 初始化动态区内容 self.dynamic_content = Frame(self.module_content_frame) self.dynamic_content.pack(fill='both', expand=True, padx=5, pady=5) def _run_current_module(self): """运行当前模块""" if self.current_module: self._run_module(self.current_module) def _run_module(self, module: str): """运行指定模块""" if module == "input_analysis": # 获取排除号码 exclude_front = self.exclude_front_entry.get() exclude_back = self.exclude_back_entry.get() # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _create_ui_element(self, parent, label_text): """创建统一的UI元素(与核心区对齐)""" frame = Frame(parent) frame.pack(fill='x', pady=2) # 标签固定宽度与核心区对齐 Label(frame, text=label_text, width=10, anchor='w').pack(side='left') # 条目容器 - 宽度与核心区对齐 entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) return entry_container def _setup_right_panel(self): """设置右侧号码池布局""" # 号码池标题 pool_title_frame = Frame(self.right_panel) pool_title_frame.pack(fill='x', pady=5) Label(pool_title_frame, text="号码池", font=('微软雅黑', 12, 'bold')).pack(anchor='w') # 号码池内容区(添加边框和2px内边距) pool_content = Frame(self.right_panel, bd=1, relief='solid', padx=2, pady=2) pool_content.pack(fill='both', expand=True, padx=5, pady=5) # 创建Canvas和Scrollbar canvas = Canvas(pool_content, highlightthickness=0) scrollbar = Scrollbar(pool_content, orient="vertical", command=canvas.yview) scrollable_frame = Frame(canvas) scrollable_frame.bind( "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) # 号码池项目 - 优化布局和样式(带2px右边距) for label, var_name, row_id in GlobalConfig.UI_CONFIG: frame = Frame(scrollable_frame) frame.grid(row=row_id, column=0, sticky='ew', padx=0, pady=1) # 移除水平padding # 左侧标签(固定宽度8字符) lbl = Label(frame, text=label, width=8, anchor='w') lbl.pack(side='left', padx=(0, 5)) # 标签右侧留5px间距 # 右侧输入框容器(带2px右边距) entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True, padx=(0, 2)) var = StringVar() self.pool_vars[var_name] = var entry = Entry(entry_container, textvariable=var, font=('微软雅黑', 9), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 底部按钮区 btn_frame = Frame(self.right_panel) btn_frame.pack(fill='x', pady=5) Button(btn_frame, text="整理", width=10, command=self._organize_data).pack(side='left', padx=5) Button(btn_frame, text="冻结", width=10, command=self._freeze_data).pack(side='left', padx=5) Button(btn_frame, text="导出", width=10).pack(side='left', padx=5) def _create_input_analysis_content(self, parent: Frame): """创建输入分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 排除号码区 exclude_frame = Frame(content_frame) exclude_frame.pack(fill='x', pady=5) # 排除号码标签 Label(exclude_frame, text="排除号码:", font=('微软雅黑', 10, 'bold')).pack(anchor='w', pady=5) # 前区排除号码 front_exclude_frame = Frame(exclude_frame) front_exclude_frame.pack(fill='x', pady=2) Label(front_exclude_frame, text="前区:", width=5, anchor='w').pack(side='left') self.exclude_front_entry = Entry(front_exclude_frame) self.exclude_front_entry.pack(side='left', fill='x', expand=True) # 后区排除号码 back_exclude_frame = Frame(exclude_frame) back_exclude_frame.pack(fill='x', pady=2) Label(back_exclude_frame, text="后区:", width=5, anchor='w').pack(side='left') self.exclude_back_entry = Entry(back_exclude_frame) self.exclude_back_entry.pack(side='left', fill='x', expand=True) # 号码输入区 input_frame = Frame(content_frame) input_frame.pack(fill='x', pady=5) Label(input_frame, text="输入号码:", font=('微软雅黑', 10, 'bold')).pack(anchor='w', pady=5) # 前区号码 front_frame = Frame(input_frame) front_frame.pack(fill='x', pady=2) Label(front_frame, text="前区:", width=5, anchor='w').pack(side='left') self.front_entry = Entry(front_frame) self.front_entry.pack(side='left', fill='x', expand=True) # 后区号码 back_frame = Frame(input_frame) back_frame.pack(fill='x', pady=2) Label(back_frame, text="后区:", width=5, anchor='w').pack(side='left') self.back_entry = Entry(back_frame) self.back_entry.pack(side='left', fill='x', expand=True) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True, pady=5) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_combination_analysis_content(self, parent: Frame): """创建组合分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 使用预定义的labels for label in self.labels['combination_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) # 保存对控件的引用 var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前区热号": self.front_hot_entry = entry elif var_name == "前数字频": self.front_freq_entry = entry elif var_name == "前频繁推": self.front_freq_rec_entry = entry elif var_name == "后区热号": self.back_hot_entry = entry elif var_name == "后数字频": self.back_freq_entry = entry elif var_name == "后低频推": self.back_infreq_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_follow_analysis_content(self, parent: Frame): """创建跟随分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['follow_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前推荐多": self.front_more_entry = entry elif var_name == "前推荐少": self.front_less_entry = entry elif var_name == "后推荐多": self.back_more_entry = entry elif var_name == "后推荐少": self.back_less_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_trend_analysis_content(self, parent: Frame): """创建趋势分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['trend_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "和值": self.sum_value_entry = entry elif var_name == "质合比": self.prime_ratio_entry = entry elif var_name == "奇偶比": self.odd_even_ratio_entry = entry elif var_name == "断区推荐": self.zone_rec_entry = entry elif var_name == "连号推荐": self.consec_rec_entry = entry elif var_name == "冷热推荐": self.hot_cold_rec_entry = entry elif var_name == "后区热号": self.hot_rec_entry = entry elif var_name == "后区冷号": self.cold_rec_entry = entry elif var_name == "趋势号": self.trend_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_number_generation_content(self, parent: Frame): """创建数字生成模块的动态内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 胆码输入区 dan_frame = Frame(content_frame) dan_frame.pack(fill='x', pady=5) # 前区胆码 front_dan_frame = Frame(dan_frame) front_dan_frame.pack(fill='x') Label(front_dan_frame, text="前区胆码:").pack(side='left') self.front_dan_entries = [] for i in range(5): entry = Entry(front_dan_frame, width=3) entry.pack(side='left', padx=2) self.front_dan_entries.append(entry) self.front_dan_entry = self.front_dan_entries[0] # 保存第一个条目引用 # 后区胆码 back_dan_frame = Frame(dan_frame) back_dan_frame.pack(fill='x') Label(back_dan_frame, text="后区胆码:").pack(side='left') self.back_dan_entries = [] for i in range(5): entry = Entry(back_dan_frame, width=3) entry.pack(side='left', padx=2) self.back_dan_entries.append(entry) self.back_dan_entry = self.back_dan_entries[0] # 保存第一个条目引用 # 生成的号码显示区 generated_frame = Frame(content_frame) generated_frame.pack(fill='x', pady=5) Label(generated_frame, text="生成号码:").pack(anchor='w') self.generated_labels = [] for i in range(1, 6): frame = Frame(generated_frame) frame.pack(fill='x') Label(frame, text=f"{i}. ").pack(side='left') label = Label(frame, text="", width=30, anchor='w') label.pack(side='left') self.generated_labels.append(label) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _run_module(self, module: str): """运行模块""" if module == "input_analysis": # 获取排除号码 exclude_front = self.exclude_front_entry.get() exclude_back = self.exclude_back_entry.get() # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _generate_recommend_numbers(self, exclude_front: str, exclude_back: str): """生成推荐号码(示例逻辑)""" # 实际应用中应调用分析模块生成推荐号码 # 这里简化为生成随机推荐号码 import random # 前区号码范围1-35 all_front = [str(idx) for idx in range(1, 36)] exclude_front_list = exclude_front.split() if exclude_front else [] available_front = [num for num in all_front if num not in exclude_front_list] # 后区号码范围1-12 all_back = [str(idx) for idx in range(1, 13)] exclude_back_list = exclude_back.split() if exclude_back else [] available_back = [num for num in all_back if num not in exclude_back_list] # 随机选择5个前区号码 if len(available_front) >= 5: recommend_front = random.sample(available_front, 5) else: recommend_front = random.sample(all_front, 5) # 随机选择2个后区号码 if len(available_back) >= 2: recommend_back = random.sample(available_back, 2) else: recommend_back = random.sample(all_back, 2) # 更新推荐号码显示 self.recommend_front_var.set(' '.join(sorted(recommend_front, key=int))) self.recommend_back_var.set(' '.join(sorted(recommend_back, key=int))) # 在结果文本中记录 self.result_text.insert('end', f"生成推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 更新号码池 self._update_pool_with_recommendations(self.recommend_front_var.get(), self.recommend_back_var.get()) def _update_pool_with_recommendations(self, front: str, back: str): """用推荐号码更新号码池""" # 发布事件更新号码池 update_event = Event( event_id=int(time.time()), type=EventType.POOL_UPDATE, source='input_analysis', target='pool', data={ 'front_numbers': front, 'back_numbers': back } ) event_center.publish(update_event) # 在结果文本中记录 self.result_text.insert('end', "号码池已更新\n") def _clear_module_data(self, module: str): """清除模块数据""" if module == "input_analysis": if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') if hasattr(self, 'recommend_front_var'): self.recommend_front_var.set('') if hasattr(self, 'recommend_back_var'): self.recommend_back_var.set('') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') elif module == "combination_analysis": if hasattr(self, 'front_hot_entry') and self.front_hot_entry: self.front_hot_entry.delete(0, 'end') if hasattr(self, 'front_freq_entry') and self.front_freq_entry: self.front_freq_entry.delete(0, 'end') if hasattr(self, 'front_freq_rec_entry') and self.front_freq_rec_entry: self.front_freq_rec_entry.delete(0, 'end') if hasattr(self, 'back_hot_entry') and self.back_hot_entry: self.back_hot_entry.delete(0, 'end') if hasattr(self, 'back_freq_entry') and self.back_freq_entry: self.back_freq_entry.delete(0, 'end') if hasattr(self, 'back_infreq_rec_entry') and self.back_infreq_rec_entry: self.back_infreq_rec_entry.delete(0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') def _save_module_data(self, module: str): """保存模块数据""" try: data = {} if module == "input_analysis": data['front'] = self.front_entry.get() data['back'] = self.back_entry.get() data['exclude_front'] = self.exclude_front_entry.get() data['exclude_back'] = self.exclude_back_entry.get() data['recommend_front'] = self.recommend_front_var.get() data['recommend_back'] = self.recommend_back_var.get() data['result'] = self.result_text.get(1.0, 'end') elif module == "combination_analysis": data['front_hot'] = self.front_hot_entry.get() data['front_freq'] = self.front_freq_entry.get() data['front_freq_rec'] = self.front_freq_rec_entry.get() data['back_hot'] = self.back_hot_entry.get() data['back_freq'] = self.back_freq_entry.get() data['back_infreq_rec'] = self.back_infreq_rec_entry.get() data['result'] = self.result_text.get(1.0, 'end') # 其他模块数据收集... filename = f"{module}_data.json" with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) messagebox.showinfo("保存成功", f"数据已保存到{filename}") except Exception as e: messagebox.showerror("保存失败", str(e)) logging.error(f"保存数据失败: {str(e)}", exc_info=True) def _handle_exclude_numbers(self, event: Event): """处理排除号码事件""" if event.data: exclude_front = event.data.get('exclude_front', '') exclude_back = event.data.get('exclude_back', '') # 更新排除号码显示 self.exclude_front_entry.delete(0, 'end') self.exclude_front_entry.insert(0, exclude_front) self.exclude_back_entry.delete(0, 'end') self.exclude_back_entry.insert(0, exclude_back) # 在结果文本中记录 self.result_text.insert('end', f"收到排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") def _handle_module_complete(self, event: Event): self.status_var.set(f"{event.source} 模块运行完成") if hasattr(self, 'result_text') and self.result_text: self.result_text.insert('end', f"\n{event.source} 模块已完成分析\n") def _on_module_renovate(self, module: str): """刷新模块""" if module == self.current_module: self._on_module_button_click(module) def _handle_ui_update(self, event: Event): """处理UI更新事件""" if not event.data or 'update_type' not in event.data: return update_type = event.data['update_type'] data = event.data.get('data', {}) # 处理核心变量更新 if update_type == 'organized_data': # 确保所有核心变量已初始化 if not hasattr(self, 'core_vars'): self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } # 更新界面变量 self.core_vars['front_area'].set(str(data.get('front_numbers', []))) self.core_vars['back_area'].set(str(data.get('back_numbers', []))) self.core_vars['front_hot'].set(str(data.get('front_hot', []))) self.core_vars['front_cold'].set(str(data.get('front_cold', []))) self.core_vars['back_hot'].set(str(data.get('back_hot', []))) self.core_vars['back_cold'].set(str(data.get('back_cold', [])))

认真阅读修改问题的地方,修改问题:强调:下面的代码是能够运行,现在修改,只是局部修改问题,不要胡乱修改。。。修改时一定要说明在什么类的什么地方,这样不要用户进行修改。找准位置,认真修改,本着用户至上的原则。问题:2025-07-16 14:35:46 - root - ERROR - 系统启动失败: ‘MainInterface’ object has no attribute ‘_init_modules’ Traceback (most recent call last): File “C:\Users\Administrator\Desktop\数字模型生成器.py”, line 1691, in main app.main_ui = MainInterface(root, pool) ^^^^^^^^^^^^^^^^^^^^^^^^^ File “C:\Users\Administrator\Desktop\数字模型生成器.py”, line 895, in init self._init_modules() # 初始化排除号码相关控件 ^^^^^^^^^^^^^^^^^^ AttributeError: ‘MainInterface’ object has no attribute '_init_modules’原代码class MainInterface: def init(self, root: Tk, pool: ‘NumberPool’): self.root = root self.pool = pool self.left_panel = None self.center_paned = None # 修正变量名 self.right_panel = None self.core_vars = {} self.pool_vars = {} self.status_var = StringVar() self.dynamic_text = None self.current_module = None self._setup_ui() self._setup_event_handlers() self.module_instances = {} self._init_modules() # 初始化排除号码相关控件 self.exclude_front_entries = [] self.exclude_back_entries = [] self.front_dan_entries = [] self.back_dan_entries = [] # 初始化结果文本控件 self.result_text = None # 初始化排除号码变量 self.exclude_front_var = StringVar() self.exclude_back_var = StringVar() self.recommend_front_var = StringVar() self.recommend_back_var = StringVar() # 初始化模块内容框架 self.dynamic_content = None self.module_content_frame = None # 模块标签定义 self.labels = { ‘InputAnalysis_analysis’: [ “输入号码:”, “前区:”, “后区:”, “推荐号码:”, “前区:”, “后区:”, ], ‘combination_analysis’: [ “前区热号:”, “前数字频:”, “前频繁推:”, “后区热号:”, “后数字频:”, “后低频推:” ], ‘follow_analysis’: [ “前推荐多:”, “前推荐少:”, “后推荐多:”, “后推荐少:” ], ‘trend_analysis’: [ “和值:”, “质合比:”, “奇偶比:”, “断区推荐:”, “连号推荐:”, “冷热推荐:”, “后区热号:”, “后区冷号:”, “趋势号:” ], ‘NumberGeneration_analysis’: [ “胆码:”, “前区:”, “后区:”, “推荐5注号码:”, “1:”, “”, “2:”, “”, “3:”, “”, “4:”, “”, “5:”, “” ], } # 初始化所有模块的条目引用 self.front_dan_entry = None self.back_dan_entry = None self.result_text = None self.exclude_front_entry = None self.exclude_back_entry = None self.front_entry = None self.back_entry = None def _setup_event_handlers(self): """初始化事件处理器""" event_center.subscribe(EventType.MODULE_COMPLETE, self._handle_module_complete) event_center.subscribe(EventType.UI_UPDATE, self._handle_ui_update) event_center.subscribe(EventType.EXCLUDE_NUMBERS, self._handle_exclude_numbers) def _setup_ui(self): self.root.title(f"大乐透智能分析平台 - {GlobalConfig.VERSION}") self.root.geometry("1400x800") # 添加主标题 title_frame = Frame(self.root) title_frame.pack(fill='x', pady=5) Label(title_frame, text="大乐透智能分析平台", font=('微软雅黑', 16, 'bold')).pack(expand=True) # 主容器 - 三栏布局 main_container = PanedWindow(self.root, orient=HORIZONTAL, sashrelief=RAISED, sashwidth=5) main_container.pack(fill='both', expand=True, padx=5, pady=(0, 5)) # 左侧面板 self.left_panel = Frame(main_container, width=200, bg="#eaeaea") main_container.add(self.left_panel, minsize=150, stretch="never") # 中间内容区 self.center_paned = PanedWindow(main_container, orient=VERTICAL, sashrelief=RAISED, sashwidth=5) main_container.add(self.center_paned, minsize=500, stretch="always") # 右侧面板 self.right_panel = Frame(main_container, width=700, bg="#f5f5f5") main_container.add(self.right_panel, minsize=250, stretch="never") # 初始化各区域 self._setup_left_panel() self._setup_center_area() self._setup_right_panel() def _setup_left_panel(self): """初始化左侧模块按钮区""" module_names = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } for module in GlobalConfig.MODULES: Button( self.left_panel, text=module_names[module], width=18, command=lambda m=module: self._on_module_button_click(m) ).pack(pady=3, padx=5, ipady=3) def _setup_center_area(self): """设置中间区域布局,分为上下两部分""" # 上半部分 - 核心区 (固定高度) self.core_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.core_frame, minsize=150, stretch="never") # 核心区内容 Label(self.core_frame, text="核心区", font=('微软雅黑', 12, 'bold')).pack(anchor='w', padx=5, pady=2) # 核心数据展示 self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } for label, var_name in [ ("前区:", 'front_area'), ("后区:", 'back_area'), ("前区热号:", 'front_hot'), ("前区冷号:", 'front_cold'), ("后区热号:", 'back_hot'), ("后区冷号:", 'back_cold') ]: frame = Frame(self.core_frame) frame.pack(fill='x', padx=5, pady=2) Label(frame, text=label, width=10, anchor='w').pack(side='left') entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) entry = Entry(entry_container, textvariable=self.core_vars[var_name], font=('微软雅黑', 10), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) # 下半部分 - 动态区 (可扩展) self.dynamic_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.dynamic_frame, minsize=200, stretch="always") # 动态区内部容器 dynamic_container = Frame(self.dynamic_frame) dynamic_container.pack(fill='both', expand=True, padx=5, pady=5) # 动态区标题 dynamic_header = Frame(dynamic_container) dynamic_header.pack(fill='x', pady=5) Label(dynamic_header, text="动态区", font=('微软雅黑', 12, 'bold')).pack(side='left') # 动态区操作按钮 btn_frame = Frame(dynamic_header) btn_frame.pack(side='right') Button(btn_frame, text="运行", width=8, command=self._run_current_module).pack(side='left', padx=2) Button(btn_frame, text="清除", width=8, command=self._clear_dynamic_content).pack(side='left', padx=2) Button(btn_frame, text="保存", width=8, command=self._save_dynamic_content).pack(side='left', padx=2) Button(btn_frame, text="刷新", width=8, command=self._refresh_dynamic).pack(side='left', padx=2) # 模块内容容器 self.module_content_frame = Frame(dynamic_container) self.module_content_frame.pack(fill='both', expand=True) # 初始化动态区内容 self.dynamic_content = Frame(self.module_content_frame) self.dynamic_content.pack(fill='both', expand=True, padx=5, pady=5) def _on_module_button_click(self, module: str): """模块按钮点击事件处理 - 修改后确保内容显示在动态区第二个框内""" self.status_var.set(f"打开 {module} 模块...") self.current_module = module # 清除之前的动态内容(保留框架结构)for widget in self.module_content_frame.winfo_children(): widget.destroy() # 创建模块专属容器 - 分为上下两部分 main_paned = PanedWindow(self.module_content_frame, orient=VERTICAL, sashrelief=RAISED, sashwidth=5) main_paned.pack(fill='both', expand=True) # 上部 - 模块标题和基本信息 top_frame = Frame(main_paned, height=50, bd=1, relief='solid') main_paned.add(top_frame, minsize=50, stretch="never") # 模块标题 module_labels = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } Label(top_frame, text=module_labels.get(module, module), font=('微软雅黑', 12, 'bold')).pack(pady=10) # 下部 - 模块具体内容(第二个框)content_frame = Frame(main_paned, bd=1, relief='solid') main_paned.add(content_frame, minsize=200, stretch="always") # 根据模块类型创建具体内容 if module == "input_analysis": self._create_input_analysis_content(content_frame) elif module == "combination_analysis": self._create_combination_analysis_content(content_frame) elif module == "follow_analysis": self._create_follow_analysis_content(content_frame) elif module == "trend_analysis": self._create_trend_analysis_content(content_frame) elif module == "number_generation": self._create_number_generation_content(content_frame) # 底部按钮区(保持在动态区内)btn_frame = Frame(self.module_content_frame) btn_frame.pack(fill='x', pady=5) Button(btn_frame, text="运行", width=8, command=lambda: self._run_module(module)).pack(side='left', padx=5) Button(btn_frame, text="清除", width=8, command=lambda: self._clear_module_data(module)).pack(side='left', padx=5) Button(btn_frame, text="保存", width=8, command=lambda: self._save_module_data(module)).pack(side='left', padx=5) Button(btn_frame, text="刷新", width=8, command=lambda: self._on_module_button_click(module)).pack(side='right', padx=5) def _run_current_module(self): """运行当前模块""" if self.current_module: self._run_module(self.current_module) def _clear_dynamic_content(self): """清除动态区内容""" if self.current_module: self._clear_module_data(self.current_module) # 额外确保清除结果文本框(如果存在)if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') def _save_dynamic_content(self): """保存动态区内容""" if self.current_module: self._save_module_data(self.current_module) else: messagebox.showinfo("提示", "请先选择并运行一个模块") def _refresh_dynamic(self): """刷新动态区内容""" if self.current_module: self._on_module_button_click(self.current_module) else: messagebox.showinfo("提示", "请先选择一个模块") def _organize_data(self): """整理号码池数据""" try: # 发布整理事件 event = Event( event_id=int(time.time()), type=EventType.ORGANIZE_DATA, source='main_ui', target='pool' ) event_center.publish(event) self.status_var.set("号码池数据已整理") except Exception as e: messagebox.showerror("整理失败", str(e)) logging.error(f"整理数据失败: {str(e)}", exc_info=True) def _freeze_data(self): """冻结号码池数据""" try: # 发布冻结事件 event = Event( event_id=int(time.time()), type=EventType.FREEZE_DATA, source='main_ui', target='pool' ) event_center.publish(event) self.status_var.set("号码池数据已冻结") except Exception as e: messagebox.showerror("冻结失败", str(e)) logging.error(f"冻结数据失败: {str(e)}", exc_info=True) def _run_module(self, module: str): """运行指定模块""" if module == "input_analysis": # 获取排除号码 exclude_front = self.exclude_front_entry.get() exclude_back = self.exclude_back_entry.get() # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _create_ui_element(self, parent, label_text): """创建统一的UI元素(与核心区对齐)""" frame = Frame(parent) frame.pack(fill='x', pady=2) # 标签固定宽度与核心区对齐 Label(frame, text=label_text, width=10, anchor='w').pack(side='left') # 条目容器 - 宽度与核心区对齐 entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) return entry_container def _setup_right_panel(self): """设置右侧号码池布局""" # 号码池标题 pool_title_frame = Frame(self.right_panel) pool_title_frame.pack(fill='x', pady=5) Label(pool_title_frame, text="号码池", font=('微软雅黑', 12, 'bold')).pack(anchor='w') # 号码池内容区(添加边框和2px内边距)pool_content = Frame(self.right_panel, bd=1, relief='solid', padx=2, pady=2) pool_content.pack(fill='both', expand=True, padx=5, pady=5) # 创建Canvas和Scrollbar canvas = Canvas(pool_content, highlightthickness=0) scrollbar = Scrollbar(pool_content, orient="vertical", command=canvas.yview) scrollable_frame = Frame(canvas) scrollable_frame.bind( "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) # 号码池项目 - 优化布局和样式(带2px右边距)for label, var_name, row_id in GlobalConfig.UI_CONFIG: frame = Frame(scrollable_frame) frame.grid(row=row_id, column=0, sticky='ew', padx=0, pady=1) # 移除水平padding # 左侧标签(固定宽度8字符)lbl = Label(frame, text=label, width=8, anchor='w') lbl.pack(side='left', padx=(0, 5)) # 标签右侧留5px间距 # 右侧输入框容器(带2px右边距)entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True, padx=(0, 2)) var = StringVar() self.pool_vars[var_name] = var entry = Entry(entry_container, textvariable=var, font=('微软雅黑', 9), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 底部按钮区 btn_frame = Frame(self.right_panel) btn_frame.pack(fill='x', pady=5) Button(btn_frame, text="整理", width=10, command=self._organize_data).pack(side='left', padx=5) Button(btn_frame, text="冻结", width=10, command=self._freeze_data).pack(side='left', padx=5) Button(btn_frame, text="导出", width=10).pack(side='left', padx=5) def _create_input_analysis_content(self, parent: Frame): """创建输入分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 排除号码区 exclude_frame = Frame(content_frame) exclude_frame.pack(fill='x', pady=5) # 排除号码标签 Label(exclude_frame, text="排除号码:", font=('微软雅黑', 10, 'bold')).pack(anchor='w', pady=5) # 前区排除号码 front_exclude_frame = Frame(exclude_frame) front_exclude_frame.pack(fill='x', pady=2) Label(front_exclude_frame, text="前区:", width=5, anchor='w').pack(side='left') self.exclude_front_entry = Entry(front_exclude_frame) self.exclude_front_entry.pack(side='left', fill='x', expand=True) # 后区排除号码 back_exclude_frame = Frame(exclude_frame) back_exclude_frame.pack(fill='x', pady=2) Label(back_exclude_frame, text="后区:", width=5, anchor='w').pack(side='left') self.exclude_back_entry = Entry(back_exclude_frame) self.exclude_back_entry.pack(side='left', fill='x', expand=True) # 号码输入区 input_frame = Frame(content_frame) input_frame.pack(fill='x', pady=5) Label(input_frame, text="输入号码:", font=('微软雅黑', 10, 'bold')).pack(anchor='w', pady=5) # 前区号码 front_frame = Frame(input_frame) front_frame.pack(fill='x', pady=2) Label(front_frame, text="前区:", width=5, anchor='w').pack(side='left') self.front_entry = Entry(front_frame) self.front_entry.pack(side='left', fill='x', expand=True) # 后区号码 back_frame = Frame(input_frame) back_frame.pack(fill='x', pady=2) Label(back_frame, text="后区:", width=5, anchor='w').pack(side='left') self.back_entry = Entry(back_frame) self.back_entry.pack(side='left', fill='x', expand=True) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True, pady=5) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_combination_analysis_content(self, parent: Frame): """创建组合分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 使用预定义的labels for label in self.labels['combination_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) # 保存对控件的引用 var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前区热号": self.front_hot_entry = entry elif var_name == "前数字频": self.front_freq_entry = entry elif var_name == "前频繁推": self.front_freq_rec_entry = entry elif var_name == "后区热号": self.back_hot_entry = entry elif var_name == "后数字频": self.back_freq_entry = entry elif var_name == "后低频推": self.back_infreq_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_follow_analysis_content(self, parent: Frame): """创建跟随分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['follow_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前推荐多": self.front_more_entry = entry elif var_name == "前推荐少": self.front_less_entry = entry elif var_name == "后推荐多": self.back_more_entry = entry elif var_name == "后推荐少": self.back_less_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_trend_analysis_content(self, parent: Frame): """创建趋势分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['trend_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "和值": self.sum_value_entry = entry elif var_name == "质合比": self.prime_ratio_entry = entry elif var_name == "奇偶比": self.odd_even_ratio_entry = entry elif var_name == "断区推荐": self.zone_rec_entry = entry elif var_name == "连号推荐": self.consec_rec_entry = entry elif var_name == "冷热推荐": self.hot_cold_rec_entry = entry elif var_name == "后区热号": self.hot_rec_entry = entry elif var_name == "后区冷号": self.cold_rec_entry = entry elif var_name == "趋势号": self.trend_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_number_generation_content(self, parent: Frame): """创建数字生成模块的动态内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 胆码输入区 dan_frame = Frame(content_frame) dan_frame.pack(fill='x', pady=5) # 前区胆码 front_dan_frame = Frame(dan_frame) front_dan_frame.pack(fill='x') Label(front_dan_frame, text="前区胆码:").pack(side='left') self.front_dan_entries = [] for i in range(5): entry = Entry(front_dan_frame, width=3) entry.pack(side='left', padx=2) self.front_dan_entries.append(entry) self.front_dan_entry = self.front_dan_entries[0] # 保存第一个条目引用 # 后区胆码 back_dan_frame = Frame(dan_frame) back_dan_frame.pack(fill='x') Label(back_dan_frame, text="后区胆码:").pack(side='left') self.back_dan_entries = [] for i in range(5): entry = Entry(back_dan_frame, width=3) entry.pack(side='left', padx=2) self.back_dan_entries.append(entry) self.back_dan_entry = self.back_dan_entries[0] # 保存第一个条目引用 # 生成的号码显示区 generated_frame = Frame(content_frame) generated_frame.pack(fill='x', pady=5) Label(generated_frame, text="生成号码:").pack(anchor='w') self.generated_labels = [] for i in range(1, 6): frame = Frame(generated_frame) frame.pack(fill='x') Label(frame, text=f"{i}. ").pack(side='left') label = Label(frame, text="", width=30, anchor='w') label.pack(side='left') self.generated_labels.append(label) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _run_module(self, module: str): """运行模块""" if module == "input_analysis": # 获取排除号码 exclude_front = self.exclude_front_entry.get() exclude_back = self.exclude_back_entry.get() # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _generate_recommend_numbers(self, exclude_front: str, exclude_back: str): """生成推荐号码(示例逻辑)""" # 实际应用中应调用分析模块生成推荐号码 # 这里简化为生成随机推荐号码 import random # 前区号码范围1-35 all_front = [str(idx) for idx in range(1, 36)] exclude_front_list = exclude_front.split() if exclude_front else [] available_front = [num for num in all_front if num not in exclude_front_list] # 后区号码范围1-12 all_back = [str(idx) for idx in range(1, 13)] exclude_back_list = exclude_back.split() if exclude_back else [] available_back = [num for num in all_back if num not in exclude_back_list] # 随机选择5个前区号码 if len(available_front) >= 5: recommend_front = random.sample(available_front, 5) else: recommend_front = random.sample(all_front, 5) # 随机选择2个后区号码 if len(available_back) >= 2: recommend_back = random.sample(available_back, 2) else: recommend_back = random.sample(all_back, 2) # 更新推荐号码显示 self.recommend_front_var.set(' '.join(sorted(recommend_front, key=int))) self.recommend_back_var.set(' '.join(sorted(recommend_back, key=int))) # 在结果文本中记录 self.result_text.insert('end', f"生成推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 更新号码池 self._update_pool_with_recommendations(self.recommend_front_var.get(), self.recommend_back_var.get()) def _update_pool_with_recommendations(self, front: str, back: str): """用推荐号码更新号码池""" # 发布事件更新号码池 update_event = Event( event_id=int(time.time()), type=EventType.POOL_UPDATE, source='input_analysis', target='pool', data={ 'front_numbers': front, 'back_numbers': back } ) event_center.publish(update_event) # 在结果文本中记录 self.result_text.insert('end', "号码池已更新\n") def _clear_module_data(self, module: str): """清除模块数据""" if module == "input_analysis": if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') if hasattr(self, 'recommend_front_var'): self.recommend_front_var.set('') if hasattr(self, 'recommend_back_var'): self.recommend_back_var.set('') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') elif module == "combination_analysis": if hasattr(self, 'front_hot_entry') and self.front_hot_entry: self.front_hot_entry.delete(0, 'end') if hasattr(self, 'front_freq_entry') and self.front_freq_entry: self.front_freq_entry.delete(0, 'end') if hasattr(self, 'front_freq_rec_entry') and self.front_freq_rec_entry: self.front_freq_rec_entry.delete(0, 'end') if hasattr(self, 'back_hot_entry') and self.back_hot_entry: self.back_hot_entry.delete(0, 'end') if hasattr(self, 'back_freq_entry') and self.back_freq_entry: self.back_freq_entry.delete(0, 'end') if hasattr(self, 'back_infreq_rec_entry') and self.back_infreq_rec_entry: self.back_infreq_rec_entry.delete(0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') def _save_module_data(self, module: str): """保存模块数据""" try: data = {} if module == "input_analysis": data['front'] = self.front_entry.get() data['back'] = self.back_entry.get() data['exclude_front'] = self.exclude_front_entry.get() data['exclude_back'] = self.exclude_back_entry.get() data['recommend_front'] = self.recommend_front_var.get() data['recommend_back'] = self.recommend_back_var.get() data['result'] = self.result_text.get(1.0, 'end') elif module == "combination_analysis": data['front_hot'] = self.front_hot_entry.get() data['front_freq'] = self.front_freq_entry.get() data['front_freq_rec'] = self.front_freq_rec_entry.get() data['back_hot'] = self.back_hot_entry.get() data['back_freq'] = self.back_freq_entry.get() data['back_infreq_rec'] = self.back_infreq_rec_entry.get() data['result'] = self.result_text.get(1.0, 'end') # 其他模块数据收集... filename = f"{module}_data.json" with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) messagebox.showinfo("保存成功", f"数据已保存到{filename}") except Exception as e: messagebox.showerror("保存失败", str(e)) logging.error(f"保存数据失败: {str(e)}", exc_info=True) def _handle_exclude_numbers(self, event: Event): """处理排除号码事件""" if event.data: exclude_front = event.data.get('exclude_front', '') exclude_back = event.data.get('exclude_back', '') # 更新排除号码显示 self.exclude_front_entry.delete(0, 'end') self.exclude_front_entry.insert(0, exclude_front) self.exclude_back_entry.delete(0, 'end') self.exclude_back_entry.insert(0, exclude_back) # 在结果文本中记录 self.result_text.insert('end', f"收到排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") def _handle_module_complete(self, event: Event): self.status_var.set(f"{event.source} 模块运行完成") if hasattr(self, 'result_text') and self.result_text: self.result_text.insert('end', f"\n{event.source} 模块已完成分析\n") def _on_module_renovate(self, module: str): """刷新模块""" if module == self.current_module: self._on_module_button_click(module) def _handle_ui_update(self, event: Event): """处理UI更新事件""" if not event.data or 'update_type' not in event.data: return update_type = event.data['update_type'] data = event.data.get('data', {}) # 处理核心变量更新 if update_type == 'organized_data': # 确保所有核心变量已初始化 if not hasattr(self, 'core_vars'): self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } # 更新界面变量 self.core_vars['front_area'].set(str(data.get('front_numbers', []))) self.core_vars['back_area'].set(str(data.get('back_numbers', []))) self.core_vars['front_hot'].set(str(data.get('front_hot', []))) self.core_vars['front_cold'].set(str(data.get('front_cold', []))) self.core_vars['back_hot'].set(str(data.get('back_hot', []))) self.core_vars['back_cold'].set(str(data.get('back_cold', [])))

修改问题:在子类界面(1.输入分析)里,用户点击刷新后,仍然没有把数据完整呈现出来,有空格。而用户点击按钮“删除”时,输入框里的数据又不能删除。。2.检查,保存和读取文件的路径[c:\用户 \administrator\桌面\saved_numbers.json]原代码import sys import threading import time import logging import json import random import os from typing import List, Dict, Optional, Callable, Any from tkinter import (Tk, Frame, Label, Button, Entry, StringVar, PanedWindow, HORIZONTAL, VERTICAL, RAISED, Canvas, Scrollbar, messagebox, Text, LabelFrame) # 添加LabelFrame from tkinter.constants import VERTICAL, RAISED from dataclasses import dataclass from enum import Enum, auto from queue import Queue 配置日志 logging.basicConfig( level=logging.INFO, format=‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’, datefmt=‘%Y-%m-%d %H:%M:%S’) logger = logging.getLogger(‘DLT_Analysis’) ==================== 全局配置 ==================== class GlobalConfig: VERSION = “大乐透智能分析平台 v5.2” POOL_SAVE_PATH = os.path.join(os.path.expanduser(“~”), “Desktop”, “号码池.json”) MODULE1_SAVE_PATH = os.path.join(os.path.expanduser(“~”), “Desktop”, “saved_numbers.json”) MODULE1_ID = “module1.module1_id” # 新增模块ID MODULE2_ID = “module2.module2_id” # 新增 MODULE3_ID = “module3.module3_id” # 新增 MODULE4_ID = “module4.module4_id” # 新增 MODULE5_ID = “module5.module5_id” # 新增 MODULES = [‘input_analysis’, ‘combination_analysis’, ‘follow_analysis’, ‘trend_analysis’, ‘number_generation’] # 号码池UI配置 UI_CONFIG = [ (“前区:”, “front_area”, 1), (“后区:”, “back_area”, 2), (“前数字频:”, “front_freq”, 3), (“前数字缺:”, “front_missing”, 4), (“后数字频:”, “back_freq”, 5), (“后数字缺:”, “back_missing”, 6), (“前频繁推:”, “front_freq_50”, 7), (“后低频推:”, “back_infreq_50”, 8), (“生组合数:”, “comb_count”, 9), (“未组合码:”, “uncombined”, 10), (“前推荐多:”, “front_rec_more”, 11), (“前推荐少:”, “front_rec_less”, 12), (“后推荐多:”, “back_rec_more”, 13), (“后推荐少:”, “back_rec_less”, 14), (“和值:”, “sum_rec”, 15), (“质合比:”, “prime_rec”, 16), (“奇偶比:”, “odd_even_rec”, 17), (“断区推荐:”, “zone_rec”, 18), (“连号推荐:”, “consec_rec”, 19), (“冷热推荐:”, “hot_cold_rec”, 20), (“后区热号:”, “hot_rec”, 21), (“后区冷号:”, “cold_rec”, 22), (“趋势号:”, “trend_rec”, 23) ] 全局应用上下文 class AppContext: def init(self): self.dialog_manager = None self.main_ui = None self.loading_queue = Queue() self.modules_loaded = {module: False for module in GlobalConfig.MODULES} self.modules_completed = app = AppContext() ==================== 事件类型 ==================== class EventType(Enum): START_COMMAND = auto() DATA_SUBMIT = auto() ACK = auto() MODULE_READY = auto() MODULE_COMPLETE = auto() UI_UPDATE = auto() DIALOG_OPEN = auto() DIALOG_CLOSE = auto() DATA_FREEZE = auto() DATA_ORGANIZE = auto() # 添加缺失的枚举值 MODULE_RUN = auto() LOADING_PROGRESS = auto() LOADING_COMPLETE = auto() EXCLUDE_NUMBERS = auto() POOL_UPDATE = auto() ==================== 事件系统 ==================== @dataclass class Event: event_id: int type: EventType source: str target: str timestamp: float = time.time() data: Optional[Dict[str, Any]] = None class EventEmitter: def init(self): self._lock: threading.Lock = threading.Lock() self._subscribers: Dict[EventType, List[Callable[[Event], None]]] = {} def subscribe(self, event_type: EventType, callback: Callable[[Event], None]): with self._lock: if event_type not in self._subscribers: self._subscribers[event_type] = [] self._subscribers[event_type].append(callback) def publish(self, event: Event): with self._lock: subscribers = self._subscribers.get(event.type, []) for callback in subscribers: try: callback(event) except Exception as e: logging.error(f"事件处理失败: {str(e)}", exc_info=True) event_center = EventEmitter() ==================== 号码池实现 ==================== class NumberPool: def init(self): self.data_store: Dict[str, Any] = { ‘front_hot’: [], ‘back_hot’: [], ‘front_freq’: {}, ‘back_freq’: {}, ‘front_missing’: [], ‘back_missing’: [], ‘recommendations’: {}, ‘generated_numbers’: [], ‘frozen’: False, ‘front_numbers’: [], ‘back_numbers’: [], ‘special_data’: {}, ‘organized_data’: None } self.lock = threading.Lock() self.current_module: Optional[str] = None self.module_status = {module: False for module in GlobalConfig.MODULES} self.running = True self._setup_subscriptions() self._load_data() def update(self, label: str, value: Any): """更新号码池数据""" with self.lock: # 根据标签名更新对应数据 if label == "前区:": self.data_store['front_numbers'] = value elif label == "后区:": self.data_store['back_numbers'] = value elif label == "前数字频:": self.data_store['front_freq'] = value elif label == "前数字缺:": self.data_store['front_missing'] = value elif label == "后数字频:": self.data_store['back_freq'] = value elif label == "后数字缺:": self.data_store['back_missing'] = value elif label == "前频繁推:": self.data_store['front_freq_50'] = value elif label == "后低频推:": self.data_store['back_infreq_50'] = value elif label == "生组合数:": self.data_store['comb_count'] = value elif label == "未组合码:": self.data_store['uncombined'] = value elif label == "前推荐多:": self.data_store['front_rec_more'] = value elif label == "前推荐少:": self.data_store['front_rec_less'] = value elif label == "后推荐多:": self.data_store['back_rec_more'] = value elif label == "后推荐少:": self.data_store['back_rec_less'] = value elif label == "和值:": self.data_store['sum_rec'] = value elif label == "质合比:": self.data_store['prime_rec'] = value elif label == "奇偶比:": self.data_store['odd_even_rec'] = value elif label == "断区推荐:": self.data_store['zone_rec'] = value elif label == "连号推荐:": self.data_store['consec_rec'] = value elif label == "冷热推荐:": self.data_store['hot_cold_rec'] = value elif label == "后区热号:": self.data_store['hot_rec'] = value elif label == "后区冷号:": self.data_store['cold_rec'] = value elif label == "趋势号:": self.data_store['trend_rec'] = value self._save_data() def _setup_subscriptions(self): event_center.subscribe(EventType.DATA_SUBMIT, self._handle_data_submit) event_center.subscribe(EventType.MODULE_READY, self._handle_module_ready) event_center.subscribe(EventType.MODULE_COMPLETE, self._handle_module_complete) event_center.subscribe(EventType.DATA_FREEZE, self._handle_freeze) event_center.subscribe(EventType.DATA_ORGANIZE, self._handle_organize) event_center.subscribe(EventType.MODULE_RUN, self._handle_module_run) event_center.subscribe(EventType.EXCLUDE_NUMBERS, self._handle_exclude_numbers) def _handle_exclude_numbers(self, event: Event): """处理排除号码事件""" if event.data: exclude_front = event.data.get('exclude_front', '').split() exclude_back = event.data.get('exclude_back', '').split() # 更新排除号码 self.data_store['excluded_front'] = [int(x) for x in exclude_front if x.isdigit()] self.data_store['excluded_back'] = [int(x) for x in exclude_back if x.isdigit()] # 日志记录 logging.info(f"更新排除号码: 前区 {exclude_front}, 后区 {exclude_back}") def _handle_module_ready(self, event: Event): if event.target == 'pool': logging.info(f"模块 {event.source} 已就绪") app.modules_loaded[event.source] = True def _handle_module_complete(self, event: Event): if event.target == 'pool': logging.info(f"模块 {event.source} 已完成运行") self.module_status[event.source] = False self.current_module = None app.modules_completed[event.source] = True def _handle_freeze(self, event: Event): if event.target == 'pool': with self.lock: self.data_store['frozen'] = True logging.info("号码池数据已冻结") def _handle_organize(self, event: Event): if event.target == 'pool': with self.lock: # 检查所有模块是否已完成 all_completed = all(app.modules_completed.values()) if not all_completed: logging.warning("无法整理数据: 还有模块未完成分析") return # 整理数据逻辑 self._organize_data() logging.info("号码池数据已整理") # 更新UI update_event = Event( event_id=int(time.time()), type=EventType.UI_UPDATE, source='pool', target='main_ui', data={'update_type': 'organized_data', 'data': self.data_store['organized_data']} ) event_center.publish(update_event) def _organize_data(self): """整理号码池数据到核心展示区""" organized = { 'front_numbers': self._organize_front_numbers(), 'back_numbers': self._organize_back_numbers(), 'front_hot': self._organize_front_hot(), 'front_cold': self._organize_front_cold(), 'back_hot': self._organize_back_hot(), 'back_cold': self._organize_back_cold() } self.data_store['organized_data'] = organized def _organize_front_numbers(self): """整理前区号码""" front_numbers = self.data_store.get('front_numbers', []) uncombined = self.data_store.get('recommendations', {}).get('uncombined', []) return sorted(list(set(front_numbers + uncombined))) def _organize_back_numbers(self): """整理后区号码""" return self.data_store.get('back_numbers', []) def _organize_front_hot(self): """整理前区热号""" front_freq = list(self.data_store.get('front_freq', {}).keys()) front_freq_rec = self.data_store.get('recommendations', {}).get('front极_freq_50', []) front_rec_more = self.data_store.get('recommendations', {}).get('front_rec_more', []) hot_cold_rec = self.data_store.get('recommendations', {}).get('hot_cold_rec', {}).get('hot', []) hot_numbers = list(set( [int(x) for x in front_freq] + front_freq_rec + front_rec_more + hot_cold_rec )) return sorted(hot_numbers) def _organize_front_cold(self): """整理前区冷号""" front_missing = self.data_store.get('front_missing', []) front_rec_less = self.data_store.get('recommendations', {}).get('front_rec_less', []) hot_cold_rec = self.data_store.get('recommendations', {}).get('hot_cold_rec', {}).get('cold', []) cold_numbers = list(set( front_missing + front_rec_less + hot_cold_rec )) return sorted(cold_numbers) def _organize_back_hot(self): """整理后区热号""" back_freq = list(self.data_store.get('back_freq', {}).keys()) back_rec_more = self.data_store.get('recommendations', {}).get('back_rec_more', []) hot_rec = self.data_store.get('recommendations', {}).get('hot_rec', []) hot_numbers = list(set( [int(x) for x in back_freq] + back_rec_more + hot_rec )) return sorted(hot_numbers) def _organize_back_cold(self): """整理后区冷号""" back_missing = self.data_store.get('back_missing', []) back_infreq_rec = self.data_store.get('recommendations', {}).get('back_infreq_50', []) back_rec_less = self.data_store.get('recommendations', {}).get('back_rec_less', []) cold_rec = self.data_store.get('recommendations', {}).get('cold_rec', []) cold_numbers = list(set( back_missing + back_infreq_rec + back_rec_less + cold_rec )) return sorted(cold_numbers) def _handle_module_run(self, event: Event): if event.target == 'pool' and event.source in GlobalConfig.MODULES: self.current_module = event.source self.module_status[event.source] = True logging.info(f"模块 {event.source} 开始运行") run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='pool', target=event.source ) event_center.publish(run_event) def _handle_data_submit(self, event: Event): if event.target == 'pool' and not self.data_store['frozen']: with self.lock: logger.info(f"收到来自 {event.source} 的数据提交") # 新增日志 if event.data: self.data_store.update(event.data) logger.debug(f"更新后的数据: {self.data_store}") # 新增日志 self.data_store.update(event.data) update_event = Event( event_id=int(time.time()), type=EventType.UI_UPDATE, source='pool', target='main_ui', data={'update_type': 'pool_update', 'data': self.data_store} ) event_center.publish(update_event) self._save_data() def _save_data(self): try: with open(GlobalConfig.POOL_SAVE_PATH, 'w', encoding='utf-8') as f: json.dump(self.data_store, f, ensure_ascii=False, indent=2) except IOError as e: logging.error(f"[号码池] 保存数据失败: {str(e)}") def _load_data(self): if os.path.exists(GlobalConfig.POOL_SAVE_PATH): try: with open(GlobalConfig.POOL_SAVE_PATH, 'r', encoding='utf-8') as f: data = json.load(f) with self.lock: self.data_store.update(data) logging.info("[号码池] 成功加载之前保存的数据") except (IOError, json.JSONDecodeError) as e: logging.error(f"[号码池] 加载数据失败: {str(e)}") ==================== 基础模块类 ==================== class BaseModule: def init(self, module_name: str): self.module_name = module_name self._setup_subscriptions() self._initialize() self.dynamic_data = {} self._dialog_opened = False # 确保所有属性都被初始化 def _setup_subscriptions(self): def filtered_handler(event: Event): if event.target == self.module_name: self._handle_start_command(event) event_center.subscribe(EventType.START_COMMAND, filtered_handler) event_center.subscribe(EventType.MODULE_RUN, self._handle_module_run) def clear_dynamic_data(self): """清除动态区数据""" for key in self.dynamic_data: if isinstance(self.dynamic_data[key], list): self.dynamic_data[key] = [] elif isinstance(self.dynamic_data[key], dict): self.dynamic_data[key] = {} else: self.dynamic_data[key] = "" def save_dynamic_data(self, file_path): """保存动态区数据到指定位置""" with open(file_path, 'w') as f: json.dump(self.dynamic_data, f) def refresh_to_pool(self, number_pool): """将动态区数据传递到号码池对应标签""" for key, value in self.dynamic_data.items(): # 确保标签名与号码池标签名一致 pool_label = f"{key}:" if not key.endswith(":") else key number_pool.update(pool_label, value) def _initialize(self): ready_event = Event( event_id=int(time.time()), type=EventType.MODULE_READY, source=self.module_name, target='pool' ) event_center.publish(ready_event) def _handle_start_command(self, event: Event): if not self._dialog_opened: logger.info(f"正在打开模块 {self.module_name} 的界面") self._open_dialog() self._dialog_opened = True else: logging.warning(f"{self.module_name} 已打开,避免重复触发") def _open_dialog(self): # 使用主循环调度UI操作,确保在主线程执行 app.main_ui.root.after(0, lambda: event_center.publish(Event( event_id=int(time.time()), type=EventType.DIALOG_OPEN, source=self.module_name, target='dialog_manager', data={'dialog_type': self.module_name} ))) def _submit_data(self, data: Dict[str, Any]): submit_event = Event( event_id=int(time.time()), type=EventType.DATA_SUBMIT, source=self.module_name, target='pool', data=data ) event_center.publish(submit_event) def _handle_module_run(self, event: Event): """处理模块运行事件""" raise NotImplementedError(f"{self.__class__.__name__} 必须实现 _handle_module_run 方法") def run(self): raise NotImplementedError("子类必须实现run方法") ==================== 输入分析模块 ==================== class InputAnalysisModule(BaseModule): def init(self, module_name: str): super().init(module_name) # 统一标签名格式(添加冒号) self.dynamic_data = { “排除号码”: { “前区”: [], “后区”: [] }, “推荐号码”: { “前区”: [], “后区”: [] } } def _handle_module_run(self, event: Event): """处理输入分析模块的运行事件""" logger.info(f"开始运行输入分析模块: {event}") self.run() def run(self): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if not dialog: return # 获取排除号码 exclude_front = app.main_ui.exclude_front_var.get() exclude_back = app.main_ui.exclude_back_var.get() # 更新动态区显示 self._update_dynamic_text(f"排除号码 - 前区: {exclude_front}, 后区: {exclude_back}\n") # 模拟分析过程 time.sleep(1) # 生成分析结果(考虑排除号码) all_front = [x for x in range(1, 36) if str(x) not in exclude_front.split()] all_back = [x for x in range(1, 13) if str(x) not in exclude_back.split()] front_nums = sorted(random.sample(all_front, 5)) back_nums = sorted(random.sample(all_back, 2)) # 更新界面显示 result_text = f"分析结果:\n前区: {front_nums}\n后区: {back_nums}\n" self._update_dynamic_text(result_text) # 提交数据到号码池 self._submit_data({ 'front_numbers': front_nums, 'back_numbers': back_nums, 'excluded_numbers': { 'front': exclude_front.split(), 'back': exclude_back.split() } }) # 标记模块完成 complete_event = Event( event_id=int(time.time()), type=EventType.MODULE_COMPLETE, source=self.module_name, target='pool' ) event_center.publish(complete_event) def _update_dynamic_text(self, text: str): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if dialog and hasattr(dialog, 'dynamic_text'): dialog.dynamic_text.insert('end', text) ==================== 组合分析模块 ==================== class CombinationAnalysisModule(BaseModule): def init(self, module_name: str): super().init(module_name) # 统一标签名格式(添加冒号) self.dynamic_data = { “前数字频”: {}, “前数字缺”: [], “后数字频”: {}, “后数字缺”: [], “前频繁推”: [], “后低频推”: [], “生组合数”: 0, “未组合码”: [] } def _handle_module_run(self, event: Event): """处理组合分析模块的运行事件""" logger.info(f"开始运行组合分析模块: {event}") self.run() def run(self): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if not dialog: return # 更新动态活动区显示 self._update_dynamic_text("开始组合分析...\n") # 模拟分析过程 time.sleep(1) # 生成分析结果 (模拟) front_hot = sorted(random.sample(range(1, 36), 5)) back_hot = sorted(random.sample(range(1, 13), 2)) front_freq = {str(i): random.randint(1, 100) for idx in range(1, 36)} back_freq = {str(i): random.randint(1, 100) for idx in range(1, 13)} front_missing = sorted(random.sample(range(1, 36), 5)) back_missing = sorted(random.sample(range(1, 13), 2)) front_freq_rec = sorted(random.sample(range(1, 36), 5)) back_infreq_rec = sorted(random.sample(range(1, 13), 2)) # 定义back_infreq_rec uncombined = [] # 定义uncombined变量 # 更新界面显示 result_text = f""" 组合分析完成: 前数字频: {front_freq} 前数字缺: {front_missing} 后数字频: {back_freq} 后数字缺: {back_missing} 前频繁推: {front_freq_rec} 后低频推: {back_infreq_rec} 生组合数: 0 未组合码: {uncombined} """ self._update_dynamic_text(result_text) # 提交数据到号码池 self._submit_data({ 'front_hot': front_hot, 'back_hot': back_hot, 'front_freq': front_freq, 'back_freq': back_freq, 'front_missing': front_missing, 'back_missing': back_missing, 'recommendations': { 'front_freq_50': front_freq_rec, 'back_infreq_50': back_infreq_rec, 'uncombined': uncombined } }) # 标记模块完成 complete_event = Event( event_id=int(time.time()), type=EventType.MODULE_COMPLETE, source=self.module_name, target='pool' ) event_center.publish(complete_event) def _update_dynamic_text(self, text: str): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if dialog and hasattr(dialog, 'dynamic_text'): dialog.dynamic_text.insert('end', text) ==================== 跟随分析模块 ==================== class FollowAnalysisModule(BaseModule): def init(self, module_name: str): super().init(module_name) # 统一标签名格式(添加冒号) self.dynamic_data = { “前推荐多”: [], “前推荐少”: [], “后推荐多”: [], “后推荐极少”: [] } def _handle_module_run(self, event: Event): """处理跟随分析模块的运行事件""" logger.info(f"开始运行跟随分析模块: {event}") self.run() def run(self): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if not dialog: return # 更新动态活动区显示 self._update_dynamic_text("开始跟随分析...\n") # 模拟分析过程 time.sleep(1) # 生成分析结果 (模拟) front_rec_more = sorted(random.sample(range(1, 36), 5)) front_rec_less = sorted(random.sample(range(1, 36), 5)) back_rec_more = sorted(random.sample(range(1, 13), 2)) back_rec_less = sorted(random.sample(range(1, 13), 2)) # 更新界面显示 result_text = f""" 跟随分析完成: 前推荐多: {front_rec_more} 前推荐少: {front_rec_less} 后推荐多: {back_rec_more} 后推荐少: {back_rec_less} """ self._update_dynamic_text(result_text) # 提交数据到号码池 self._submit_data({ 'recommendations': { 'front_rec_more': front_rec_more, 'front_rec_less': front_rec_less, 'back_rec_more': back_rec_more, 'back_rec_less': back_rec_less, } }) # 标记模块完成 complete_event = Event( event_id=int(time.time()), type=EventType.MODULE_COMPLETE, source=self.module_name, target='pool' ) event_center.publish(complete_event) def _update_dynamic_text(self, text: str): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if dialog and hasattr(dialog, 'dynamic_text'): dialog.dynamic_text.insert('end', text) ==================== 趋势分析模块 ==================== class TrendAnalysisModule(BaseModule): def init(self, module_name: str): super().init(module_name) # 统一标签名格式(添加冒号) self.dynamic_data = { “和值”: “”, “质合比”: “”, “奇偶比”: “”, “断区推荐”: [], “连号推荐”: [], “冷热推荐”: [], “后区热号”: [], “后区冷号”: [], “趋势号”: [] } def _handle_module_run(self, event: Event): """处理趋势分析模块的运行事件""" logger.info(f"开始运行趋势分析模块: {event}") self.run() def run(self): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if not dialog: return # 更新动态活动区显示 self._update_dynamic_text("开始趋势分析...\n") # 模拟分析过程 time.sleep(1) # 生成分析结果 (模拟) sum_value = random.randint(60, 125) prime_ratio = f"{random.randint(1, 5)}:{random.randint(1, 5)}" odd_even_ratio = f"{random.randint(1, 5)}:{random.randint(1, 5)}" zone_rec = random.choice(["一区", "二区", "三区", "四区", "五区", "六区", "七区"]) consec_rec = random.sample(range(1, 36), 2) hot_rec = sorted(random.sample(range(1, 13), 2)) cold_rec = sorted(random.sample(range(1, 13), 2)) trend_rec = sorted(random.sample(range(1, 36), 5)) hot_cold_rec = { 'hot': sorted(random.sample(range(1, 36), 5)), 'cold': sorted(random.sample(range(1, 36), 5)) } # 更新界面显示 result_text = f""" 趋势分析完成: 和值推荐: {sum_value} 质合比: {prime_ratio} 奇偶比: {odd_even_ratio} 断区推荐: {zone_rec} 连号推荐: {consec_rec} 后区热号: {hot_rec} 后区冷号: {cold_rec} 趋势号: {trend_rec} """ self._update_dynamic_text(result_text) # 提交数据到号码池 self._submit_data({ 'recommendations': { 'sum_rec': sum_value, 'prime_rec': prime_ratio, 'odd_even_rec': odd_even_ratio, 'zone_rec': zone_rec, 'consec_rec': consec_rec, 'hot_rec': hot_rec, 'cold_rec': cold_rec, 'trend_rec': trend_rec, 'hot_cold_rec': hot_cold_rec }, 'trend_data': { 'hot_numbers': hot_rec, 'cold_numbers': cold_rec, 'trend_numbers': trend_rec } }) # 标记模块完成 complete_event = Event( event_id=int(time.time()), type=EventType.MODULE_COMPLETE, source=self.module_name, target='pool' ) event_center.publish(complete_event) def _update_dynamic_text(self, text: str): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if dialog and hasattr(dialog, 'dynamic_text'): dialog.dynamic_text.insert('end', text) ==================== 数字生成模块 ==================== class NumberGenerationModule(BaseModule): def init(self, module_name: str): super().init(module_name) # 统一标签名格式(添加冒号) self.dynamic_data = { “胆码”: { “前区”: [], “后区”: [] }, “推荐5注号码”: { “1”: “”, “2”: “”, “3”: “”, “4”: “”, “5”: “” } } def _handle_module_run(self, event: Event): """处理数字生成模块的运行事件""" logger.info(f"开始运行数字生成模块: {event}") self.run() def run(self): dialog = app.dialog_manager.active_dialogs.get(self.module_name) if not dialog: return # 获取胆码数据 front_dan = dialog.content_frame.front_dan_entry.get() back_dan = dialog.content_frame.back_dan_entry.get() # 在动态活动区显示分析过程 self._update_dynamic_text(f"开始数字生成...\n胆码数据: 前区:{front_dan}, 后区:{back_dan}\n") # 生成号码 generated_numbers = self._generate_numbers(front_dan, back_dan) # 更新动态活动区显示 result_text = "生成的5注号码:\n" for num, nums in enumerate(generated_numbers, 1): result_text += f"{num}: 前区:{nums['front']} 后区:{nums['back']}\n" self._update_dynamic_text(result_text) # 提交数据到号码池 self._submit_data({ 'generated_numbers': generated_numbers }) # 标记模块完成 complete_event = Event( event_id=int(time.time()), type=EventType.MODULE_COMPLETE, source=self.module_name, target='pool' ) event_center.publish(complete_event) def _update_dynamic_text(self, text: str): """更新动态区文本""" dialog = getattr(self, 'dialog', None) if dialog and hasattr(dialog, 'dynamic_text'): dialog.dynamic_text.insert('end', text) @staticmethod def _generate_numbers(front_dan: str, back_dan: str): """使用胆码生成号码""" # 这里实现具体的号码生成逻辑 # 返回生成的号码列表 generated_numbers = [] for _ in range(5): front_nums = sorted(random.sample(range(1, 36), 5)) back_nums = sorted(random.sample(range(1, 13), 2)) generated_numbers.append({ 'front': front_nums, 'back': back_nums }) return generated_numbers ==================== 主界面修改 ==================== class MainInterface: def init(self, root: Tk, pool: ‘NumberPool’): self.root = root self.pool = pool self.left_panel = None self.center_paned = None # 修正变量名 self.right_panel = None self.core_vars = {} self.pool_vars = {} self.status_var = StringVar() self.dynamic_text = None self.current_module = None self._setup_ui() self._setup_event_handlers() self.module_instances = {} self.exclude_front_entries = [] self.exclude_back_entries = [] self.front_dan_entries = [] self.back_dan_entries = [] # 初始化结果文本控件 self.result_text = None # 初始化排除号码变量 self.exclude_front_var = StringVar() self.exclude_back_var = StringVar() self.recommend_front_var = StringVar() self.recommend_back_var = StringVar() # 初始化模块内容框架 self.dynamic_content = None self.module_content_frame = None # 新增模块ID属性 self.module_ids = { ‘input_analysis’: GlobalConfig.MODULE1_ID, ‘combination_analysis’: GlobalConfig.MODULE2_ID, ‘follow_analysis’: GlobalConfig.MODULE3_ID, ‘trend_analysis’: GlobalConfig.MODULE4_ID, ‘number_generation’: GlobalConfig.MODULE5_ID, } # 模块标签定义 self.labels = { ‘input_analysis’: [ # 修正为小写 “排除号码:”, “前区:”, “后区:”, “推荐号码:”, “前区:”, “后区:”, ], ‘combination_analysis’: [ “前数字频:”, “前数字缺:”, “后数字频:”, “后数字缺:”, “前频繁推:”, “后低频推:”, “生组合数:”, “未组合码:” ], ‘follow_analysis’: [ “前推荐多:”, “前推荐少:”, “后推荐多:”, “后推荐少:” ], ‘trend_analysis’: [ “和值:”, “质合比:”, “奇偶比:”, “断区推荐:”, “连号推荐:”, “冷热推荐:”, “后区热号:”, “后区冷号:”, “趋势号:” ], ‘number_generation’: [ # 修正为小写 “胆码:”, “前区:”, “后区:”, “推荐5注号码:”, “1:”, “”, “2:”, “”, “3:”, “”, “4:”, “”, “5:”, “” ], } # 初始化所有模块的条目引用 self.front_dan_entry = None self.back_dan_entry = None self.result_text = None self.exclude_front_entry = None self.exclude_back_entry = None self.front_entry = None self.back_entry = None def _setup_event_handlers(self): """初始化事件处理器""" event_center.subscribe(EventType.MODULE_COMPLETE, self._handle_module_complete) event_center.subscribe(EventType.UI_UPDATE, self._handle_ui_update) event_center.subscribe(EventType.EXCLUDE_NUMBERS, self._handle_exclude_numbers) def _setup_ui(self): self.root.title(f"大乐透智能分析平台 - {GlobalConfig.VERSION}") self.root.geometry("1400x800") # 添加主标题 title_frame = Frame(self.root) title_frame.pack(fill='x', pady=5) Label(title_frame, text="大乐透智能分析平台", font=('微软雅黑', 16, 'bold')).pack(expand=True) # 主容器 - 三栏布局 main_container = PanedWindow(self.root, orient=HORIZONTAL, sashrelief=RAISED, sashwidth=5) main_container.pack(fill='both', expand=True, padx=5, pady=(0, 5)) # 左侧面板 self.left_panel = Frame(main_container, width=200, bg="#eaeaea") main_container.add(self.left_panel, minsize=150, stretch="never") # 中间内容区 self.center_paned = PanedWindow(main_container, orient=VERTICAL, sashrelief=RAISED, sashwidth=5) main_container.add(self.center_paned, minsize=500, stretch="always") # 右侧面板 self.right_panel = Frame(main_container, width=700, bg="#f5f5f5") main_container.add(self.right_panel, minsize=250, stretch="never") # 初始化各区域 self._setup_left_panel() self._setup_center_area() self._setup_right_panel() def _setup_left_panel(self): """初始化左侧模块按钮区""" module_names = { 'input_analysis': '1. 输入分析', 'combination_analysis': '2. 组合分析', 'follow_analysis': '3. 跟随分析', 'trend_analysis': '4. 趋势分析', 'number_generation': '5. 数字生成' } for module in GlobalConfig.MODULES: Button( self.left_panel, text=module_names[module], width=18, command=lambda m=module: self._on_module_button_click(m) ).pack(pady=3, padx=5, ipady=3) def _setup_center_area(self): """设置中间区域布局,分为上下两部分""" # 上半部分 - 核心区 (固定高度) self.core_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.core_frame, minsize=150, stretch="never") # 核心区内容 Label(self.core_frame, text="核心区", font=('微软雅黑', 12, 'bold')).pack(anchor='w', padx=5, pady=2) # 核心数据展示 self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } for label, var_name in [ ("前区:", 'front_area'), ("后区:", 'back_area'), ("前区热号:", 'front_hot'), ("前区冷号:", 'front_cold'), ("后区热号:", 'back_hot'), ("后区冷号:", 'back_cold') ]: frame = Frame(self.core_frame) frame.pack(fill='x', padx=5, pady=2) Label(frame, text=label, width=10, anchor='w').pack(side='left') entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) entry = Entry(entry_container, textvariable=self.core_vars[var_name], font=('微软雅黑', 10), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) # 下半部分 - 动态区 self.dynamic_frame = Frame(self.center_paned, bd=1, relief='solid') self.center_paned.add(self.dynamic_frame, minsize=200, stretch="always") # 主容器使用Grid布局 self.dynamic_container = Frame(self.dynamic_frame) self.dynamic_container.pack(fill='both', expand=True) self.dynamic_container.grid_rowconfigure(0, weight=1) self.dynamic_container.grid_columnconfigure(0, weight=1) # 操作按钮放在右下角 self.btn_frame = Frame(self.dynamic_container) self.btn_frame.grid(row=1, column=0, sticky='se', pady=5, padx=5) Button(self.btn_frame, text="运行", width=8, command=self._run_current_module).pack(side='left', padx=2) Button(self.btn_frame, text="清除", width=8, command=self._clear_dynamic_content).pack(side='left', padx=2) Button(self.btn_frame, text="保存", width=8, command=self._save_dynamic_content).pack(side='left', padx=2) Button(self.btn_frame, text="刷新", width=8, command=self._refresh_dynamic).pack(side='left', padx=2) # 模块内容容器 - 确保先创建 self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') # 初始化默认内容 self._init_default_dynamic_content() def _init_default_dynamic_content(self): """初始化默认动态区内容""" # 清除现有内容 for widget in self.module_content_frame.winfo_children(): widget.destroy() # 创建新内容 self.dynamic_content = Frame(self.module_content_frame) self.dynamic_content.pack(fill='both', expand=True) Label(self.dynamic_content, text="请从左侧选择分析模块", font=('微软雅黑', 12)).pack(expand=True, pady=50) def _on_module_button_click(self, module: str): """模块按钮点击事件处理""" self.status_var.set(f"打开 {module} 模块...") self.current_module = module # 确保 module_content_frame 存在且可操作 if not hasattr(self, 'module_content_frame') or self.module_content_frame is None: self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') self._init_default_dynamic_content() logging.warning("module_content_frame 已紧急初始化") try: # 1️⃣ 彻底清除旧内容(包括残留按钮) for widget in self.module_content_frame.winfo_children(): widget.destroy() self.module_content_frame.update_idletasks() # 强制更新布局 # 2️⃣ 创建新内容容器 content_frame = Frame(self.module_content_frame) content_frame.pack(fill='both', expand=True) # 3️⃣ 根据模块类型创建具体内容 if module == "input_analysis": self._create_input_analysis_content(content_frame) elif module == "combination_analysis": self._create_combination_analysis_content(content_frame) elif module == "follow_analysis": self._create_follow_analysis_content(content_frame) elif module == "trend_analysis": self._create_trend_analysis_content(content_frame) elif module == "number_generation": self._create_number_generation_content(content_frame) # 4️⃣ 创建按钮容器(带唯一标识) # 先销毁已存在的按钮容器(严格检查类型和标记) for widget in self.module_content_frame.winfo_children(): if isinstance(widget, Frame) and hasattr(widget, 'is_button_container'): widget.destroy() # 创建新按钮容器并标记 btn_frame = Frame(self.module_content_frame) btn_frame.is_button_container = True # 标记为按钮容器 btn_frame.pack(side='bottom', fill='x', pady=5) btn_frame.pack_propagate(False) # 防止内部组件影响容器大小 # 按钮容器内部布局 btn_container = Frame(btn_frame) btn_container.pack(side='right') # 创建按钮(使用lambda时绑定当前模块状态) Button(btn_container, text="运行", width=8, command=lambda m=module: self._run_module(m)).pack(side='left', padx=2) Button(btn_container, text="清除", width=8, command=lambda m=module: self._clear_module_data(m)).pack(side='left', padx=2) Button(btn_container, text="保存", width=8, command=lambda m=module: self._save_module_data(m)).pack(side='left', padx=2) Button(btn_container, text="刷新", width=8, command=lambda m=module: self._on_module_button_click(m)).pack(side='left', padx=2) except Exception as e: # 5️⃣ 严重异常处理(强制重建界面) logging.critical(f"模块切换异常: {str(e)}", exc_info=True) if hasattr(self, 'module_content_frame'): self.module_content_frame.destroy() self.module_content_frame = Frame(self.dynamic_container) self.module_content_frame.grid(row=0, column=0, sticky='nsew') self._init_default_dynamic_content() def _run_current_module(self): """运行当前模块""" if self.current_module: self._run_module(self.current_module) def _clear_dynamic_content(self): """清除动态区内容""" if self.current_module: self._clear_module_data(self.current_module) # 额外确保清除结果文本框(如果存在) if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') def _save_dynamic_content(self): """保存动态区内容""" if self.current_module: self._save_module_data(self.current_module) else: messagebox.showinfo("提示", "请先选择并运行一个模块") def _refresh_dynamic(self): """刷新动态区内容""" if self.current_module: self._on_module_button_click(self.current_module) else: messagebox.showinfo("提示", "请先选择一个模块") def _organize_data(self): """整理号码池数据""" try: # 发布整理事件 event = Event( event_id=int(time.time()), type=EventType.ORGANIZE_DATA, source='main_ui', target='pool' ) event_center.publish(event) self.status_var.set("号码池数据已整理") except Exception as e: messagebox.showerror("整理失败", str(e)) logging.error(f"整理数据失败: {str(e)}", exc_info=True) def _freeze_data(self): """冻结号码池数据""" try: # 发布冻结事件 event = Event( event_id=int(time.time()), type=EventType.FREEZE_DATA, source='main_ui', target='pool' ) event_center.publish(event) self.status_var.set("号码池数据已冻结") except Exception as e: messagebox.showerror("冻结失败", str(e)) logging.error(f"冻结数据失败: {str(e)}", exc_info=True) def _run_module(self, module: str): """运行指定模块""" if module == "input_analysis": # 获取排除号码 exclude_front = self.exclude_front_entry.get() exclude_back = self.exclude_back_entry.get() # 发布排除号码事件 exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module ) event_center.publish(run_event) def _create_ui_element(self, parent, label_text): """创建统一的UI元素(与核心区对齐)""" frame = Frame(parent) frame.pack(fill='x', pady=2) # 标签固定宽度与核心区对齐 Label(frame, text=label_text, width=10, anchor='w').pack(side='left') # 条目容器 - 宽度与核心区对齐 entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True) return entry_container def _setup_right_panel(self): """设置右侧号码池布局""" # 号码池标题 pool_title_frame = Frame(self.right_panel) pool_title_frame.pack(fill='x', pady=5) Label(pool_title_frame, text="号码池", font=('微软雅黑', 12, 'bold')).pack(anchor='w') # 号码池内容区(添加边框和2px内边距) pool_content = Frame(self.right_panel, bd=1, relief='solid', padx=2, pady=2) pool_content.pack(fill='both', expand=True, padx=5, pady=5) # 创建Canvas和Scrollbar canvas = Canvas(pool_content, highlightthickness=0) scrollbar = Scrollbar(pool_content, orient="vertical", command=canvas.yview) scrollable_frame = Frame(canvas) scrollable_frame.bind( "<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) # 号码池项目 - 优化布局和样式(带2px右边距) for label, var_name, row_id in GlobalConfig.UI_CONFIG: frame = Frame(scrollable_frame) frame.grid(row=row_id, column=0, sticky='ew', padx=0, pady=1) # 移除水平padding # 左侧标签(固定宽度8字符) lbl = Label(frame, text=label, width=8, anchor='w') lbl.pack(side='left', padx=(0, 5)) # 标签右侧留5px间距 # 右侧输入框容器(带2px右边距) entry_container = Frame(frame) entry_container.pack(side='left', fill='x', expand=True, padx=(0, 2)) var = StringVar() self.pool_vars[var_name] = var entry = Entry(entry_container, textvariable=var, font=('微软雅黑', 9), state='readonly', readonlybackground='#f0f0f0', relief='sunken', bd=1) entry.pack(fill='x', expand=True) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 底部按钮区 btn_frame = Frame(self.right_panel) btn_frame.pack(fill='x', pady=5) Button(btn_frame, text="整理", width=10, command=self._organize_data).pack(side='left', padx=5) Button(btn_frame, text="冻结", width=10, command=self._freeze_data).pack(side='left', padx=5) Button(btn_frame, text="导出", width=10).pack(side='left', padx=5) def _create_input_analysis_content(self, parent: Frame): """创建输入分析模块内容 - 增加模块ID标识""" # 主容器使用Grid布局 main_frame = Frame(parent) main_frame.pack(fill='both', expand=True) # 添加模块ID标识(右上角) module_id_label = Label(main_frame, text=f"模块ID: {GlobalConfig.MODULE1_ID}", font=('微软雅黑', 8), fg='gray') module_id_label.pack(anchor='ne', padx=10, pady=5) # === 排除号码区 === exclude_frame = LabelFrame(main_frame, text=" 排除号码 ", font=('微软雅黑', 12, 'bold')) exclude_frame.pack(fill='x', padx=20, pady=10, ipady=5) # 使用预定义的标签文本 labels = self.labels['input_analysis'] # 排除号码标签 Label(exclude_frame, text=labels[0], font=('微软雅黑', 10, 'bold')).pack(anchor='w', padx=10, pady=(5, 0)) # 前区排除 Label(exclude_frame, text=labels[1], font=('微软雅黑', 10)).pack(anchor='w', padx=10, pady=5) front_entries_frame = Frame(exclude_frame) front_entries_frame.pack(fill='x', padx=10, pady=5) self.exclude_front_entries = [] for i in range(10): # 10个前区输入框 entry_frame = Frame(front_entries_frame) entry_frame.pack(side='left', padx=2) Label(entry_frame, text=f"{i + 1}:").pack(side='left') entry = Entry(entry_frame, width=3, font=('微软雅黑', 10)) entry.pack(side='left') # 添加验证函数 - 前区限制1-35 entry.config(validate="key", validatecommand=( entry.register(lambda text: self._validate_number(text, 1, 35)), '%P')) entry.bind("<KeyRelease>", self._auto_format_entry) entry.bind("<Left>", lambda e, d=-1: self._navigate_entry(e, d)) entry.bind("<Right>", lambda e, d=1: self._navigate_entry(e, d)) self.exclude_front_entries.append(entry) # 后区排除 Label(exclude_frame, text=labels[2], font=('微软雅黑', 10)).pack(anchor='w', padx=10, pady=(10, 5)) back_entries_frame = Frame(exclude_frame) back_entries_frame.pack(fill='x', padx=10, pady=5) self.exclude_back_entries = [] for i in range(10): # 10个后区输入框 entry_frame = Frame(back_entries_frame) entry_frame.pack(side='left', padx=2) Label(entry_frame, text=f"{i + 1}:").pack(side='left') entry = Entry(entry_frame, width=3, font=('微软雅黑', 10)) entry.pack(side='left') # 添加验证函数 - 后区限制1-12 entry.config(validate="key", validatecommand=( entry.register(lambda text: self._validate_number(text, 1, 12)), '%P')) entry.bind("<KeyRelease>", self._auto_format_entry) entry.bind("<Left>", lambda e, d=-1: self._navigate_entry(e, d)) entry.bind("<Right>", lambda e, d=1: self._navigate_entry(e, d)) self.exclude_back_entries.append(entry) # 加载保存的数据 - 关键修改点 # 加载保存的数据 - 使用正确的路径常量 if os.path.exists(GlobalConfig.MODULE1_SAVE_PATH): # 修复路径变量名 try: with open(GlobalConfig.MODULE1_SAVE_PATH, 'r', encoding='utf-8') as f: # 修复路径变量名 saved_data = json.load(f) # 恢复前区排除号码 if 'exclude_front' in saved_data: for i, num in enumerate(saved_data['exclude_front']): if i < len(self.exclude_front_entries): formatted_num = f"{int(num):02d}" if num.isdigit() else num self.exclude_front_entries[i].delete(0, 'end') self.exclude_front_entries[i].insert(0, formatted_num) # 恢复后区排除号码 if 'exclude_back' in saved_data: for i, num in enumerate(saved_data['exclude_back']): if i < len(self.exclude_back_entries): formatted_num = f"{int(num):02d}" if num.isdigit() else num self.exclude_back_entries[i].delete(0, 'end') self.exclude_back_entries[i].insert(0, formatted_num) except Exception as e: logging.error(f"加载保存数据失败: {str(e)}") # 确保保存了排除号码输入框的引用 self.exclude_front_entry = self.exclude_front_entries[0] # 保存第一个前区排除输入框 self.exclude_back_entry = self.exclude_back_entries[0] # 保存第一个后区排除输入框 # === 推荐号码区 === recommend_frame = LabelFrame(main_frame, text=" 推荐号码 ", font=('微软雅黑', 12, 'bold')) recommend_frame.pack(fill='x', padx=20, pady=10, ipady=5) # 推荐号码标签 Label(recommend_frame, text=labels[3], font=('微软雅黑', 10, 'bold')).pack(anchor='w', padx=10, pady=(5, 0)) # 前区推荐 front_rec_frame = Frame(recommend_frame) front_rec_frame.pack(fill='x', padx=10, pady=5) Label(front_rec_frame, text=labels[4], font=('微软雅黑', 10)).pack(side='left') self.recommend_front_var = StringVar() Entry(front_rec_frame, textvariable=self.recommend_front_var, state='readonly', font=('微软雅黑', 10), readonlybackground='#f0f5f0').pack(side='left', fill='x', expand=True, padx=5) # 后区推荐 back_rec_frame = Frame(recommend_frame) back_rec_frame.pack(fill='x', padx=10, pady=5) Label(back_rec_frame, text=labels[5], font=('微软雅黑', 10)).pack(side='left') self.recommend_back_var = StringVar() Entry(back_rec_frame, textvariable=self.recommend_back_var, state='readonly', font=('微软雅黑', 10), readonlybackground='#f0f5f0').pack(side='left', fill='x', expand=True, padx=5) # === 结果区 === result_frame = LabelFrame(main_frame, text=" 分析结果 ", font=('微软雅黑', 12, 'bold')) result_frame.pack(fill='both', expand=True, padx=20, pady=10, ipady=5) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word', font=('微软雅黑', 10)) self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) # 添加初始提示 self.result_text.insert('end', "请设置排除号码后点击'运行'按钮开始分析\n") # 强制刷新界面 self.root.update_idletasks() def _handle_entry_input(self, event): """处理输入框相关事件的总入口""" if event.keysym in ('Left', 'Right'): # 处理方向键导航 self._navigate_entry(event, 1 if event.keysym == 'Right' else -1) else: # 处理输入自动格式化 self._auto_format_entry(event) def _auto_format_entry(self, event): """ 自动格式化输入框内容 功能: 1. 自动将1-9的数字补零显示为01-09 2. 输入满2位后自动跳到下一个输入框 """ entry = event.widget current = entry.get().strip() if current.isdigit(): # 只处理数字输入 # 自动补零处理 formatted = self._format_number(current) if formatted != current: entry.delete(0, 'end') entry.insert(0, formatted) # 输入满2位后自动跳转 if len(current) == 2: self._focus_adjacent_entry(event.widget, 1) # 正向跳转 def _focus_adjacent_entry(self, current_entry, direction): """ 焦点跳转到相邻输入框 :param current_entry: 当前输入框控件 :param direction: 跳转方向 (1:下一个, -1:上一个) """ all_entries = self.exclude_front_entries + self.exclude_back_entries try: current_index = all_entries.index(current_entry) target_index = current_index + direction if 0 <= target_index < len(all_entries): all_entries[target_index].focus() except ValueError: pass def _navigate_entry(self, event, direction): """使用方向键在输入框间导航""" self._focus_adjacent_entry(event.widget, direction) def _format_number(self, num_str: str) -> str: """ 格式化数字为两位数 :param num_str: 输入的数字字符串 :return: 格式化后的两位数字符串 """ if not num_str.isdigit(): return num_str # 非数字不处理 num = int(num_str) if 1 <= num <= 9: # 1-9的数字补零 return f"0{num}" return str(num) if num > 0 else num_str # 保留0和负数原样 def _create_combination_analysis_content(self, parent: Frame): """创建组合分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 使用预定义的labels for label in self.labels['combination_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) # 保存对控件的引用 var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "前区热号": self.front_hot_entry = entry elif var_name == "前数字频": self.front_freq_entry = entry elif var_name == "前频繁推": self.front_freq_rec_entry = entry elif var_name == "后区热号": self.back_hot_entry = entry elif var_name == "后数字频": self.back_freq_entry = entry elif var_name == "后低频推": self.back_infreq_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_follow_analysis_content(self, parent: Frame): """创建跟随分析模块的特定内容(修复版)""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 使用预定义的labels(确保不重复创建按钮) for label in self.labels['follow_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) # 保存控件引用 var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 结果显示区(不包含按钮) result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_trend_analysis_content(self, parent: Frame): """创建趋势分析模块的特定内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) for label in self.labels['trend_analysis']: frame = Frame(content_frame) frame.pack(fill='x', pady=2) Label(frame, text=label, width=12, anchor='w', font=('微软雅黑', 10, 'bold')).pack(side='left') entry = Entry(frame, width=30, state='readonly', readonlybackground='#f0f0f0') entry.pack(side='left', padx=5) var_name = label.replace(':', '').replace(' ', '') setattr(self, f"{var_name}_entry", entry) # 直接保存到实例变量 if var_name == "和值": self.sum_value_entry = entry elif var_name == "质合比": self.prime_ratio_entry = entry elif var_name == "奇偶比": self.odd_even_ratio_entry = entry elif var_name == "断区推荐": self.zone_rec_entry = entry elif var_name == "连号推荐": self.consec_rec_entry = entry elif var_name == "冷热推荐": self.hot_cold_rec_entry = entry elif var_name == "后区热号": self.hot_rec_entry = entry elif var_name == "后区冷号": self.cold_rec_entry = entry elif var_name == "趋势号": self.trend_rec_entry = entry # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _create_number_generation_content(self, parent: Frame): """创建数字生成模块的动态内容""" content_frame = Frame(parent) content_frame.pack(fill='both', expand=True, padx=10, pady=10) # 胆码输入区 dan_frame = Frame(content_frame) dan_frame.pack(fill='x', pady=5) # 前区胆码 front_dan_frame = Frame(dan_frame) front_dan_frame.pack(fill='x') Label(front_dan_frame, text="前区胆码:").pack(side='left') self.front_dan_entries = [] for i in range(5): entry = Entry(front_dan_frame, width=3) entry.pack(side='left', padx=2) self.front_dan_entries.append(entry) self.front_dan_entry = self.front_dan_entries[0] # 保存第一个条目引用 # 后区胆码 back_dan_frame = Frame(dan_frame) back_dan_frame.pack(fill='x') Label(back_dan_frame, text="后区胆码:").pack(side='left') self.back_dan_entries = [] for i in range(5): entry = Entry(back_dan_frame, width=3) entry.pack(side='left', padx=2) self.back_dan_entries.append(entry) self.back_dan_entry = self.back_dan_entries[0] # 保存第一个条目引用 # 生成的号码显示区 generated_frame = Frame(content_frame) generated_frame.pack(fill='x', pady=5) Label(generated_frame, text="生成号码:").pack(anchor='w') self.generated_labels = [] for i in range(1, 6): frame = Frame(generated_frame) frame.pack(fill='x') Label(frame, text=f"{i}. ").pack(side='left') label = Label(frame, text="", width=30, anchor='w') label.pack(side='left') self.generated_labels.append(label) # 结果显示区 result_frame = Frame(content_frame) result_frame.pack(fill='both', expand=True) scrollbar = Scrollbar(result_frame) scrollbar.pack(side='right', fill='y') self.result_text = Text(result_frame, yscrollcommand=scrollbar.set, wrap='word') self.result_text.pack(fill='both', expand=True) scrollbar.config(command=self.result_text.yview) def _run_module(self, module: str): """运行指定模块(增加模块ID标识)""" if module == "input_analysis": # 获取并格式化排除号码 exclude_front_list = [] for entry in self.exclude_front_entries: num = entry.get() if num: # 只处理非空输入 formatted = self._format_number(num) exclude_front_list.append(formatted) exclude_back_list = [] for entry in self.exclude_back_entries: num = entry.get() if num: # 只处理非空输入 formatted = self._format_number(num) exclude_back_list.append(formatted) exclude_front = ' '.join(exclude_front_list) exclude_back = ' '.join(exclude_back_list) # 发布排除号码事件(包含模块ID) exclude_event = Event( event_id=int(time.time()), type=EventType.EXCLUDE_NUMBERS, source='main_ui', target='pool', data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back, 'module_id': GlobalConfig.MODULE1_ID # 添加模块ID } ) event_center.publish(exclude_event) # 在结果文本中记录 self.result_text.insert('end', f"已设置排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") # 发布模块运行事件 run_event = Event( event_id=int(time.time()), type=EventType.MODULE_RUN, source='main_ui', target=module, data={ 'exclude_front': exclude_front, 'exclude_back': exclude_back } ) event_center.publish(run_event) # 生成推荐号码 self._generate_recommend_numbers(exclude_front, exclude_back) def _generate_recommend_numbers(self, exclude_front: str, exclude_back: str): """生成推荐号码(示例逻辑)""" # 实际应用中应调用分析模块生成推荐号码 # 这里简化为生成随机推荐号码 import random # 前区号码范围1-35 all_front = [str(idx) for idx in range(1, 36)] exclude_front_list = exclude_front.split() if exclude_front else [] available_front = [num for num in all_front if num not in exclude_front_list] # 后区号码范围1-12 all_back = [str(idx) for idx in range(1, 13)] exclude_back_list = exclude_back.split() if exclude_back else [] available_back = [num for num in all_back if num not in exclude_back_list] # 随机选择5个前区号码 if len(available_front) >= 5: recommend_front = random.sample(available_front, 5) else: recommend_front = random.sample(all_front, 5) # 随机选择2个后区号码 if len(available_back) >= 2: recommend_back = random.sample(available_back, 2) else: recommend_back = random.sample(all_back, 2) # 更新推荐号码显示 self.recommend_front_var.set(' '.join(sorted(recommend_front, key=int))) self.recommend_back_var.set(' '.join(sorted(recommend_back, key=int))) # 在结果文本中记录 self.result_text.insert('end', f"生成推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 更新号码池 self._update_pool_with_recommendations(self.recommend_front_var.get(), self.recommend_back_var.get()) def update_recommendations(self, fronts: List[int], backs: List[int]): """更新推荐号码显示(使用号码池路径保存)""" formatted_fronts = [str(num).zfill(2) for num in fronts] formatted_backs = [str(num).zfill(2) for num in backs] self.recommend_front_var.set(' '.join(formatted_fronts)) self.recommend_back_var.set(' '.join(formatted_backs)) # 保存到号码池文件 try: pool_data = { 'recommended_fronts': formatted_fronts, 'recommended_backs': formatted_backs, 'module_id': GlobalConfig.MODULE1_ID, 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S") } with open(GlobalConfig.POOL_SAVE_PATH, 'w', encoding='utf-8') as f: json.dump(pool_data, f, indent=2, ensure_ascii=False) logging.info(f"推荐号码已保存到号码池: {GlobalConfig.POOL_SAVE_PATH}") except Exception as e: logging.error(f"保存号码池失败: {str(e)}") def _clear_module_data(self, module: str): """清除模块数据""" if module == "input_analysis": if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') if hasattr(self, 'recommend_front_var'): self.recommend_front_var.set('') if hasattr(self, 'recommend_back_var'): self.recommend_back_var.set('') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') elif module == "combination_analysis": if hasattr(self, 'front_hot_entry') and self.front_hot_entry: self.front_hot_entry.delete(0, 'end') if hasattr(self, 'front_freq_entry') and self.front_freq_entry: self.front_freq_entry.delete(0, 'end') if hasattr(self, 'front_freq_rec_entry') and self.front_freq_rec_entry: self.front_freq_rec_entry.delete(0, 'end') if hasattr(self, 'back_hot_entry') and self.back_hot_entry: self.back_hot_entry.delete(0, 'end') if hasattr(self, 'back_freq_entry') and self.back_freq_entry: self.back_freq_entry.delete(0, 'end') if hasattr(self, 'back_infreq_rec_entry') and self.back_infreq_rec_entry: self.back_infreq_rec_entry.delete(0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'result_text') and self.result_text: self.result_text.delete(1.0, 'end') if hasattr(self, 'front_entry') and self.front_entry: self.front_entry.delete(0, 'end') if hasattr(self, 'back_entry') and self.back_entry: self.back_entry.delete(0, 'end') if hasattr(self, 'exclude_front_entry') and self.exclude_front_entry: self.exclude_front_entry.delete(0, 'end') if hasattr(self, 'exclude_back_entry') and self.exclude_back_entry: self.exclude_back_entry.delete(0, 'end') def _save_module_data(self, module: str): """保存模块数据(修复路径问题)""" try: data = {} if module == "input_analysis": # 收集排除号码 exclude_front_list = [] for entry in self.exclude_front_entries: num = entry.get() if num: exclude_front_list.append(num) exclude_back_list = [] for entry in self.exclude_back_entries: num = entry.get() if num: exclude_back_list.append(num) data = { 'exclude_front': exclude_front_list, 'exclude_back': exclude_back_list, 'module_id': GlobalConfig.MODULE1_ID, 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S") } # 使用正确的路径常量 filename = GlobalConfig.MODULE1_SAVE_PATH # 修复路径变量名 with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) messagebox.showinfo("保存成功", f"排除号码已保存到{filename}") elif module == "combination_analysis": # 其他模块的保存逻辑... pass elif module == "combination_analysis": data['front_hot'] = self.front_hot_entry.get() data['front_freq'] = self.front_freq_entry.get() data['front_freq_rec'] = self.front_freq_rec_entry.get() data['back_hot'] = self.back_hot_entry.get() data['back_freq'] = self.back_freq_entry.get() data['back_infreq_rec'] = self.back_infreq_rec_entry.get() data['result'] = self.result_text.get(1.0, 'end') # 其他模块数据收集... # 使用模块专用保存路径 filename = GlobalConfig.MODULE1_SAVE_PATH with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) messagebox.showinfo("保存成功", f"排除号码已保存到{filename}") except Exception as e: messagebox.showerror("保存失败", str(e)) logging.error(f"保存数据失败: {str(e)}", exc_info=True) def _handle_exclude_numbers(self, event: Event): """处理排除号码事件""" if event.data: exclude_front = event.data.get('exclude_front', '') exclude_back = event.data.get('exclude_back', '') # 更新排除号码显示 self.exclude_front_entry.delete(0, 'end') self.exclude_front_entry.insert(0, exclude_front) self.exclude_back_entry.delete(0, 'end') self.exclude_back_entry.insert(0, exclude_back) # 在结果文本中记录 self.result_text.insert('end', f"收到排除号码: 前区 {exclude_front}, 后区 {exclude_back}\n") def _handle_module_complete(self, event: Event): self.status_var.set(f"{event.source} 模块运行完成") if event.source == "input_analysis" and hasattr(self, 'result_text') and self.result_text: # 更新推荐号码显示 if 'recommend_front' in event.data: self.recommend_front_var.set(event.data['recommend_front']) if 'recommend_back' in event.data: self.recommend_back_var.set(event.data['recommend_back']) # 在结果文本中记录 self.result_text.insert('end', f"\n{event.source} 模块已完成分析\n") self.result_text.insert('end', f"推荐号码: 前区 {self.recommend_front_var.get()}, 后区 {self.recommend_back_var.get()}\n") # 同步更新号码池 update_event = Event( event_id=int(time.time()), type=EventType.POOL_UPDATE, source='input_analysis', target='pool', data={ 'front_numbers': self.recommend_front_var.get(), 'back_numbers': self.recommend_back_var.get() } ) event_center.publish(update_event) self.result_text.insert('end', "号码池已同步更新\n") def _on_module_renovate(self, module: str): """刷新模块""" if module == self.current_module: self._on_module_button_click(module) def _handle_ui_update(self, event: Event): """处理UI更新事件""" if not event.data or 'update_type' not in event.data: return update_type = event.data['update_type'] data = event.data.get('data', {}) # 处理核心变量更新 if update_type == 'organized_data': # 确保所有核心变量已初始化 if not hasattr(self, 'core_vars'): self.core_vars = { 'front_area': StringVar(), 'back_area': StringVar(), 'front_hot': StringVar(), 'front_cold': StringVar(), 'back_hot': StringVar(), 'back_cold': StringVar() } # 更新界面变量 self.core_vars['front_area'].set(str(data.get('front_numbers', []))) self.core_vars['back_area'].set(str(data.get('back_numbers', []))) self.core_vars['front_hot'].set(str(data.get('front_hot', []))) self.core_vars['front_cold'].set(str(data.get('front_cold', []))) self.core_vars['back_hot'].set(str(data.get('back_hot', []))) self.core_vars['back_cold'].set(str(data.get('back_cold', []))) ==================== 主程序 ==================== def main(): try: root = Tk() root.geometry(“1200x800”) # 初始化核心组件 pool = NumberPool() app.main_ui = MainInterface(root, pool) # 初始化所有模块 modules = { 'input_analysis': InputAnalysisModule, 'combination_analysis': CombinationAnalysisModule, 'follow_analysis': FollowAnalysisModule, 'trend_analysis': TrendAnalysisModule, 'number_generation': NumberGenerationModule } # 先创建所有模块实例 module_instances = {} for name, cls in modules.items(): module_instances[name] = cls(name) time.sleep(0.1) # 确保顺序初始化 root.mainloop() except Exception as e: logging.error(f"系统启动失败: {str(e)}", exc_info=True) sys.exit(1) if name == “main”: main()

import os import cv2 import sys import argparse import time # add path realpath = os.path.abspath(__file__) _sep = os.path.sep realpath = realpath.split(_sep) try: zoo_root_index = next(i for i, part in enumerate(realpath) if 'rknn_model_zoo' in part) rknn_model_zoo_path = os.path.join(realpath[0]+_sep, *realpath[1:zoo_root_index+1]) sys.path.append(rknn_model_zoo_path) except StopIteration: raise ValueError("Could not find 'rknn_model_zoo' directory in the path: {}".format(os.path.abspath(__file__))) from py_utils.coco_utils import COCO_test_helper import numpy as np OBJ_THRESH = 0.25 NMS_THRESH = 0.45 # The follew two param is for map test # OBJ_THRESH = 0.001 # NMS_THRESH = 0.65 IMG_SIZE = (640, 640) # (width, height), such as (1280, 736) CLASSES = ("car","white") coco_id_list = [1,2] def filter_boxes(boxes, box_confidences, box_class_probs): """Filter boxes with object threshold. """ box_confidences = box_confidences.reshape(-1) candidate, class_num = box_class_probs.shape class_max_score = np.max(box_class_probs, axis=-1) classes = np.argmax(box_class_probs, axis=-1) _class_pos = np.where(class_max_score* box_confidences >= OBJ_THRESH) scores = (class_max_score* box_confidences)[_class_pos] boxes = boxes[_class_pos] classes = classes[_class_pos] return boxes, classes, scores def nms_boxes(boxes, scores): """Suppress non-maximal boxes. # Returns keep: ndarray, index of effective boxes. """ x = boxes[:, 0] y = boxes[:, 1] w = boxes[:, 2] - boxes[:, 0] h = boxes[:, 3] - boxes[:, 1] areas = w * h order = scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) xx1 = np.maximum(x[i], x[order[1:]]) yy1 = np.maximum(y[i], y[order[1:]]) xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]]) yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]]) w1 = np.maximum(0.0, xx2 - xx1 + 0.00001) h1 = np.maximum(0.0, yy2 - yy1 + 0.00001) inter = w1 * h1 ovr = inter / (areas[i] + areas[order[1:]] - inter) inds = np.where(ovr <= NMS_THRESH)[0] order = order[inds + 1] keep = np.array(keep) return keep def dfl(position): # Distribution Focal Loss (DFL) import torch x = torch.tensor(position) n,c,h,w = x.shape p_num = 4 mc = c//p_num y = x.reshape(n,p_num,mc,h,w) y = y.softmax(2) acc_metrix = torch.tensor(range(mc)).float().reshape(1,1,mc,1,1) y = (y*acc_metrix).sum(2) return y.numpy() def box_process(position): grid_h, grid_w = position.shape[2:4] col, row = np.meshgrid(np.arange(0, grid_w), np.arange(0, grid_h)) col = col.reshape(1, 1, grid_h, grid_w) row = row.reshape(1, 1, grid_h, grid_w) grid = np.concatenate((col, row), axis=1) stride = np.array([IMG_SIZE[1]//grid_h, IMG_SIZE[0]//grid_w]).reshape(1,2,1,1) position = dfl(position) box_xy = grid +0.5 -position[:,0:2,:,:] box_xy2 = grid +0.5 +position[:,2:4,:,:] xyxy = np.concatenate((box_xy*stride, box_xy2*stride), axis=1) return xyxy def post_process(input_data): boxes, scores, classes_conf = [], [], [] defualt_branch=3 pair_per_branch = len(input_data)//defualt_branch # Python 忽略 score_sum 输出 for i in range(defualt_branch): boxes.append(box_process(input_data[pair_per_branch*i])) classes_conf.append(input_data[pair_per_branch*i+1]) scores.append(np.ones_like(input_data[pair_per_branch*i+1][:,:1,:,:], dtype=np.float32)) def sp_flatten(_in): ch = _in.shape[1] _in = _in.transpose(0,2,3,1) return _in.reshape(-1, ch) boxes = [sp_flatten(_v) for _v in boxes] classes_conf = [sp_flatten(_v) for _v in classes_conf] scores = [sp_flatten(_v) for _v in scores] boxes = np.concatenate(boxes) classes_conf = np.concatenate(classes_conf) scores = np.concatenate(scores) # filter according to threshold boxes, classes, scores = filter_boxes(boxes, scores, classes_conf) # nms nboxes, nclasses, nscores = [], [], [] for c in set(classes): inds = np.where(classes == c) b = boxes[inds] c = classes[inds] s = scores[inds] keep = nms_boxes(b, s) if len(keep) != 0: nboxes.append(b[keep]) nclasses.append(c[keep]) nscores.append(s[keep]) if not nclasses and not nscores: return None, None, None boxes = np.concatenate(nboxes) classes = np.concatenate(nclasses) scores = np.concatenate(nscores) return boxes, classes, scores def draw(image, boxes, scores, classes): for box, score, cl in zip(boxes, scores, classes): top, left, right, bottom = [int(_b) for _b in box] print("%s @ (%d %d %d %d) %.3f" % (CLASSES[cl], top, left, right, bottom, score)) cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2) cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score), (top, left - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) def setup_model(args): model_path = args.model_path if model_path.endswith('.pt') or model_path.endswith('.torchscript'): platform = 'pytorch' from py_utils.pytorch_executor import Torch_model_container model = Torch_model_container(args.model_path) elif model_path.endswith('.rknn'): platform = 'rknn' from py_utils.rknn_executor import RKNN_model_container model = RKNN_model_container(args.model_path, args.target, args.device_id) elif model_path.endswith('onnx'): platform = 'onnx' from py_utils.onnx_executor import ONNX_model_container model = ONNX_model_container(args.model_path) else: assert False, "{} is not rknn/pytorch/onnx model".format(model_path) print('Model-{} is {} model, starting val'.format(model_path, platform)) return model, platform def img_check(path): img_type = ['.jpg', '.jpeg', '.png', '.bmp'] for _type in img_type: if path.endswith(_type) or path.endswith(_type.upper()): return True return False if __name__ == '__main__': # Create a dummy args object class Args: pass args = Args() args.model_path = '/home/cat/NPU/rknn_model_zoo-main/examples/yolov8/model/whitenu8.rknn' args.target = 'rk3576' args.device_id = None # init model model, platform = setup_model(args) co_helper = COCO_test_helper(enable_letter_box=True) # init camera cap = cv2.VideoCapture(0) if not cap.isOpened(): print("Error: Could not open camera.") exit() print("Press 'q' to quit.") # run test while True: start_time = time.time() ret, img_src = cap.read() if not ret: print("Error: Failed to capture frame.") break # Due to rga init with (0,0,0), we using pad_color (0,0,0) instead of (114, 114, 114) pad_color = (0,0,0) img = co_helper.letter_box(im= img_src.copy(), new_shape=(IMG_SIZE[1], IMG_SIZE[0]), pad_color=(0,0,0)) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # preprocee if not rknn model if platform in ['pytorch', 'onnx']: input_data = img.transpose((2,0,1)) input_data = input_data.reshape(1,*input_data.shape).astype(np.float32) input_data = input_data/255. else: input_data = img outputs = model.run([input_data]) boxes, classes, scores = post_process(outputs) img_p = img_src.copy() if boxes is not None: draw(img_p, co_helper.get_real_box(boxes), scores, classes) end_time = time.time() fps = 1 / (end_time - start_time) cv2.putText(img_p, f"FPS: {int(fps)}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow("yolov8 detection", img_p) if cv2.waitKey(1) & 0xFF == ord('q'): break # release cap.release() cv2.destroyAllWindows() model.release()

检查代码是否可运行,是否高效,是否可CPUimport sys import os import json import time import wave import numpy as np import pandas as pd import matplotlib.pyplot as plt import soundfile as sf from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QTextEdit, QFileDialog, QProgressBar, QGroupBox, QComboBox, QCheckBox, QMessageBox) from PyQt5.QtCore import QThread, pyqtSignal from pydub import AudioSegment from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification import whisper from pyannote.audio import Pipeline from docx import Document from docx.shared import Inches import librosa import tempfile from collections import defaultdict import re from concurrent.futures import ThreadPoolExecutor, as_completed import torch from torch.cuda import is_available as cuda_available import logging import gc # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 全局模型缓存 MODEL_CACHE = {} class AnalysisThread(QThread): progress = pyqtSignal(int) message = pyqtSignal(str) analysis_complete = pyqtSignal(dict) error = pyqtSignal(str) def __init__(self, audio_files, keyword_file, whisper_model_path, pyannote_model_path, emotion_model_path): super().__init__() self.audio_files = audio_files self.keyword_file = keyword_file self.whisper_model_path = whisper_model_path self.pyannote_model_path = pyannote_model_path self.emotion_model_path = emotion_model_path self.running = True self.cached_models = {} self.temp_files = [] # 用于管理临时文件 self.lock = torch.multiprocessing.Lock() # 用于模型加载的锁 def run(self): try: # 加载关键词 self.message.emit("正在加载关键词...") keywords = self.load_keywords() # 预加载模型 self.message.emit("正在预加载模型...") self.preload_models() results = [] total_files = len(self.audio_files) for idx, audio_file in enumerate(self.audio_files): if not self.running: self.message.emit("分析已停止") return self.message.emit(f"正在处理文件: {os.path.basename(audio_file)} ({idx + 1}/{total_files})") file_result = self.analyze_file(audio_file, keywords) if file_result: results.append(file_result) # 定期清理内存 if idx % 5 == 0: gc.collect() torch.cuda.empty_cache() if cuda_available() else None self.progress.emit(int((idx + 1) / total_files * 100)) self.analysis_complete.emit({"results": results, "keywords": keywords}) self.message.emit("分析完成!") except Exception as e: import traceback error_msg = f"分析过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.error.emit(error_msg) logger.error(error_msg) finally: # 清理临时文件 self.cleanup_temp_files() def cleanup_temp_files(self): """清理所有临时文件""" for temp_file in self.temp_files: if os.path.exists(temp_file): try: os.unlink(temp_file) except Exception as e: logger.warning(f"删除临时文件失败: {temp_file}, 原因: {str(e)}") def preload_models(self): """预加载所有模型到缓存(添加线程安全)""" global MODEL_CACHE # 使用锁确保线程安全 with self.lock: # 检查全局缓存是否已加载模型 if 'whisper' in MODEL_CACHE and 'pyannote' in MODEL_CACHE and 'emotion_classifier' in MODEL_CACHE: self.cached_models = MODEL_CACHE self.message.emit("使用缓存的模型") return self.cached_models = {} try: # 加载语音识别模型 if 'whisper' not in MODEL_CACHE: self.message.emit("正在加载语音识别模型...") MODEL_CACHE['whisper'] = whisper.load_model( self.whisper_model_path, device="cuda" if cuda_available() else "cpu" ) self.cached_models['whisper'] = MODEL_CACHE['whisper'] # 加载说话人分离模型 if 'pyannote' not in MODEL_CACHE: self.message.emit("正在加载说话人分离模型...") MODEL_CACHE['pyannote'] = Pipeline.from_pretrained( self.pyannote_model_path, use_auth_token=True ) self.cached_models['pyannote'] = MODEL_CACHE['pyannote'] # 加载情感分析模型 if 'emotion_classifier' not in MODEL_CACHE: self.message.emit("正在加载情感分析模型...") device = 0 if cuda_available() else -1 tokenizer = AutoTokenizer.from_pretrained(self.emotion_model_path) model = AutoModelForSequenceClassification.from_pretrained(self.emotion_model_path) # 尝试使用半精度浮点数减少内存占用 try: if device != -1: model = model.half() except Exception: pass # 如果失败则继续使用全精度 MODEL_CACHE['emotion_classifier'] = pipeline( "text-classification", model=model, tokenizer=tokenizer, device=device ) self.cached_models['emotion_classifier'] = MODEL_CACHE['emotion_classifier'] except Exception as e: raise Exception(f"模型加载失败: {str(e)}") def analyze_file(self, audio_file, keywords): """分析单个音频文件(优化内存使用)""" try: # 确保音频为WAV格式 wav_file, is_temp = self.convert_to_wav(audio_file) if is_temp: self.temp_files.append(wav_file) # 获取音频信息 duration, sample_rate, channels = self.get_audio_info(wav_file) # 说话人分离 - 使用较小的音频片段处理大文件 diarization = self.process_diarization(wav_file, duration) # 识别客服和客户 agent_segments, customer_segments = self.identify_speakers(wav_file, diarization, keywords['opening']) # 并行处理客服和客户音频 agent_result, customer_result = {}, {} with ThreadPoolExecutor(max_workers=2) as executor: agent_future = executor.submit( self.process_speaker_audio, wav_file, agent_segments, "客服" ) customer_future = executor.submit( self.process_speaker_audio, wav_file, customer_segments, "客户" ) agent_result = agent_future.result() customer_result = customer_future.result() # 情感分析 - 批处理提高效率 agent_emotion, customer_emotion = self.analyze_emotions( [agent_result.get('text', ''), customer_result.get('text', '')] ) # 服务规范检查 opening_check = self.check_opening(agent_result.get('text', ''), keywords['opening']) closing_check = self.check_closing(agent_result.get('text', ''), keywords['closing']) forbidden_check = self.check_forbidden(agent_result.get('text', ''), keywords['forbidden']) # 沟通技巧分析 speech_rate = self.analyze_speech_rate(agent_result.get('segments', [])) volume_analysis = self.analyze_volume(wav_file, agent_segments, sample_rate) # 问题解决率分析 resolution_rate = self.analyze_resolution( agent_result.get('text', ''), customer_result.get('text', ''), keywords['resolution'] ) return { "file_name": os.path.basename(audio_file), "duration": duration, "agent_text": agent_result.get('text', ''), "customer_text": customer_result.get('text', ''), "opening_check": opening_check, "closing_check": closing_check, "forbidden_check": forbidden_check, "agent_emotion": agent_emotion, "customer_emotion": customer_emotion, "speech_rate": speech_rate, "volume_mean": volume_analysis.get('mean', -60), "volume_std": volume_analysis.get('std', 0), "resolution_rate": resolution_rate } except Exception as e: error_msg = f"处理文件 {os.path.basename(audio_file)} 时出错: {str(e)}" self.error.emit(error_msg) logger.error(error_msg, exc_info=True) return None finally: # 清理临时文件 if is_temp and os.path.exists(wav_file): try: os.unlink(wav_file) except Exception: pass def process_diarization(self, wav_file, duration): """分块处理说话人分离,避免大文件内存溢出""" # 对于短音频直接处理 if duration <= 600: # 10分钟以下 return self.cached_models['pyannote'](wav_file) # 对于长音频分块处理 self.message.emit(f"音频较长({duration:.1f}秒),将分块处理...") diarization_result = [] chunk_size = 300 # 5分钟块 for start in range(0, int(duration), chunk_size): if not self.running: return [] end = min(start + chunk_size, duration) self.message.emit(f"处理片段: {start}-{end}秒") # 提取音频片段 with tempfile.NamedTemporaryFile(suffix='.wav') as tmpfile: self.extract_audio_segment(wav_file, start, end, tmpfile.name) segment_diarization = self.cached_models['pyannote'](tmpfile.name) # 调整时间偏移 for segment, _, speaker in segment_diarization.itertracks(yield_label=True): diarization_result.append(( segment.start + start, segment.end + start, speaker )) return diarization_result def extract_audio_segment(self, input_file, start_sec, end_sec, output_file): """提取音频片段""" audio = AudioSegment.from_wav(input_file) start_ms = int(start_sec * 1000) end_ms = int(end_sec * 1000) segment = audio[start_ms:end_ms] segment.export(output_file, format="wav") def process_speaker_audio(self, wav_file, segments, speaker_type): """处理说话人音频(优化内存使用)""" if not segments: return {'text': "", 'segments': []} text = "" segment_details = [] whisper_model = self.cached_models['whisper'] # 处理每个片段 for idx, (start, end) in enumerate(segments): if not self.running: break # 每处理5个片段报告一次进度 if idx % 5 == 0: self.message.emit(f"{speaker_type}: 处理片段 {idx+1}/{len(segments)}") duration = end - start segment_text = self.transcribe_audio_segment(wav_file, start, end, whisper_model) segment_details.append({ 'start': start, 'end': end, 'duration': duration, 'text': segment_text }) text += segment_text + " " return { 'text': text.strip(), 'segments': segment_details } def identify_speakers(self, wav_file, diarization, opening_keywords): """ 改进的客服识别方法 1. 检查前三个片段是否有开场白关键词 2. 如果片段不足三个,则检查所有存在的片段 3. 如果无法确定客服,则默认第二个说话人是客服 """ if not diarization: return [], [] speaker_segments = defaultdict(list) speaker_first_occurrence = {} # 记录每个说话人的首次出现时间 # 收集所有说话人片段并记录首次出现时间 for item in diarization: if len(item) == 3: # 来自分块处理的结果 start, end, speaker = item else: # 来自pyannote的直接结果 segment, _, speaker = item start, end = segment.start, segment.end speaker_segments[speaker].append((start, end)) if speaker not in speaker_first_occurrence or start < speaker_first_occurrence[speaker]: speaker_first_occurrence[speaker] = start # 如果没有说话人 if not speaker_segments: return [], [] # 如果只有一个说话人 if len(speaker_segments) == 1: speaker = list(speaker_segments.keys())[0] return speaker_segments[speaker], [] # 计算每个说话人的开场白得分 speaker_scores = {} whisper_model = self.cached_models['whisper'] for speaker, segments in speaker_segments.items(): score = 0 # 检查前三个片段(如果存在) check_segments = segments[:3] # 最多取前三个片段 for start, end in check_segments: # 转录片段 text = self.transcribe_audio_segment(wav_file, start, end, whisper_model) # 检查开场白关键词 for keyword in opening_keywords: if keyword and keyword in text: score += 1 break # 找到一个关键词就加分并跳出循环 speaker_scores[speaker] = score # 尝试找出得分最高的说话人 max_score = max(speaker_scores.values()) max_speakers = [spk for spk, score in speaker_scores.items() if score == max_score] # 如果有唯一最高分说话人,作为客服 if len(max_speakers) == 1: agent_speaker = max_speakers[0] else: # 无法通过开场白确定客服时,默认第二个说话人是客服 # 按首次出现时间排序 sorted_speakers = sorted(speaker_first_occurrence.items(), key=lambda x: x[1]) # 确保至少有两个说话人 if len(sorted_speakers) >= 2: # 取时间上第二个出现的说话人 agent_speaker = sorted_speakers[1][0] else: # 如果只有一个说话人(理论上不会进入此分支,但安全处理) agent_speaker = sorted_speakers[0][0] # 分离客服和客户片段 agent_segments = speaker_segments[agent_speaker] customer_segments = [] for speaker, segments in speaker_segments.items(): if speaker != agent_speaker: customer_segments.extend(segments) return agent_segments, customer_segments def load_keywords(self): """从Excel文件加载关键词(增强健壮性)""" try: df = pd.read_excel(self.keyword_file) # 确保列存在 columns = ['opening', 'closing', 'forbidden', 'resolution'] for col in columns: if col not in df.columns: raise ValueError(f"关键词文件缺少必要列: {col}") keywords = { "opening": [str(k).strip() for k in df['opening'].dropna().tolist() if str(k).strip()], "closing": [str(k).strip() for k in df['closing'].dropna().tolist() if str(k).strip()], "forbidden": [str(k).strip() for k in df['forbidden'].dropna().tolist() if str(k).strip()], "resolution": [str(k).strip() for k in df['resolution'].dropna().tolist() if str(k).strip()] } # 检查是否有足够的关键词 if not any(keywords.values()): raise ValueError("关键词文件中没有找到有效关键词") return keywords except Exception as e: raise Exception(f"加载关键词文件失败: {str(e)}") def convert_to_wav(self, audio_file): """将音频文件转换为WAV格式(增强健壮性)""" try: if not os.path.exists(audio_file): raise FileNotFoundError(f"音频文件不存在: {audio_file}") if audio_file.lower().endswith('.wav'): return audio_file, False # 使用临时文件避免磁盘IO with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmpfile: output_file = tmpfile.name audio = AudioSegment.from_file(audio_file) audio.export(output_file, format='wav') return output_file, True except Exception as e: raise Exception(f"音频转换失败: {str(e)}") def get_audio_info(self, wav_file): """获取音频文件信息(增强健壮性)""" try: if not os.path.exists(wav_file): raise FileNotFoundError(f"音频文件不存在: {wav_file}") # 使用soundfile获取更可靠的信息 with sf.SoundFile(wav_file) as f: duration = len(f) / f.samplerate sample_rate = f.samplerate channels = f.channels return duration, sample_rate, channels except Exception as e: raise Exception(f"获取音频信息失败: {str(e)}") def transcribe_audio_segment(self, wav_file, start, end, model): """转录单个音频片段 - 优化内存使用""" # 使用pydub加载音频 audio = AudioSegment.from_wav(wav_file) # 转换为毫秒 start_ms = int(start * 1000) end_ms = int(end * 1000) segment_audio = audio[start_ms:end_ms] # 使用临时文件 with tempfile.NamedTemporaryFile(suffix='.wav') as tmpfile: segment_audio.export(tmpfile.name, format="wav") try: result = model.transcribe( tmpfile.name, fp16=cuda_available() # 使用FP16加速(如果可用) ) return result['text'] except RuntimeError as e: if "out of memory" in str(e).lower(): # 尝试释放内存后重试 torch.cuda.empty_cache() gc.collect() result = model.transcribe( tmpfile.name, fp16=cuda_available() ) return result['text'] raise def analyze_emotions(self, texts): """批量分析文本情感(提高效率)""" if not any(t.strip() for t in texts): return [{"label": "中性", "score": 0.0} for _ in texts] # 截断长文本以提高性能 processed_texts = [t[:500] if len(t) > 500 else t for t in texts] # 批量处理 classifier = self.cached_models['emotion_classifier'] results = classifier(processed_texts, truncation=True, max_length=512, batch_size=4) # 确保返回格式一致 emotions = [] for result in results: if isinstance(result, list) and result: emotions.append({ "label": result[0]['label'], "score": result[0]['score'] }) else: emotions.append({ "label": "中性", "score": 0.0 }) return emotions def check_opening(self, text, opening_keywords): """检查开场白(使用正则表达式提高准确性)""" if not text or not opening_keywords: return False pattern = "|".join(re.escape(k) for k in opening_keywords) return bool(re.search(pattern, text)) def check_closing(self, text, closing_keywords): """检查结束语(使用正则表达式提高准确性)""" if not text or not closing_keywords: return False pattern = "|".join(re.escape(k) for k in closing_keywords) return bool(re.search(pattern, text)) def check_forbidden(self, text, forbidden_keywords): """检查服务禁语(使用正则表达式提高准确性)""" if not text or not forbidden_keywords: return False pattern = "|".join(re.escape(k) for k in forbidden_keywords) return bool(re.search(pattern, text)) def analyze_speech_rate(self, segments): """改进的语速分析 - 基于实际识别文本""" if not segments: return 0 total_chars = 0 total_duration = 0 for segment in segments: # 计算片段时长(秒) duration = segment['duration'] total_duration += duration # 计算中文字符数(去除标点和空格) chinese_chars = sum(1 for char in segment['text'] if '\u4e00' <= char <= '\u9fff') total_chars += chinese_chars if total_duration == 0: return 0 # 语速 = 总字数 / 总时长(分钟) return total_chars / (total_duration / 60) def analyze_volume(self, wav_file, segments, sample_rate): """改进的音量分析 - 使用librosa计算RMS分贝值""" if not segments: return {"mean": -60, "std": 0} # 使用soundfile加载音频(更高效) try: y, sr = sf.read(wav_file, dtype='float32') if sr != sample_rate: y = librosa.resample(y, orig_sr=sr, target_sr=sample_rate) sr = sample_rate except Exception: # 回退到librosa y, sr = librosa.load(wav_file, sr=sample_rate, mono=True) all_dB = [] for start, end in segments: start_sample = int(start * sr) end_sample = int(end * sr) # 确保片段在有效范围内 if start_sample < len(y) and end_sample <= len(y): segment_audio = y[start_sample:end_sample] # 计算RMS并转换为dB rms = librosa.feature.rms(y=segment_audio)[0] dB = librosa.amplitude_to_db(rms, ref=1.0) # 使用标准参考值 all_dB.extend(dB) if not all_dB: return {"mean": -60, "std": 0} return { "mean": float(np.mean(all_dB)), "std": float(np.std(all_dB)) } def analyze_resolution(self, agent_text, customer_text, resolution_keywords): """分析问题解决率(使用更智能的匹配)""" # 检查客户是否提到问题 problem_patterns = [ "问题", "故障", "解决", "怎么办", "如何", "为什么", "不行", "不能", "无法", "错误", "bug", "issue", "疑问", "咨询" ] problem_regex = re.compile("|".join(problem_patterns)) has_problem = bool(problem_regex.search(customer_text)) # 检查客服是否提供解决方案 solution_regex = re.compile("|".join(re.escape(k) for k in resolution_keywords)) solution_found = bool(solution_regex.search(agent_text)) # 如果没有检测到问题,则认为已解决 if not has_problem: return True return solution_found def stop(self): """停止分析""" self.running = False self.message.emit("正在停止分析...") class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("外呼电话录音包质检分析系统") self.setGeometry(100, 100, 1000, 700) self.setStyleSheet(""" QMainWindow { background-color: #f0f0f0; } QGroupBox { font-weight: bold; border: 1px solid gray; border-radius: 5px; margin-top: 1ex; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; } QPushButton { background-color: #4CAF50; color: white; border: none; padding: 5px 10px; border-radius: 3px; } QPushButton:hover { background-color: #45a049; } QPushButton:disabled { background-color: #cccccc; } QProgressBar { border: 1px solid grey; border-radius: 3px; text-align: center; } QProgressBar::chunk { background-color: #4CAF50; width: 10px; } QTextEdit { font-family: Consolas, Monaco, monospace; } """) # 初始化变量 self.audio_files = [] self.keyword_file = "" self.whisper_model_path = "./models/whisper-small" self.pyannote_model_path = "./models/pyannote-speaker-diarization" self.emotion_model_path = "./models/Erlangshen-Roberta-110M-Sentiment" self.output_dir = os.path.expanduser("~/质检报告") # 创建主控件 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(10) main_layout.setContentsMargins(15, 15, 15, 15) # 文件选择区域 file_group = QGroupBox("文件选择") file_layout = QVBoxLayout(file_group) file_layout.setSpacing(8) # 音频文件选择 audio_layout = QHBoxLayout() self.audio_label = QLabel("音频文件/文件夹:") audio_layout.addWidget(self.audio_label) self.audio_path_edit = QLineEdit() self.audio_path_edit.setPlaceholderText("请选择音频文件或文件夹") audio_layout.addWidget(self.audio_path_edit, 3) self.audio_browse_btn = QPushButton("浏览...") self.audio_browse_btn.clicked.connect(self.browse_audio) audio_layout.addWidget(self.audio_browse_btn) file_layout.addLayout(audio_layout) # 关键词文件选择 keyword_layout = QHBoxLayout() self.keyword_label = QLabel("关键词文件:") keyword_layout.addWidget(self.keyword_label) self.keyword_path_edit = QLineEdit() self.keyword_path_edit.setPlaceholderText("请选择Excel格式的关键词文件") keyword_layout.addWidget(self.keyword_path_edit, 3) self.keyword_browse_btn = QPushButton("浏览...") self.keyword_browse_btn.clicked.connect(self.browse_keyword) keyword_layout.addWidget(self.keyword_browse_btn) file_layout.addLayout(keyword_layout) main_layout.addWidget(file_group) # 模型设置区域 model_group = QGroupBox("模型设置") model_layout = QVBoxLayout(model_group) model_layout.setSpacing(8) # Whisper模型路径 whisper_layout = QHBoxLayout() whisper_layout.addWidget(QLabel("Whisper模型路径:")) self.whisper_edit = QLineEdit(self.whisper_model_path) whisper_layout.addWidget(self.whisper_edit, 3) model_layout.addLayout(whisper_layout) # Pyannote模型路径 pyannote_layout = QHBoxLayout() pyannote_layout.addWidget(QLabel("Pyannote模型路径:")) self.pyannote_edit = QLineEdit(self.pyannote_model_path) pyannote_layout.addWidget(self.pyannote_edit, 3) model_layout.addLayout(pyannote_layout) # 情感分析模型路径 emotion_layout = QHBoxLayout() emotion_layout.addWidget(QLabel("情感分析模型路径:")) self.emotion_edit = QLineEdit(self.emotion_model_path) emotion_layout.addWidget(self.emotion_edit, 3) model_layout.addLayout(emotion_layout) # 输出目录 output_layout = QHBoxLayout() output_layout.addWidget(QLabel("输出目录:")) self.output_edit = QLineEdit(self.output_dir) self.output_edit.setPlaceholderText("请选择报告输出目录") output_layout.addWidget(self.output_edit, 3) self.output_browse_btn = QPushButton("浏览...") self.output_browse_btn.clicked.connect(self.browse_output) output_layout.addWidget(self.output_browse_btn) model_layout.addLayout(output_layout) main_layout.addWidget(model_group) # 控制按钮区域 control_layout = QHBoxLayout() control_layout.setSpacing(10) self.start_btn = QPushButton("开始分析") self.start_btn.setStyleSheet("background-color: #2196F3;") self.start_btn.clicked.connect(self.start_analysis) control_layout.addWidget(self.start_btn) self.stop_btn = QPushButton("停止分析") self.stop_btn.setStyleSheet("background-color: #f44336;") self.stop_btn.clicked.connect(self.stop_analysis) self.stop_btn.setEnabled(False) control_layout.addWidget(self.stop_btn) self.clear_btn = QPushButton("清空") self.clear_btn.clicked.connect(self.clear_all) control_layout.addWidget(self.clear_btn) main_layout.addLayout(control_layout) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setValue(0) self.progress_bar.setFormat("就绪") self.progress_bar.setMinimumHeight(25) main_layout.addWidget(self.progress_bar) # 日志输出区域 log_group = QGroupBox("分析日志") log_layout = QVBoxLayout(log_group) self.log_text = QTextEdit() self.log_text.setReadOnly(True) log_layout.addWidget(self.log_text) main_layout.addWidget(log_group, 1) # 给日志区域更多空间 # 状态区域 status_layout = QHBoxLayout() self.status_label = QLabel("状态: 就绪") status_layout.addWidget(self.status_label, 1) self.file_count_label = QLabel("已选择0个音频文件") status_layout.addWidget(self.file_count_label) main_layout.addLayout(status_layout) # 初始化分析线程 self.analysis_thread = None def browse_audio(self): """浏览音频文件或文件夹""" options = QFileDialog.Options() files, _ = QFileDialog.getOpenFileNames( self, "选择音频文件", "", "音频文件 (*.mp3 *.wav *.amr *.ogg *.flac *.m4a);;所有文件 (*)", options=options ) if files: self.audio_files = files self.audio_path_edit.setText("; ".join(files)) self.file_count_label.setText(f"已选择{len(files)}个音频文件") self.log_text.append(f"已选择{len(files)}个音频文件") def browse_keyword(self): """浏览关键词文件""" options = QFileDialog.Options() file, _ = QFileDialog.getOpenFileName( self, "选择关键词文件", "", "Excel文件 (*.xlsx *.xls);;所有文件 (*)", options=options ) if file: self.keyword_file = file self.keyword_path_edit.setText(file) self.log_text.append(f"已选择关键词文件: {file}") def browse_output(self): """浏览输出目录""" options = QFileDialog.Options() directory = QFileDialog.getExistingDirectory( self, "选择输出目录", self.output_dir, options=options ) if directory: self.output_dir = directory self.output_edit.setText(directory) self.log_text.append(f"输出目录设置为: {directory}") def start_analysis(self): """开始分析""" if not self.audio_files: self.show_warning("请先选择音频文件") return if not self.keyword_file: self.show_warning("请先选择关键词文件") return if not os.path.exists(self.keyword_file): self.show_warning("关键词文件不存在,请重新选择") return # 检查模型路径 model_paths = [ self.whisper_edit.text(), self.pyannote_edit.text(), self.emotion_edit.text() ] for path in model_paths: if not os.path.exists(path): self.show_warning(f"模型路径不存在: {path}") return # 更新模型路径 self.whisper_model_path = self.whisper_edit.text() self.pyannote_model_path = self.pyannote_edit.text() self.emotion_model_path = self.emotion_edit.text() self.output_dir = self.output_edit.text() # 创建输出目录 os.makedirs(self.output_dir, exist_ok=True) self.log_text.append("开始分析...") self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.status_label.setText("状态: 分析中...") self.progress_bar.setFormat("分析中... 0%") self.progress_bar.setValue(0) # 创建并启动分析线程 self.analysis_thread = AnalysisThread( self.audio_files, self.keyword_file, self.whisper_model_path, self.pyannote_model_path, self.emotion_model_path ) self.analysis_thread.progress.connect(self.update_progress) self.analysis_thread.message.connect(self.log_text.append) self.analysis_thread.analysis_complete.connect(self.on_analysis_complete) self.analysis_thread.error.connect(self.on_analysis_error) self.analysis_thread.finished.connect(self.on_analysis_finished) self.analysis_thread.start() def update_progress(self, value): """更新进度条""" self.progress_bar.setValue(value) self.progress_bar.setFormat(f"分析中... {value}%") def stop_analysis(self): """停止分析""" if self.analysis_thread and self.analysis_thread.isRunning(): self.analysis_thread.stop() self.log_text.append("正在停止分析...") self.stop_btn.setEnabled(False) def clear_all(self): """清空所有内容""" self.audio_files = [] self.keyword_file = "" self.audio_path_edit.clear() self.keyword_path_edit.clear() self.log_text.clear() self.progress_bar.setValue(0) self.progress_bar.setFormat("就绪") self.status_label.setText("状态: 就绪") self.file_count_label.setText("已选择0个音频文件") self.log_text.append("已清空所有内容") def show_warning(self, message): """显示警告消息""" QMessageBox.warning(self, "警告", message) self.log_text.append(f"警告: {message}") def on_analysis_complete(self, result): """分析完成处理""" try: self.log_text.append("正在生成报告...") if not result.get("results"): self.log_text.append("警告: 没有生成任何分析结果") return # 生成Excel报告 excel_path = os.path.join(self.output_dir, "质检分析报告.xlsx") self.generate_excel_report(result, excel_path) # 生成Word报告 word_path = os.path.join(self.output_dir, "质检分析报告.docx") self.generate_word_report(result, word_path) self.log_text.append(f"分析报告已保存至: {excel_path}") self.log_text.append(f"可视化报告已保存至: {word_path}") self.log_text.append("分析完成!") self.status_label.setText(f"状态: 分析完成!报告保存至: {self.output_dir}") self.progress_bar.setFormat("分析完成!") # 显示完成消息 QMessageBox.information( self, "分析完成", f"分析完成!报告已保存至:\n{excel_path}\n{word_path}" ) except Exception as e: import traceback error_msg = f"生成报告时出错: {str(e)}\n{traceback.format_exc()}" self.log_text.append(error_msg) logger.error(error_msg) def on_analysis_error(self, message): """分析错误处理""" self.log_text.append(f"错误: {message}") self.status_label.setText("状态: 发生错误") self.progress_bar.setFormat("发生错误") QMessageBox.critical(self, "分析错误", message) def on_analysis_finished(self): """分析线程结束处理""" self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) def generate_excel_report(self, result, output_path): """生成Excel报告(增强健壮性)""" try: # 从结果中提取数据 data = [] for res in result['results']: data.append({ "文件名": res['file_name'], "音频时长(秒)": res['duration'], "开场白检查": "通过" if res['opening_check'] else "未通过", "结束语检查": "通过" if res['closing_check'] else "未通过", "服务禁语检查": "通过" if not res['forbidden_check'] else "未通过", "客服情感": res['agent_emotion']['label'], "客服情感得分": res['agent_emotion']['score'], "客户情感": res['customer_emotion']['label'], "客户情感得分": res['customer_emotion']['score'], "语速(字/分)": res['speech_rate'], "平均音量(dB)": res['volume_mean'], "音量标准差": res['volume_std'], "问题解决率": "是" if res['resolution_rate'] else "否" }) # 创建DataFrame并保存 df = pd.DataFrame(data) # 尝试使用openpyxl引擎(更稳定) try: df.to_excel(output_path, index=False, engine='openpyxl') except ImportError: df.to_excel(output_path, index=False) # 添加汇总统计 try: with pd.ExcelWriter(output_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer: summary_data = { "统计项": ["总文件数", "开场白通过率", "结束语通过率", "服务禁语通过率", "问题解决率"], "数值": [ len(result['results']), df['开场白检查'].value_counts().get('通过', 0) / len(df), df['结束语检查'].value_counts().get('通过', 0) / len(df), df['服务禁语检查'].value_counts().get('通过', 0) / len(df), df['问题解决率'].value_counts().get('是', 0) / len(df) ] } summary_df = pd.DataFrame(summary_data) summary_df.to_excel(writer, sheet_name='汇总统计', index=False) except Exception as e: self.log_text.append(f"添加汇总统计时出错: {str(e)}") except Exception as e: raise Exception(f"生成Excel报告失败: {str(e)}") def generate_word_report(self, result, output_path): """生成Word报告(增强健壮性)""" try: doc = Document() # 添加标题 doc.add_heading('外呼电话录音质检分析报告', 0) # 添加基本信息 doc.add_heading('分析概况', level=1) doc.add_paragraph(f"分析时间: {time.strftime('%Y-%m-%d %H:%M:%S')}") doc.add_paragraph(f"分析文件数量: {len(result['results'])}") doc.add_paragraph(f"关键词文件: {os.path.basename(self.keyword_file)}") # 添加汇总统计 doc.add_heading('汇总统计', level=1) # 创建汇总表格 table = doc.add_table(rows=5, cols=2) table.style = 'Table Grid' # 表头 hdr_cells = table.rows[0].cells hdr_cells[0].text = '统计项' hdr_cells[1].text = '数值' # 计算统计数据 df = pd.DataFrame(result['results']) pass_rates = { "开场白通过率": df['opening_check'].mean() if not df.empty else 0, "结束语通过率": df['closing_check'].mean() if not df.empty else 0, "服务禁语通过率": (1 - df['forbidden_check']).mean() if not df.empty else 0, "问题解决率": df['resolution_rate'].mean() if not df.empty else 0 } # 填充表格 rows = [ ("总文件数", len(result['results'])), ("开场白通过率", f"{pass_rates['开场白通过率']:.2%}"), ("结束语通过率", f"{pass_rates['结束语通过率']:.2%}"), ("服务禁语通过率", f"{pass_rates['服务禁语通过率']:.2%}"), ("问题解决率", f"{pass_rates['问题解决率']:.2%}") ] for i, row_data in enumerate(rows): if i < len(table.rows): row_cells = table.rows[i].cells row_cells[0].text = row_data[0] row_cells[1].text = str(row_data[1]) # 添加情感分析图表 if result['results']: doc.add_heading('情感分析', level=1) # 客服情感分布 agent_emotions = [res['agent_emotion']['label'] for res in result['results']] agent_emotion_counts = pd.Series(agent_emotions).value_counts() if not agent_emotion_counts.empty: fig, ax = plt.subplots(figsize=(6, 4)) agent_emotion_counts.plot.pie(autopct='%1.1f%%', ax=ax) ax.set_title('客服情感分布') ax.set_ylabel('') # 移除默认的ylabel plt.tight_layout() # 保存图表到临时文件 chart_path = os.path.join(self.output_dir, "agent_emotion_chart.png") plt.savefig(chart_path, dpi=100, bbox_inches='tight') plt.close() doc.add_picture(chart_path, width=Inches(4)) doc.add_paragraph('图1: 客服情感分布') # 客户情感分布 customer_emotions = [res['customer_emotion']['label'] for res in result['results']] customer_emotion_counts = pd.Series(customer_emotions).value_counts() if not customer_emotion_counts.empty: fig, ax = plt.subplots(figsize=(6, 4)) customer_emotion_counts.plot.pie(autopct='%1.1f%%', ax=ax) ax.set_title('客户情感分布') ax.set_ylabel('') # 移除默认的ylabel plt.tight_layout() chart_path = os.path.join(self.output_dir, "customer_emotion_chart.png") plt.savefig(chart_path, dpi=100, bbox_inches='tight') plt.close() doc.add_picture(chart_path, width=Inches(4)) doc.add_paragraph('图2: 客户情感分布') # 添加详细分析结果 doc.add_heading('详细分析结果', level=1) # 创建详细表格 table = doc.add_table(rows=1, cols=6) table.style = 'Table Grid' # 表头 hdr_cells = table.rows[0].cells headers = ['文件名', '开场白', '结束语', '禁语', '客服情感', '问题解决'] for i, header in enumerate(headers): hdr_cells[i].text = header # 填充数据 for res in result['results']: row_cells = table.add_row().cells row_cells[0].text = res['file_name'] row_cells[1].text = "✓" if res['opening_check'] else "✗" row_cells[2].text = "✓" if res['closing_check'] else "✗" row_cells[3].text = "✗" if res['forbidden_check'] else "✓" row_cells[4].text = res['agent_emotion']['label'] row_cells[5].text = "✓" if res['resolution_rate'] else "✗" # 保存文档 doc.save(output_path) except Exception as e: raise Exception(f"生成Word报告失败: {str(e)}") if __name__ == "__main__": # 检查是否安装了torch try: import torch except ImportError: print("警告: PyTorch 未安装,情感分析可能无法使用GPU加速") app = QApplication(sys.argv) # 设置应用样式 app.setStyle("Fusion") window = MainWindow() window.show() sys.exit(app.exec_())

import math import os import struct from argparse import ArgumentParser import av import numpy as np import open3d as o3d import rosbag import yaml from sensor_msgs import point_cloud2 import subprocess from protoc.octopudata_localizationinfo_pb2 import LocalizationInfoFrame, LocalizationInfo from protoc.octopudata_trackedobject_pb2 import TrackedObjectFrame, Object, TrackedObject from protoc.octopusdata_controlcommand_pb2 import CommandFrame, ControlCommand from protoc.octopusdata_gnss_pb2 import GnssPoint, GnssPoints from protoc.octopusdata_plantrajectory_pb2 import Trajectory, TrajectoryPoint, PlanTrajectory from protoc.octopusdata_predictionobstacles_pb2 import PerceptionObstacle, \ Obstacle, PredictionTrajectory, PathPoint, PredictionObstacles from protoc.octopusdata_routingpath_pb2 import RoutingPath, Path, Point, RoutingFrames from protoc.octopusdata_vehicleinfo_pb2 import VehicleFrame, VehicleInfo av.logging.set_level(av.logging.PANIC) codec_ctx = av.codec.Codec('hevc','r') h265_code = codec_ctx.create() class Pose: def __init__(self, q0, q1, q2, q3, x, y, z): self.q0 = q0 self.q1 = q1 self.q2 = q2 self.q3 = q3 self.x = x self.y = y self.z = z def get_ts(secs, nsecs): return int(secs * 1000 + nsecs / 1000000) def get_modulo(x, y, z): return math.sqrt(x * x + y * y + z * z) def yaw_from_quaternions(w, x, y, z): a = 2 * (w * z + x * y) b = 1 - 2 * (y * y + z * z) return math.atan2(a, b) def pose_has_nan(p): return math.isnan(p.x) or math.isnan(p.y) or math.isnan(p.z) or \ math.isnan(p.q0) or math.isnan(p.q1) or math.isnan(p.q2) or \ math.isnan(p.q3) def get_t(q0, q1, q2, q3, x, y, z): aa = 1 - 2 * (q2 * q2 + q3 * q3) ab = 2 * (q1 * q2 - q0 * q3) ac = 2 * (q1 * q3 + q0 * q2) ba = 2 * (q1 * q2 + q0 * q3) bb = 1 - 2 * (q1 * q1 + q3 * q3) bc = 2 * (q2 * q3 - q0 * q1) ca = 2 * (q1 * q3 - q0 * q2) cb = 2 * (q2 * q3 + q0 * q1) cc = 1 - 2 * (q1 * q1 + q2 * q2) t = np.mat([[aa, ab, ac, x], [ba, bb, bc, y], [ca, cb, cc, z], [0, 0, 0, 1]]) return t def get_label(perception_typ, subtype): if perception_typ == 3: return 'Pedestrian' elif perception_typ == 4: return 'Bi_Tricycle' elif perception_typ == 5: if subtype == 5: return 'Truck' elif subtype == 6: return 'Bus' else: return 'Car' else: return 'unknow' def main(args): for file in os.listdir(args.input): if file.endswith('.bag'): bag_path = os.path.join(args.input, file) bag = rosbag.Bag(bag_path, "r") output_dir = os.getenv('output_dir') if not os.path.exists(os.path.join(output_dir, 'innoPtClound_A4')): os.makedirs(os.path.join(output_dir, 'innoPtClound_A4')) if not os.path.exists(os.path.join(output_dir, 'innoPtClound_B2')): os.makedirs(os.path.join(output_dir, 'innoPtClound_B2')) if not os.path.exists(os.path.join(output_dir, 'radar_track_array_0')): os.makedirs(os.path.join(output_dir, 'radar_track_array_0')) if not os.path.exists(os.path.join(output_dir, 'radar_track_array_3')): os.makedirs(os.path.join(output_dir, 'radar_track_array_3')) if not os.path.exists(os.path.join(output_dir, 'mdc_camera_instance_74')): os.makedirs(os.path.join(output_dir, 'mdc_camera_instance_74')) if not os.path.exists(os.path.join(output_dir, 'mdc_camera_instance_73')): os.makedirs(os.path.join(output_dir, 'mdc_camera_instance_73')) if not os.path.exists(os.path.join(output_dir, 'mdc_camera_instance_72')): os.makedirs(os.path.join(output_dir, 'mdc_camera_instance_72')) if not os.path.exists(os.path.join(output_dir, 'mdc_camera_instance_71')): os.makedirs(os.path.join(output_dir, 'mdc_camera_instance_71')) routes = [] controls = [] plans = [] preds = [] gnss = [] vehs = [] locs = [] objs = [] ego_pose = None has_camera_71 = False has_camera_72 = False has_camera_73 = False has_camera_74 = False has_lidar_A4 = False has_lidar_B2 = False has_radar_0 = False has_radar_3 = False lidar_num = 0 image_num = 0 radar_num = 0 for topic, msg, t in bag.read_messages(): time_stamp = int(t.to_sec() * 1000) # 以 rosbag 时间戳(t)为基准,转换为 13 位时间戳 if topic == '/innoPtClound_A4': ### 图达通 时间辍应该是13位数字,图达通雷达8位,华为96线6位 # file_path = os.path.join(output_dir, 'innoPtClound_A4', '{}.pcd'.format(int(msg.header.stamp.secs * 1000 + # msg.header.stamp.nsecs / 1000000))) file_path = os.path.join(output_dir, 'innoPtClound_A4', '{}.pcd'.format(time_stamp)) # print(file_path) # 提取点云数据,包括 x, y, z points = list(point_cloud2.read_points(msg, field_names=["x", "y", "z", "intensity"], skip_nans=True)) if points: # 转换为 numpy 数组,添加 intensity, ring, timestamp 字段 np_points = np.array(points) # (N, 3), 包含 x, y, z # 转换为 Open3D 格式点云 pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(np_points[:, :3]) # x, y, z pcd.colors = o3d.utility.Vector3dVector(np.tile(np_points[:, 3:4] / np_points[:, 3:4].max(), (1, 3))) # 用 intensity 作为灰度颜色 o3d.io.write_point_cloud(file_path, pcd) lidar_num += 1 has_lidar_A4 = True elif topic == '/innoPtClound_B2': ### 图达通 # file_path = os.path.join(output_dir, 'innoPtClound_B2', '{}.pcd'.format(int(msg.header.stamp.secs * 1000 + # msg.header.stamp.nsecs / 1000000))) file_path = os.path.join(output_dir, 'innoPtClound_B2', '{}.pcd'.format(time_stamp)) # print(file_path) # 提取点云数据,包括 x, y, z points = list(point_cloud2.read_points(msg, field_names=["x", "y", "z", "intensity"], skip_nans=True)) if points: # 转换为 numpy 数组,添加 intensity, ring, timestamp 字段 np_points = np.array(points) # (N, 3), 包含 x, y, z # 转换为 Open3D 格式点云 pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(np_points[:, :3]) # x, y, z pcd.colors = o3d.utility.Vector3dVector(np.tile(np_points[:, 3:4] / np_points[:, 3:4].max(), (1, 3))) # 用 intensity 作为灰度颜色 o3d.io.write_point_cloud(file_path, pcd) lidar_num += 1 has_lidar_B2 = True elif topic == 'mdc_camera_instance_74': ### 相机 时间辍应该是13位数字 # time_stamp = int(msg.header.stamp.secs * 1000 + # msg.header.stamp.nsecs / 1000000) file_path = os.path.join(output_dir, 'mdc_camera_instance_74', '{}.jpg'.format(time_stamp)) packet = av.packet.Packet(msg.data) try: out = h265_code.decode(packet) for frame in out: if frame.format.name != 'rgb24': frame = frame.reformat(format='rgb24') img = frame.to_image() img.save(file_path) image_num += 1 has_camera_74 = True except Exception as e: print("{} frame can not trans to jpg".format(time_stamp), e) elif topic == 'mdc_camera_instance_73': ### 相机 时间辍应该是13位数字 # time_stamp = int(msg.header.stamp.secs * 1000 + # msg.header.stamp.nsecs / 1000000) file_path = os.path.join(output_dir, 'mdc_camera_instance_73', '{}.jpg'.format(time_stamp)) packet = av.packet.Packet(msg.data) try: out = h265_code.decode(packet) for frame in out: if frame.format.name != 'rgb24': frame = frame.reformat(format='rgb24') img = frame.to_image() img.save(file_path) image_num += 1 has_camera_73 = True except Exception as e: print("{} frame can not trans to jpg".format(time_stamp), e) elif topic == 'mdc_camera_instance_72': ### 相机 # time_stamp = int(msg.header.stamp.secs * 1000 + # msg.header.stamp.nsecs / 1000000) file_path = os.path.join(output_dir, 'mdc_camera_instance_72', '{}.jpg'.format(time_stamp)) packet = av.packet.Packet(msg.data) try: out = h265_code.decode(packet) for frame in out: if frame.format.name != 'rgb24': frame = frame.reformat(format='rgb24') img = frame.to_image() img.save(file_path) image_num += 1 has_camera_72 = True except Exception as e: print("{} frame can not trans to jpg".format(time_stamp), e) elif topic == 'mdc_camera_instance_71': ### 相机 # time_stamp = int(msg.header.stamp.secs * 1000 + # msg.header.stamp.nsecs / 1000000) file_path = os.path.join(output_dir, 'mdc_camera_instance_71', '{}.jpg'.format(time_stamp)) packet = av.packet.Packet(msg.data) try: out = h265_code.decode(packet) for frame in out: if frame.format.name != 'rgb24': frame = frame.reformat(format='rgb24') img = frame.to_image() img.save(file_path) image_num += 1 has_camera_71 = True except Exception as e: print("{} frame can not trans to jpg".format(time_stamp), e) elif topic == '/radar_track_array_0': ### 大陆408 时间辍应该是13位数字 # file_path = os.path.join(output_dir, 'radar_track_array_0', '{}.pcd'.format(int(msg.header.stamp.secs * 1000 + # msg.header.stamp.nsecs / 1000000))) file_path = os.path.join(output_dir, 'radar_track_array_0', '{}.pcd'.format(time_stamp)) points = [] for track in msg.trackList: x, y, z = track.x, track.y, track.z vx, vy, ax, ay = track.vx, track.vy, track.ax, track.ay rcs, snr, yawRate = track.rcs, track.snr, track.yawRate obj_id, trackType, lifetime = track.id, track.trackType, track.lifetime # 过滤无效点 if np.isnan(x) or np.isnan(y) or np.isnan(z): continue points.append([x, y, z, vx, vy, ax, ay, rcs, snr, yawRate, obj_id, trackType, lifetime]) if not points: print("没有有效点云数据") continue points = np.array(points) # **写入 PCD 文件** with open(file_path, 'w') as f: # **写入 PCD 头部** f.write("# .PCD v0.7 - Point Cloud Data file format\n") f.write("VERSION 0.7\n") f.write(f"FIELDS x y z vx vy ax ay rcs snr yawRate id trackType lifetime\n") f.write("SIZE 4 4 4 4 4 4 4 4 4 4 4 4 4\n") f.write("TYPE F F F F F F F F F F I I I\n") # F = float, I = int f.write("COUNT 1 1 1 1 1 1 1 1 1 1 1 1 1\n") f.write(f"WIDTH {points.shape[0]}\n") f.write("HEIGHT 1\n") f.write("VIEWPOINT 0 0 0 1 0 0 0\n") f.write(f"POINTS {points.shape[0]}\n") f.write("DATA ascii\n") # **写入点云数据** np.savetxt(f, points, fmt="%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %d %d %d") radar_num += 1 has_radar_0 = True elif topic == '/radar_track_array_3': ### 大陆408 # file_path = os.path.join(output_dir, 'radar_track_array_3', '{}.pcd'.format(int(msg.header.stamp.secs * 1000 + # msg.header.stamp.nsecs / 1000000))) file_path = os.path.join(output_dir, 'radar_track_array_3', '{}.pcd'.format(time_stamp)) points = [] for track in msg.trackList: x, y, z = track.x, track.y, track.z vx, vy, ax, ay = track.vx, track.vy, track.ax, track.ay rcs, snr, yawRate = track.rcs, track.snr, track.yawRate obj_id, trackType, lifetime = track.id, track.trackType, track.lifetime # 过滤无效点 if np.isnan(x) or np.isnan(y) or np.isnan(z): continue points.append([x, y, z, vx, vy, ax, ay, rcs, snr, yawRate, obj_id, trackType, lifetime]) if not points: print("没有有效点云数据") continue points = np.array(points) # **写入 PCD 文件** with open(file_path, 'w') as f: # **写入 PCD 头部** f.write("# .PCD v0.7 - Point Cloud Data file format\n") f.write("VERSION 0.7\n") f.write(f"FIELDS x y z vx vy ax ay rcs snr yawRate id trackType lifetime\n") f.write("SIZE 4 4 4 4 4 4 4 4 4 4 4 4 4\n") f.write("TYPE F F F F F F F F F F I I I\n") # F = float, I = int f.write("COUNT 1 1 1 1 1 1 1 1 1 1 1 1 1\n") f.write(f"WIDTH {points.shape[0]}\n") f.write("HEIGHT 1\n") f.write("VIEWPOINT 0 0 0 1 0 0 0\n") f.write(f"POINTS {points.shape[0]}\n") f.write("DATA ascii\n") # **写入点云数据** np.savetxt(f, points, fmt="%.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %.6f %d %d %d") radar_num += 1 has_radar_3 = True elif topic == '/routing/routing_response_viz': rv = RoutingPath() rv.timestamp = int(t.secs * 1000 + t.nsecs / 1000000) rv.stamp_secs = t.secs rv.stamp_nsecs = t.nsecs mark_list = list() for mark in msg.markers: path_pb = Path() path_pb.id = mark.id point_list = [] for point in mark.points: point_pb = Point() point_pb.x = point.x point_pb.y = point.y point_pb.z = point.z point_list.append(point_pb) path_pb.path_point.extend(point_list) mark_list.append(path_pb) rv.routing_path_info.extend(mark_list) routes.append(rv) elif topic == '/holo/ControlCommand': cf = CommandFrame() cf.timestamp = int(t.secs * 1000 + t.nsecs / 1000000) cf.stamp_secs = t.secs cf.stamp_nsecs = t.nsecs cf.acceleration = msg.acceleration cf.front_wheel_angle = msg.front_wheel_angle cf.gear = msg.gear controls.append(cf) elif topic == '/planning/trajectory': tj = Trajectory() tj.timestamp = int(t.secs * 1000 + t.nsecs / 1000000) tj.stamp_secs = t.secs tj.stamp_nsecs = t.nsecs p_list = [] for point in msg.trajectory_points: p = TrajectoryPoint() p.x = point.path_point.point.x p.y = point.path_point.point.y p.z = point.path_point.point.z p.theta = point.path_point.theta p.kappa = point.path_point.kappa p.v = point.v p.a = point.a p.relative_time = point.relative_time p_list.append(p) tj.trajectory_points.extend(p_list) plans.append(tj) elif topic == '/prediction/prediction_obstacles': tr_pb = PerceptionObstacle() tr_pb.timestamp = int(msg.header.stamp.secs * 1000 + msg.header.stamp.nsecs / 1000000) tr_pb.stamp_secs = msg.header.stamp.secs tr_pb.stamp_nsecs = msg.header.stamp.nsecs obj_list = list() for obj in msg.prediction_obstacle: ob_pb = Obstacle() ob_pb.obstacle_timestamp = int(obj.timestamp * 1000) ob_pb.id = obj.perception_obstacle.id ob_pb.x = obj.perception_obstacle.position.x ob_pb.y = obj.perception_obstacle.position.y ob_pb.z = obj.perception_obstacle.position.z traj_pbs = [] for traj in obj.trajectory: traj_pb = PredictionTrajectory() points_pbs = [] for trajectory_point in traj.trajectory_points: point_pb = PathPoint() point_pb.x = trajectory_point.path_point.point.x point_pb.y = trajectory_point.path_point.point.y point_pb.z = trajectory_point.path_point.point.z point_pb.theta = trajectory_point.path_point.theta point_pb.kappa = trajectory_point.path_point.kappa point_pb.lane_id = trajectory_point.path_point.lane_id point_pb.v = trajectory_point.v point_pb.a = trajectory_point.a point_pb.relative_time = trajectory_point.relative_time points_pbs.append(point_pb) traj_pb.path_point.extend(points_pbs) traj_pbs.append(traj_pb) ob_pb.prediction_trajectory.extend(traj_pbs) obj_list.append(ob_pb) tr_pb.obstacle_info.extend(obj_list) preds.append(tr_pb) elif topic == '/inspvax': pb_loc_gnss = GnssPoint() pb_loc_gnss.stamp_secs = msg.header.stamp.secs # 1 pb_loc_gnss.stamp_nsecs = msg.header.stamp.nsecs # 2 pb_loc_gnss.timestamp = get_ts(msg.header.stamp.secs, msg.header.stamp.nsecs) pb_loc_gnss.latitude = msg.latitude # 3 pb_loc_gnss.longitude = msg.longitude # 4 pb_loc_gnss.elevation = msg.altitude gnss.append(pb_loc_gnss) elif topic == '/holo/VehicleInfoMagotan': veh_pb = VehicleFrame() veh_pb.stamp_secs = msg.timestamp.secs # 1 veh_pb.stamp_nsecs = msg.timestamp.nsecs # 2 veh_pb.timestamp = get_ts(veh_pb.stamp_secs, veh_pb.stamp_nsecs) veh_pb.gear_value = msg.gear # 4 veh_pb.vehicle_speed = msg.vehicle_speed * 3.6 # 5 veh_pb.steering_angle = msg.steering_angle # 6 veh_pb.longitude_acc = msg.longitude_acc veh_pb.lateral_acc = msg.lateral_acc veh_pb.turn_left_light = msg.turn_left_light veh_pb.turn_right_light = msg.turn_right_light veh_pb.brake = msg.brake_torque veh_pb.autonomy_status = 0 vehs.append(veh_pb) elif topic == '/localization/localization_info': lo_pb = LocalizationInfoFrame() lo_pb.timestamp = get_ts(msg.header.stamp.secs, msg.header.stamp.nsecs) lo_pb.stamp_secs = msg.header.stamp.secs lo_pb.stamp_nsecs = msg.header.stamp.nsecs lo_pb.pose_position_x = msg.pose.position.x lo_pb.pose_position_y = msg.pose.position.y lo_pb.pose_position_z = msg.pose.position.z lo_pb.pose_orientation_x = msg.pose.orientation.x lo_pb.pose_orientation_y = msg.pose.orientation.y lo_pb.pose_orientation_z = msg.pose.orientation.z lo_pb.pose_orientation_w = msg.pose.orientation.w lo_pb.pose_orientation_yaw = \ yaw_from_quaternions(msg.pose.orientation.w, msg.pose.orientation.x, msg.pose.orientation.y, msg.pose.orientation.z) lo_pb.velocity_linear = get_modulo(msg.pose.linear_velocity.x, msg.pose.linear_velocity.y, msg.pose.linear_velocity.z) lo_pb.velocity_angular = get_modulo(msg.pose.angular_velocity.x, msg.pose.angular_velocity.y, msg.pose.angular_velocity.z) lo_pb.acceleration_linear = get_modulo(msg.pose.linear_acceleration_vrf.x, msg.pose.linear_acceleration_vrf.y, msg.pose.linear_acceleration_vrf.z) lo_pb.acceleration_angular = get_modulo(msg.pose.angular_velocity_vrf.x, msg.pose.angular_velocity_vrf.y, msg.pose.angular_velocity_vrf.z) locs.append(lo_pb) ego_pose = Pose(msg.pose.orientation.w, msg.pose.orientation.x, msg.pose.orientation.y, msg.pose.orientation.z, msg.pose.position.x, msg.pose.position.y, msg.pose.position.z) elif topic == '/perception/perception_obstacles': if ego_pose is None or pose_has_nan(ego_pose): continue tr_pb = TrackedObjectFrame() tr_pb.timestamp = get_ts(msg.header.stamp.secs, msg.header.stamp.nsecs) tr_pb.stamp_secs = msg.header.stamp.secs tr_pb.stamp_nsecs = msg.header.stamp.nsecs obj_list = list() for object in msg.perception_obstacle: ob_pb = Object() ob_pb.id = object.id ob_pb.label = get_label(object.type, object.sub_type) ob_pb.pose_position_x = object.position.x ob_pb.pose_position_y = object.position.y ob_pb.pose_position_z = object.position.z ob_pb.pose_orientation_x = 0 ob_pb.pose_orientation_y = 0 ob_pb.pose_orientation_z = math.sin(object.theta / 2) ob_pb.pose_orientation_w = math.cos(object.theta / 2) ob_pb.pose_orientation_yaw = object.theta ob_pb.dimensions_x = object.length ob_pb.dimensions_y = object.width ob_pb.dimensions_z = object.height ob_pb.speed_vector_linear_x = object.velocity.x ob_pb.speed_vector_linear_y = object.velocity.y ob_pb.speed_vector_linear_z = object.velocity.z world_obj = np.transpose(np.array([[object.position.x, object.position.y, object.position.z, 1]])) world_ego_t = get_t(ego_pose.q0, ego_pose.q1, ego_pose.q2, ego_pose.q3, ego_pose.x, ego_pose.y, ego_pose.z) try: world_ego_invt = np.linalg.pinv(world_ego_t) except Exception as err: print('pinv failed:', world_ego_t) raise err vehicle_obj = world_ego_invt * world_obj ob_pb.relative_position_x = vehicle_obj[0] ob_pb.relative_position_y = vehicle_obj[1] ob_pb.relative_position_z = vehicle_obj[2] obj_list.append(ob_pb) tr_pb.objects.extend(obj_list) objs.append(tr_pb) print(f"lidar_num : {lidar_num}") print(f"image_num : {image_num}") print(f"radar_num : {radar_num}") folders = [] if len(routes) > 0: os.makedirs(os.path.join(output_dir, 'routing_routing_response_viz')) folders.append({'folder': 'routing_routing_response_viz', 'sensor_type': 'routing_path'}) route_out = RoutingFrames() route_out.routing_frame.extend(routes) with open(os.path.join(output_dir, 'routing_routing_response_viz', 'route.pb'), "wb") as c: c.write(route_out.SerializeToString()) if len(controls) > 0: os.makedirs(os.path.join(output_dir, 'holo_ControlCommand')) folders.append({'folder': 'holo_ControlCommand', 'sensor_type': 'control'}) ctl_cmd_pb_out = ControlCommand() ctl_cmd_pb_out.command_frame.extend(controls) with open(os.path.join(output_dir, 'holo_ControlCommand', 'control.pb'), "wb") as c: c.write(ctl_cmd_pb_out.SerializeToString()) if len(plans) > 0: os.makedirs(os.path.join(output_dir, 'planning_trajectory')) folders.append({'folder': 'planning_trajectory', 'sensor_type': 'planning_trajectory'}) plan_traj_pb_out = PlanTrajectory() plan_traj_pb_out.trajectory_info.extend(plans) with open(os.path.join(output_dir, 'planning_trajectory', 'planning.pb'), "wb") as p: p.write(plan_traj_pb_out.SerializeToString()) if len(preds) > 0: os.makedirs(os.path.join(output_dir, 'prediction_prediction_obstacles')) folders.append({'folder': 'prediction_prediction_obstacles', 'sensor_type': 'predicted_objects'}) pred_obstacles_pb_out = PredictionObstacles() pred_obstacles_pb_out.perception_obstacle.extend(preds) with open(os.path.join(output_dir, 'prediction_prediction_obstacles', 'predicted.pb'), "wb") as p: p.write(pred_obstacles_pb_out.SerializeToString()) if len(gnss) > 0: os.makedirs(os.path.join(output_dir, 'inspvax')) folders.append({'folder': 'inspvax', 'sensor_type': 'gnss'}) gn_pb_out = GnssPoints() gn_pb_out.gnss_points.extend(gnss) with open(os.path.join(output_dir, 'inspvax', 'gnss.pb'), "wb") as g: g.write(gn_pb_out.SerializeToString()) if len(vehs) > 0: os.makedirs(os.path.join(output_dir, 'holo_VehicleInfoMagotan')) folders.append({'folder': 'holo_VehicleInfoMagotan', 'sensor_type': 'vehicle'}) veh_pb_out = VehicleInfo() veh_pb_out.vehicle_info.extend(vehs) with open(os.path.join(output_dir, 'holo_VehicleInfoMagotan', 'vehicle.pb'), "wb") as v: v.write(veh_pb_out.SerializeToString()) if len(locs) > 0: os.makedirs(os.path.join(output_dir, 'localization_localization_info')) folders.append({'folder': 'localization_localization_info', 'sensor_type': 'ego_tf'}) lo_pb_out = LocalizationInfo() lo_pb_out.localization_info.extend(locs) with open(os.path.join(output_dir, 'localization_localization_info', 'ego_tf.pb'), "wb") as lo: lo.write(lo_pb_out.SerializeToString()) if len(objs) > 0: os.makedirs(os.path.join(output_dir, 'perception_perception_obstacles')) folders.append({'folder': 'perception_perception_obstacles', 'sensor_type': 'object_array_vision'}) tr_pb_out = TrackedObject() tr_pb_out.tracked_object.extend(objs) with open(os.path.join(output_dir, 'perception_perception_obstacles', 'object_array_vision.pb'), "wb") as tr: tr.write(tr_pb_out.SerializeToString()) if has_camera_74: folders.append({'folder': 'mdc_camera_instance_74', 'sensor_type': 'camera'}) if has_camera_73: folders.append({'folder': 'mdc_camera_instance_73', 'sensor_type': 'camera'}) if has_camera_72: folders.append({'folder': 'mdc_camera_instance_72', 'sensor_type': 'camera'}) if has_camera_71: folders.append({'folder': 'mdc_camera_instance_71', 'sensor_type': 'camera'}) if has_lidar_A4: if args.calibration_id: folders.append({'folder': 'innoPtClound_A4', 'sensor_type': 'lidar', 'calibration_item_id': args.calibration_id}) else: folders.append({'folder': 'innoPtClound_A4', 'sensor_type': 'lidar'}) if has_lidar_B2: if args.calibration_id: folders.append({'folder': 'innoPtClound_B2', 'sensor_type': 'lidar', 'calibration_item_id': args.calibration_id}) else: folders.append({'folder': 'innoPtClound_B2', 'sensor_type': 'lidar'}) if has_radar_0: folders.append({'folder': 'radar_track_array_0', 'sensor_type': 'radar'}) if has_radar_3: folders.append({'folder': 'radar_track_array_3', 'sensor_type': 'radar'}) collect_yaml = {'folders': folders} with open(os.path.join(output_dir, "opendata_to_platform.yaml"), 'w', encoding='utf-8') as collect_file: yaml.safe_dump(collect_yaml, collect_file) with open(os.path.join(os.getenv('output_dir'), '_SUCCESS'), 'w') as f: f.write("") os.system('chmod -R a+r ${output_dir}/*') if __name__ == '__main__': parser = ArgumentParser() parser.add_argument('-i', '--input', help="input bag path", default=os.getenv('rosbag_path')) parser.add_argument('-o', '--output', default=os.getenv('output_dir'), help="result output directory, default to ./bags/") parser.add_argument('-ci', '--calibration_id', type=int) params = parser.parse_args() main(params)

#!/usr/bin/env python3 import os import pandas as pd from glob import glob import sys import argparse from openpyxl.styles import Alignment from openpyxl import load_workbook from pathlib import Path import shutil import tempfile # 常见编码列表 COMMON_ENCODINGS = ['utf-8', 'latin1', 'gbk', 'iso-8859-1', 'latin1', 'cp1252','gb2312'] #COMMON_ENCODINGS = [‘utf-8’, ‘latin1’, ‘iso-8859-1’, ‘cp1252’, ‘gbk’, ‘gb2312’, ‘big5’] def detect_encoding(file_path): """静默检测文件编码""" for encoding in COMMON_ENCODINGS: try: with open(file_path, 'r', encoding=encoding) as f: f.read(1024) # 尝试读取前1KB内容 return encoding except: continue return 'utf-8' # 默认使用utf-8 def apply_left_alignment(output_file): """应用左对齐样式到所有单元格""" try: wb = load_workbook(output_file) ws = wb.active # 创建左对齐样式 left_align = Alignment(horizontal='left') # 应用到所有单元格 for row in ws.iter_rows(): for cell in row: cell.alignment = left_align wb.save(output_file) return True except Exception as e: print(f"应用左对齐样式时出错: {str(e)}", file=sys.stderr) return False def clean_file_content(file_path): """清理文件内容:执行字节替换操作""" try: with open(file_path, 'rb') as file: content = file.read() # 执行字节替换操作 cleaned_content = content.replace(b'\x01', b'\r').replace(b'\x00', b' ').replace(b'\x0A', b' ') with open(file_path, 'wb') as file: file.write(cleaned_content) return True except Exception as e: print(f"清理文件 {file_path} 时出错: {str(e)}", file=sys.stderr) return False def read_txt_file(file_path, sep): """读取单个文本文件,不处理标题行""" encoding = detect_encoding(file_path) try: return pd.read_csv( file_path, sep=sep, encoding=encoding, engine='python', header=None, # 不将第一行作为标题 dtype=str, # 所有数据作为字符串处理 keep_default_na=False, # 不将空值转换为NaN on_bad_lines='skip' # 跳过格式错误的行 ) except Exception as e: print(f"读取文件 {file_path} 时出错: {str(e)}", file=sys.stderr) return None def merge_txt_files(input_dir, output_file, sep='\t', recursive=False, clean_files=False): """ 合并目录下所有文本文件到单个Excel文件 :param input_dir: 输入目录路径 :param output_file: 输出Excel文件路径 :param sep: 文本文件分隔符,默认为制表符 :param recursive: 是否递归搜索子目录 :param clean_files: 是否在合并前执行字节替换清理 """ # 获取所有文本文件 pattern = os.path.join(input_dir, '**', '*.txt') if recursive \ else os.path.join(input_dir, '*.txt') txt_files = glob(pattern, recursive=recursive) if not txt_files: print(f"在 {input_dir} 中未找到任何.txt文件", file=sys.stderr) return False # 如果需要清理文件,创建临时目录处理 if clean_files: temp_dir = tempfile.mkdtemp() print(f"创建临时目录: {temp_dir}") # 复制文件到临时目录并清理 for file_path in txt_files: temp_path = os.path.join(temp_dir, os.path.basename(file_path)) shutil.copy2(file_path, temp_path) if not clean_file_content(temp_path): print(f"清理失败: {os.path.basename(file_path)}", file=sys.stderr) continue print(f"已清理: {os.path.basename(file_path)}") # 使用临时目录中的文件 input_dir = temp_dir pattern = os.path.join(temp_dir, '*.txt') txt_files = glob(pattern, recursive=False) all_data = [] for file_path in txt_files: df = read_txt_file(file_path, sep) if df is not None and not df.empty: all_data.append(df) print(f"已处理: {os.path.basename(file_path)}") if not all_data: print("所有文件均为空或无法读取", file=sys.stderr) return False try: # 合并所有数据 combined_df = pd.concat(all_data, ignore_index=True) # 写入Excel文件 combined_df.to_excel(output_file, sheet_name='合并数据', index=False, header=False) print(f"已创建Excel文件: {output_file}") # 应用左对齐样式 if apply_left_alignment(output_file): return True return False except Exception as e: print(f"合并或写入文件时出错: {str(e)}", file=sys.stderr) return False finally: # 清理临时目录 if clean_files and 'temp_dir' in locals(): shutil.rmtree(temp_dir) print(f"已删除临时目录: {temp_dir}") if __name__ == "__main__": parser = argparse.ArgumentParser( description='合并多个文本文件到单个Excel文件,支持字节替换清理', formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser.add_argument('-i', '--input', required=True, help='包含.txt文件的输入目录路径') parser.add_argument('-o', '--output', default='合并数据.xlsx', help='输出Excel文件路径') parser.add_argument('-s', '--sep', default='\t', help='文本文件中的分隔符,如",", ";", "\\t"等') parser.add_argument('-r', '--recursive', action='store_true', help='递归搜索子目录中的文件') parser.add_argument('-c', '--clean', action='store_true', help='在合并前执行字节替换清理(替换 \\x01, \\x00, \\x0A)') args = parser.parse_args() # 确保输出目录存在 output_dir = os.path.dirname(args.output) if output_dir and not os.path.exists(output_dir): os.makedirs(output_dir) success = merge_txt_files( args.input, args.output, args.sep, args.recursive, args.clean ) sys.exit(0 if success else 1) 智能纠错:修改读取文件格式错误的问题

import tkinter as tk from tkinter import ttk, filedialog, messagebox import pandas as pd import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import tensorflow as tf from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Dense, Lambda from tensorflow.keras.optimizers import Adam from sklearn.preprocessing import MinMaxScaler import os import time mpl.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS'] mpl.rcParams['axes.unicode_minus'] = False # 关键修复:使用 ASCII 减号 # 设置中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False class PINNModel(tf.keras.Model): def __init__(self, num_layers=4, hidden_units=32, **kwargs): super(PINNModel, self).__init__(**kwargs) self.dense_layers = [Dense(hidden_units, activation='tanh') for _ in range(num_layers)] self.final_layer = Dense(1, activation='linear') # 添加带约束的物理参数 self.k_raw = tf.Variable(0.01, trainable=True, dtype=tf.float32, name='k_raw') self.k = tf.math.sigmoid(self.k_raw) * 0.5 # 约束在0-0.5之间 def call(self, inputs): t, h = inputs x = tf.concat([t, h], axis=1) for layer in self.dense_layers: x = layer(x) return self.final_layer(x) def physics_loss(self, t, h_current): """计算物理损失(基于离散渗流方程)""" # 预测下一时刻的水位 h_next_pred = self([t, h_current]) # 离散渗流方程: h_{t+1} = h_t - k * h_t (时间步长=1) residual = h_next_pred - h_current * (1 - self.k) return tf.reduce_mean(tf.square(residual)) class DamSeepageModel: def __init__(self, root): self.root = root self.root.title("大坝渗流预测模型(PINNs)") self.root.geometry("1200x800") # 初始化数据 self.train_df = None #训练集 self.test_df = None #测试集 self.model = None self.scaler = MinMaxScaler(feature_range=(0, 1)) self.evaluation_metrics = {} # 创建主界面 self.create_widgets() def create_widgets(self): # 创建主框架 main_frame = ttk.Frame(self.root, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 左侧控制面板 control_frame = ttk.LabelFrame(main_frame, text="模型控制", padding=10) control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5) # 文件选择部分 file_frame = ttk.LabelFrame(control_frame, text="数据文件", padding=10) file_frame.pack(fill=tk.X, pady=5) # 训练集选择 ttk.Label(file_frame, text="训练集:").grid(row=0, column=0, sticky=tk.W, pady=5) self.train_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.train_file_var, width=30, state='readonly').grid(row=0, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("train")).grid(row=0, column=2) # 测试集选择 ttk.Label(file_frame, text="测试集:").grid(row=1, column=0, sticky=tk.W, pady=5) self.test_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.test_file_var, width=30, state='readonly').grid(row=1, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("test")).grid(row=1, column=2) # PINNs参数设置 param_frame = ttk.LabelFrame(control_frame, text="PINNs参数", padding=10) param_frame.pack(fill=tk.X, pady=10) # 隐藏层数量 ttk.Label(param_frame, text="网络层数:").grid(row=0, column=0, sticky=tk.W, pady=5) self.num_layers_var = tk.IntVar(value=4) ttk.Spinbox(param_frame, from_=2, to=8, increment=1, textvariable=self.num_layers_var, width=10).grid(row=0, column=1, padx=5) # 每层神经元数量 ttk.Label(param_frame, text="神经元数/层:").grid(row=1, column=0, sticky=tk.W, pady=5) self.hidden_units_var = tk.IntVar(value=32) ttk.Spinbox(param_frame, from_=16, to=128, increment=4, textvariable=self.hidden_units_var, width=10).grid(row=1, column=1, padx=5) # 训练轮次 ttk.Label(param_frame, text="训练轮次:").grid(row=2, column=0, sticky=tk.W, pady=5) self.epochs_var = tk.IntVar(value=500) ttk.Spinbox(param_frame, from_=100, to=2000, increment=100, textvariable=self.epochs_var, width=10).grid(row=2, column=1, padx=5) # 物理损失权重 ttk.Label(param_frame, text="物理损失权重:").grid(row=3, column=0, sticky=tk.W, pady=5) self.physics_weight_var = tk.DoubleVar(value=0.5) ttk.Spinbox(param_frame, from_=0.1, to=1.0, increment=0.1, textvariable=self.physics_weight_var, width=10).grid(row=3, column=1, padx=5) # 控制按钮 btn_frame = ttk.Frame(control_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="训练模型", command=self.train_model).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="预测结果", command=self.predict).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="保存结果", command=self.save_results).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.RIGHT, padx=5) # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label(control_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(fill=tk.X, side=tk.BOTTOM) # 右侧结果显示区域 result_frame = ttk.Frame(main_frame) result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建标签页 self.notebook = ttk.Notebook(result_frame) self.notebook.pack(fill=tk.BOTH, expand=True) # 损失曲线标签页 self.loss_frame = ttk.Frame(self.notebook) self.notebook.add(self.loss_frame, text="训练损失") # 预测结果标签页 self.prediction_frame = ttk.Frame(self.notebook) self.notebook.add(self.prediction_frame, text="预测结果") # 指标显示 self.metrics_var = tk.StringVar() metrics_label = ttk.Label( self.prediction_frame, textvariable=self.metrics_var, font=('TkDefaultFont', 10, 'bold'), relief='ridge', padding=5 ) metrics_label.pack(fill=tk.X, padx=5, pady=5) # 初始化绘图区域 self.fig, self.ax = plt.subplots(figsize=(10, 6)) self.canvas = FigureCanvasTkAgg(self.fig, master=self.prediction_frame) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # 损失曲线画布 self.loss_fig, self.loss_ax = plt.subplots(figsize=(10, 4)) self.loss_canvas = FigureCanvasTkAgg(self.loss_fig, master=self.loss_frame) self.loss_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) def select_file(self, file_type): """选择Excel文件""" file_path = filedialog.askopenfilename( title=f"选择{file_type}集Excel文件", filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")] ) if file_path: try: df = pd.read_excel(file_path) # 时间特征处理 time_features = ['year', 'month', 'day'] missing_time_features = [feat for feat in time_features if feat not in df.columns] if missing_time_features: messagebox.showerror("列名错误", f"Excel文件缺少预处理后的时间特征列: {', '.join(missing_time_features)}") return # 创建时间戳列 (增强兼容性) time_cols = ['year', 'month', 'day'] if 'hour' in df.columns: time_cols.append('hour') if 'minute' in df.columns: time_cols.append('minute') if 'second' in df.columns: time_cols.append('second') # 填充缺失的时间单位 for col in ['hour', 'minute', 'second']: if col not in df.columns: df[col] = 0 df['datetime'] = pd.to_datetime(df[time_cols]) # 设置时间索引 df = df.set_index('datetime') # 计算相对时间(天) df['days'] = (df.index - df.index[0]).days # 保存数据 if file_type == "train": self.train_df = df self.train_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载训练集: {len(self.train_df)}条数据") else: self.test_df = df self.test_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载测试集: {len(self.test_df)}条数据") except Exception as e: messagebox.showerror("文件错误", f"读取文件失败: {str(e)}") def calculate_metrics(self, y_true, y_pred): """计算评估指标""" from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score mse = mean_squared_error(y_true, y_pred) rmse = np.sqrt(mse) mae = mean_absolute_error(y_true, y_pred) non_zero_idx = np.where(y_true != 0)[0] if len(non_zero_idx) > 0: mape = np.mean(np.abs((y_true[non_zero_idx] - y_pred[non_zero_idx]) / y_true[non_zero_idx])) * 100 else: mape = float('nan') r2 = r2_score(y_true, y_pred) return { 'MSE': mse, 'RMSE': rmse, 'MAE': mae, 'MAPE': mape, 'R2': r2 } def train_model(self): """训练PINNs模型(带早停机制)""" if self.train_df is None: messagebox.showwarning("警告", "请先选择训练集文件") return try: self.status_var.set("正在预处理数据...") self.root.update() # 从训练集中切分80%训练子集和20%验证子集(时间顺序切分) split_ratio = 0.8 split_idx = int(len(self.train_df) * split_ratio) train_subset = self.train_df.iloc[:split_idx] valid_subset = self.train_df.iloc[split_idx:] # 检查数据量是否足够 if len(train_subset) < 2 or len(valid_subset) < 2: messagebox.showerror("数据错误", "训练集数据量不足(至少需要2个时间步)") return # 数据预处理(训练子集拟合scaler,验证子集用相同scaler) train_subset_scaled = self.scaler.fit_transform(train_subset[['水位']]) valid_subset_scaled = self.scaler.transform(valid_subset[['水位']]) # 准备训练数据 t_train = train_subset['days'].values[1:].reshape(-1, 1).astype(np.float32) h_train = train_subset_scaled[:-1].astype(np.float32) h_next_train = train_subset_scaled[1:].astype(np.float32) # 准备验证数据 t_valid = valid_subset['days'].values[1:].reshape(-1, 1).astype(np.float32) h_valid = valid_subset_scaled[:-1].astype(np.float32) h_next_valid = valid_subset_scaled[1:].astype(np.float32) # 创建模型和优化器 self.model = PINNModel( num_layers=self.num_layers_var.get(), hidden_units=self.hidden_units_var.get() ) optimizer = Adam(learning_rate=0.001) # 构建训练/验证数据集 train_dataset = tf.data.Dataset.from_tensor_slices(((t_train, h_train), h_next_train)) train_dataset = train_dataset.shuffle(buffer_size=1024).batch(32) valid_dataset = tf.data.Dataset.from_tensor_slices(((t_valid, h_valid), h_next_valid)) valid_dataset = valid_dataset.batch(32) # 验证集无需shuffle # 损失记录 train_data_loss_history = [] physics_loss_history = [] valid_data_loss_history = [] # 早停机制参数(可根据需求调整) patience =int(self.epochs_var.get()/3) # 允许验证损失不改善的最大轮次数 min_delta = 1e-4 # 验证损失需改善的最小幅度(小于此值视为无改善) best_valid_loss = float('inf') # 初始最佳验证损失设为无穷大 wait = 0 # 记录连续未改善的轮次数 best_epoch = 0 # 记录最佳验证损失对应的轮次 # 用于保存最佳模型权重(避免早停时模型未保存最佳状态) best_weights = None start_time = time.time() # 自定义训练循环(添加早停判断) for epoch in range(self.epochs_var.get()): # 训练阶段 epoch_train_data_loss = [] epoch_physics_loss = [] for step, ((t_batch, h_batch), h_next_batch) in enumerate(train_dataset): with tf.GradientTape() as tape: h_pred = self.model([t_batch, h_batch]) data_loss = tf.reduce_mean(tf.square(h_next_batch - h_pred)) physics_loss = self.model.physics_loss(t_batch, h_batch) loss = data_loss + self.physics_weight_var.get() * physics_loss grads = tape.gradient(loss, self.model.trainable_variables) optimizer.apply_gradients(zip(grads, self.model.trainable_variables)) epoch_train_data_loss.append(data_loss.numpy()) epoch_physics_loss.append(physics_loss.numpy()) # 验证阶段 epoch_valid_data_loss = [] for ((t_v_batch, h_v_batch), h_v_next_batch) in valid_dataset: h_v_pred = self.model([t_v_batch, h_v_batch]) valid_data_loss = tf.reduce_mean(tf.square(h_v_next_batch - h_v_pred)) epoch_valid_data_loss.append(valid_data_loss.numpy()) # 计算平均损失 avg_train_data_loss = np.mean(epoch_train_data_loss) avg_physics_loss = np.mean(epoch_physics_loss) avg_valid_data_loss = np.mean(epoch_valid_data_loss) # 记录损失 train_data_loss_history.append(avg_train_data_loss) physics_loss_history.append(avg_physics_loss) valid_data_loss_history.append(avg_valid_data_loss) # 早停机制核心逻辑 current_valid_loss = avg_valid_data_loss if current_valid_loss < best_valid_loss - min_delta: # 验证损失显著改善:更新最佳状态,保存模型权重 best_valid_loss = current_valid_loss best_epoch = epoch + 1 # 轮次从1开始计数 wait = 0 # 保存当前最佳模型权重(使用TensorFlow的权重保存功能) best_weights = self.model.get_weights() else: # 验证损失未改善:增加等待计数 wait += 1 # 等待计数超过patience时触发早停 if wait >= patience: self.status_var.set(f"触发早停!最佳轮次: {best_epoch},最佳验证损失: {best_valid_loss:.4f}") # 恢复最佳模型权重(避免最终模型是过拟合的) if best_weights is not None: self.model.set_weights(best_weights) break # 退出训练循环 # 更新状态(显示验证损失) if epoch % 10 == 0: k_value = self.model.k.numpy() elapsed = time.time() - start_time self.status_var.set( f"训练中 | 轮次: {epoch + 1}/{self.epochs_var.get()} | " f"训练数据损失: {avg_train_data_loss:.4f} | " f"物理损失: {avg_physics_loss:.4f} | " f"验证数据损失: {avg_valid_data_loss:.4f} | " f"k: {k_value:.6f} | 时间: {elapsed:.1f}秒 | 早停等待: {wait}/{patience}" ) self.root.update() # 绘制损失曲线(包含早停位置) self.loss_ax.clear() epochs_range = range(1, len(train_data_loss_history) + 1) self.loss_ax.plot(epochs_range, train_data_loss_history, 'b-', label='训练数据损失') self.loss_ax.plot(epochs_range, physics_loss_history, 'r--', label='物理损失') self.loss_ax.plot(epochs_range, valid_data_loss_history, 'g-.', label='验证数据损失') # 标记早停位置(如果触发了早停) if wait >= patience: self.loss_ax.axvline(x=best_epoch, color='m', linestyle='--', label=f'早停位置(轮次{best_epoch})') self.loss_ax.scatter(best_epoch, best_valid_loss, color='m', zorder=5) self.loss_ax.set_title('PINNs训练与验证损失(含早停)') self.loss_ax.set_xlabel('轮次') self.loss_ax.set_ylabel('损失', rotation=0) self.loss_ax.legend() self.loss_ax.grid(True) self.loss_ax.set_yscale('log') self.loss_canvas.draw() # 训练完成提示(区分正常结束和早停) elapsed = time.time() - start_time if wait >= patience: completion_msg = ( f"早停触发 | 最佳轮次: {best_epoch} | 最佳验证损失: {best_valid_loss:.4f} | " f"总时间: {elapsed:.1f}秒" ) else: completion_msg = ( f"训练完成 | 总轮次: {self.epochs_var.get()} | " f"最终训练数据损失: {train_data_loss_history[-1]:.4f} | " f"最终物理损失: {physics_loss_history[-1]:.4f} | " f"最终验证数据损失: {valid_data_loss_history[-1]:.4f} | " f"总时间: {elapsed:.1f}秒" ) self.status_var.set(completion_msg) messagebox.showinfo("训练完成", f"PINNs模型训练成功完成!\n{completion_msg}") except Exception as e: messagebox.showerror("训练错误", f"模型训练失败:\n{str(e)}") self.status_var.set("训练失败") def predict(self): """使用PINNs模型进行预测""" if self.model is None: messagebox.showwarning("警告", "请先训练模型") return if self.test_df is None: messagebox.showwarning("警告", "请先选择测试集文件") return try: self.status_var.set("正在生成预测...") self.root.update() # 预处理测试数据 test_scaled = self.scaler.transform(self.test_df[['水位']]) # 准备时间特征 t_test = self.test_df['days'].values.reshape(-1, 1).astype(np.float32) # 递归预测 predictions = [] for i in range(len(t_test)): h_current = np.array([[test_scaled[i][0]]]).astype(np.float32) h_pred = self.model([t_test[i:i + 1], h_current]) predictions.append(h_pred.numpy()[0][0]) # 反归一化 predictions = np.array(predictions).reshape(-1, 1) predictions = self.scaler.inverse_transform(predictions) actual_values = self.scaler.inverse_transform(test_scaled) # 创建时间索引 test_time = self.test_df.index # 清除现有图表 self.ax.clear() # 绘制结果 self.ax.plot(test_time, actual_values, 'b-', label='真实值') self.ax.plot(test_time, predictions, 'r--', label='预测值') self.ax.set_title('大坝渗流水位预测结果(PINNs)') self.ax.set_xlabel('时间') self.ax.set_ylabel('测压管水位', rotation=0) self.ax.legend() # 添加网格和样式 self.ax.grid(True, alpha=0.3) # 计算并显示评估指标 self.evaluation_metrics = self.calculate_metrics( actual_values.flatten(), predictions.flatten() ) metrics_text = ( f"MSE: {self.evaluation_metrics['MSE']:.4f} | " f"RMSE: {self.evaluation_metrics['RMSE']:.4f} | " f"MAE: {self.evaluation_metrics['MAE']:.4f} | " f"MAPE: {self.evaluation_metrics['MAPE']:.2f}% | " f"R²: {self.evaluation_metrics['R2']:.4f}" ) # 更新文本标签 self.metrics_var.set(metrics_text) # 在图表上添加指标 self.ax.text( 0.5, 1.05, metrics_text, transform=self.ax.transAxes, ha='center', fontsize=10, bbox=dict(facecolor='white', alpha=0.8) ) # 调整布局并显示图表 plt.tight_layout() self.canvas.draw() # 保存预测结果 self.predictions = predictions self.actual_values = actual_values self.test_time = test_time self.status_var.set("预测完成,结果已显示") except Exception as e: messagebox.showerror("预测错误", f"预测失败:\n{str(e)}") self.status_var.set("预测失败") def save_results(self): """保存预测结果""" if not hasattr(self, 'predictions'): messagebox.showwarning("警告", "请先生成预测结果") return save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")] ) if not save_path: return try: # 创建结果DataFrame result_df = pd.DataFrame({ '时间': self.test_time, '实际水位': self.actual_values.flatten(), '预测水位': self.predictions.flatten() }) # 创建评估指标DataFrame metrics_df = pd.DataFrame([self.evaluation_metrics]) # 保存到Excel with pd.ExcelWriter(save_path) as writer: result_df.to_excel(writer, sheet_name='预测结果', index=False) metrics_df.to_excel(writer, sheet_name='评估指标', index=False) # 保存图表 chart_path = os.path.splitext(save_path)[0] + "_chart.png" self.fig.savefig(chart_path, dpi=300) self.status_var.set(f"结果已保存至: {os.path.basename(save_path)}") messagebox.showinfo("保存成功", f"预测结果和图表已保存至:\n{save_path}\n{chart_path}") except Exception as e: messagebox.showerror("保存错误", f"保存结果失败:\n{str(e)}") def reset(self): """重置程序状态""" self.train_df = None self.test_df = None self.model = None self.train_file_var.set("") self.test_file_var.set("") # 清除图表 if hasattr(self, 'ax'): self.ax.clear() if hasattr(self, 'loss_ax'): self.loss_ax.clear() # 重绘画布 if hasattr(self, 'canvas'): self.canvas.draw() if hasattr(self, 'loss_canvas'): self.loss_canvas.draw() # 清除状态 self.status_var.set("已重置,请选择新数据") # 清除预测结果 if hasattr(self, 'predictions'): del self.predictions # 清除指标文本 if hasattr(self, 'metrics_var'): self.metrics_var.set("") messagebox.showinfo("重置", "程序已重置,可以开始新的分析") if __name__ == "__main__": root = tk.Tk() app = DamSeepageModel(root) root.mainloop() 我的这个模型中,MSE,RMSE等评估指标是否发挥作用,如果没有,请你将它们引入模型训练中

import tkinter as tk from tkinter import ttk, filedialog, messagebox import pandas as pd import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from sklearn.preprocessing import MinMaxScaler import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping import os plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体 plt.rcParams['axes.unicode_minus'] = False class DamSeepageModel: def __init__(self, root): self.root = root self.root.title("大坝渗流预测模型") self.root.geometry("1200x800") # 初始化数据 self.train_df = None self.test_df = None self.model = None self.scaler = MinMaxScaler(feature_range=(0, 1)) self.evaluation_metrics = {} # 存储评估指标结果 # 创建主界面 self.create_widgets() def create_widgets(self): # 创建主框架 main_frame = ttk.Frame(self.root, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 左侧控制面板 control_frame = ttk.LabelFrame(main_frame, text="模型控制", padding=10) control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5) # 文件选择部分 file_frame = ttk.LabelFrame(control_frame, text="数据文件", padding=10) file_frame.pack(fill=tk.X, pady=5) # 训练集选择 ttk.Label(file_frame, text="训练集:").grid(row=0, column=0, sticky=tk.W, pady=5) self.train_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.train_file_var, width=30, state='readonly').grid(row=0, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("train")).grid(row=0, column=2) # 测试集选择 ttk.Label(file_frame, text="测试集:").grid(row=1, column=0, sticky=tk.W, pady=5) self.test_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.test_file_var, width=30, state='readonly').grid(row=1, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("test")).grid(row=1, column=2) # 参数设置部分 param_frame = ttk.LabelFrame(control_frame, text="模型参数", padding=10) param_frame.pack(fill=tk.X, pady=10) # 时间窗口大小 ttk.Label(param_frame, text="时间窗口大小:").grid(row=0, column=0, sticky=tk.W, pady=5) self.window_size_var = tk.IntVar(value=60) ttk.Spinbox(param_frame, from_=10, to=200, increment=5, textvariable=self.window_size_var, width=10).grid(row=0, column=1, padx=5) # LSTM单元数量 ttk.Label(param_frame, text="LSTM单元数:").grid(row=1, column=0, sticky=tk.W, pady=5) self.lstm_units_var = tk.IntVar(value=50) ttk.Spinbox(param_frame, from_=10, to=200, increment=10, textvariable=self.lstm_units_var, width=10).grid(row=1, column=1, padx=5) # 训练轮次 ttk.Label(param_frame, text="训练轮次:").grid(row=2, column=0, sticky=tk.W, pady=5) self.epochs_var = tk.IntVar(value=100) ttk.Spinbox(param_frame, from_=10, to=500, increment=10, textvariable=self.epochs_var, width=10).grid(row=2, column=1, padx=5) # 批处理大小 ttk.Label(param_frame, text="批处理大小:").grid(row=3, column=0, sticky=tk.W, pady=5) self.batch_size_var = tk.IntVar(value=32) ttk.Spinbox(param_frame, from_=16, to=128, increment=16, textvariable=self.batch_size_var, width=10).grid(row=3, column=1, padx=5) # 控制按钮 btn_frame = ttk.Frame(control_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="训练模型", command=self.train_model).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="预测结果", command=self.predict).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="保存结果", command=self.save_results).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.RIGHT, padx=5) # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label(control_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(fill=tk.X, side=tk.BOTTOM) # 右侧结果显示区域 result_frame = ttk.Frame(main_frame) result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建标签页 self.notebook = ttk.Notebook(result_frame) self.notebook.pack(fill=tk.BOTH, expand=True) # 损失曲线标签页 self.loss_frame = ttk.Frame(self.notebook) self.notebook.add(self.loss_frame, text="训练损失") # 预测结果标签页 self.prediction_frame = ttk.Frame(self.notebook) self.notebook.add(self.prediction_frame, text="预测结果") # 添加指标文本框 self.metrics_var = tk.StringVar() metrics_label = ttk.Label( self.prediction_frame, textvariable=self.metrics_var, font=('TkDefaultFont', 10, 'bold'), relief='ridge', padding=5 ) metrics_label.pack(fill=tk.X, padx=5, pady=5) # 初始化绘图区域 self.fig, self.ax = plt.subplots(figsize=(10, 6)) self.canvas = FigureCanvasTkAgg(self.fig, master=self.prediction_frame) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) self.loss_fig, self.loss_ax = plt.subplots(figsize=(10, 4)) self.loss_canvas = FigureCanvasTkAgg(self.loss_fig, master=self.loss_frame) self.loss_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # 文件选择 def select_file(self, file_type): """选择Excel文件""" file_path = filedialog.askopenfilename( title=f"选择{file_type}集Excel文件", filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")] ) if file_path: try: # 读取Excel文件 df = pd.read_excel(file_path) # 时间特征列 time_features = ['year', 'month', 'day'] missing_time_features = [feat for feat in time_features if feat not in df.columns] if '水位' not in df.columns: messagebox.showerror("列名错误", "Excel文件必须包含'水位'列") return if missing_time_features: messagebox.showerror("列名错误", f"Excel文件缺少预处理后的时间特征列: {', '.join(missing_time_features)}\n" "请确保已使用预处理功能添加这些列") return # 创建完整的时间戳列 # 处理可能缺失的小时、分钟、秒数据 if 'hour' in df.columns and 'minute' in df.columns and 'second' in df.columns: df['datetime'] = pd.to_datetime( df[['year', 'month', 'day', 'hour', 'minute', 'second']] ) elif 'hour' in df.columns and 'minute' in df.columns: df['datetime'] = pd.to_datetime( df[['year', 'month', 'day', 'hour', 'minute']].assign(second=0) ) else: df['datetime'] = pd.to_datetime(df[['year', 'month', 'day']]) # 设置时间索引 df = df.set_index('datetime') # 保存数据 if file_type == "train": self.train_df = df self.train_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载训练集: {len(self.train_df)}条数据") else: self.test_df = df self.test_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载测试集: {len(self.test_df)}条数据") except Exception as e: messagebox.showerror("文件错误", f"读取文件失败: {str(e)}") # 添加评估指标计算函数 def calculate_metrics(self, y_true, y_pred): """计算各种评估指标""" from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score mse = mean_squared_error(y_true, y_pred) rmse = np.sqrt(mse) mae = mean_absolute_error(y_true, y_pred) # 避免除以零错误 non_zero_idx = np.where(y_true != 0)[0] if len(non_zero_idx) > 0: mape = np.mean(np.abs((y_true[non_zero_idx] - y_pred[non_zero_idx]) / y_true[non_zero_idx])) * 100 else: mape = float('nan') r2 = r2_score(y_true, y_pred) return { 'MSE': mse, 'RMSE': rmse, 'MAE': mae, 'MAPE': mape, 'R2': r2 } def create_dataset(self, data, window_size): """创建时间窗口数据集""" X, y = [], [] for i in range(len(data) - window_size): X.append(data[i:(i + window_size), 0]) y.append(data[i + window_size, 0]) return np.array(X), np.array(y) def create_dynamic_plot_callback(self): """创建动态绘图回调实例,用于实时显示训练损失曲线""" class DynamicPlotCallback(tf.keras.callbacks.Callback): def __init__(self, gui_app): self.gui_app = gui_app # 引用主GUI实例 self.train_loss = [] # 存储训练损失 self.val_loss = [] # 存储验证损失 def on_epoch_end(self, epoch, logs=None): """每个epoch结束时更新图表""" logs = logs or {} # 收集损失数据 self.train_loss.append(logs.get('loss')) self.val_loss.append(logs.get('val_loss')) # 更新GUI中的图表(在主线程中执行) self.gui_app.root.after(0, self._update_plot) def _update_plot(self): """实际更新图表的函数""" try: # 清除现有图表 self.gui_app.loss_ax.clear() # 绘制训练和验证损失曲线 epochs = range(1, len(self.train_loss) + 1) self.gui_app.loss_ax.plot(epochs, self.train_loss, 'b-', label='训练损失') self.gui_app.loss_ax.plot(epochs, self.val_loss, 'r-', label='验证损失') # 设置图表属性 self.gui_app.loss_ax.set_title('模型训练损失') self.gui_app.loss_ax.set_xlabel('轮次') self.gui_app.loss_ax.set_ylabel('损失', rotation=0) self.gui_app.loss_ax.legend(loc='upper right') self.gui_app.loss_ax.grid(True, alpha=0.3) # 自动调整Y轴范围 all_losses = self.train_loss + self.val_loss min_loss = max(0, min(all_losses) * 0.9) max_loss = max(all_losses) * 1.1 self.gui_app.loss_ax.set_ylim(min_loss, max_loss) # 刷新画布 self.gui_app.loss_canvas.draw() # 更新状态栏显示最新损失 current_epoch = len(self.train_loss) if current_epoch > 0: latest_train_loss = self.train_loss[-1] latest_val_loss = self.val_loss[-1] if self.val_loss else 0 self.gui_app.status_var.set( f"训练中 | 轮次: {current_epoch} | " f"训练损失: {latest_train_loss:.6f} | " f"验证损失: {latest_val_loss:.6f}" ) self.gui_app.root.update() except Exception as e: print(f"更新图表时出错: {str(e)}") # 返回回调实例 return DynamicPlotCallback(self) def train_model(self): """训练LSTM模型""" if self.train_df is None: messagebox.showwarning("警告", "请先选择训练集文件") return try: self.status_var.set("正在预处理数据...") self.root.update() # 数据预处理 train_scaled = self.scaler.fit_transform(self.train_df[['水位']]) # 创建时间窗口数据集 window_size = self.window_size_var.get() X_train, y_train = self.create_dataset(train_scaled, window_size) # 调整LSTM输入格式 X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1)) # 构建LSTM模型 self.model = Sequential() self.model.add(LSTM( self.lstm_units_var.get(), return_sequences=True, input_shape=(window_size, 1) )) self.model.add(LSTM(self.lstm_units_var.get())) self.model.add(Dense(1)) self.model.compile( optimizer=Adam(learning_rate=0.001), loss='mean_squared_error' ) # 创建验证集(在训练之前) val_size = int(0.2 * len(X_train)) X_val, y_val = X_train[:val_size], y_train[:val_size] X_train, y_train = X_train[val_size:], y_train[val_size:] # 定义评估回调类 class MetricsCallback(tf.keras.callbacks.Callback): def __init__(self, X_val, y_val, scaler, gui_app): # 添加gui_app参数 super().__init__() self.X_val = X_val self.y_val = y_val self.scaler = scaler self.gui_app = gui_app # 直接存储引用 self.best_r2 = -float('inf') self.best_weights = None def on_epoch_end(self, epoch, logs=None): # 预测验证集(添加verbose=0避免输出) val_pred = self.model.predict(self.X_val, verbose=0) # 反归一化 val_pred_orig = self.scaler.inverse_transform(val_pred) y_val_orig = self.scaler.inverse_transform(self.y_val.reshape(-1, 1)) # 计算指标(使用self.gui_app) metrics = self.gui_app.calculate_metrics(y_val_orig, val_pred_orig) # 更新日志 logs = logs or {} logs.update({f'val_{k}': v for k, v in metrics.items()}) # 保存最佳权重(基于R²) if metrics['R2'] > self.best_r2: self.best_r2 = metrics['R2'] self.best_weights = self.model.get_weights() # 更新状态栏(使用self.gui_app) status = (f"训练中 | 轮次: {epoch + 1} | " f"损失: {logs.get('loss', 0):.6f} | " f"验证R²: {metrics['R2']:.4f}") self.gui_app.status_var.set(status) self.gui_app.root.update() # 添加回调(传递所有四个参数) metrics_callback = MetricsCallback(X_val, y_val, self.scaler, self) # 添加self参数 # 添加早停机制 early_stopping = EarlyStopping( monitor='val_loss', # 监控验证集损失 patience=self.epochs_var.get()/3, # 连续20轮无改善则停止 min_delta=0.0001, # 最小改善阈值 restore_best_weights=True, # 恢复最佳权重 verbose=1 # 显示早停信息 ) # 在model.fit中添加回调 history = self.model.fit( X_train, y_train, epochs=self.epochs_var.get(), batch_size=self.batch_size_var.get(), validation_data=(X_val, y_val), callbacks=[early_stopping, metrics_callback], # 添加新回调 verbose=0 ) # 训练结束后恢复最佳权重 if metrics_callback.best_weights is not None: self.model.set_weights(metrics_callback.best_weights) # 绘制损失曲线 self.loss_ax.clear() self.loss_ax.plot(history.history['loss'], label='训练损失') self.loss_ax.plot(history.history['val_loss'], label='验证损失') self.loss_ax.set_title('模型训练损失') self.loss_ax.set_xlabel('轮次') self.loss_ax.set_ylabel('损失',rotation=0) self.loss_ax.legend() self.loss_ax.grid(True) self.loss_canvas.draw() # 根据早停情况更新状态信息 if early_stopping.stopped_epoch > 0: stopped_epoch = early_stopping.stopped_epoch best_epoch = early_stopping.best_epoch final_loss = history.history['loss'][-1] best_loss = min(history.history['val_loss']) self.status_var.set( f"训练在{stopped_epoch + 1}轮提前终止 | " f"最佳模型在第{best_epoch + 1}轮 | " f"最终损失: {final_loss:.6f} | " f"最佳验证损失: {best_loss:.6f}" ) messagebox.showinfo( "训练完成", f"模型训练提前终止!\n" f"最佳模型在第{best_epoch + 1}轮\n" f"最佳验证损失: {best_loss:.6f}" ) else: final_loss = history.history['loss'][-1] self.status_var.set(f"模型训练完成 | 最终损失: {final_loss:.6f}") messagebox.showinfo("训练完成", "模型训练成功完成!") except Exception as e: messagebox.showerror("训练错误", f"模型训练失败:\n{str(e)}") self.status_var.set("训练失败") def predict(self): """使用模型进行预测""" if self.model is None: messagebox.showwarning("警告", "请先训练模型") return if self.test_df is None: messagebox.showwarning("警告", "请先选择测试集文件") return try: self.status_var.set("正在生成预测...") self.root.update() # 预处理测试数据 test_scaled = self.scaler.transform(self.test_df[['水位']]) # 创建测试集时间窗口 window_size = self.window_size_var.get() X_test, y_test = self.create_dataset(test_scaled, window_size) X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1)) # 进行预测 test_predict = self.model.predict(X_test) # 反归一化 test_predict = self.scaler.inverse_transform(test_predict) y_test_orig = self.scaler.inverse_transform(y_test.reshape(-1, 1)) # 创建时间索引 test_time = self.test_df.index[window_size:window_size + len(test_predict)] # 绘制图表 self.fig, self.ax = plt.subplots(figsize=(12, 6)) # 使用时间索引作为x轴 self.ax.plot(test_time, y_test_orig, label='真实值') self.ax.plot(test_time, test_predict, label='预测值', linestyle='--') self.ax.set_title('大坝渗流水位预测结果') self.ax.set_xlabel('时间') self.ax.set_ylabel('测压管水位') self.ax.legend() self.ax.grid(True) self.ax.tick_params(axis='x', rotation=45) # 计算并添加评估指标文本 self.evaluation_metrics = self.calculate_metrics( y_test_orig.flatten(), test_predict.flatten() ) metrics_text = ( f"MSE: {self.evaluation_metrics['MSE']:.4f} | " f"RMSE: {self.evaluation_metrics['RMSE']:.4f} | " f"MAE: {self.evaluation_metrics['MAE']:.4f} | " f"MAPE: {self.evaluation_metrics['MAPE']:.2f}% | " f"R²: {self.evaluation_metrics['R2']:.4f}" ) self.ax.text( 0.5, 1.05, metrics_text, transform=self.ax.transAxes, ha='center', fontsize=10, bbox=dict(facecolor='white', alpha=0.8) ) # 添加分隔线(移至绘图设置之后) # 注意:这里使用数值索引而不是时间对象 split_point = 0 # 测试集开始位置 self.ax.axvline(x=split_point, color='k', linestyle='--', alpha=0.5) self.ax.text( split_point, np.min(y_test_orig) * 0.9, ' 训练/测试分界', rotation=90, verticalalignment='bottom' ) # 调整布局并显示图表 plt.tight_layout() if hasattr(self, 'canvas'): self.canvas.draw() else: plt.show() self.status_var.set("预测完成,结果已显示") except Exception as e: messagebox.showerror("预测错误", f"预测失败:\n{str(e)}") self.status_var.set("预测失败") def save_results(self): """保存预测结果""" if not hasattr(self, 'test_predict') or self.test_predict is None: messagebox.showwarning("警告", "请先生成预测结果") return save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")] ) if not save_path: return try: # 创建包含预测结果和评估指标的DataFrame window_size = self.window_size_var.get() test_time = self.test_df.index[window_size:window_size + len(self.test_predict)] metrics_df = pd.DataFrame([self.evaluation_metrics]) result_df = pd.DataFrame({ '时间': test_time, '实际水位': self.test_df['水位'][window_size:window_size + len(self.test_predict)].values, '预测水位': self.test_predict.flatten() }) # 保存到Excel的不同sheet with pd.ExcelWriter(save_path) as writer: result_df.to_excel(writer, sheet_name='预测结果', index=False) metrics_df.to_excel(writer, sheet_name='评估指标', index=False) # 保存图表 chart_path = os.path.splitext(save_path)[0] + "_chart.png" self.fig.savefig(chart_path, dpi=300) self.status_var.set(f"结果已保存至: {os.path.basename(save_path)}") messagebox.showinfo("保存成功", f"预测结果和图表已保存至:\n{save_path}\n{chart_path}") except Exception as e: messagebox.showerror("保存错误", f"保存结果失败:\n{str(e)}") def reset(self): """重置程序状态""" self.train_df = None self.test_df = None self.model = None self.train_file_var.set("") self.test_file_var.set("") self.ax.clear() self.loss_ax.clear() self.canvas.draw() self.loss_canvas.draw() self.data_text.delete(1.0, tk.END) self.status_var.set("已重置,请选择新数据") messagebox.showinfo("重置", "程序已重置,可以开始新的分析") if __name__ == "__main__": root = tk.Tk() app = DamSeepageModel(root) root.mainloop() 整个代码逐行检查一下

import tkinter as tk from tkinter import ttk, filedialog, messagebox import pandas as pd import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from sklearn.preprocessing import MinMaxScaler import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense,Layer from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping import os plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体 plt.rcParams['axes.unicode_minus'] = False class Attention(Layer): def __init__(self, **kwargs): super(Attention, self).__init__(**kwargs) def build(self, input_shape): self.W = self.add_weight(name='attention_weight', shape=(input_shape[-1], 1), initializer='random_normal', trainable=True) self.b = self.add_weight(name='attention_bias', shape=(input_shape[1], 1), initializer='zeros', trainable=True) super(Attention, self).build(input_shape) def call(self, x): e = tf.tanh(tf.matmul(x, self.W) + self.b) a = tf.nn.softmax(e, axis=1) output = x * a return tf.reduce_sum(output, axis=1) class DamSeepageModel: def __init__(self, root): self.root = root self.root.title("大坝渗流预测模型") self.root.geometry("1200x800") # 初始化数据 self.train_df = None self.test_df = None self.model = None self.scaler = MinMaxScaler(feature_range=(0, 1)) # 创建主界面 self.create_widgets() def create_widgets(self): # 创建主框架 main_frame = ttk.Frame(self.root, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 左侧控制面板 control_frame = ttk.LabelFrame(main_frame, text="模型控制", padding=10) control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5) # 文件选择部分 file_frame = ttk.LabelFrame(control_frame, text="数据文件", padding=10) file_frame.pack(fill=tk.X, pady=5) # 训练集选择 ttk.Label(file_frame, text="训练集:").grid(row=0, column=0, sticky=tk.W, pady=5) self.train_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.train_file_var, width=30, state='readonly').grid(row=0, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("train")).grid(row=0, column=2) # 测试集选择 ttk.Label(file_frame, text="测试集:").grid(row=1, column=0, sticky=tk.W, pady=5) self.test_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.test_file_var, width=30, state='readonly').grid(row=1, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("test")).grid(row=1, column=2) # 参数设置部分 param_frame = ttk.LabelFrame(control_frame, text="模型参数", padding=10) param_frame.pack(fill=tk.X, pady=10) # 时间窗口大小 ttk.Label(param_frame, text="时间窗口大小:").grid(row=0, column=0, sticky=tk.W, pady=5) self.window_size_var = tk.IntVar(value=60) ttk.Spinbox(param_frame, from_=10, to=200, increment=5, textvariable=self.window_size_var, width=10).grid(row=0, column=1, padx=5) # LSTM单元数量 ttk.Label(param_frame, text="LSTM单元数:").grid(row=1, column=0, sticky=tk.W, pady=5) self.lstm_units_var = tk.IntVar(value=50) ttk.Spinbox(param_frame, from_=10, to=200, increment=10, textvariable=self.lstm_units_var, width=10).grid(row=1, column=1, padx=5) # 训练轮次 ttk.Label(param_frame, text="训练轮次:").grid(row=2, column=0, sticky=tk.W, pady=5) self.epochs_var = tk.IntVar(value=100) ttk.Spinbox(param_frame, from_=10, to=500, increment=10, textvariable=self.epochs_var, width=10).grid(row=2, column=1, padx=5) # 批处理大小 ttk.Label(param_frame, text="批处理大小:").grid(row=3, column=0, sticky=tk.W, pady=5) self.batch_size_var = tk.IntVar(value=32) ttk.Spinbox(param_frame, from_=16, to=128, increment=16, textvariable=self.batch_size_var, width=10).grid(row=3, column=1, padx=5) # 控制按钮 btn_frame = ttk.Frame(control_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="训练模型", command=self.train_model).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="预测结果", command=self.predict).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="保存结果", command=self.save_results).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.RIGHT, padx=5) # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label(control_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(fill=tk.X, side=tk.BOTTOM) # 右侧结果显示区域 result_frame = ttk.Frame(main_frame) result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建标签页 self.notebook = ttk.Notebook(result_frame) self.notebook.pack(fill=tk.BOTH, expand=True) # 损失曲线标签页 self.loss_frame = ttk.Frame(self.notebook) self.notebook.add(self.loss_frame, text="训练损失") # 预测结果标签页 self.prediction_frame = ttk.Frame(self.notebook) self.notebook.add(self.prediction_frame, text="预测结果") # 初始化绘图区域 self.fig, self.ax = plt.subplots(figsize=(10, 6)) self.canvas = FigureCanvasTkAgg(self.fig, master=self.prediction_frame) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) self.loss_fig, self.loss_ax = plt.subplots(figsize=(10, 4)) self.loss_canvas = FigureCanvasTkAgg(self.loss_fig, master=self.loss_frame) self.loss_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # 文件选择 def select_file(self, file_type): """选择Excel文件""" file_path = filedialog.askopenfilename( title=f"选择{file_type}集Excel文件", filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")] ) if file_path: try: # 读取Excel文件 df = pd.read_excel(file_path) # 时间特征列 time_features = ['year', 'month', 'day'] missing_time_features = [feat for feat in time_features if feat not in df.columns] if '水位' not in df.columns: messagebox.showerror("列名错误", "Excel文件必须包含'水位'列") return if missing_time_features: messagebox.showerror("列名错误", f"Excel文件缺少预处理后的时间特征列: {', '.join(missing_time_features)}\n" "请确保已使用预处理功能添加这些列") return # 创建完整的时间戳列 # 处理可能缺失的小时、分钟、秒数据 if 'hour' in df.columns and 'minute' in df.columns and 'second' in df.columns: df['datetime'] = pd.to_datetime( df[['year', 'month', 'day', 'hour', 'minute', 'second']] ) elif 'hour' in df.columns and 'minute' in df.columns: df['datetime'] = pd.to_datetime( df[['year', 'month', 'day', 'hour', 'minute']].assign(second=0) ) else: df['datetime'] = pd.to_datetime(df[['year', 'month', 'day']]) # 设置时间索引 df = df.set_index('datetime') # 保存数据 if file_type == "train": self.train_df = df self.train_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载训练集: {len(self.train_df)}条数据") else: self.test_df = df self.test_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载测试集: {len(self.test_df)}条数据") except Exception as e: messagebox.showerror("文件错误", f"读取文件失败: {str(e)}") def create_dataset(self, data, window_size): """创建时间窗口数据集""" X, y = [], [] for i in range(len(data) - window_size): X.append(data[i:(i + window_size), 0]) y.append(data[i + window_size, 0]) return np.array(X), np.array(y) def create_dynamic_plot_callback(self): """创建动态绘图回调实例,用于实时显示训练损失曲线""" class DynamicPlotCallback(tf.keras.callbacks.Callback): def __init__(self, gui_app): self.gui_app = gui_app # 引用主GUI实例 self.train_loss = [] # 存储训练损失 self.val_loss = [] # 存储验证损失 def on_epoch_end(self, epoch, logs=None): """每个epoch结束时更新图表""" logs = logs or {} # 收集损失数据 self.train_loss.append(logs.get('loss')) self.val_loss.append(logs.get('val_loss')) # 更新GUI中的图表(在主线程中执行) self.gui_app.root.after(0, self._update_plot) def _update_plot(self): """实际更新图表的函数""" try: # 清除现有图表 self.gui_app.loss_ax.clear() # 绘制训练和验证损失曲线 epochs = range(1, len(self.train_loss) + 1) self.gui_app.loss_ax.plot(epochs, self.train_loss, 'b-', label='训练损失') self.gui_app.loss_ax.plot(epochs, self.val_loss, 'r-', label='验证损失') # 设置图表属性 self.gui_app.loss_ax.set_title('模型训练损失') self.gui_app.loss_ax.set_xlabel('轮次') self.gui_app.loss_ax.set_ylabel('损失', rotation=0) self.gui_app.loss_ax.legend(loc='upper right') self.gui_app.loss_ax.grid(True, alpha=0.3) # 自动调整Y轴范围 all_losses = self.train_loss + self.val_loss min_loss = max(0, min(all_losses) * 0.9) max_loss = max(all_losses) * 1.1 self.gui_app.loss_ax.set_ylim(min_loss, max_loss) # 刷新画布 self.gui_app.loss_canvas.draw() # 更新状态栏显示最新损失 current_epoch = len(self.train_loss) if current_epoch > 0: latest_train_loss = self.train_loss[-1] latest_val_loss = self.val_loss[-1] if self.val_loss else 0 self.gui_app.status_var.set( f"训练中 | 轮次: {current_epoch} | " f"训练损失: {latest_train_loss:.6f} | " f"验证损失: {latest_val_loss:.6f}" ) self.gui_app.root.update() except Exception as e: print(f"更新图表时出错: {str(e)}") # 返回回调实例 return DynamicPlotCallback(self) def train_model(self): """训练LSTM模型""" if self.train_df is None: messagebox.showwarning("警告", "请先选择训练集文件") return try: self.status_var.set("正在预处理数据...") self.root.update() # 数据预处理 train_scaled = self.scaler.fit_transform(self.train_df[['水位']]) # 创建时间窗口数据集 window_size = self.window_size_var.get() X_train, y_train = self.create_dataset(train_scaled, window_size) # 调整LSTM输入格式 X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1)) # 构建LSTM模型 self.model = Sequential() self.model.add(LSTM( self.lstm_units_var.get(), return_sequences=True, input_shape=(window_size, 1) )) self.model.add(LSTM(self.lstm_units_var.get(), return_sequences=True)) self.model.add(Attention()) self.model.add(Dense(1)) self.model.compile( optimizer=Adam(learning_rate=0.001), loss='mean_squared_error' ) # 添加早停机制 early_stopping = EarlyStopping( monitor='val_loss', # 监控验证集损失 patience=20, # 连续20轮无改善则停止 min_delta=0.0001, # 最小改善阈值 restore_best_weights=True, # 恢复最佳权重 verbose=1 # 显示早停信息 ) # 训练模型 self.status_var.set("正在训练模型...") self.root.update() history = self.model.fit( X_train, y_train, epochs=self.epochs_var.get(), batch_size=self.batch_size_var.get(), validation_split=0.2, # 使用20%数据作为验证集 callbacks=[early_stopping], # 添加早停回调 verbose=0 ) # 绘制损失曲线 self.loss_ax.clear() self.loss_ax.plot(history.history['loss'], label='训练损失') self.loss_ax.plot(history.history['val_loss'], label='验证损失') self.loss_ax.set_title('模型训练损失') self.loss_ax.set_xlabel('轮次') self.loss_ax.set_ylabel('损失',rotation=0) self.loss_ax.legend() self.loss_ax.grid(True) self.loss_canvas.draw() # 根据早停情况更新状态信息 if early_stopping.stopped_epoch > 0: stopped_epoch = early_stopping.stopped_epoch best_epoch = early_stopping.best_epoch final_loss = history.history['loss'][-1] best_loss = min(history.history['val_loss']) self.status_var.set( f"训练在{stopped_epoch + 1}轮提前终止 | " f"最佳模型在第{best_epoch + 1}轮 | " f"最终损失: {final_loss:.6f} | " f"最佳验证损失: {best_loss:.6f}" ) messagebox.showinfo( "训练完成", f"模型训练提前终止!\n" f"最佳模型在第{best_epoch + 1}轮\n" f"最佳验证损失: {best_loss:.6f}" ) else: final_loss = history.history['loss'][-1] self.status_var.set(f"模型训练完成 | 最终损失: {final_loss:.6f}") messagebox.showinfo("训练完成", "模型训练成功完成!") except Exception as e: messagebox.showerror("训练错误", f"模型训练失败:\n{str(e)}") self.status_var.set("训练失败") def predict(self): """使用模型进行预测""" if self.model is None: messagebox.showwarning("警告", "请先训练模型") return if self.test_df is None: messagebox.showwarning("警告", "请先选择测试集文件") return try: self.status_var.set("正在生成预测...") self.root.update() # 预处理测试数据 test_scaled = self.scaler.transform(self.test_df[['水位']]) # 创建测试集时间窗口 window_size = self.window_size_var.get() X_test, y_test = self.create_dataset(test_scaled, window_size) X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1)) # 进行预测 test_predict = self.model.predict(X_test) # 反归一化 test_predict = self.scaler.inverse_transform(test_predict) y_test_orig = self.scaler.inverse_transform([y_test]).T # 创建时间索引 test_time = self.test_df.index[window_size:window_size + len(test_predict)] # 绘制结果 self.ax.clear() self.ax.plot(self.train_df.index, self.train_df['水位'], 'b-', label='训练集数据') self.ax.plot(test_time, self.test_df['水位'][window_size:window_size + len(test_predict)], 'g-', label='测试集数据') self.ax.plot(test_time, test_predict, 'r--', label='模型预测') # 添加分隔线 split_point = test_time[0] self.ax.axvline(x=split_point, color='k', linestyle='--', alpha=0.5) self.ax.text(split_point, self.ax.get_ylim()[0] * 0.9, ' 训练/测试分界', rotation=90) self.ax.set_title('大坝渗流水位预测结果') self.ax.set_xlabel('时间') self.ax.set_ylabel('测压管水位',rotation=0) self.ax.legend() self.ax.grid(True) self.ax.tick_params(axis='x', rotation=45) self.fig.tight_layout() self.canvas.draw() self.status_var.set("预测完成,结果已显示") except Exception as e: messagebox.showerror("预测错误", f"预测失败:\n{str(e)}") self.status_var.set("预测失败") def save_results(self): """保存预测结果""" if not hasattr(self, 'test_predict') or self.test_predict is None: messagebox.showwarning("警告", "请先生成预测结果") return save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")] ) if not save_path: return try: # 创建包含预测结果的DataFrame window_size = self.window_size_var.get() test_time = self.test_df.index[window_size:window_size + len(self.test_predict)] result_df = pd.DataFrame({ '时间': test_time, '实际水位': self.test_df['水位'][window_size:window_size + len(self.test_predict)].values, '预测水位': self.test_predict.flatten() }) # 保存到Excel result_df.to_excel(save_path, index=False) # 保存图表 chart_path = os.path.splitext(save_path)[0] + "_chart.png" self.fig.savefig(chart_path, dpi=300) self.status_var.set(f"结果已保存至: {os.path.basename(save_path)}") messagebox.showinfo("保存成功", f"预测结果和图表已保存至:\n{save_path}\n{chart_path}") except Exception as e: messagebox.showerror("保存错误", f"保存结果失败:\n{str(e)}") def reset(self): """重置程序状态""" self.train_df = None self.test_df = None self.model = None self.train_file_var.set("") self.test_file_var.set("") self.ax.clear() self.loss_ax.clear() self.canvas.draw() self.loss_canvas.draw() self.data_text.delete(1.0, tk.END) self.status_var.set("已重置,请选择新数据") messagebox.showinfo("重置", "程序已重置,可以开始新的分析") if __name__ == "__main__": root = tk.Tk() app = DamSeepageModel(root) root.mainloop() 我这个是大坝渗流预测模型,看看代码还有什么可以改进的,我现在训练的时候训练损失很低但是验证损失水平比较高

最新推荐

recommend-type

freude弗莱德FP-12A电脑DSP调音软件下载

freude弗莱德FP-12A电脑DSP调音软件下载
recommend-type

网络设备技术指标.docx

网络设备技术指标.docx
recommend-type

软件规范知识培训(1).ppt

软件规范知识培训(1).ppt
recommend-type

iOS 12.3 - 13.4 Checkra1n Win版越狱超详细保姆级教程

资源下载链接为: https://pan.quark.cn/s/67c535f75d4c iOS 12.3 - 13.4 Checkra1n Win版越狱超详细保姆级教程
recommend-type

Java JDK 8u251 for Mac OS X 64位安装包

资源下载链接为: https://pan.quark.cn/s/9648a1f24758 Java JDK(Java Development Kit)是Java编程语言的核心组件,为开发和运行Java程序提供了必要的工具和环境。JDK 8是Oracle公司推出的一个重要版本,它引入了许多新特性和改进,极大地提升了开发效率和代码质量,对开发者来说具有极高的实用价值。 本次提供的“jdk-8u251-macosx-x64.dmg”安装包是专为Mac OS X系统设计的64位版本,其中不仅包含了Java运行环境(JRE),还涵盖了丰富的开发工具,方便用户在Mac平台上进行Java程序的开发与运行。 JDK 8的关键更新和特性如下: Lambda表达式:这是JDK 8的一项重大语法创新,允许开发者使用简洁的匿名函数替代复杂的多行回调代码,从而使代码更加简洁、易读且高效。 方法引用与构造器引用:与Lambda表达式配合使用,可以更直观地引用已有的方法或构造器,进一步减少冗余代码,提升代码的可维护性。 Stream API:这是一个用于处理集合的新API,采用声明式处理方式,使集合操作(如过滤、映射和归约等)更加高效且易于理解。 日期和时间API的改进:JDK 8对日期和时间API进行了重构,引入了java.time包,包含LocalDate、LocalTime和LocalDateTime等类,替代了原有的java.util.Date和java.util.Calendar,使日期和时间的处理更加友好和灵活。 Optional类:为解决null对象导致的空指针异常问题,JDK 8引入了Optional类,它是一个容器对象,可以表示一个值存在或不存在,从而有效避免空指针异常。 接口的默认方法和静态方法:接口现在可以定义默认方法(使用default关键字)和静态方法。默认方法允许在不破坏向后
recommend-type

VC图像编程全面资料及程序汇总

【标题】:"精通VC图像编程资料全览" 【知识点】: VC即Visual C++,是微软公司推出的一个集成开发环境(IDE),专门用于C++语言的开发。VC图像编程涉及到如何在VC++开发环境中处理和操作图像。在VC图像编程中,开发者通常会使用到Windows API中的GDI(图形设备接口)或GDI+来进行图形绘制,以及DirectX中的Direct2D或DirectDraw进行更高级的图形处理。 1. GDI(图形设备接口): - GDI是Windows操作系统提供的一套应用程序接口,它允许应用程序通过设备无关的方式绘制图形。 - 在VC图像编程中,主要使用CDC类(设备上下文类)来调用GDI函数进行绘制,比如绘制线条、填充颜色、显示文本等。 - CDC类提供了很多函数,比如`MoveTo`、`LineTo`、`Rectangle`、`Ellipse`、`Polygon`等,用于绘制基本的图形。 - 对于图像处理,可以使用`StretchBlt`、`BitBlt`、`TransparentBlt`等函数进行图像的位块传输。 2. GDI+: - GDI+是GDI的后继技术,提供了更丰富的图形处理功能。 - GDI+通过使用`Graphics`类来提供图像的绘制、文本的渲染、图像的处理和颜色管理等功能。 - GDI+引入了对矢量图形、渐变色、复杂的文本格式和坐标空间等更高级的图形处理功能。 - `Image`类是GDI+中用于图像操作的基础类,通过它可以进行图像的加载、保存、旋转、缩放等操作。 3. DirectX: - DirectX是微软推出的一系列API集合,用于在Windows平台上进行高性能多媒体编程。 - DirectX中的Direct2D是用于硬件加速的二维图形API,专门用于UI元素和简单的图形渲染。 - DirectDraw主要用于硬件加速的位图操作,比如全屏游戏开发中的画面渲染。 4. 位图操作: - 在VC图像编程中,位图操作是一个重要的部分。需要了解如何加载、保存和处理位图(BMP)文件。 - 可以使用位图文件格式的解析,来访问位图的像素数据,进行像素级别的图像处理和修改。 5. 高级图像处理技术: - 包括图像滤镜、图像转换、图像压缩和解压缩技术。 - 需要掌握一些图像处理算法,比如卷积、FFT(快速傅里叶变换)、DCT(离散余弦变换)等。 - 了解图像的色彩空间转换,比如RGB到YUV的转换,这在视频处理中非常重要。 6. 图像库的使用: - 除了直接使用API进行图像处理之外,还可以使用开源的图像处理库,如OpenCV。 - OpenCV是一个跨平台的计算机视觉和机器学习软件库,它提供了很多高级的图像处理功能。 【压缩包子文件的文件名称列表】: VC++ 文件名称列表显示为"VC++",这表明文件包中可能包含了Visual C++的项目、源代码文件、动态链接库(DLLs)、编译器设置、链接器设置等。如果要使用这些文件进行VC图像编程,需要确保Visual C++开发环境已经安装并且配置好,同时需要安装好所有依赖的库和工具。 要开始VC图像编程,开发者需要具备C++编程基础,熟悉Windows编程概念,并且对图形学有一定的了解。掌握VC图像编程技巧,对于进行桌面应用程序开发、图像处理软件开发以及游戏开发等都是至关重要的。
recommend-type

Pokemmo响应速度翻倍:多线程处理的高级技巧

# 摘要 多线程处理是提高软件并发性能和响应速度的关键技术,本文从基础概念与优势出发,深入探讨了多线程的实现机制,包括线程模型、调度算法、编程关键技术、线程安全性最佳实践、死锁预防与处理等方面。通
recommend-type

人名列表滚动抽奖

<think>嗯,用户想实现一个滚动抽奖功能,需要前端动画效果和随机选择逻辑。首先,我得考虑前端怎么实现滚动效果。可能用CSS动画或者JavaScript的requestAnimationFrame?比如,使用CSS的@keyframes来控制位移,或者用JS动态更新样式。然后,随机选择算法,可能需要确保公平性,比如用Fisher-Yates洗牌算法,或者用Math.random()来生成随机索引。然后,用户可能需要平滑的滚动动画,比如先快速滚动,然后逐渐减速,最后停在选中的人名上。这可能需要设置定时器,逐步改变位置,或者使用CSS过渡效果。另外,还要考虑性能,避免页面卡顿,可能需要使用硬件加
recommend-type

一站式JSF开发环境:即解压即用JAR包

标题:“jsf开发完整JAR包”所指的知识点: 1. JSF全称JavaServer Faces,是Java EE(现EE4J)规范之一,用于简化Java Web应用中基于组件的用户界面构建。JSF提供了一种模型-视图-控制器(MVC)架构的实现,使得开发者可以将业务逻辑与页面表示分离。 2. “开发完整包”意味着这个JAR包包含了JSF开发所需的所有类库和资源文件。通常来说,一个完整的JSF包会包含核心的JSF库,以及一些可选的扩展库,例如PrimeFaces、RichFaces等,这些扩展库提供了额外的用户界面组件。 3. 在一个项目中使用JSF,开发者无需单独添加每个必要的JAR文件到项目的构建路径中。因为打包成一个完整的JAR包后,所有这些依赖都被整合在一起,极大地方便了开发者的部署工作。 4. “解压之后就可以直接导入工程中使用”表明这个JAR包是一个可执行的归档文件,可能是一个EAR包或者一个可直接部署的Java应用包。解压后,开发者只需将其内容导入到他们的IDE(如Eclipse或IntelliJ IDEA)中,或者将其放置在Web应用服务器的正确目录下,就可以立即进行开发。 描述中所指的知识点: 1. “解压之后就可以直接导入工程中使用”说明这个JAR包是预先配置好的,它可能包含了所有必要的配置文件,例如web.xml、faces-config.xml等,这些文件是JSF项目运行所必需的。 2. 直接使用意味着减少了开发者配置环境和处理依赖的时间,有助于提高开发效率。 标签“jsf jar包”所指的知识点: 1. 标签指明了JAR包的内容是专门针对JSF框架的。因此,这个JAR包包含了JSF规范所定义的API以及可能包含的具体实现,比如Mojarra或MyFaces。 2. “jar包”是一种Java平台的归档文件格式,用于聚合多个文件到一个文件中。在JSF开发中,JAR文件经常被用来打包和分发库或应用程序。 文件名称列表“jsf”所指的知识点: 1. “jsf”文件名可能意味着这是JSF开发的核心库,它应该包含了所有核心的JavaServer Faces类文件以及资源文件。 2. 如果是使用特定版本的JSF,例如“jsf-2.2.jar”,则表明文件内包含了对应版本的JSF实现。这种情况下,开发者必须确认他们所使用的Web服务器或应用程序服务器支持该版本的JSF。 3. 文件名称也可能是“jsf-components.jar”、“jsf-impl.jar”等,表明这个JAR包是JSF的一个子模块或特定功能组件。例如,“jsf-components.jar”可能包含了一系列用于在JSF应用中使用的自定义组件。 4. 对于开发者而言,了解文件名称中所蕴含的信息非常重要,因为这将决定他们需要下载哪些JAR包来满足特定项目的需求。 综合以上信息,开发者在使用JSF进行Java Web应用开发时,会通过一个预先配置好的JAR包来快速地搭建和启动项目。这样做不仅简化了项目初始化的过程,也使得开发者能够更加聚焦于业务逻辑的实现和界面设计,而不必深究底层框架配置的细节。
recommend-type

Pokemmo内存优化揭秘:专家教你如何降低50%资源消耗

# 摘要 本文综述了Pokemmo游戏的内存优化方法,从内存管理基础出发,探讨内存使用效率的影响因素,并介绍了性能监控与分析工具。在内存优化实践技巧章节中,详细讨论了代码层面的优化、数据结构和算法选择对内存效率的影响,并通过案例分析展示了实际的优化过程。针对Pokemmo游戏特点,分析了内存消耗特性并提出了特定优化技术。最后,本文展望了未来内存管理技术的发展方向,以及游戏开发中面临的新挑战,为Pokemmo及类似游戏提供了优化建议。 # 关键字 内存优化;内存管理;性能监控;数据结构;算法效率;游戏开发 参考资源链接:[Pokemmo必备资源包:四种ROM与汉化补丁](https://we