热门文章
最新发布
-
pyqt5学了有用吗?能赚钱吗 前言 前2天没更新文章,主要是我一直在忙一个客户的开发工作。主要是WP站点的战群管理程序,可以方便的把生成的文章快速发布到各个站群里面,还集成了后台功能,主要都是数据库读写 做了啥? 给各位看个图片 mk7m6f7c.png图片 这是开发过程中迭代的备份文件,终于在昨天晚上完工了 mk7m71pj.png图片 给大家看看是怎么样的一个工程 mk7m873o.png图片 程序是python310写的,总共2900行代码,界面没怎么美化,只是做了排版 mk7m9j1w.png图片 里边做了挺多功能,还插入了md语法解析工具,因为客户是用ai生成的文档批量导入 它的界面也是pyqt5写的,总体程序就不给大家看完了,毕竟客户花钱开发的 结语 接下来我会恢复更新文章,还是希望大家能好好学pyqt5+python,因为是真能赚钱 -
Web/H5跳转小程序观看激励广告后下载 系统源码 实现流量变现赚取广告收益 演示Web H5跳转小程序观看激励广告后下载程序源码,实现流量变现赚取广告收益 和资源变现差不多,就是让用户观看广告获取想要的资源或者其他东西 用户看完广告你就会获取利润,从而进行流量变现 8660bce851.png图片 8660bce920.png图片 Web/H5跳转小程序观看激励广告后下载 系统源码 实现流量变现赚取广告收益 下载地址:https://pan.quark.cn/s/62aeead175fc 提取码: Web/H5跳转小程序观看激励广告后下载 系统源码 实现流量变现赚取广告收益 下载地址:https://pan.baidu.com/s/1KPV6vCls7kjYKXWdK1qnKA?pwd=v501 提取码:v501 -
多平台短视频解析水印 v3.0 程序源码 多平台视频解析水印 v3.0 程序源码与视频解析工具 当下已能对4大主流平台的视频或图文进行解析: 抖音 – 视频、图文 快手 – 视频、图集 小红书 – 视频、图文(自动转换为JPG格式) B站 – 视频(支持BV号、完整链接) 核心功能如下: 无水印视频下载 – 自动去除平台添加的水印 批量图片下载 – 图文自动保存文本与图片 图片格式转换 – WebP/PNG自动转换为JPG 下载进度展示 – 实时呈现下载速度和进度 文件管理 – 支持打开目录、删除文件 界面特色: 窗口比例为16:9,最小化尺寸1024×576 采用无边框设计,可自定义标题栏 左侧为菜单,右侧是内容区的布局 具备启动闪屏与进度条加载效果 现代卡片式UI,带有阴影效果 下载规则: 目录命名:[平台标识]+6字符标题 视频文件:[平台标识]视频文件.mp4 文本文件:[平台标识]文本内容.txt 图片文件:1.jpg、2.jpg、3.jpg… 自动去重:若文件已存在则跳过下载 使用方法: 步骤1:获取分享链接 在支持的平台APP里,点击分享按钮,复制分享链接 步骤2:粘贴解析 打开软件,在输入框右键粘贴链接,点击“开始解析”按钮 步骤3:下载内容 解析成功后,点击“下载视频”或“下载全部图片”按钮 步骤4:查看文件 在“下载管理”页面可查看进度,完成后点击“打开”按钮查看文件 注意事项: 支持的链接格式: 完整链接(https://…) 短链接(各平台短链接) 分享文本(包含链接的完整文本) 文件路径限制处理: 自动应对Windows 260字符路径限制 文件名过长时自动截断 特殊字符自动替换为下划线 下载设置: 可自定义下载目录 支持自动下载(解析后即刻下载) 下载记录持久保存 特殊说明: B站支持BV号直接解析 默认下载1080P清晰度 DASH格式暂不支持(需音视频合并) 45e4ece836.jpeg图片 隐藏内容,请前往内页查看详情 -
工商年报申报系统源码 个体工商户年报注销H5搭建源码 此程序花费四百购入,各项功能均已测试,可正常使用,附带前端 uniapp 未编译的源码。 该程序采用 thinkphp 框架,使用 php 语言编写,前后端未分离,只需将编译好的源码放置于后端目录即可。 支付对接的是微信官方 v3 接口。 源码中的短信通知功能仅在注册和登录环节可用,若需下单通知等功能,可进行二次开发。 搭建教程如下: 环境要求:PHP8.0 及以上版本,MySQL 5.7。 将前端源码导入 HBuilder X 工具,修改 appid 和.env.development 中的请求域名,替换为自己的域名后,选择打包发行网站。将打包好的文件存至网站根目录/public/mobile 下。 若不想进行打包操作,可直接在源码根目录/public/mobile/assets/index.6bbd7918.js 文件中搜索“canyin.dkewl.com”,并替换为你的域名。 将修改后的后端源码上传至网站根目录。 设置运行目录为 public,并配置 thinkphp 伪静态。 上传并导入数据库源码。 网站前台访问地址:域名/mobile 网站后台访问地址:域名/admin 账号:admin 密码:123456 若需要小程序端,可用 h5 进行封装。 b847add655.png图片 隐藏内容,请前往内页查看详情 -
晓翼易支付 PHP API 对接教程:支付退款代付全攻略 晓翼易支付全API对接实操教程:复制即用不踩坑,支付/退款/代付一套搞定 做PHP开发这么久,真心觉得找个好用的支付工具太难了——要么API文档写得云里雾里,示例代码跑起来一堆报错;要么只支持基础支付,退款、代付还得额外对接别的平台;更坑的是有些工具签名逻辑复杂,抠半天代码还验签失败。 mk2lq7ea.png图片 我们字节曜博客一直用晓翼易支付,从对接到底层稳定度都亲测过,现在把全套API的实操教程整理出来,涵盖统一下单、订单查询、退款、代付所有核心功能,每个接口都附可直接复制的PHP代码,替换密钥就能跑,新手半小时也能搞定,分享给有需要的朋友~ 笔者是在竞赛室偷偷写的教程,撰写不易,如有在所难免,文档在此:文档,以文档为准 一、对接前必做:3分钟搞定基础配置(所有API通用) 不管对接哪个接口,先把基础环境和密钥配置好,这步走对了,后面能少踩80%的坑: 1. 先拿核心信息(登录商户后台就能找到) 商户号(mch_id):你的专属标识,在「个人资料」→「基本信息」里一眼就能看到; RSA密钥对:去「API信息」→「密钥管理」生成,会拿到两个密钥——「商户私钥」和「平台公钥」,记好别搞混; 接口网关地址:固定是 https://pay.ziyeyao.com/api/v2,不用自己瞎找; 回调地址(notify_url):自己服务器上的接口地址,得能公网访问,还不能带参数,支付、退款、代付成功后平台会主动通知你。 2. 通用工具函数(必存!所有API都要靠它) 先把签名生成、验签、发送请求的通用函数写好,后续每个接口直接调用就行,不用重复写代码。 <?php /** * 晓翼易支付通用工具类 * 签名、请求、验签全靠它,直接复制用 */ class XyPayUtil { // 替换成你自己的商户私钥(PEM格式,带头尾) private static $privateKey = '-----BEGIN PRIVATE KEY----- 你的商户私钥内容,原样粘贴,别删任何字符 -----END PRIVATE KEY-----'; // 替换成你自己的平台公钥(PEM格式,带头尾) private static $publicKey = '-----BEGIN PUBLIC KEY----- 你的平台公钥内容,原样粘贴 -----END PUBLIC KEY-----'; /** * 生成SHA256WithRSA签名(对接核心,别改逻辑) * @param array $params 要签名的参数数组 * @return string 签名字符串 */ public static function createSign($params) { // 1. 参数按ASCII升序排序(必须这步,不然验签失败) ksort($params); // 2. 拼接成"key=value&key=value"格式 $signStr = http_build_query($params); // 3. 加载私钥 $privateKey = openssl_get_privatekey(self::$privateKey); if (!$privateKey) { throw new Exception("私钥加载失败!检查下私钥格式,是不是漏了头尾标识"); } // 4. 生成签名 openssl_sign($signStr, $sign, $privateKey, OPENSSL_ALGO_SHA256); openssl_free_key($privateKey); // 5. base64编码返回 return base64_encode($sign); } /** * 验证平台返回的签名(防止数据被篡改) * @param array $params 平台返回的参数(不含sign) * @param string $sign 平台返回的签名字符串 * @return bool 验证结果(true=通过) */ public static function verifySign($params, $sign) { ksort($params); $signStr = http_build_query($params); $publicKey = openssl_get_publickey(self::$publicKey); if (!$publicKey) { throw new Exception("公钥加载失败!检查公钥格式是否正确"); } $result = openssl_verify($signStr, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256); openssl_free_key($publicKey); return $result === 1; } /** * 发送POST请求(不用自己调curl,直接用) * @param string $url 接口地址 * @param array $params 请求参数 * @return array 解析后的JSON数据 */ public static function postRequest($url, $params) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 测试环境可以关闭SSL校验,生产环境一定要打开! curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $response = curl_exec($ch); if (curl_errno($ch)) { throw new Exception("请求失败:" . curl_error($ch)); } curl_close($ch); // 解析返回的JSON $result = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception("返回数据格式错了:" . $response); } return $result; } } ?>重点提醒: 私钥和公钥一定要原样粘贴,别删头尾的-----BEGIN PRIVATE KEY-----这些标识,也别多空格; 生产环境记得把CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST改成true,更安全; 密钥别直接写在代码里,生产环境建议存在单独的配置文件,权限设为只读,防止泄露。 二、核心API对接教程(PHP代码直接复制用) 1. 统一下单API——最常用!创建支付订单 不管是卖会员、卖产品,还是博客变现,第一步都是创建支付订单,这个接口会返回支付链接或二维码,用户付完钱会自动回调你的notify_url。 PHP调用代码(替换3处信息就能跑) <?php require_once 'XyPayUtil.php'; // 引入上面的工具类 // 1. 构造请求参数(按自己的业务改) $params = [ 'mch_id' => '123456', // 替换成你的商户号 'out_trade_no' => 'XY' . date('YmdHis') . rand(1000, 9999), // 订单号规则:前缀+时间戳+随机数,避免重复 'total_fee' => 0.01, // 金额(元),测试用0.01元,正式环境改实际金额 'body' => '字节曜博客VIP会员', // 商品描述,会显示在支付页面 'notify_url' => 'https://你的域名/pay/notify.php', // 支付成功后回调的地址 'timestamp' => time(), // 当前时间戳(秒),不用改 'pay_type' => 'wxpay' // 支付方式:wxpay=微信,alipay=支付宝,按需选 ]; // 2. 生成签名(不用动) $params['sign'] = XyPayUtil::createSign($params); // 3. 调用统一下单接口(固定地址,不用改) $url = 'https://pay.ziyeyao.com/api/v2/unified_order'; try { $result = XyPayUtil::postRequest($url, $params); // 4. 验证返回签名(防止数据被篡改) $sign = $result['sign']; unset($result['sign']); // 先移除sign字段再验签 if (!XyPayUtil::verifySign($result, $sign)) { die("签名验证失败!返回数据可能被篡改"); } // 5. 处理结果 if ($result['code'] == 0) { // 成功:拿到支付链接,可生成二维码让用户扫码 echo "订单创建成功!支付链接:" . $result['pay_url']; // 实际项目中可以跳转支付页面,或生成二维码 } else { die("订单创建失败:" . $result['msg']); } } catch (Exception $e) { die("调用失败:" . $e->getMessage()); } ?>成功返回示例(参考格式) { "code": 0, "msg": "success", "out_trade_no": "XY202601061530221234", "trade_no": "XYPay123456789", "pay_url": "https://pay.ziyeyao.com/pay?order=123456", "timestamp": 1735689622, "sign": "加密后的签名字符串" }2. 订单查询API——查支付状态(防止漏单) 有时候用户付了钱,但回调没收到,这时候就用这个接口主动查订单状态,避免漏单。 PHP调用代码 <?php require_once 'XyPayUtil.php'; // 1. 构造参数(只需要商户号和要查的订单号) $params = [ 'mch_id' => '123456', // 你的商户号 'out_trade_no' => 'XY202601061530221234', // 要查询的订单号(之前创建的) 'timestamp' => time() ]; // 2. 生成签名 $params['sign'] = XyPayUtil::createSign($params); // 3. 调用查询接口 $url = 'https://pay.ziyeyao.com/api/v2/order_query'; try { $result = XyPayUtil::postRequest($url, $params); // 验签 $sign = $result['sign']; unset($result['sign']); if (!XyPayUtil::verifySign($result, $sign)) { die("签名验证失败"); } // 处理结果 if ($result['code'] == 0) { echo "订单状态:" . $result['trade_state']; // SUCCESS=支付成功,NOTPAY=未支付 echo "支付金额:" . $result['total_fee'] . "元"; echo "支付时间:" . $result['pay_time']; } else { die("查询失败:" . $result['msg']); } } catch (Exception $e) { die("查询失败:" . $e->getMessage()); } ?>3. 退款申请API——订单退款(支持全额/部分退) 用户要退款、订单出错,都用这个接口,支持全额或部分退款,退款结果会回调你填的notify_url。 PHP调用代码 <?php require_once 'XyPayUtil.php'; // 1. 构造退款参数 $params = [ 'mch_id' => '123456', // 你的商户号 'out_trade_no' => 'XY202601061530221234', // 原支付订单号 'out_refund_no' => 'RF' . date('YmdHis') . rand(1000, 9999), // 退款单号,自己生成,唯一 'refund_fee' => 0.01, // 退款金额(不能超过原订单金额) 'refund_desc' => '用户主动申请退款', // 退款原因,会显示在后台 'notify_url' => 'https://你的域名/refund/notify.php', // 退款结果回调地址 'timestamp' => time() ]; // 2. 生成签名 $params['sign'] = XyPayUtil::createSign($params); // 3. 调用退款接口 $url = 'https://pay.ziyeyao.com/api/v2/refund'; try { $result = XyPayUtil::postRequest($url, $params); // 验签 $sign = $result['sign']; unset($result['sign']); if (!XyPayUtil::verifySign($result, $sign)) { die("签名验证失败"); } // 处理结果 if ($result['code'] == 0) { echo "退款申请提交成功!退款单号:" . $result['out_refund_no']; // 后续可以通过退款查询接口查进度 } else { die("退款申请失败:" . $result['msg']); } } catch (Exception $e) { die("退款调用失败:" . $e->getMessage()); } ?>4. 退款查询API——查退款进度(用户催单用) 用户问“退款什么时候到账”,用这个接口查状态,不用登录后台手动查。 PHP调用代码 <?php require_once 'XyPayUtil.php'; // 1. 构造参数 $params = [ 'mch_id' => '123456', // 你的商户号 'out_refund_no' => 'RF202601061535225678', // 要查的退款单号 'timestamp' => time() ]; // 2. 生成签名 $params['sign'] = XyPayUtil::createSign($params); // 3. 调用查询接口 $url = 'https://pay.ziyeyao.com/api/v2/refund_query'; try { $result = XyPayUtil::postRequest($url, $params); // 验签 $sign = $result['sign']; unset($result['sign']); if (!XyPayUtil::verifySign($result, $sign)) { die("签名验证失败"); } // 处理结果 if ($result['code'] == 0) { echo "退款状态:" . $result['refund_state']; // SUCCESS=到账,PROCESSING=处理中,FAIL=失败 echo "退款到账时间:" . $result['refund_time']; echo "退款金额:" . $result['refund_fee'] . "元"; } else { die("查询失败:" . $result['msg']); } } catch (Exception $e) { die("查询失败:" . $e->getMessage()); } ?>5. 代付申请API——给用户打款(佣金/提现用) 给合作方结佣金、用户提现,直接用这个接口打款到微信或支付宝,不用手动转账。 PHP调用代码 <?php require_once 'XyPayUtil.php'; // 1. 构造代付参数 $params = [ 'mch_id' => '123456', // 你的商户号 'out_transfer_no' => 'TF' . date('YmdHis') . rand(1000, 9999), // 代付单号,唯一 'payee_type' => 'wx', // 收款类型:wx=微信,alipay=支付宝 'payee_account' => '13800138000', // 收款账号(微信/支付宝手机号或账号) 'payee_name' => '张三', // 收款人姓名(必须和账号实名一致,否则打款失败) 'transfer_fee' => 1.00, // 代付金额(元) 'transfer_desc' => '字节曜博客佣金结算', // 备注,收款人能看到 'notify_url' => 'https://你的域名/transfer/notify.php', // 代付结果回调地址 'timestamp' => time() ]; // 2. 生成签名 $params['sign'] = XyPayUtil::createSign($params); // 3. 调用代付接口 $url = 'https://pay.ziyeyao.com/api/v2/transfer'; try { $result = XyPayUtil::postRequest($url, $params); // 验签 $sign = $result['sign']; unset($result['sign']); if (!XyPayUtil::verifySign($result, $sign)) { die("签名验证失败"); } // 处理结果 if ($result['code'] == 0) { echo "代付申请提交成功!代付单号:" . $result['out_transfer_no']; } else { die("代付申请失败:" . $result['msg']); } } catch (Exception $e) { die("代付调用失败:" . $e->getMessage()); } ?>6. 回调通知API——最容易踩坑!必看 支付、退款、代付成功后,平台会主动往你的notify_url发POST请求,这步处理不好会重复回调、漏单,分享我实测能用的通用回调代码: PHP回调处理代码(支付/退款/代付通用) <?php require_once 'XyPayUtil.php'; // 1. 获取平台回调的参数(兼容JSON和表单提交) $postData = file_get_contents('php://input'); $params = json_decode($postData, true); if (!$params) { $params = $_POST; // 表单格式兼容 } // 2. 验证签名(重中之重!防止伪造回调) try { $sign = $params['sign']; unset($params['sign']); // 移除sign再验签 if (!XyPayUtil::verifySign($params, $sign)) { die("sign error"); // 签名错,直接返回error } // 3. 按类型处理业务逻辑 $type = $params['type']; // trade=支付,refund=退款,transfer=代付 if ($type == 'trade') { // 支付成功:更新订单状态、开通会员等 $outTradeNo = $params['out_trade_no']; // 你的订单号 $tradeState = $params['trade_state']; if ($tradeState == 'SUCCESS') { // 这里写你的业务代码,比如: // update_order_status($outTradeNo, 'paid'); // 更新订单状态 // open_vip($outTradeNo); // 开通会员 echo "success"; // 必须返回纯字符串success,平台才停止回调 exit; } } elseif ($type == 'refund') { // 退款成功:更新退款订单状态 echo "success"; exit; } elseif ($type == 'transfer') { // 代付成功:更新代付订单状态 echo "success"; exit; } // 不管成功失败,都返回success避免重复回调 echo "success"; } catch (Exception $e) { die("fail"); } ?>回调踩坑提醒: 返回值必须是纯字符串success,不能带HTML、空格、换行,否则平台会每隔1分钟回调一次; 业务逻辑要加“幂等性处理”(比如判断订单是否已处理,避免重复开通会员); 回调接口别设超时,建议把业务逻辑异步处理(比如扔到消息队列),避免平台等待超时。 三、为啥推荐晓翼易支付?实测大半年的真心话 用过不少支付工具,晓翼易支付是我最省心的,尤其适合个人开发者和小团队: 代码真的能直接用!上面的代码我博客一直在用,替换商户号和密钥就能跑,不用自己抠签名、调curl; 功能全还稳定:支付、退款、代付一套接口全cover,不用对接多个平台;服务器是集群部署的,高峰期也没掉过链子,接口响应快; 无门槛+费率低:个人开发者不用营业执照就能开通,新用户还有专属费率(比行业平均低0.2%),省不少手续费; 客服靠谱:遇到问题加客服微信,基本秒回,甚至能远程帮你看代码,比自己查文档省太多时间。 现在我身边不少做博客、小电商、工具类产品的开发者都在用,核心就是“对接简单、用着放心”。如果是PHP开发者,不管是个人变现还是小团队业务,直接冲就行——半小时搞定对接,剩下的时间专心做核心业务,不比抠支付接口香? 最后总结几个实操重点 所有接口的核心是“签名和验签”,通用工具类别改逻辑,只换自己的密钥; 订单号、退款单号、代付单号必须唯一,按“前缀+时间戳+随机数”生成最稳妥; 回调一定要返回纯字符串success,业务逻辑加幂等性处理; 生产环境记得打开SSL校验,密钥别泄露,最好存在配置文件里。 如果对接中遇到问题,直接加我QQ2273962061,能远程帮你看一眼,比自己瞎琢磨省太多时间~ -
PyQt5信号与槽原理:从基础到进阶(附参数传递代码) 第11篇:PyQt5信号与槽原理:从基础到进阶(完整代码) 哈喽~ 欢迎来到PyQt5系列的第11篇!进入阶段三,我们将聚焦PyQt5的核心交互机制——信号与槽(Signal & Slot)。前两阶段我们已经用信号与槽实现了按钮点击、控件状态变化等基础交互,但很多同学可能只知其然不知其所以然。这一章我们从原理入手,深入讲解信号与槽的本质、多种绑定方式、参数传递、信号断开等进阶用法,帮你彻底掌握PyQt5交互的核心逻辑! mk237ph4.png图片 一、先搞懂:信号与槽的核心概念(事件驱动模型) 在学习进阶用法前,必须先明确信号与槽的本质,这是理解所有复杂交互的基础: 1. 核心定义 信号(Signal):控件的某个动作或状态变化(如按钮点击clicked、输入框内容变化textChanged、窗口关闭close),是“事件的触发者”; 槽(Slot):信号触发后执行的函数/方法(如按钮点击后执行on_btn_click),是“事件的响应者”; 绑定(connect):将信号与槽关联起来,形成“触发动作→执行响应”的逻辑链,是PyQt5事件驱动模型的核心。 2. 事件驱动模型图解 用户操作(如点击按钮)→ 控件发出信号(clicked)→ 信号触发绑定的槽函数 → 执行槽函数逻辑(如修改界面、处理数据) 特点:无需主动轮询事件,信号触发时自动执行槽函数,效率高、逻辑清晰; 优势:解耦——控件只负责发出信号,不关心哪个槽函数响应;槽函数只负责处理逻辑,不关心哪个信号触发,灵活度极高。 二、信号与槽的基础绑定方式(3种常用) 前两阶段我们主要用了控件.信号.connect(槽函数)的基础绑定方式,这一章我们拓展另外两种常用绑定方式,并对比各自的适用场景。 1. 方式1:代码手动绑定(最常用,灵活度最高) 这是我们之前一直使用的方式,直接通过代码将信号与槽函数关联,支持所有场景。 效果大概就是这样只 mk22u820.png图片 mk22u9e1.png图片 mk22uafw.png图片 完整代码:基础手动绑定 import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel class BasicSignalSlot(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("信号与槽基础绑定演示") self.resize(300, 200) layout = QVBoxLayout() self.label = QLabel("未点击按钮", alignment=1) self.btn = QPushButton("点击触发信号") layout.addWidget(self.label) layout.addWidget(self.btn) self.setLayout(layout) # 手动绑定:按钮clicked信号 → on_btn_click槽函数 self.btn.clicked.connect(self.on_btn_click) def on_btn_click(self): """槽函数:响应按钮点击信号""" self.label.setText("按钮被点击!信号触发成功") if __name__ == "__main__": app = QApplication(sys.argv) window = BasicSignalSlot() window.show() sys.exit(app.exec_())2. 方式2:Qt Designer可视化绑定(快速开发) 对于复杂界面,用Qt Designer(PyQt5-tools自带)拖拽控件后,可直接在界面中绑定信号与槽,无需手动写connect代码,适合快速开发。 操作步骤(核心流程): 打开Qt Designer:终端输入designer(Windows)/ 手动查找designer.exe(macOS/Linux需手动查找路径); 新建QWidget项目,拖拽一个QPushButton和QLabel; 点击菜单栏「Edit → Edit Signals/Slots」,进入信号槽编辑模式; 鼠标点击按钮并拖拽到标签上,松开后弹出绑定窗口; 左侧选择按钮的clicked()信号,右侧选择标签的setText(QString)槽函数,点击「OK」完成绑定; 保存为.ui文件,用pyuic5 -o ui_main.py main.ui转换为Python代码,直接运行即可。 优点:可视化操作,无需记忆信号/槽函数名称;缺点:灵活性不足,复杂逻辑仍需手动写代码。 3. 方式3:装饰器绑定(PyQt5.4+支持,简洁优雅) 用@pyqtSlot()装饰器标记槽函数,无需显式调用connect,代码更简洁,适合小型项目。 完整代码:装饰器绑定 import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel from PyQt5.QtCore import pyqtSlot class DecoratorSignalSlot(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("装饰器绑定信号与槽") self.resize(300, 200) layout = QVBoxLayout() self.label = QLabel("未点击按钮", alignment=1) self.btn = QPushButton("点击触发信号") layout.addWidget(self.label) layout.addWidget(self.btn) self.setLayout(layout) # 装饰器绑定:无需显式connect,通过@pyqtSlot关联 self.btn.clicked.connect(self.on_btn_click) @pyqtSlot() # 标记该函数为槽函数 def on_btn_click(self): self.label.setText("装饰器绑定:信号触发成功") if __name__ == "__main__": app = QApplication(sys.argv) window = DecoratorSignalSlot() window.show() sys.exit(app.exec_())三种绑定方式对比 绑定方式优点缺点适用场景代码手动绑定灵活度最高,支持复杂逻辑需手动写connect代码大多数项目(推荐)Qt Designer绑定可视化操作,快速开发复杂逻辑不支持,灵活性差简单界面、快速原型装饰器绑定代码简洁,无需显式connect功能有限,不支持动态绑定/解绑小型项目、简单交互三、进阶用法1:带参数的信号与槽(核心难点) 很多场景下,信号需要传递参数给槽函数(如输入框内容变化时传递文本、滑块拖动时传递数值),这是信号与槽的核心进阶用法,需掌握3种参数传递方式。 1. 方式1:信号自带参数(直接接收) PyQt5很多内置信号自带参数(如textChanged(str)、valueChanged(int)),槽函数直接定义对应参数即可接收。 完整代码:接收信号自带参数 mk233i3x.png图片 import sys from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QVBoxLayout, QLabel class SignalWithParam(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("带参数的信号与槽(自带参数)") self.resize(350, 200) layout = QVBoxLayout() self.edit = QLineEdit() self.edit.setPlaceholderText("输入文本,实时显示") self.label = QLabel("输入的文本:", alignment=1) layout.addWidget(self.edit) layout.addWidget(self.label) self.setLayout(layout) # 输入框textChanged信号(自带str参数)→ on_text_change槽函数 self.edit.textChanged.connect(self.on_text_change) def on_text_change(self, text): """槽函数:接收信号自带的text参数""" self.label.setText(f"输入的文本:{text}") if __name__ == "__main__": app = QApplication(sys.argv) window = SignalWithParam() window.show() sys.exit(app.exec_())2. 方式2:lambda表达式传递自定义参数 当需要给槽函数传递自定义参数(而非信号自带参数)时,用lambda表达式作为中间桥梁,灵活传递多参数。 完整代码:lambda传递自定义参数 import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QLabel class LambdaParamSignalSlot(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("lambda传递自定义参数") self.resize(400, 200) layout = QHBoxLayout() self.label = QLabel("未点击任何按钮", alignment=1) # 创建3个按钮,传递不同参数 btn1 = QPushButton("按钮1") btn2 = QPushButton("按钮2") btn3 = QPushButton("按钮3") # lambda传递自定义参数:信号触发时,将参数传递给槽函数 btn1.clicked.connect(lambda: self.on_btn_click(1, "按钮1被点击")) btn2.clicked.connect(lambda: self.on_btn_click(2, "按钮2被点击")) btn3.clicked.connect(lambda: self.on_btn_click(3, "按钮3被点击")) layout.addWidget(btn1) layout.addWidget(btn2) layout.addWidget(btn3) layout.addWidget(self.label) self.setLayout(layout) def on_btn_click(self, btn_id, msg): """槽函数:接收自定义参数(按钮ID和提示信息)""" self.label.setText(f"ID:{btn_id} | {msg}") if __name__ == "__main__": app = QApplication(sys.argv) window = LambdaParamSignalSlot() window.show() sys.exit(app.exec_())3. 方式3:functools.partial传递参数(多参数更优雅) 当需要传递多个参数且逻辑复杂时,用functools.partial比lambda更优雅,支持默认参数、关键字参数。 完整代码:partial传递参数 import sys from functools import partial from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel class PartialParamSignalSlot(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("partial传递多参数") self.resize(350, 250) layout = QVBoxLayout() self.label = QLabel("未点击按钮", alignment=1) # 创建按钮,用partial传递多参数(按钮ID、名称、颜色) btn1 = QPushButton("红色按钮") btn2 = QPushButton("蓝色按钮") btn3 = QPushButton("绿色按钮") # partial传递参数:第一个参数是槽函数,后续是自定义参数 btn1.clicked.connect(partial(self.on_btn_click, 1, "红色按钮", "#e74c3c")) btn2.clicked.connect(partial(self.on_btn_click, 2, "蓝色按钮", "#3498db")) btn3.clicked.connect(partial(self.on_btn_click, 3, "绿色按钮", "#2ecc71")) layout.addWidget(btn1) layout.addWidget(btn2) layout.addWidget(btn3) layout.addWidget(self.label) self.setLayout(layout) def on_btn_click(self, btn_id, btn_name, color): """槽函数:接收多个自定义参数""" self.label.setText(f"ID:{btn_id} | 选中:{btn_name}") self.label.setStyleSheet(f"color: {color}; font-size: 16px;") if __name__ == "__main__": app = QApplication(sys.argv) window = PartialParamSignalSlot() window.show() sys.exit(app.exec_())四、进阶用法2:信号与槽的断开(disconnect) 有时需要动态解除信号与槽的绑定(如按钮禁用时停止响应点击),用disconnect()方法实现,支持3种断开方式。 完整代码:信号与槽的断开 import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel class DisconnectSignalSlot(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("信号与槽的断开(disconnect)") self.resize(350, 250) layout = QVBoxLayout() self.label = QLabel("状态:未绑定信号", alignment=1) self.bind_btn = QPushButton("绑定信号") self.unbind_btn = QPushButton("断开信号") self.test_btn = QPushButton("测试信号(点击无响应)") # 绑定“绑定/断开”按钮的信号 self.bind_btn.clicked.connect(self.on_bind) self.unbind_btn.clicked.connect(self.on_unbind) layout.addWidget(self.label) layout.addWidget(self.bind_btn) layout.addWidget(self.unbind_btn) layout.addWidget(self.test_btn) self.setLayout(layout) # 记录信号是否绑定 self.is_bound = False def on_bind(self): """绑定信号""" if not self.is_bound: self.test_btn.clicked.connect(self.on_test_click) self.is_bound = True self.label.setText("状态:信号已绑定(点击测试按钮有响应)") self.test_btn.setText("测试信号(点击有响应)") def on_unbind(self): """断开信号""" if self.is_bound: self.test_btn.clicked.disconnect(self.on_test_click) self.is_bound = False self.label.setText("状态:信号已断开(点击测试按钮无响应)") self.test_btn.setText("测试信号(点击无响应)") def on_test_click(self): """测试信号的槽函数""" self.label.setText("测试信号触发成功!") if __name__ == "__main__": app = QApplication(sys.argv) window = DisconnectSignalSlot() window.show() sys.exit(app.exec_())三种断开方式说明 控件.信号.disconnect(槽函数):断开指定信号与指定槽函数的绑定(最常用); 控件.信号.disconnect():断开该信号的所有绑定槽函数; 控件.disconnect():断开该控件的所有信号与槽的绑定(慎用,可能误删必要绑定)。 五、进阶用法3:同一信号绑定多个槽函数 一个信号可以同时绑定多个槽函数,信号触发时,槽函数会按绑定顺序依次执行,适合复杂逻辑拆分。 完整代码:同一信号绑定多槽函数 import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel class MultiSlotSignal(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("同一信号绑定多个槽函数") self.resize(350, 250) layout = QVBoxLayout() self.label1 = QLabel("槽函数1:未执行", alignment=1) self.label2 = QLabel("槽函数2:未执行", alignment=1) self.label3 = QLabel("槽函数3:未执行", alignment=1) self.btn = QPushButton("点击触发所有槽函数") # 同一信号(btn.clicked)绑定3个槽函数 self.btn.clicked.connect(self.slot1) self.btn.clicked.connect(self.slot2) self.btn.clicked.connect(self.slot3) layout.addWidget(self.label1) layout.addWidget(self.label2) layout.addWidget(self.label3) layout.addWidget(self.btn) self.setLayout(layout) def slot1(self): self.label1.setText("槽函数1:执行成功(顺序1)") def slot2(self): self.label2.setText("槽函数2:执行成功(顺序2)") def slot3(self): self.label3.setText("槽函数3:执行成功(顺序3)") if __name__ == "__main__": app = QApplication(sys.argv) window = MultiSlotSignal() window.show() sys.exit(app.exec_())六、信号与槽的核心原理补充 1. 信号与槽的匹配规则 信号无参数 → 槽函数可无参数,或有默认参数; 信号有参数 → 槽函数必须接收所有参数(或用*args接收任意参数); 槽函数参数不能多于信号参数(否则会报错)。 2. 常用内置信号(高频汇总) 控件常用信号信号含义自带参数QPushButtonclicked()按钮点击无QLineEdittextChanged(str)文本内容变化str(当前文本)QSlidervalueChanged(int)滑块值变化int(当前值)QCheckBoxstateChanged(int)选中状态变化int(2=选中,0=未选中)QComboBoxcurrentIndexChanged(int)选中索引变化int(当前索引)QTableWidgetitemClicked(QTableWidgetItem)单元格点击QTableWidgetItem(单元格对象)七、常见问题排查 1. 信号绑定后槽函数不执行 问题原因1:信号/槽函数名称写错(如clicked写成click,on_btn_click写成on_btn_click1); 问题原因2:控件实例名错误(如self.btn写成btn,未绑定到当前对象); 问题原因3:槽函数参数不匹配(信号有参数但槽函数未接收); 问题原因4:控件被禁用(setDisabled(True)),无法发出信号。 2. 传递参数时报错(TypeError) 问题原因1:lambda表达式语法错误(如参数传递格式错误); 问题原因2:partial传递的参数数量与槽函数不匹配; 问题原因3:信号自带参数与自定义参数冲突(如同时接收信号参数和自定义参数,需用lambda整合)。 3. 断开信号时报错(RuntimeError) 问题原因1:信号未绑定该槽函数,强行断开; 问题原因2:同一信号绑定多次同一槽函数,断开时只需要断开一次; 解决方案:断开前先判断是否绑定(如用is_bound标记)。 总结 核心本质:信号与槽是PyQt5事件驱动模型的核心,实现“触发动作→响应逻辑”的解耦关联; 绑定方式:代码手动绑定(推荐)、Qt Designer可视化绑定(快速开发)、装饰器绑定(简洁); 进阶重点:带参数的信号与槽(lambda/partial传递参数)、信号断开、多槽函数绑定; 关键规则:槽函数参数数量不能多于信号参数,参数类型需匹配; 下一章我们将学习自定义信号——当内置信号无法满足需求时,如何自己定义信号并传递任意参数,实现更复杂的交互逻辑。 如果在实操中遇到信号与槽绑定、参数传递的问题,或者想了解某个复杂场景的信号槽用法,欢迎在评论区留言讨论~ -
PHP开源广告投放系统源码 - 短链接生成+广告效果跟踪工具 PHP开源广告投放系统源码:精准跟踪+高效管理的广告解决方案 给大家分享一款专为广告资源管理打造的开源工具——PHP开源广告投放系统源码!基于FastAdmin框架+MySQL数据库构建,聚焦广告投放全流程需求,集成短链接生成、效果跟踪、模板选择等核心功能,支持个性化定制与二次开发,免费开源且部署简单,妥妥的中小型广告主、个人开发者及需要管理广告资源的用户的实用选择! mk22ax54.png图片 一、核心功能模块:覆盖广告投放全需求 1. 短链接生成与管理:便捷传播无压力 支持长URL转短链接,系统自动生成唯一标识符,建立长URL与短码的关联映射,形成简洁易记的短链接。 短链接访问时自动重定向至目标页面,避免冗长URL带来的传播不便,适配微博、短信等字符限制场景。 支持自定义短链接后缀,满足个性化分享需求,便于品牌化传播或记忆。 2. 广告效果精准跟踪:数据驱动优化投放 全面记录广告页面访问数据,包括访问次数(PV)、独立访客数(UV)、访问来源、地理位置等关键信息。 生成详细数据分析报告,帮助用户直观了解广告投放效果,精准把握用户行为与偏好,为优化投放策略提供数据支撑。 3. 跳转页面与自定义功能:灵活适配场景 提供多套预设跳转页面模板,用户可根据广告风格、行业属性选择适配模板,无需单独设计页面。 支持个性化定制,除自定义短链接后缀外,可根据业务需求二次开发扩展功能,适配不同广告投放场景。 二、核心特色:广告管理系统的差异化优势 提升传播效率:短链接简洁易记、占用字符少,便于分享传播,有效降低用户访问门槛,提升点击转化。 数据可视化:完整的访问统计与数据分析功能,告别盲目投放,通过数据洞察优化广告策略,提升投放效果。 架构灵活易扩展:前端基于HTML开发,后端依托FastAdmin框架,代码结构清晰,支持二次开发与功能扩展,适配个性化需求。 部署门槛低:无需复杂技术配置,按指引完成环境搭建与基础设置即可上线,非专业开发者也能快速上手。 三、环境与部署要点 1. 基础环境要求 服务器:支持Nginx或Apache作为Web服务器,需具备稳定网络连接。 技术环境:PHP 7.3及以上版本,MySQL 5.7及以上版本(用于存储数据)。 2. 核心部署步骤 解压文件:将源码文件解压至站点目录。 数据库配置:导入系统提供的.sql文件,修改application\database.php中的数据库连接信息(主机名、数据库名、用户名、密码等)。 配置伪静态:根据使用的Web服务器(Nginx/Apache)设置对应伪静态规则,确保系统正常访问。 登录后台:通过域名/adminn.php/index/login访问后台,默认账号admin、密码123456,登录后可关闭调试模式(修改application\config.php中app_debug为false)。 四、适用场景:谁适合用这款源码? 中小型广告主:管理自有广告资源,通过短链接传播与效果跟踪,提升广告投放效率与转化效果。 个人开发者:搭建个人广告投放平台,或作为FastAdmin框架实战案例,学习PHP广告系统开发逻辑。 需要精准跟踪的用户:用于活动推广、产品宣传等场景,通过数据统计优化传播策略,降低投放成本。 源码下载 广告投放系统 下载地址:https://pan.quark.cn/s/22e5127448d8 提取码: -
Go+Vue开源软件授权管理系统源码 - 支持在线离线授权 Go+Vue开源软件授权管理系统源码:安全可控的授权解决方案 给大家分享一款专为软件授权管理打造的开源资源——Go+Vue开源软件授权管理系统源码!基于Go 1.23+(Gin框架)+ Vue3+技术栈+MySQL 8+数据库构建,聚焦授权全生命周期管理,支持在线/离线授权与硬件指纹绑定,提供完整的客户、授权、API服务模块,开源免费且可扩展,妥妥的商业软件开发者、技术团队的授权管理利器! mk22222m.png图片 一、核心功能模块:覆盖授权管理全场景 1. 客户与授权核心管理 客户管理:支持完整的客户信息录入、存储与状态控制,可精准管理授权对应的客户主体,方便后续追溯与维护。 授权生成:支持在线、离线两种授权模式,适配不同使用场景;内置硬件指纹绑定机制,确保授权与设备绑定,防止非法复制或滥用,提升授权安全性。 授权生命周期管理:实时监控授权状态(生效、过期、禁用等),可对授权进行创建、修改、撤销等操作,全流程可视化管控。 2. 部署与服务支持 部署包生成:自动生成包含授权配置的部署包,简化软件部署流程,确保授权配置与软件无缝衔接。 RESTful API服务:提供授权验证、激活、心跳监控等接口,方便与各类软件系统集成,快速实现授权功能对接。 跨平台工具:配套多平台硬件信息获取工具,支持采集不同设备的硬件指纹,为绑定授权提供数据支撑。 3. 系统运维管理 管理员认证:内置管理员权限控制,保障系统后台操作安全,防止未授权访问。 监控仪表盘:直观展示授权数据、客户信息等核心指标,方便管理员实时掌握系统运行状态。 二、核心特色:授权管理系统的差异化优势 技术栈稳定高效:后端基于Go语言Gin框架,性能强劲、并发处理能力强;前端采用Vue3+现代化UI组件,界面流畅、操作直观,架构清晰易维护。 授权安全可控:通过硬件指纹绑定机制,实现授权与设备强关联,搭配在线/离线双授权模式,兼顾灵活性与安全性,有效防范授权破解、非法传播。 扩展性强:作为开源项目,代码结构模块化,支持二次开发,可根据业务需求新增授权规则、扩展客户管理维度或集成更多第三方服务。 部署灵活多样:支持Docker部署、单机部署或系统服务部署,适配不同运维场景,无需复杂配置即可快速上线。 学习价值突出:完整展示Go+Vue技术栈的协同开发逻辑,包含权限管理、安全机制、部署流程等核心模块,是学习现代Web应用架构的优质实战案例。 三、技术栈与部署要点 1. 核心技术栈 前端:Vue.js 3+ + 现代化UI组件 后端:Go 1.23+(Gin框架)、GORM(ORM工具)、Viper(配置管理)、Logrus(日志管理) 数据库:MySQL 8+ 配置格式:YAML文件 部署方式:Docker、单机部署、系统服务部署 2. 部署核心逻辑 先配置YAML格式的系统配置文件,完成数据库连接、服务端口等基础设置; 部署后端服务与前端界面,确保API接口正常通信; 利用跨平台工具采集硬件信息,配置授权规则后即可生成与分发授权。 四、适用场景:谁适合用这款源码? 商业软件开发者:为自研商业软件搭建专属授权管理系统,通过在线/离线授权控制软件使用范围,防范盗版。 技术团队/企业:需要对内部软件、付费工具进行授权管控,实现客户分级授权、授权到期提醒等功能。 Go/Vue学习者:作为Go+Vue全栈开发实战案例,学习Gin框架使用、前后端分离架构、权限管理与安全机制设计。 下载源码 源码 下载地址:https://pan.quark.cn/s/b20744efedd4 提取码: -
PyQt5实战项目:简易Excel表格数据管理器(完整可运行代码) 第10篇:阶段二实战项目:简易Excel表格数据管理器(完整代码) 哈喽~ 欢迎来到PyQt5系列的第10篇!这是阶段二的收官实战项目——我们将整合前9章的核心知识点(布局管理器、QTableWidget、容器控件、标准对话框、自定义交互),开发一个“仿照Excel的简易表格数据管理器”。这个项目覆盖Excel最常用的基础功能(新建/打开/保存、行列增删、数据排序、单元格格式设置等),全程代码可直接运行,帮你把零散的知识点串联成完整的项目开发能力! mk0p53ij.png图片 一、项目核心目标与知识点整合 1. 项目目标 开发一个轻量级表格管理器,实现Excel的核心基础功能,满足日常简单的数据编辑/管理需求,界面风格贴近Excel,操作逻辑直观。 mk0ohoys.png图片 2. 整合的核心知识点 知识点应用场景QTableWidget核心表格控件,承载数据展示/编辑QTabWidget多工作表切换(模拟Excel的多sheet)QGroupBox功能按钮分组(编辑区/格式区/数据区)布局管理器(网格/线性)整体界面排版,保证自适应标准对话框(文件/字体/颜色/消息)打开/保存文件、设置单元格格式、操作提示信号与槽按钮交互、表格事件响应3. 核心功能清单 ✅ 多工作表管理(新建/删除sheet、切换sheet); ✅ 文件操作(新建空白表格、打开CSV文件、保存CSV文件); ✅ 行列管理(插入/删除行/列、清空表格); ✅ 数据编辑(单元格编辑、选中行/列高亮); ✅ 格式设置(单元格字体、颜色、居中对齐); ✅ 数据操作(按列排序、获取选中单元格数据); ✅ 操作提示(成功/失败/警告类消息框)。 二、项目整体架构 1. 界面布局设计 整体界面分为3个区域: 顶部功能区:文件操作按钮(新建/打开/保存) + 工作表切换(QTabWidget); 中部功能按钮区:用QGroupBox分为“编辑区”“格式区”“数据区”,摆放功能按钮; 底部表格区:QTableWidget核心表格,占界面主要空间,支持自适应缩放。 2. 核心功能模块 文件模块:处理CSV文件的新建/打开/保存; 工作表模块:管理多sheet的增删改查; mk0ois8v.png图片 编辑模块:行列增删、清空表格; mk0ojcv5.png图片 格式模块:单元格字体、颜色、对齐方式设置; mk0ojroj.png图片 数据模块:数据排序、选中数据获取。 mk0p33q1.png图片 三、完整代码实现 import sys import os import csv from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QGroupBox, QPushButton, QTableWidget, QTableWidgetItem, QTabWidget, QFileDialog, QFontDialog, QColorDialog, QMessageBox ) from PyQt5.QtGui import QFont, QColor, QIcon from PyQt5.QtCore import Qt, pyqtSlot # 主窗口(继承QMainWindow,支持菜单栏/工具栏,更贴近Excel) class SimpleExcel(QMainWindow): def __init__(self): super().__init__() self.init_ui() self.current_file_path = None # 记录当前打开的文件路径 def init_ui(self): # 基础窗口设置 self.setWindowTitle("简易Excel表格管理器") self.resize(1000, 700) self.setMinimumSize(800, 500) # 设置最小尺寸,避免缩放过小 # ---------- 中心控件(所有内容放在中心控件中) ---------- central_widget = QWidget() self.setCentralWidget(central_widget) # ---------- 主布局(垂直) ---------- main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(15) main_layout.setContentsMargins(15, 15, 15, 15) # ---------- 1. 顶部功能区:文件操作 + 工作表切换 ---------- top_layout = QVBoxLayout() # 1.1 文件操作按钮(水平布局) file_btn_layout = QHBoxLayout() self.new_file_btn = QPushButton("新建") self.open_file_btn = QPushButton("打开") self.save_file_btn = QPushButton("保存") # 按钮样式 for btn in [self.new_file_btn, self.open_file_btn, self.save_file_btn]: btn.setFixedSize(80, 35) btn.setStyleSheet("QPushButton { font-size: 14px; }") file_btn_layout.addWidget(self.new_file_btn) file_btn_layout.addWidget(self.open_file_btn) file_btn_layout.addWidget(self.save_file_btn) file_btn_layout.addStretch() # 右侧留白 # 1.2 工作表切换(QTabWidget) self.sheet_tab = QTabWidget() self.sheet_tab.setTabPosition(QTabWidget.South) # 标签在下方(贴近Excel) self.sheet_tab.setTabsClosable(True) # 显示关闭按钮 # 添加默认工作表 self.add_new_sheet("Sheet1") # 顶部布局组合 top_layout.addLayout(file_btn_layout) top_layout.addWidget(self.sheet_tab) # ---------- 2. 中部功能按钮区(分组) ---------- btn_group_layout = QGridLayout() btn_group_layout.setSpacing(10) # 2.1 编辑区分组 edit_group = QGroupBox("编辑区") edit_layout = QVBoxLayout(edit_group) # 编辑区按钮 self.add_row_btn = QPushButton("插入行") self.del_row_btn = QPushButton("删除行") self.add_col_btn = QPushButton("插入列") self.del_col_btn = QPushButton("删除列") self.clear_btn = QPushButton("清空表格") edit_btns = [self.add_row_btn, self.del_row_btn, self.add_col_btn, self.del_col_btn, self.clear_btn] for btn in edit_btns: btn.setFixedSize(80, 30) edit_layout.addWidget(btn) btn_group_layout.addWidget(edit_group, 0, 0) # 2.2 格式区分组 format_group = QGroupBox("格式区") format_layout = QVBoxLayout(format_group) # 格式区按钮 self.font_btn = QPushButton("字体设置") self.color_btn = QPushButton("字体颜色") self.align_btn = QPushButton("居中对齐") format_btns = [self.font_btn, self.color_btn, self.align_btn] for btn in format_btns: btn.setFixedSize(80, 30) format_layout.addWidget(btn) btn_group_layout.addWidget(format_group, 0, 1) # 2.3 数据区分组 data_group = QGroupBox("数据区") data_layout = QVBoxLayout(data_group) # 数据区按钮 self.sort_asc_btn = QPushButton("升序排序") self.sort_desc_btn = QPushButton("降序排序") self.get_data_btn = QPushButton("选中数据") self.add_sheet_btn = QPushButton("新建Sheet") data_btns = [self.sort_asc_btn, self.sort_desc_btn, self.get_data_btn, self.add_sheet_btn] for btn in data_btns: btn.setFixedSize(80, 30) data_layout.addWidget(btn) btn_group_layout.addWidget(data_group, 0, 2) # ---------- 3. 底部表格区 ---------- # 表格区占主要空间,设置拉伸权重 table_layout = QVBoxLayout() table_layout.setStretchFactor(table_layout, 1) # ---------- 组合所有布局 ---------- main_layout.addLayout(top_layout) main_layout.addLayout(btn_group_layout) main_layout.addLayout(table_layout, stretch=1) # 表格区拉伸权重1,占更多空间 # ---------- 信号绑定 ---------- self.bind_signals() # ---------- 核心方法:添加新工作表 ---------- def add_new_sheet(self, sheet_name): """添加新的工作表(QTableWidget)""" # 创建表格控件 table = QTableWidget() table.setRowCount(10) # 默认10行 table.setColumnCount(5) # 默认5列 # 设置表格样式(贴近Excel) table.setAlternatingRowColors(True) # 隔行变色 table.horizontalHeader().setStretchLastSection(True) # 最后一列拉伸 table.setSelectionBehavior(QTableWidget.SelectRows) # 选中整行 # 添加到标签页 self.sheet_tab.addTab(table, sheet_name) # 切换到新工作表 self.sheet_tab.setCurrentWidget(table) return table # ---------- 信号绑定 ---------- def bind_signals(self): # 文件操作 self.new_file_btn.clicked.connect(self.on_new_file) self.open_file_btn.clicked.connect(self.on_open_file) self.save_file_btn.clicked.connect(self.on_save_file) # 工作表操作 self.add_sheet_btn.clicked.connect(self.on_add_sheet) self.sheet_tab.tabCloseRequested.connect(self.on_close_sheet) # 编辑操作 self.add_row_btn.clicked.connect(self.on_add_row) self.del_row_btn.clicked.connect(self.on_del_row) self.add_col_btn.clicked.connect(self.on_add_col) self.del_col_btn.clicked.connect(self.on_del_col) self.clear_btn.clicked.connect(self.on_clear_table) # 格式操作 self.font_btn.clicked.connect(self.on_set_font) self.color_btn.clicked.connect(self.on_set_color) self.align_btn.clicked.connect(self.on_set_align) # 数据操作 self.sort_asc_btn.clicked.connect(lambda: self.on_sort_data(Qt.AscendingOrder)) self.sort_desc_btn.clicked.connect(lambda: self.on_sort_data(Qt.DescendingOrder)) self.get_data_btn.clicked.connect(self.on_get_selected_data) # ---------- 文件操作槽函数 ---------- def on_new_file(self): """新建空白表格""" # 清空所有工作表 while self.sheet_tab.count() > 0: self.sheet_tab.removeTab(0) # 添加默认工作表 self.add_new_sheet("Sheet1") self.current_file_path = None QMessageBox.information(self, "成功", "新建空白表格完成!") def on_open_file(self): """打开CSV文件""" file_path, _ = QFileDialog.getOpenFileName( self, "打开CSV文件", "", "CSV Files (*.csv);;All Files (*.*)" ) if not file_path: return try: # 清空现有工作表 while self.sheet_tab.count() > 0: self.sheet_tab.removeTab(0) # 创建新工作表并加载数据 table = self.add_new_sheet(os.path.basename(file_path)) # 读取CSV文件 with open(file_path, "r", encoding="utf-8") as f: reader = csv.reader(f) # 获取所有行数据 rows = list(reader) if not rows: QMessageBox.warning(self, "提示", "文件为空!") return # 设置表格行列数 table.setRowCount(len(rows)) table.setColumnCount(len(rows[0])) # 填充数据 for row_idx, row_data in enumerate(rows): for col_idx, col_data in enumerate(row_data): item = QTableWidgetItem(str(col_data)) item.setTextAlignment(Qt.AlignCenter) table.setItem(row_idx, col_idx, item) self.current_file_path = file_path QMessageBox.information(self, "成功", f"已打开文件:{os.path.basename(file_path)}") except Exception as e: QMessageBox.critical(self, "错误", f"打开文件失败:{str(e)}") def on_save_file(self): """保存CSV文件""" # 获取当前表格 current_table = self.sheet_tab.currentWidget() if not isinstance(current_table, QTableWidget): QMessageBox.warning(self, "提示", "无有效表格可保存!") return # 获取保存路径 if self.current_file_path: save_path = self.current_file_path else: save_path, _ = QFileDialog.getSaveFileName( self, "保存CSV文件", "", "CSV Files (*.csv)" ) if not save_path: return # 补充后缀 if not save_path.endswith(".csv"): save_path += ".csv" try: # 写入CSV文件 with open(save_path, "w", encoding="utf-8", newline="") as f: writer = csv.writer(f) # 遍历表格所有行 for row in range(current_table.rowCount()): row_data = [] for col in range(current_table.columnCount()): item = current_table.item(row, col) row_data.append(item.text() if item else "") writer.writerow(row_data) self.current_file_path = save_path QMessageBox.information(self, "成功", f"文件已保存到:{save_path}") except Exception as e: QMessageBox.critical(self, "错误", f"保存文件失败:{str(e)}") # ---------- 工作表操作槽函数 ---------- def on_add_sheet(self): """新建工作表""" sheet_count = self.sheet_tab.count() + 1 self.add_new_sheet(f"Sheet{sheet_count}") QMessageBox.information(self, "成功", f"新建工作表Sheet{sheet_count}完成!") def on_close_sheet(self, index): """关闭指定工作表""" if self.sheet_tab.count() <= 1: QMessageBox.warning(self, "提示", "至少保留一个工作表!") return self.sheet_tab.removeTab(index) QMessageBox.information(self, "成功", "工作表已删除!") # ---------- 编辑操作槽函数 ---------- def get_current_table(self): """获取当前选中的表格,辅助函数""" table = self.sheet_tab.currentWidget() if not isinstance(table, QTableWidget): QMessageBox.warning(self, "提示", "无有效表格!") return None return table def on_add_row(self): """插入行""" table = self.get_current_table() if not table: return # 在选中行下方插入,无选中则在最后插入 current_row = table.currentRow() insert_row = current_row + 1 if current_row >= 0 else table.rowCount() table.insertRow(insert_row) QMessageBox.information(self, "成功", f"在第{insert_row+1}行插入新行!") def on_del_row(self): """删除行""" table = self.get_current_table() if not table: return current_row = table.currentRow() if current_row < 0: QMessageBox.warning(self, "提示", "请先选中要删除的行!") return table.removeRow(current_row) QMessageBox.information(self, "成功", f"第{current_row+1}行已删除!") def on_add_col(self): """插入列""" table = self.get_current_table() if not table: return current_col = table.currentColumn() insert_col = current_col + 1 if current_col >= 0 else table.columnCount() table.insertColumn(insert_col) QMessageBox.information(self, "成功", f"在第{insert_col+1}列插入新列!") def on_del_col(self): """删除列""" table = self.get_current_table() if not table: return current_col = table.currentColumn() if current_col < 0: QMessageBox.warning(self, "提示", "请先选中要删除的列!") return table.removeColumn(current_col) QMessageBox.information(self, "成功", f"第{current_col+1}列已删除!") def on_clear_table(self): """清空表格(保留行列数)""" table = self.get_current_table() if not table: return # 确认清空 reply = QMessageBox.question(self, "确认", "确定要清空表格所有数据吗?", QMessageBox.Yes | QMessageBox.No) if reply != QMessageBox.Yes: return # 清空所有单元格内容 for row in range(table.rowCount()): for col in range(table.columnCount()): table.setItem(row, col, QTableWidgetItem("")) QMessageBox.information(self, "成功", "表格数据已清空!") # ---------- 格式操作槽函数 ---------- def on_set_font(self): """设置选中单元格字体""" table = self.get_current_table() if not table: return # 获取选中单元格 selected_items = table.selectedItems() if not selected_items: QMessageBox.warning(self, "提示", "请先选中要设置格式的单元格!") return # 弹出字体选择对话框 font, ok = QFontDialog.getFont(QFont("微软雅黑", 12), self, "选择字体") if ok: for item in selected_items: item.setFont(font) QMessageBox.information(self, "成功", "字体设置完成!") def on_set_color(self): """设置选中单元格字体颜色""" table = self.get_current_table() if not table: return selected_items = table.selectedItems() if not selected_items: QMessageBox.warning(self, "提示", "请先选中要设置格式的单元格!") return # 弹出颜色选择对话框 color = QColorDialog.getColor(Qt.black, self, "选择字体颜色") if color.isValid(): for item in selected_items: item.setForeground(color) QMessageBox.information(self, "成功", "字体颜色设置完成!") def on_set_align(self): """设置选中单元格居中对齐""" table = self.get_current_table() if not table: return selected_items = table.selectedItems() if not selected_items: QMessageBox.warning(self, "提示", "请先选中要设置格式的单元格!") return for item in selected_items: item.setTextAlignment(Qt.AlignCenter) QMessageBox.information(self, "成功", "居中对齐设置完成!") # ---------- 数据操作槽函数 ---------- def on_sort_data(self, order): """数据排序(按选中列)""" table = self.get_current_table() if not table: return current_col = table.currentColumn() if current_col < 0: QMessageBox.warning(self, "提示", "请先选中要排序的列!") return # 排序 table.sortItems(current_col, order) sort_type = "升序" if order == Qt.AscendingOrder else "降序" QMessageBox.information(self, "成功", f"按第{current_col+1}列{sort_type}排序完成!") def on_get_selected_data(self): """获取选中单元格数据""" table = self.get_current_table() if not table: return selected_items = table.selectedItems() if not selected_items: QMessageBox.warning(self, "提示", "请先选中单元格!") return # 整理选中数据 data_text = "选中数据:\n" for item in selected_items: data_text += f"行{item.row()+1}列{item.column()+1}:{item.text()}\n" QMessageBox.information(self, "选中数据", data_text) # ---------- 程序入口 ---------- if __name__ == "__main__": # 解决中文显示问题(可选) QApplication.setStyle("Fusion") app = QApplication(sys.argv) window = SimpleExcel() window.show() sys.exit(app.exec_())四、核心功能解析 1. 多工作表管理 核心方法:add_new_sheet() 创建新的QTableWidget并添加到QTabWidget; 关闭保护:on_close_sheet() 中判断工作表数量,确保至少保留1个; 标签位置:setTabPosition(QTabWidget.South) 把标签放在下方,贴近Excel的sheet位置。 2. CSV文件读写 读取:用csv.reader()读取所有行,再逐行填充到QTableWidget; 写入:遍历表格所有单元格,收集数据后用csv.writer()写入; 编码:统一用encoding="utf-8",避免中文乱码(Windows若乱码可尝试gbk)。 3. 单元格格式设置 选中单元格:selectedItems() 获取所有选中的单元格项; 字体设置:QFontDialog.getFont() 弹出字体选择框,返回选中的字体对象; 颜色设置:QColorDialog.getColor() 返回颜色对象,用setForeground()应用; 对齐方式:setTextAlignment(Qt.AlignCenter) 设置居中,支持左/右对齐。 4. 行列操作 插入行/列:insertRow()/insertColumn(),优先在选中位置插入,无选中则在末尾; 删除行/列:removeRow()/removeColumn(),需先判断是否选中; 清空表格:遍历所有单元格,设置为空字符串(保留行列数)。 五、常见问题排查 1. 文件相关问题 CSV打开乱码:读写时确保encoding="utf-8",Windows系统可替换为encoding="gbk"; 保存文件失败:检查文件路径是否有权限(如系统盘根目录),建议保存到用户目录; 文件为空提示:读取CSV时判断rows是否为空,避免表格无数据。 2. 表格操作问题 无有效表格提示:所有表格操作前调用get_current_table(),判断是否为QTableWidget; 选中行/列无响应:确保表格setSelectionBehavior(QTableWidget.SelectRows),支持整行选中; 排序无效果:排序需指定列(currentColumn()),确保选中了有效列。 3. 界面适配问题 窗口缩放过小:设置setMinimumSize(800, 500),避免控件重叠; 表格不自适应:horizontalHeader().setStretchLastSection(True) 让最后一列拉伸; 按钮排版混乱:用QGridLayout管理分组按钮,固定按钮大小,避免拉伸变形。 六、功能拓展建议(进阶方向) 这个基础版本可拓展更多Excel功能,适合进阶学习: 单元格合并:setSpan() 实现单元格合并/拆分; 公式计算:支持简单公式(如求和=SUM(A1:A5)); 数据筛选:按条件筛选表格数据; 样式保存:支持单元格背景色、边框设置; Excel格式支持:用openpyxl/pandas库支持.xlsx文件(需额外安装)。 总结 项目核心价值:这是阶段二的收官项目,整合了前9章的所有核心知识点,从“零散知识点”到“完整项目”,帮你建立PyQt5项目开发的整体思维; 核心技术点:QTableWidget是表格开发的核心,QTabWidget实现多页面,QGroupBox规整界面,标准对话框处理交互,信号与槽串联所有功能; 实战思维:开发GUI项目需先设计界面布局→拆分功能模块→实现核心功能→处理异常→优化体验; 后续方向:阶段三将进入PyQt5进阶内容(信号与槽深入、自定义控件、多线程、数据库交互),进一步提升项目开发能力。 如果在运行代码时遇到问题,或者想拓展某个进阶功能(如单元格合并、Excel格式支持),欢迎在评论区留言讨论~ -
PyQt5容器控件:QTabWidget与QGroupBox(附多功能窗口实战) 第9篇:PyQt5容器控件:QTabWidget与QGroupBox(完整代码) 哈喽~ 欢迎来到PyQt5系列的第9篇!上一章我们掌握了QDialog和各类标准对话框,解决了界面的交互弹窗问题。今天我们聚焦容器控件——这类控件的核心作用是“管理其他控件”,把功能相关的控件分组/分页面摆放,让复杂界面更规整、层次感更强。我们会详细讲解两种高频容器控件:QGroupBox(分组框)和QTabWidget(标签页控件),全程搭配完整可运行代码,新手也能轻松掌握! mk0o8rmi.png图片 一、先明确:容器控件的核心定位 容器控件本身不实现业务功能,而是作为“控件的容器”存在,核心价值是提升界面的结构化和可读性: QGroupBox(分组框):把功能相关的控件(如设置项、表单字段)“分组”展示,带标题和可选边框,用户能快速识别控件的功能归属; QTabWidget(标签页控件):把不同功能模块(如文本编辑、数据表格、系统设置)“分页面”展示,通过点击标签切换页面,极大节省界面空间,适合多功能集成的窗口。 二、QGroupBox(分组框)详解:控件分组管理 QGroupBox是最基础的容器控件,核心是“给控件加分组标题+边框”,支持“可勾选分组”(勾选后才启用内部控件),是规整表单/设置界面的必备工具。 1. QGroupBox基础用法(完整代码) 实现两个分组框(“界面设置”和“数据设置”),演示分组框的基础属性和可勾选功能: mk0ns3om.png图片 import sys from PyQt5.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QCheckBox, QComboBox, QPushButton ) from PyQt5.QtCore import Qt class GroupBoxDemo(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("QGroupBox分组框基础演示") self.resize(500, 400) # 主布局(垂直):两个分组框 + 确认按钮 main_layout = QVBoxLayout() main_layout.setSpacing(20) main_layout.setContentsMargins(30, 30, 30, 30) # ---------- 分组框1:界面设置(普通分组,不可勾选) ---------- ui_group = QGroupBox("界面设置") # 设置分组标题 ui_group.setAlignment(Qt.AlignCenter) # 标题居中对齐 # 分组框内部布局(网格布局) ui_layout = QHBoxLayout() ui_layout.setSpacing(15) # 添加内部控件 theme_label = QLabel("主题:") theme_combo = QComboBox() theme_combo.addItems(["浅色主题", "深色主题", "系统主题"]) font_size_label = QLabel("字体大小:") font_size_combo = QComboBox() font_size_combo.addItems(["12px", "14px", "16px"]) # 添加到分组框布局 ui_layout.addWidget(theme_label) ui_layout.addWidget(theme_combo) ui_layout.addWidget(font_size_label) ui_layout.addWidget(font_size_combo) # 绑定布局到分组框 ui_group.setLayout(ui_layout) # ---------- 分组框2:数据设置(可勾选分组,勾选才启用内部控件) ---------- data_group = QGroupBox("数据设置") data_group.setCheckable(True) # 开启勾选功能 data_group.setChecked(False) # 默认未勾选(内部控件禁用) # 分组框内部布局(水平) data_layout = QHBoxLayout() data_layout.setSpacing(15) # 添加内部控件 auto_save_check = QCheckBox("自动保存") auto_sync_check = QCheckBox("自动同步") # 添加到分组框布局 data_layout.addWidget(auto_save_check) data_layout.addWidget(auto_sync_check) # 绑定布局到分组框 data_group.setLayout(data_layout) # 确认按钮 confirm_btn = QPushButton("保存设置") confirm_btn.setFixedSize(100, 30) # 将分组框和按钮添加到主布局 main_layout.addWidget(ui_group) main_layout.addWidget(data_group) main_layout.addWidget(confirm_btn, alignment=Qt.AlignCenter) # 绑定主布局到窗口 self.setLayout(main_layout) if __name__ == "__main__": app = QApplication(sys.argv) window = GroupBoxDemo() window.show() sys.exit(app.exec_())2. QGroupBox核心方法解析 QGroupBox的核心围绕“分组样式”和“交互控制”,重点掌握: 方法作用setTitle(标题文本)设置分组框的标题setAlignment(对齐方式)设置标题对齐(如Qt.AlignCenter居中、Qt.AlignLeft左对齐)setCheckable(True/False)是否开启“勾选功能”(勾选后内部控件启用,未勾选禁用)setChecked(True/False)设置勾选状态(仅当setCheckable(True)时生效)setStyleSheet(样式)自定义分组框样式(如边框颜色、标题字体)3. QGroupBox实战:表单字段分组 结合之前学的表单布局,用QGroupBox将用户表单分为“基础信息”和“扩展信息”两组,提升表单可读性: mk0nukbw.png图片 import sys from PyQt5.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QFormLayout, QGroupBox, QLineEdit, QComboBox, QTextEdit, QPushButton ) from PyQt5.QtCore import Qt class GroupBoxFormDemo(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("QGroupBox表单分组实战") self.resize(500, 450) # 主布局 main_layout = QVBoxLayout() main_layout.setSpacing(20) main_layout.setContentsMargins(30, 30, 30, 30) # ---------- 分组1:基础信息 ---------- basic_group = QGroupBox("基础信息(必填)") basic_layout = QFormLayout() basic_layout.setSpacing(15) # 添加表单控件 basic_layout.addRow("用户名:", QLineEdit()) basic_layout.addRow("密码:", QLineEdit()) basic_layout.addRow("性别:", QComboBox()) basic_layout.addRow("手机号:", QLineEdit()) # 绑定布局 basic_group.setLayout(basic_layout) # ---------- 分组2:扩展信息 ---------- extend_group = QGroupBox("扩展信息(选填)") extend_layout = QFormLayout() extend_layout.setSpacing(15) # 添加表单控件 extend_layout.addRow("邮箱:", QLineEdit()) extend_layout.addRow("地址:", QLineEdit()) extend_layout.addRow("备注:", QTextEdit()) # 绑定布局 extend_group.setLayout(extend_layout) # 提交按钮 submit_btn = QPushButton("提交表单") submit_btn.setFixedSize(100, 30) # 添加到主布局 main_layout.addWidget(basic_group) main_layout.addWidget(extend_group) main_layout.addWidget(submit_btn, alignment=Qt.AlignCenter) self.setLayout(main_layout) if __name__ == "__main__": app = QApplication(sys.argv) window = GroupBoxFormDemo() window.show() sys.exit(app.exec_())三、QTabWidget(标签页控件)详解:多页面切换 QTabWidget是“多页面”界面的核心控件,比如浏览器的标签页、软件的功能面板,核心是“一个窗口承载多个功能页面”,极大节省界面空间。 1. QTabWidget基础用法(完整代码) 实现包含“文本编辑页”“数据表格页”“设置页”的多标签窗口,演示标签页的添加、切换、删除等核心操作: mk0nwa5q.png图片 import sys from PyQt5.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QTextEdit, QTableWidget, QTableWidgetItem, QPushButton, QLabel, QComboBox ) from PyQt5.QtGui import QIcon from PyQt5.QtCore import Qt class TabWidgetDemo(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("QTabWidget标签页基础演示") self.resize(800, 500) # 主布局(垂直):标签页控件 + 操作按钮 main_layout = QVBoxLayout() main_layout.setSpacing(15) main_layout.setContentsMargins(15, 15, 15, 15) # ---------- 创建QTabWidget核心控件 ---------- self.tab_widget = QTabWidget() # 设置标签位置(可选:North/南South/东East/西West,默认North) self.tab_widget.setTabPosition(QTabWidget.North) # 允许关闭标签页(显示×按钮) self.tab_widget.setTabsClosable(True) # 标签栏可滚动(标签过多时显示左右箭头) self.tab_widget.setTabBarAutoHide(False) # ---------- 添加标签页 ---------- # 页1:文本编辑页 text_page = QWidget() text_layout = QVBoxLayout(text_page) text_edit = QTextEdit() text_edit.setPlaceholderText("文本编辑页:在这里输入内容...") text_layout.addWidget(text_edit) # 添加标签页(参数:页面控件,标签文本,可选:图标) self.tab_widget.addTab(text_page, "文本编辑") # 页2:数据表格页 table_page = QWidget() table_layout = QVBoxLayout(table_page) table = QTableWidget() table.setRowCount(5) table.setColumnCount(4) table.setHorizontalHeaderLabels(["ID", "姓名", "性别", "年龄"]) # 填充测试数据 test_data = [ [1, "张三", "男", 25], [2, "李四", "女", 28], [3, "王五", "男", 30], [4, "赵六", "女", 22], [5, "钱七", "男", 27] ] for row in range(5): for col in range(4): item = QTableWidgetItem(str(test_data[row][col])) item.setTextAlignment(Qt.AlignCenter) table.setItem(row, col, item) table_layout.addWidget(table) self.tab_widget.addTab(table_page, "数据表格") # 页3:设置页 setting_page = QWidget() setting_layout = QVBoxLayout(setting_page) # 设置页用水平布局放控件 setting_h_layout = QHBoxLayout() setting_h_layout.addWidget(QLabel("主题:")) setting_h_layout.addWidget(QComboBox()) setting_layout.addLayout(setting_h_layout) setting_layout.addWidget(QLabel("设置页:在这里配置系统参数...")) self.tab_widget.addTab(setting_page, "系统设置") # ---------- 操作按钮区 ---------- btn_layout = QHBoxLayout() add_tab_btn = QPushButton("添加新标签页") close_tab_btn = QPushButton("关闭当前标签页") switch_tab_btn = QPushButton("切换到设置页") for btn in [add_tab_btn, close_tab_btn, switch_tab_btn]: btn.setFixedSize(120, 30) btn_layout.addWidget(add_tab_btn) btn_layout.addWidget(close_tab_btn) btn_layout.addWidget(switch_tab_btn) btn_layout.addStretch() # ---------- 绑定布局和信号 ---------- main_layout.addWidget(self.tab_widget) main_layout.addLayout(btn_layout) self.setLayout(main_layout) # 信号绑定 add_tab_btn.clicked.connect(self.add_new_tab) close_tab_btn.clicked.connect(self.close_current_tab) switch_tab_btn.clicked.connect(self.switch_to_setting_tab) # 标签页关闭信号(点击×按钮触发) self.tab_widget.tabCloseRequested.connect(self.close_specified_tab) # 标签页切换信号 self.tab_widget.currentChanged.connect(self.on_tab_changed) # ---------- 标签页操作槽函数 ---------- def add_new_tab(self): """添加新的空白标签页""" new_page = QWidget() new_layout = QVBoxLayout(new_page) new_layout.addWidget(QLabel("这是新添加的空白标签页")) # 添加标签页(带自定义文本) tab_index = self.tab_widget.addTab(new_page, f"新标签{self.tab_widget.count()+1}") # 切换到新添加的标签页 self.tab_widget.setCurrentIndex(tab_index) def close_current_tab(self): """关闭当前选中的标签页""" current_index = self.tab_widget.currentIndex() if current_index >= 0: # 确保有标签页可关闭 self.tab_widget.removeTab(current_index) def close_specified_tab(self, index): """关闭指定索引的标签页(点击×按钮触发)""" self.tab_widget.removeTab(index) def switch_to_setting_tab(self): """切换到设置页(通过索引,设置页是第3个,索引为2)""" self.tab_widget.setCurrentIndex(2) def on_tab_changed(self, index): """标签页切换时触发,打印当前标签页名称""" tab_text = self.tab_widget.tabText(index) print(f"当前切换到:{tab_text}页") if __name__ == "__main__": app = QApplication(sys.argv) window = TabWidgetDemo() window.show() sys.exit(app.exec_())2. QTabWidget核心方法解析 QTabWidget的核心围绕“标签页的增删改查和切换”,重点掌握: 方法作用addTab(页面控件, 标签文本, 图标)添加标签页(返回新标签页的索引)insertTab(索引, 页面控件, 标签文本)在指定索引位置插入标签页removeTab(索引)删除指定索引的标签页setCurrentIndex(索引)切换到指定索引的标签页currentIndex()获取当前选中的标签页索引tabText(索引)获取指定索引标签页的文本setTabPosition(位置)设置标签位置(North/South/East/West)setTabsClosable(True)显示标签页的关闭按钮(×)setTabIcon(索引, QIcon)给指定标签页设置图标3. QTabWidget实战:带图标的标签页 给标签页添加图标(提升界面美观度),并实现“标签页内容自适应”: mk0o39iv.png图片 图片素材 mk0o4oaf.png图片 mk0o4v3i.png图片 mk0o50fc.png图片 import sys from PyQt5.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QTabWidget, QTextEdit, QTableWidget, QTableWidgetItem ) from PyQt5.QtGui import QIcon from PyQt5.QtCore import Qt # 注意:需提前准备3个图标文件(text.png、table.png、setting.png)放在同级目录 # 若无图标,可注释掉setTabIcon相关代码 # 本站提供素材图片下载 class TabWidgetIconDemo(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("QTabWidget带图标标签页实战") self.resize(800, 500) # 主布局 main_layout = QVBoxLayout() self.tab_widget = QTabWidget() # 页1:文本编辑页(带图标) text_page = QWidget() text_layout = QVBoxLayout(text_page) text_layout.addWidget(QTextEdit("文本编辑页")) self.tab_widget.addTab(text_page, "文本编辑") # 设置图标 self.tab_widget.setTabIcon(0, QIcon("text.png")) # 页2:数据表格页(带图标) table_page = QWidget() table_layout = QVBoxLayout(table_page) table = QTableWidget(5, 4) table.setHorizontalHeaderLabels(["ID", "姓名", "性别", "年龄"]) table_layout.addWidget(table) self.tab_widget.addTab(table_page, "数据表格") self.tab_widget.setTabIcon(1, QIcon("table.png")) # 页3:设置页(带图标) setting_page = QWidget() setting_layout = QVBoxLayout(setting_page) setting_layout.addWidget(QTextEdit("系统设置页")) self.tab_widget.addTab(setting_page, "系统设置") self.tab_widget.setTabIcon(2, QIcon("setting.png")) # 标签页内容自适应 self.tab_widget.setSizePolicy( self.tab_widget.sizePolicy().horizontalPolicy(), self.tab_widget.sizePolicy().verticalPolicy() ) main_layout.addWidget(self.tab_widget) self.setLayout(main_layout) if __name__ == "__main__": app = QApplication(sys.argv) window = TabWidgetIconDemo() window.show() sys.exit(app.exec_())四、综合案例:多功能工具窗口(QTabWidget+QGroupBox) 整合QTabWidget(多页面)和QGroupBox(分组),实现一个“多功能工具窗口”——包含文本编辑页、数据表格页、设置页(设置页用QGroupBox分组),贴合实际项目的界面风格: mk0o7d0r.png图片 import sys from PyQt5.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QTextEdit, QTableWidget, QTableWidgetItem, QGroupBox, QLabel, QComboBox, QCheckBox, QPushButton, QFileDialog, QMessageBox ) from PyQt5.QtCore import Qt class MultiToolWindow(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("多功能工具窗口(QTabWidget+QGroupBox)") self.resize(800, 600) # 主布局 main_layout = QVBoxLayout() self.tab_widget = QTabWidget() # ---------- 页1:文本编辑页 ---------- text_page = QWidget() text_layout = QVBoxLayout(text_page) # 文本编辑区 self.text_edit = QTextEdit() self.text_edit.setPlaceholderText("请输入文本...") # 操作按钮 text_btn_layout = QHBoxLayout() open_btn = QPushButton("打开文件") save_btn = QPushButton("保存文件") for btn in [open_btn, save_btn]: btn.setFixedSize(80, 30) text_btn_layout.addWidget(open_btn) text_btn_layout.addWidget(save_btn) text_btn_layout.addStretch() # 添加到文本页布局 text_layout.addLayout(text_btn_layout) text_layout.addWidget(self.text_edit) self.tab_widget.addTab(text_page, "文本编辑") # ---------- 页2:数据表格页 ---------- table_page = QWidget() table_layout = QVBoxLayout(table_page) self.table = QTableWidget() self.table.setRowCount(0) self.table.setColumnCount(4) self.table.setHorizontalHeaderLabels(["ID", "姓名", "性别", "年龄"]) table_layout.addWidget(self.table) self.tab_widget.addTab(table_page, "数据表格") # ---------- 页3:设置页(用QGroupBox分组) ---------- setting_page = QWidget() setting_layout = QVBoxLayout(setting_page) # 分组1:界面设置 ui_group = QGroupBox("界面设置") ui_layout = QHBoxLayout() ui_layout.addWidget(QLabel("主题:")) ui_layout.addWidget(QComboBox()) ui_group.setLayout(ui_layout) # 分组2:数据设置 data_group = QGroupBox("数据设置") data_group.setCheckable(True) data_layout = QHBoxLayout() data_layout.addWidget(QCheckBox("自动保存")) data_layout.addWidget(QCheckBox("自动同步")) data_group.setLayout(data_layout) # 保存设置按钮 save_setting_btn = QPushButton("保存设置") save_setting_btn.setFixedSize(100, 30) # 添加到设置页布局 setting_layout.addWidget(ui_group) setting_layout.addWidget(data_group) setting_layout.addWidget(save_setting_btn, alignment=Qt.AlignCenter) self.tab_widget.addTab(setting_page, "系统设置") # ---------- 绑定布局和信号 ---------- main_layout.addWidget(self.tab_widget) self.setLayout(main_layout) # 信号绑定 open_btn.clicked.connect(self.open_file) save_btn.clicked.connect(self.save_file) save_setting_btn.clicked.connect(self.save_setting) # ---------- 文本页功能 ---------- def open_file(self): file_path, _ = QFileDialog.getOpenFileName(self, "打开文件", "", "Text Files (*.txt)") if file_path: with open(file_path, "r", encoding="utf-8") as f: self.text_edit.setText(f.read()) QMessageBox.information(self, "成功", "文件打开成功!") def save_file(self): file_path, _ = QFileDialog.getSaveFileName(self, "保存文件", "", "Text Files (*.txt)") if file_path: with open(file_path, "w", encoding="utf-8") as f: f.write(self.text_edit.toPlainText()) QMessageBox.information(self, "成功", "文件保存成功!") # ---------- 设置页功能 ---------- def save_setting(self): QMessageBox.information(self, "成功", "设置保存成功!") if __name__ == "__main__": app = QApplication(sys.argv) window = MultiToolWindow() window.show() sys.exit(app.exec_())五、常见问题排查 1. QGroupBox相关问题 问题1:分组框内控件排列混乱 → 解决:分组框必须绑定布局(如QVBoxLayout/QHBoxLayout),再将控件添加到布局,而非直接添加到分组框; 问题2:可勾选分组框勾选后内部控件仍禁用 → 解决:确保内部控件未手动设置setEnabled(False),分组框的setCheckable(True)仅控制“是否可勾选”,setChecked(True)才会启用内部控件; 问题3:分组框标题不显示 → 解决:检查setTitle()是否传入空字符串,或样式表覆盖了标题显示(可重置样式表测试)。 2. QTabWidget相关问题 问题1:标签页内容不自适应窗口缩放 → 解决:标签页的页面控件(如QWidget)必须绑定布局,且布局内的控件未设置固定大小; 问题2:标签页关闭按钮不显示 → 解决:调用setTabsClosable(True),且确保Qt版本支持(PyQt5 5.6+均支持); 问题3:添加图标后标签页不显示图标 → 解决:检查图标路径是否正确(绝对路径/相对路径),图标格式是否支持(png/jpg/ico); 问题4:标签页切换无响应 → 解决:currentChanged信号绑定的槽函数需检查参数(索引)是否正确,避免索引越界。 总结 QGroupBox:核心用于控件分组,提升界面可读性,支持“可勾选分组”(勾选启用内部控件),是表单/设置界面的必备工具; QTabWidget:核心用于多页面切换,节省界面空间,支持标签页增删改查、图标设置、关闭按钮等,是多功能窗口的核心控件; 组合使用:实际项目中常将QTabWidget(多页面)和QGroupBox(分组)结合,比如“设置页用QGroupBox分组,多个功能页用QTabWidget切换”; 下一章我们将进入阶段二的收尾——阶段二实战项目:仿照Excel简易表格数据管理器,整合布局管理器、表格控件、容器控件的核心知识点,完成一个完整的实战项目。 如果在容器控件开发中遇到界面排版、标签页切换的问题,或者想拓展更复杂的多标签功能(如拖拽标签页、标签页右键菜单),欢迎在评论区留言讨论~ -
独角数卡开源源码 - 免费虚拟数字商品交易系统(支持多支付) 独角数卡:开源免费虚拟数字商品交易系统源码,自动化交易解决方案 给大家分享一款专为虚拟数字商品交易打造的优质源码——独角数卡开源免费系统源码!基于Laravel框架+MySQL数据库构建,集成支付、订单、用户管理等全流程功能,支持多语言与高度定制化,无需复杂开发即可搭建游戏点卡、软件激活码、会员账号等虚拟商品的自动化交易平台,开源无隐藏费用,妥妥的站长、开发者及虚拟商品创业者的高效工具! mk0mx59s.png图片 一、核心功能模块:覆盖虚拟商品交易全流程 1. 核心交易功能:自动化运营,降低人工成本 商品管理:支持虚拟数字商品的上传、编辑、删除与分类管理,可快速上架游戏点卡、激活码、会员账号等商品,灵活控制库存数量。 订单处理:全流程自动化,自动生成订单、监控支付状态,支付完成后自动触发发货通知(如卡密自动发放),无需人工干预,提升交易效率。 支付集成:内置丰富主流支付方式,覆盖国内外用户需求——支付宝(当面付、PC/手机支付)、微信支付(企业扫码、Payjs、码支付)、PayPal(默认美元)、V免签支付、全网易支付等,无需额外开发即可对接使用。 用户管理:支持用户注册、登录、个人信息管理,用户可查询订单、查看发货记录,增强用户粘性与交易信任度。 2. 扩展功能:助力业务增长与全球化 多语言支持:内置多语言包,可切换不同语言界面,便于拓展全球市场,吸引海外用户。 个性化定制:支持前端模板自定义,可根据品牌形象调整界面风格,打造专属交易平台视觉体验。 优惠活动管理:可创建满减、折扣等优惠活动,刺激用户消费,提升销售额与复购率。 数据统计分析:提供详细销售数据统计,直观展示交易状况,帮助商家制定科学营销策略。 二、核心特色:虚拟交易系统的差异化优势 开源免费无门槛:遵循MIT开源协议,所有代码开放可查看、修改,免费下载使用,无隐藏费用,降低创业与开发成本。 安全稳定性能强:依托Laravel框架的安全性与稳定性,保障系统运行流畅,有效防护数据泄露、支付风险等问题。 响应式全端适配:前端采用响应式设计,兼容PC、手机、平板等多种设备,用户在任何终端都能便捷浏览、下单,提升转化效率。 高度可定制扩展:基于Laravel框架的模块化结构,开发者可轻松二次开发,新增功能模块或适配特殊业务场景,灵活性拉满。 支付接口全覆盖:已集成多种主流支付方式,无需单独对接,上线即可支持多渠道收款,适配不同用户支付习惯。 本站推荐使用晓翼易支付,T1结算,结算流畅,卡钱死老冯,费率低至3%就可以对接官方支付,不是易封号掉线的码支付!我就用码支付封了2个微信号 三、环境与部署要点 1. 基础环境要求 系统环境:Linux(Windows未测试,建议优先选择)。 核心组件:PHP 7.4(需安装fileinfo、redis扩展,支持php-cli)、Nginx ≥1.16、MySQL ≥5.6、Redis(缓存服务)、Supervisor(进程管理)、Composer(PHP包管理器)。 必开函数:putenv、proc_open、pcntl_signal、pcntl_alarm(建议安装opcache扩展优化性能)。 2. 简易部署指引 后台访问:通过/admin路径登录,默认管理员账号与密码均为admin,登录后需及时修改密码。 部署流程:配置基础环境→安装依赖→导入数据库→对接支付接口→自定义模板与商品,即可快速上线运营。 四、适用场景:谁适合用这款源码? 虚拟商品创业者:快速搭建游戏点卡、软件激活码、影视会员、学习资料等虚拟商品的交易平台,实现自动化销售,降低运营成本。 站长/开发者:作为Laravel框架实战案例,学习虚拟交易系统开发逻辑;或基于源码二次开发,定制专属功能的交易平台。 中小企业/团队:搭建内部虚拟商品分发平台,或面向客户的数字产品销售站点,无需投入大量开发资源,快速落地业务。 获取源码 dujiaoka-master.zip 下载地址:https://pan.quark.cn/s/e8711a09bf2b 提取码: -
HTML纯静态源码商城模板 - 无后端快速搭建数字产品交易平台 HTML纯静态源码商城网站模板:轻量无后端,快速搭建数字产品交易平台 给大家分享一款专为源码销售与数字产品展示打造的实用模板——HTML纯静态源码商城网站模板!基于纯HTML+CSS+JavaScript构建,无需依赖后端或数据库,3步即可完成部署,自带完整的商城交互功能与现代美观的界面,不管是搭建源码交易平台、软件商城,还是数字产品展示站点,都能高效落地,妥妥的前端开发者、个人站长及源码销售者的优质工具! mk0magr4.png图片 一、核心功能模块:覆盖商城核心交易场景 1. 完整页面体系:满足从浏览到转化全流程 首页:聚焦流量转化,展示推荐商品、热销源码、分类导航等模块,搭配会员特权入口(年度/月度/永久会员),直观呈现下载权限与优惠,快速吸引用户关注核心资源。 商品列表页:支持按“热门/新品/精选”分类展示源码,提供价格区间筛选(如¥0-100、¥100-500等)、技术框架筛选(Vue.js、React、Bootstrap等)及多维度排序(价格、浏览量、上架时间),帮助用户精准定位目标商品。 商品详情页:全面展示源码信息,包括价格(含折扣对比,如¥368→¥298)、技术栈、功能描述、截图预览及下载链接;悬浮按钮实时显示折扣信息、浏览量,搭配“收藏”“加入购物车”功能,降低用户操作门槛,提升转化效率。 2. 实用交互功能:优化用户浏览与购买体验 多维度筛选排序:用户可按价格、销量、评分筛选商品,按上架时间、浏览量排序,避免信息过载,快速找到所需源码。 会员体系适配:页面内置会员特权展示,清晰标注不同会员的下载次数(如年度会员每月100次)、技术支持等级及退款政策,助力会员转化。 可视化商品展示:采用图片+文字结合的布局,源码截图清晰直观,搭配用户评价(如开发者反馈“代码结构清晰,节省开发时间”),增强用户信任度。 图标交互增强:引入Font Awesome图标库,购物车、收藏、筛选等功能以直观图标呈现,操作逻辑清晰,提升界面专业度与交互流畅性。 二、核心特色:静态商城模板的差异化优势 1. 零后端依赖,部署极简单 无需配置数据库或后端语言,仅需将模板文件上传至GitHub Pages、Vercel、Netlify等任意静态托管平台,即可快速上线;无需担心服务器维护或数据库安全,小白也能10分钟完成部署。 2. 全端适配,体验无差异 采用响应式设计,自动适配PC、平板、手机等不同设备屏幕:PC端展示完整分类与会员体系,移动端优化导航为折叠样式、简化筛选步骤,确保用户在任何设备上都能流畅浏览与操作。 3. 高可扩展,灵活定制 代码结构清晰且注释完整,支持两种定制方向: 样式定制:可直接修改CSS文件调整主题色调、字体风格,匹配个人或品牌视觉需求; 功能扩展:能轻松集成后端接口(如支付、用户登录),或新增页面(如订单管理、帮助中心),适配从“静态展示”到“动态交易”的升级需求。 4. 场景适配精准,落地性强 专为源码销售、数字产品(如软件、插件)设计,页面模块(如技术框架筛选、源码截图展示、会员下载权限)高度贴合目标场景,无需大幅修改即可直接用于实际运营,降低开发与时间成本。 三、适用场景:谁适合用这款模板? 前端开发者:作为HTML/CSS/JS实战案例,学习静态商城布局与交互逻辑;或基于模板二次开发,快速交付源码商城类项目。 个人站长/源码销售者:搭建个人源码交易平台,展示并销售自己开发的模板、插件,无需投入后端开发成本,专注内容运营。 小型团队:快速搭建数字产品(如软件、工具插件)展示站点,用于客户演示或轻量交易,节省服务器与开发资源。 源码获取 code-SHOP.zip 下载地址:https://pan.quark.cn/s/9275153a7083 提取码: -
ThinkPHP AI网址导航系统源码 - 蓝色大气AI工具聚合平台(开源免费) ThinkPHP蓝色大气AI网址导航系统源码:高效AI资源聚合解决方案 给大家分享一款专为AI工具与网址聚合打造的实用源码——ThinkPHP蓝色大气AI网址导航系统源码!基于ThinkPHP6框架+MySQL数据库构建,主打简洁大气的蓝色界面与灵活的管理功能,专注AI相关网址的整合与展示,无需复杂开发即可搭建高效易用的AI工具导航平台,开源免费且易扩展,妥妥的AI从业者、开发者及资源聚合需求者的优质选择! mjzq6koc.png图片 一、核心功能模块:聚焦AI导航核心需求 1. 网址管理:高效整合AI资源 支持快速添加、编辑、删除网址信息,可集中管理各类AI工具、AI相关网站,分类清晰便于查找。 适配AI场景分类,如聊天AI、绘画AI、实用AI工具等,贴合AI资源聚合的核心需求,用户可快速定位目标工具。 2. 个性化定制:灵活适配使用习惯 提供自定义分类与排序功能,可根据AI工具类型、使用频率等调整展示顺序,满足个性化导航需求。 后台支持自由设置导航颜色,除默认蓝色主题外,可按需切换配色方案,打造专属视觉风格。 3. 后台管理:便捷运维无需专业技能 通过/admin地址即可登录后台,界面简洁明了,可轻松管理网站内容、网址分类、用户信息及系统设置。 操作逻辑简单,无需复杂配置,无论是个人维护还是小型团队运营,都能高效完成日常管理。 二、核心特色:AI导航源码的差异化优势 框架成熟稳定:基于ThinkPHP6框架开发,采用MVC架构,分离业务逻辑、数据与界面,代码管理与维护更高效,开发者上手门槛低。 界面体验优质:以蓝色为主色调,设计简洁大气、清新舒适,布局合理且功能分区明确,用户能快速找到所需AI工具,提升使用效率。 数据库配置便捷:通过.env文件即可配置数据库连接信息,支持MySQL5.6及以上版本,数据存储稳定安全,管理更省心。 安装部署简单:明确测试环境要求(PHP8.0+MySQL5.6),只需设置运行目录、伪静态规则,配置数据库即可启动,新手也能快速完成部署(默认后台账号admin,密码123456)。 三、适用场景:谁适合用这款源码? AI资源聚合者:搭建专属AI工具导航平台,整合聊天AI、绘画AI、实用AI工具等资源,方便自己或他人查找使用。 开发者:作为ThinkPHP6框架实战案例,学习MVC架构应用与导航系统开发;或基于源码二次开发,拓展更多AI相关功能(如工具评分、用户收藏等)。 企业/团队:搭建内部AI工具导航,统一管理工作中常用的AI资源,提升团队协作效率。 个人用户:打造个性化AI工具导航页,集中管理自己常用的AI网站,告别网址杂乱查找不便的问题。 源码下载 全新UI的AI网址导航系统源码 基于Thinkphp6框架.zip 下载地址:https://pan.quark.cn/s/9f5fabe6db0c 提取码: -
X浏览器安卓版 - 无广告轻量浏览器 1.4MB极速省流 X浏览器:安卓无广告轻量浏览器,小巧强大还安全 给大家推荐一款安卓端宝藏浏览器——X浏览器无广告免费版!主打“轻量极速+安全纯净+高度定制”,1.4MB超小体积不占内存,无推送、无后台进程,还支持插件扩展与个性化设置,不管是日常搜索、资源获取,还是追求纯净浏览体验,都能轻松满足! mjzpzmxs.png图片 一、核心功能:小巧体积藏全功能 1. 基础浏览与搜索 内置搜索栏,支持各类内容、资源快速搜索,满足日常信息获取需求。 搭载强大极速内核,加载速度媲美主流浏览器,网页打开流畅无卡顿。 2. 个性化定制 支持主题快速切换,选择喜欢的主题点击应用即可完成设置,操作简单。 提供快捷手势、外观设定等多种个性化选项,可根据使用习惯自定义调整,适配不同用户偏好。 3. 插件与扩展能力 支持集成第三方下载器、播放器,拓展基础功能场景。 内建支持油猴脚本,可按需安装脚本增强浏览器能力,满足进阶使用需求。 4. 广告与隐私防护 全新广告标识方式,避免与复制功能冲突,使用更顺畅。 只申请极少用户权限,无新闻推送、无后台进程,保护隐私同时节能省电。 二、核心特色:轻量浏览器的差异化优势 极致轻量化:1.4MB超小体积,极简交互设计,占用极少手机资源,运行无负担。 极速省流:加载速度快如闪电,无冗余功能消耗流量,兼顾速度与省流需求。 纯净无扰:无新闻推送、无广告弹窗、无后台常驻,界面干净整洁,专注浏览本身。 安全可靠:注重隐私保护,权限申请少,绿色安全,使用更放心。 三、适用人群与场景 追求纯净体验的用户:厌烦浏览器推送、广告弹窗,想要简洁浏览环境的人群。 手机配置较低的用户:超小体积不占用过多内存,老旧手机也能流畅运行。 喜欢个性化的用户:需要自定义手势、主题,打造专属浏览器的人群。 进阶需求用户:依赖油猴脚本、第三方工具集成,需要拓展浏览器能力的用户。 下载app X浏览器 -4.2.2免费版.apk 下载地址:https://pan.quark.cn/s/8bef86cfe161 提取码: