用Python/PyQt5开发一个自动任务消息弹窗
1.需求分析
- 作为从不闲着的开发人员,无时无刻不在思考怎样做用户的贴心小管家,不能让用户漏干一丁点活,不能让老板多花一分钱,多花的每一分钱都应该花在我身上,最近又被大领导PUA,说实现xxx化就给加工资,3到5K不是问题,一激动 又脑子好使了,瞬间就想到了自动化办公,事后清醒了才想起来忘记问是3K-5K还是3-5K,不过没关系,我觉悟高,毕竟我喜欢干活,要不然怎么会有干不完的活
- 活有了,该想想怎么干,需求的核心是实现自动化办公,本公司工程部的所有任务分配都是在工程管理系统上完成,分配后由专门的负责人通知到用户,这个通知得靠人去完成及追踪,这个部分很不符合我们的自动化宗旨,我们就从这部分需求下手,用户任务自动下发通知,用户实时掌控工作任务状况
2.实现逻辑
需求有了,我们看看具体的实施逻辑
- 首先,所有的订单制作分配任务是已经有了的,我们只需要获取当日的所有工作任务,此部分由管理系统提供接口以便我们获取任务数据,此部分演示我们用模拟数据
- 其次,我们解析并整理任务数据以便我们精准的推送到相关制作人员
- 再次,我们以消息弹窗的方式精准推送用户任务,用户在哪里登录我们就在哪里弹窗
- 最后,用户接收到任务后确认认领,已认领的任务将不再推送,不认领5分钟自动推送一次
- 终结,我们的弹窗怎样运行,直接使用自动任务计划(每五分钟自动运行,弹窗少于5分不操作自动退出)
效果演示
3.代码实现
- 获取用户登录信息,不知道用户在哪里怎么弹窗
封装好的class
调用class获取想要的用户数据class GetUserLocationInformation: """ 获取用户的登录信息(InCAMPro系统) """ def __init__(self): self.UserLocationList = [] self.UserHostList = [] self.UserInfoDict = {} self.DOMTree = xml.dom.minidom.parse("/incam/server/config/users.xml") self.GetUserLocationInfo() def GetUserLocationInfo(self): Users = self.DOMTree.documentElement.getElementsByTagName("user") for u in Users: user_name = u.getAttribute("name") d_type = u.getElementsByTagName('properties')[0] self.UserInfoDict[user_name + "fullName"] = d_type.getAttribute("fullName") GateWay = '/incam/release/bin/gateway' tmp_list = os.popen("{0} \"WHO *\"".format(GateWay)).read().split() for I in tmp_list: ChinaUserName = self.UserInfoDict[I.split("@")[0] + "fullName"] AtHostName = (I.split(".")[0].split("@")[1]).lower() self.UserLocationList.append(ChinaUserName) self.UserHostList.append(AtHostName)
# 用户登录信息获取 us = GetUserLocationInformation() ChainUserList = us.UserLocationList # 获取当前执行主机所有登录InCAMPro的用户 UserHostsList = us.UserHostList # 获取本机需要通知的 # 用户没有在本机登录直接退出不提醒 if HostName not in UserHostsList: sys.exit() MessageUser = [] K = 0 while K < len(UserHostsList): if UserHostsList[K] == HostName: MessageUser.append(ChainUserList[K]) K += 1 # 本机没有要通知的用户直接退出不提醒 if len(MessageUser) == 0: sys.exit()
- 获取工程管理系统任务列表,通过接口查询获取数据,此部分我们用模拟的数据(直接读取数据库模拟数据)
数据库数据示例:
用户任务清单示例:
获取用户清单并整理成我们需要的字典:
返回字典如下:# 获取任务列表数据(模拟数据) _sql = """ SELECT NoticeUser AS 通知人员, NoticeInfo AS 通知信息, ReplyUrl AS 处理链接, NoticeType AS 消息类型, CreateDate AS 创建时间 FROM ask_cam_sys_notice WHERE NoticeState=0 """ SqlDataObject = Sql.exec_query(_sql) # 没有待处理任务时直接退出不提醒 if len(SqlDataObject) == 0: sys.exit() UserInfoDict = {} j = 0 for i in SqlDataObject: if i[0] is not None: UserInfoDict[i[0] + "-" + str(j)] = {} UserInfoDict[i[0] + "-" + str(j)]["详细事件"] = i[1].upper() UserInfoDict[i[0] + "-" + str(j)]["操作链接"] = i[2] UserInfoDict[i[0] + "-" + str(j)]["消息类型"] = i[3] UserInfoDict[i[0] + "-" + str(j)]["创建时间"] = i[4] j += 1
{ "NoticeUser": "唐伟", "NoticeInfo": "你有新的料号123待制作", "ReplyUrl": "", "NoticeType": "知会", "CreateTime": "2024-12-04T17:30:30" }, { "NoticeUser": "唐伟", "NoticeInfo": "查看任意1链接", "ReplyUrl": "www.hao123.com", "NoticeType": "", "CreateTime": "2024-12-04T17:31:09" }, { "NoticeUser": "唐伟", "NoticeInfo": "查看任意2链接", "ReplyUrl": "www.baidu.com", "NoticeType": "", "CreateTime": "2024-12-04T18:05:41" }
- 编写弹窗主程式及处理逻辑
调用弹窗class并设置4分钟后自动关系(自动关闭时间需比自动任务间隔时间短)class Ui_MainWindow3(QtWidgets.QWidget): def __init__(self): self.screen_resolution = app.desktop().screenGeometry() self.width, self.height = self.screen_resolution.width(), self.screen_resolution.height() QtWidgets.QWidget.__init__(self) self.ButtonDict = {} self.MessageUser = [] self.ImagePath = Gui.images_path self._ui() self.retranslateUi() self.beautifyUi() def _ui(self): self.setWindowIcon(QIcon(self.ImagePath + "/NccIcon.png")) self.setFixedSize(700, 500) self.setGeometry(self.width - 700, self.height - 392, 700, 500) self.setWindowOpacity(1.0) self.setWindowFlags(QtCore.Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # 无边框&置顶 self.pe = QPalette() self.setAutoFillBackground(True) self.pe.setColor(QPalette.Background, Qt.darkGreen) self.setPalette(self.pe) # 设置一个计时器 self.count = 239 self.timer = QTimer() # 初始化一个定时器 self.timer.setInterval(1000) self.timer.start() self.timer.timeout.connect(self.operate) # 计时结束调用operate()方法 # 关闭按钮 self.pushbutton_close = QtWidgets.QPushButton() self.pushbutton_close.setFixedSize(30, 30) self.pushbutton_close.setObjectName("pushButton") self.pushbutton_close.clicked.connect(self.close) # 关闭窗口 # 显示窗口关闭倒计时 self.lcd = QtWidgets.QLabel() self.lcd.setFixedSize(351, 30) # 显示提示标题 self.label = QtWidgets.QLabel() self.label.setObjectName("label") self.label.setAlignment(Qt.AlignCenter) # 显示通知信息 self.text_label = QtWidgets.QLabel() self.text_label.setFixedSize(679, 91) self.text_label.setObjectName("text_label") self.text_label.setAlignment(Qt.AlignCenter) # 滚动条 self.topFiller = QtWidgets.QWidget() self.topFiller.setMinimumSize(669, 90) # 详细操作链接按钮 self.ThingButtonBox = QtWidgets.QVBoxLayout() filename = 0 for k, v in UserInfoDict.items(): # 被通知用户没有登录InCAMPro不提醒 if k.split("-")[0] in MessageUser: tmp_ty = UserInfoDict[k]["详细事件"].split("】")[0].replace("【", "") self.MessageUser.append(k.split("-")[0]) self.ButtonDict[k] = QtWidgets.QCommandLinkButton(self.topFiller) self.ButtonDict[k].setStyleSheet('QCommandLinkButton{' 'border:none;' 'font-size:14px;' 'color:#FF7F2A; ' '} ' ) # self.ButtonDict[k].setFixedSize(480, 71) self.ButtonDict[k].setToolTip("单击打开链接{0}".format(UserInfoDict[k]["操作链接"])) self.ButtonDict[k].setText(f'{UserInfoDict[k]["详细事件"]} ' f'时间:{UserInfoDict[k]["创建时间"]}-->{k.split("-")[0]}') self.ButtonDict[k].clicked.connect(lambda unknown_bool=False, url=UserInfoDict[k]["操作链接"], ty=tmp_ty, tmp_type=UserInfoDict[k]["消息类型"], but=self.ButtonDict[k]: self.pushButtonMethods(unknown_bool, url, ty, tmp_type, but)) self.ButtonDict[k].move(10, filename * 20) filename += 1 # self.ThingButtonBox.addWidget(self.ButtonDict[k], 0, Qt.AlignTop) self.scroll = QtWidgets.QScrollArea() self.scroll.setWidget(self.topFiller) # # 被通知用户不在待通知用户中跳过 # if len(self.MessageUser) == 0: # sys.exit() # 显示空行 self.row_label = QtWidgets.QLabel() self.row_label.setObjectName(" ") self.row_label.setAlignment(Qt.AlignCenter) # 容器 self.MainBox = QtWidgets.QVBoxLayout() self.CloseButtonBox = QtWidgets.QHBoxLayout() self.CloseButtonBox.addWidget(self.pushbutton_close) self.CloseButtonBox.addWidget(self.lcd) self.CloseButtonBox.setAlignment(QtCore.Qt.AlignLeft) self.setLayout(self.MainBox) self.MainBox.addLayout(self.CloseButtonBox) self.MainBox.addWidget(self.label) self.MainBox.addWidget(self.text_label) self.ThingButtonBox.addWidget(self.scroll) self.MainBox.addLayout(self.ThingButtonBox) self.MainBox.addWidget(self.row_label) def retranslateUi(self): self.setWindowTitle("消息框") self.pushbutton_close.setText("×") self.pushbutton_close.setToolTip("关闭窗口") self.label.setText("温馨提示") self.text_label.setText("{0}您好!您有未处理的任务请查收\n请点击下列详细事项链接及时处理\n" "祝您工作愉快!".format("&".join(list(set(self.MessageUser))))) def beautifyUi(self): self.lcd.setStyleSheet('QLabel{' 'color:red;' 'font-size:18px;' 'font-family:楷体;' '}') self.pushbutton_close.setStyleSheet('QPushButton{' 'background:red; ' 'font-size:30px;' 'color:black;' 'border-radius:15px;' '} ' 'QPushButton:hover{' 'background:#F76677;' '}' ) self.label.setStyleSheet('QLabel{' 'color:white;' 'font-size:40px;' 'font-family:楷体;' '}' ) self.text_label.setStyleSheet('QLabel{' 'color:darkGray;' 'background:white;' 'border:2px solid #F3F3F5;' 'border-radius:35px;' 'font-size:16pt; ' 'font-weight:400;' 'font-family: 楷体;' '}' ) @staticmethod def pushButtonMethods(unknown_bool, url, ty, tmp_type, but): if tmp_type not in ["知会"]: # 直接打开链接 QtGui.QDesktopServices.openUrl(QtCore.QUrl(url)) else: but.setStyleSheet('QCommandLinkButton{' 'border:none;' 'font-size:14px;' 'color:#C2BBB8; ' '} ' ) sql_string = """ UPDATE ask_cam_sys_notice SET NoticeState={0} WHERE ReplyUrl='{1}' """.format(1, url) Sql.exec_query(sql_string) def operate(self): if self.count >= 0: self.lcd.setText(" 距离窗口关闭还有{0}秒".format(str(self.count))) self.count -= 1 else: self.timer.stop() self.count = 59
完整代码if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) Gui = MainGui() Sql = AskCamMainServer(_mode=False) HostName = (os.environ.get("HOSTNAME")).lower() # 用户登录信息获取 us = GetUserLocationInformation() ChainUserList = us.UserLocationList UserHostsList = us.UserHostList # 用户没有在本机登录直接退出不提醒 if HostName not in UserHostsList: sys.exit() MessageUser = [] K = 0 while K < len(UserHostsList): if UserHostsList[K] == HostName: MessageUser.append(ChainUserList[K]) K += 1 # 本机没有要通知的用户直接退出不提醒 if len(MessageUser) == 0: sys.exit() # 获取任务列表数据(模拟数据) _sql = """ SELECT NoticeUser AS 通知人员, NoticeInfo AS 通知信息, ReplyUrl AS 处理链接, NoticeType AS 消息类型, CreateDate AS 创建时间 FROM ask_cam_sys_notice WHERE NoticeState=0 """ SqlDataObject = Sql.exec_query(_sql) # 没有待处理任务时直接退出不提醒 if len(SqlDataObject) == 0: sys.exit() # 用户任务清单获取 UserInfoDict = {} j = 0 for i in SqlDataObject: if i[0] is not None: UserInfoDict[i[0] + "-" + str(j)] = {} UserInfoDict[i[0] + "-" + str(j)]["详细事件"] = i[1].upper() UserInfoDict[i[0] + "-" + str(j)]["操作链接"] = i[2] UserInfoDict[i[0] + "-" + str(j)]["消息类型"] = i[3] UserInfoDict[i[0] + "-" + str(j)]["创建时间"] = i[4] j += 1 # 界面及主程式 ui = Ui_MainWindow3() ui.show() # 自动关闭程式配置 QTimer.singleShot(240000, ui.close) # 4分钟后自动关闭 sys.exit(app.exec_())
#!/usr/bin/env py3k from PyQt5 import QtCore, QtWidgets, QtGui from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QPalette, QIcon import sys import os from xml.dom.minidom import parse import xml.dom.minidom sys.path.append('/incam/server/site_data/scripts/py3_package') from SqlClasses import AskCamMainServer from GuiClasses import MainGui class Ui_MainWindow3(QtWidgets.QWidget): def __init__(self): self.screen_resolution = app.desktop().screenGeometry() self.width, self.height = self.screen_resolution.width(), self.screen_resolution.height() QtWidgets.QWidget.__init__(self) self.ButtonDict = {} self.MessageUser = [] self.ImagePath = Gui.images_path self._ui() self.retranslateUi() self.beautifyUi() def _ui(self): self.setWindowIcon(QIcon(self.ImagePath + "/NccIcon.png")) self.setFixedSize(700, 500) self.setGeometry(self.width - 700, self.height - 392, 700, 500) self.setWindowOpacity(1.0) self.setWindowFlags(QtCore.Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # 无边框&置顶 self.pe = QPalette() self.setAutoFillBackground(True) self.pe.setColor(QPalette.Background, Qt.darkGreen) self.setPalette(self.pe) # 设置一个计时器 self.count = 239 self.timer = QTimer() # 初始化一个定时器 self.timer.setInterval(1000) self.timer.start() self.timer.timeout.connect(self.operate) # 计时结束调用operate()方法 # 关闭按钮 self.pushbutton_close = QtWidgets.QPushButton() self.pushbutton_close.setFixedSize(30, 30) self.pushbutton_close.setObjectName("pushButton") self.pushbutton_close.clicked.connect(self.close) # 关闭窗口 # 显示窗口关闭倒计时 self.lcd = QtWidgets.QLabel() self.lcd.setFixedSize(351, 30) # 显示提示标题 self.label = QtWidgets.QLabel() self.label.setObjectName("label") self.label.setAlignment(Qt.AlignCenter) # 显示通知信息 self.text_label = QtWidgets.QLabel() self.text_label.setFixedSize(679, 91) self.text_label.setObjectName("text_label") self.text_label.setAlignment(Qt.AlignCenter) # 滚动条 self.topFiller = QtWidgets.QWidget() self.topFiller.setMinimumSize(669, 90) # 详细操作链接按钮 self.ThingButtonBox = QtWidgets.QVBoxLayout() filename = 0 for k, v in UserInfoDict.items(): # 被通知用户没有登录InCAMPro不提醒 if k.split("-")[0] in MessageUser: tmp_ty = UserInfoDict[k]["详细事件"].split("】")[0].replace("【", "") self.MessageUser.append(k.split("-")[0]) self.ButtonDict[k] = QtWidgets.QCommandLinkButton(self.topFiller) self.ButtonDict[k].setStyleSheet('QCommandLinkButton{' 'border:none;' 'font-size:14px;' 'color:#FF7F2A; ' '} ' ) # self.ButtonDict[k].setFixedSize(480, 71) self.ButtonDict[k].setToolTip("单击打开链接{0}".format(UserInfoDict[k]["操作链接"])) self.ButtonDict[k].setText(f'{UserInfoDict[k]["详细事件"]} ' f'时间:{UserInfoDict[k]["创建时间"]}-->{k.split("-")[0]}') self.ButtonDict[k].clicked.connect(lambda unknown_bool=False, url=UserInfoDict[k]["操作链接"], ty=tmp_ty, tmp_type=UserInfoDict[k]["消息类型"], but=self.ButtonDict[k]: self.pushButtonMethods(unknown_bool, url, ty, tmp_type, but)) self.ButtonDict[k].move(10, filename * 20) filename += 1 # self.ThingButtonBox.addWidget(self.ButtonDict[k], 0, Qt.AlignTop) self.scroll = QtWidgets.QScrollArea() self.scroll.setWidget(self.topFiller) # # 被通知用户不在待通知用户中跳过 # if len(self.MessageUser) == 0: # sys.exit() # 显示空行 self.row_label = QtWidgets.QLabel() self.row_label.setObjectName(" ") self.row_label.setAlignment(Qt.AlignCenter) # 容器 self.MainBox = QtWidgets.QVBoxLayout() self.CloseButtonBox = QtWidgets.QHBoxLayout() self.CloseButtonBox.addWidget(self.pushbutton_close) self.CloseButtonBox.addWidget(self.lcd) self.CloseButtonBox.setAlignment(QtCore.Qt.AlignLeft) self.setLayout(self.MainBox) self.MainBox.addLayout(self.CloseButtonBox) self.MainBox.addWidget(self.label) self.MainBox.addWidget(self.text_label) self.ThingButtonBox.addWidget(self.scroll) self.MainBox.addLayout(self.ThingButtonBox) self.MainBox.addWidget(self.row_label) def retranslateUi(self): self.setWindowTitle("消息框") self.pushbutton_close.setText("×") self.pushbutton_close.setToolTip("关闭窗口") self.label.setText("温馨提示") self.text_label.setText("{0}您好!您有未处理的任务请查收\n请点击下列详细事项链接及时处理\n" "祝您工作愉快!".format("&".join(list(set(self.MessageUser))))) def beautifyUi(self): self.lcd.setStyleSheet('QLabel{' 'color:red;' 'font-size:18px;' 'font-family:楷体;' '}') self.pushbutton_close.setStyleSheet('QPushButton{' 'background:red; ' 'font-size:30px;' 'color:black;' 'border-radius:15px;' '} ' 'QPushButton:hover{' 'background:#F76677;' '}' ) self.label.setStyleSheet('QLabel{' 'color:white;' 'font-size:40px;' 'font-family:楷体;' '}' ) self.text_label.setStyleSheet('QLabel{' 'color:darkGray;' 'background:white;' 'border:2px solid #F3F3F5;' 'border-radius:35px;' 'font-size:16pt; ' 'font-weight:400;' 'font-family: 楷体;' '}' ) @staticmethod def pushButtonMethods(unknown_bool, url, ty, tmp_type, but): if tmp_type not in ["知会"]: # 直接打开链接 QtGui.QDesktopServices.openUrl(QtCore.QUrl(url)) else: but.setStyleSheet('QCommandLinkButton{' 'border:none;' 'font-size:14px;' 'color:#C2BBB8; ' '} ' ) sql_string = """ UPDATE ask_cam_sys_notice SET NoticeState={0} WHERE ReplyUrl='{1}' """.format(1, url) Sql.exec_query(sql_string) def operate(self): if self.count >= 0: self.lcd.setText(" 距离窗口关闭还有{0}秒".format(str(self.count))) self.count -= 1 else: self.timer.stop() self.count = 59 class GetUserLocationInformation: """ 获取用户的登录信息(InCAMPro系统) """ def __init__(self): self.UserLocationList = [] self.UserHostList = [] self.UserInfoDict = {} self.DOMTree = xml.dom.minidom.parse("/incam/server/config/users.xml") self.GetUserLocationInfo() def GetUserLocationInfo(self): Users = self.DOMTree.documentElement.getElementsByTagName("user") for u in Users: user_name = u.getAttribute("name") d_type = u.getElementsByTagName('properties')[0] self.UserInfoDict[user_name + "fullName"] = d_type.getAttribute("fullName") GateWay = '/incam/release/bin/gateway' tmp_list = os.popen("{0} \"WHO *\"".format(GateWay)).read().split() for I in tmp_list: ChinaUserName = self.UserInfoDict[I.split("@")[0] + "fullName"] AtHostName = (I.split(".")[0].split("@")[1]).lower() self.UserLocationList.append(ChinaUserName) self.UserHostList.append(AtHostName) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) Gui = MainGui() Sql = AskCamMainServer(_mode=False) HostName = (os.environ.get("HOSTNAME")).lower() # 用户登录信息获取 us = GetUserLocationInformation() ChainUserList = us.UserLocationList UserHostsList = us.UserHostList # 用户没有在本机登录直接退出不提醒 if HostName not in UserHostsList: sys.exit() MessageUser = [] K = 0 while K < len(UserHostsList): if UserHostsList[K] == HostName: MessageUser.append(ChainUserList[K]) K += 1 # 本机没有要通知的用户直接退出不提醒 if len(MessageUser) == 0: sys.exit() # 获取任务列表数据(模拟数据) _sql = """ SELECT NoticeUser AS 通知人员, NoticeInfo AS 通知信息, ReplyUrl AS 处理链接, NoticeType AS 消息类型, CreateDate AS 创建时间 FROM ask_cam_sys_notice WHERE NoticeState=0 """ SqlDataObject = Sql.exec_query(_sql) # 没有待处理任务时直接退出不提醒 if len(SqlDataObject) == 0: sys.exit() # 用户任务清单获取 UserInfoDict = {} j = 0 for i in SqlDataObject: if i[0] is not None: UserInfoDict[i[0] + "-" + str(j)] = {} UserInfoDict[i[0] + "-" + str(j)]["详细事件"] = i[1].upper() UserInfoDict[i[0] + "-" + str(j)]["操作链接"] = i[2] UserInfoDict[i[0] + "-" + str(j)]["消息类型"] = i[3] UserInfoDict[i[0] + "-" + str(j)]["创建时间"] = i[4] j += 1 # 界面及主程式 ui = Ui_MainWindow3() ui.show() # 自动关闭程式配置 QTimer.singleShot(240000, ui.close) # 4分钟后自动关闭 sys.exit(app.exec_())
- 自动执行任务计划编写(CentOS 7为例)
编写一个SHELL脚本启动弹窗,并配置必要的环境变量,放置在弹窗程式相同路径下并命名为"tw.sh"
配置自动任务计划#!/bin/sh DISPLAY=:0 # 设置环境变量(当使用vnc时指定弹窗端口号) export DISPLAY source /etc/profile py3k /incam/server/site_data/scripts/CAM_public_system/自动消息弹窗/cam_message_box.py
- (1).进入root用户,终端下输入su回车
$ su 密码: <输入密码后回车> #
- (2).编辑任务计划
# crontab -e # 输入以下内容 # 自动消息弹窗(每5分钟自动执行) */5 * * * * /bin/sh /incam/server/site_data/scripts/CAM_public_system/自动消息弹窗/tw.sh >/日志文件保存路径/crontab.log 2>&1
- (1).进入root用户,终端下输入su回车