From 07b20a2f210a46e6ba9113e18a43222810cd680c Mon Sep 17 00:00:00 2001 From: shuaikangzhou <863909694@qq.com> Date: Wed, 15 Nov 2023 21:57:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=81=94=E7=B3=BB=E4=BA=BA?= =?UTF-8?q?=E5=A4=B4=E5=83=8F=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 144 +++++++------- app/DataBase/micro_msg.py | 20 +- app/components/CAvatar.py | 283 ++++++++++++++++++++++++++++ app/components/__init__.py | 1 + app/components/contact_info_ui.py | 57 ++++++ app/person.py | 13 ++ app/ui_pc/contact/__init__.py | 1 + app/ui_pc/contact/contactUi.py | 53 ++++++ app/ui_pc/contact/contactUi.ui | 84 +++++++++ app/ui_pc/contact/contact_window.py | 99 ++++++++++ app/ui_pc/mainview.py | 12 +- 11 files changed, 677 insertions(+), 90 deletions(-) create mode 100644 app/components/CAvatar.py create mode 100644 app/components/contact_info_ui.py create mode 100644 app/ui_pc/contact/__init__.py create mode 100644 app/ui_pc/contact/contactUi.py create mode 100644 app/ui_pc/contact/contactUi.ui create mode 100644 app/ui_pc/contact/contact_window.py diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 46102d6..c810fe6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,25 +4,17 @@ - @@ -671,7 +662,8 @@ - diff --git a/app/DataBase/micro_msg.py b/app/DataBase/micro_msg.py index 972a258..9a99a2c 100644 --- a/app/DataBase/micro_msg.py +++ b/app/DataBase/micro_msg.py @@ -1,9 +1,17 @@ +import os.path import sqlite3 -from pprint import pprint -DB = sqlite3.connect("./de_MicroMsg.db", check_same_thread=False) -# '''创建游标''' -cursor = DB.cursor() +DB = None +cursor = None +micromsg_path = "./app/Database/Msg/MicroMsg.db" +if os.path.exists(micromsg_path): + DB = sqlite3.connect(micromsg_path, check_same_thread=False) + # '''创建游标''' + cursor = DB.cursor() + + +def is_database_exist(): + return os.path.exists(micromsg_path) def get_contact(): @@ -14,8 +22,8 @@ def get_contact(): ''' cursor.execute(sql) result = cursor.fetchall() - pprint(result) - print(len(result)) + # pprint(result) + # print(len(result)) return result diff --git a/app/components/CAvatar.py b/app/components/CAvatar.py new file mode 100644 index 0000000..09dbb09 --- /dev/null +++ b/app/components/CAvatar.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Created on 2019年7月26日 +@author: Irony +@site: https://pyqt5.com https://github.com/892768447 +@email: 892768447@qq.com +@file: CustomWidgets.CAvatar +@description: 头像 +""" +import os + +from PyQt5.QtCore import QUrl, QRectF, Qt, QSize, QTimer, QPropertyAnimation, \ + QPointF, pyqtProperty +from PyQt5.QtGui import QPixmap, QColor, QPainter, QPainterPath, QMovie +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkDiskCache, \ + QNetworkRequest +from PyQt5.QtWidgets import QWidget, qApp + +__Author__ = 'Irony' +__Copyright__ = 'Copyright (c) 2019 Irony' +__Version__ = 1.0 + + +class CAvatar(QWidget): + Circle = 0 # 圆圈 + Rectangle = 1 # 圆角矩形 + SizeLarge = QSize(128, 128) + SizeMedium = QSize(64, 64) + SizeSmall = QSize(32, 32) + StartAngle = 0 # 起始旋转角度 + EndAngle = 360 # 结束旋转角度 + + def __init__(self, *args, shape=0, url='', cacheDir=False, size=QSize(64, 64), animation=False, **kwargs): + super(CAvatar, self).__init__(*args, **kwargs) + self.url = '' + self._angle = 0 # 角度 + self.pradius = 0 # 加载进度条半径 + self.animation = animation # 是否使用动画 + self._movie = None # 动态图 + self._pixmap = QPixmap() # 图片对象 + self.pixmap = QPixmap() # 被绘制的对象 + self.isGif = url.endswith('.gif') + # 进度动画定时器 + self.loadingTimer = QTimer(self, timeout=self.onLoading) + # 旋转动画 + self.rotateAnimation = QPropertyAnimation( + self, b'angle', self, loopCount=1) + self.setShape(shape) + self.setCacheDir(cacheDir) + self.setSize(size) + self.setUrl(url) + + def paintEvent(self, event): + super(CAvatar, self).paintEvent(event) + # 画笔 + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.setRenderHint(QPainter.HighQualityAntialiasing, True) + painter.setRenderHint(QPainter.SmoothPixmapTransform, True) + # 绘制 + path = QPainterPath() + diameter = min(self.width(), self.height()) + if self.shape == self.Circle: + radius = int(diameter / 2) + elif self.shape == self.Rectangle: + radius = 4 + halfW = self.width() / 2 + halfH = self.height() / 2 + painter.translate(halfW, halfH) + path.addRoundedRect( + QRectF(-halfW, -halfH, diameter, diameter), radius, radius) + painter.setClipPath(path) + # 如果是动画效果 + if self.rotateAnimation.state() == QPropertyAnimation.Running: + painter.rotate(self._angle) # 旋转 + painter.drawPixmap( + QPointF(-self.pixmap.width() / 2, -self.pixmap.height() // 2), self.pixmap) + else: + painter.drawPixmap(-int(halfW), -int(halfH), self.pixmap) + # 如果在加载 + if self.loadingTimer.isActive(): + diameter = 2 * self.pradius + painter.setBrush( + QColor(45, 140, 240, int((1 - self.pradius / 10) * 255))) + painter.setPen(Qt.NoPen) + painter.drawRoundedRect( + QRectF(-self.pradius, -self.pradius, diameter, diameter), self.pradius, self.pradius) + + def enterEvent(self, event): + """鼠标进入动画 + :param event: + """ + if not (self.animation and not self.isGif): + return + self.rotateAnimation.stop() + cv = self.rotateAnimation.currentValue() or self.StartAngle + self.rotateAnimation.setDuration( + 540 if cv == 0 else int(cv / self.EndAngle * 540)) + self.rotateAnimation.setStartValue(cv) + self.rotateAnimation.setEndValue(self.EndAngle) + self.rotateAnimation.start() + + def leaveEvent(self, event): + """鼠标离开动画 + :param event: + """ + if not (self.animation and not self.isGif): + return + self.rotateAnimation.stop() + cv = self.rotateAnimation.currentValue() or self.EndAngle + self.rotateAnimation.setDuration(int(cv / self.EndAngle * 540)) + self.rotateAnimation.setStartValue(cv) + self.rotateAnimation.setEndValue(self.StartAngle) + self.rotateAnimation.start() + + def onLoading(self): + """更新进度动画 + """ + if self.loadingTimer.isActive(): + if self.pradius > 9: + self.pradius = 0 + self.pradius += 1 + else: + self.pradius = 0 + self.update() + + def onFinished(self): + """图片下载完成 + """ + self.loadingTimer.stop() + self.pradius = 0 + reply = self.sender() + + if self.isGif: + self._movie = QMovie(reply, b'gif', self) + if self._movie.isValid(): + self._movie.frameChanged.connect(self._resizeGifPixmap) + self._movie.start() + else: + data = reply.readAll().data() + reply.deleteLater() + del reply + self._pixmap.loadFromData(data) + if self._pixmap.isNull(): + self._pixmap = QPixmap(self.size()) + self._pixmap.fill(QColor(204, 204, 204)) + self._resizePixmap() + + def onError(self, code): + """下载出错了 + :param code: + """ + self._pixmap = QPixmap(self.size()) + self._pixmap.fill(QColor(204, 204, 204)) + self._resizePixmap() + + def refresh(self): + """强制刷新 + """ + self._get(self.url) + + def isLoading(self): + """判断是否正在加载 + """ + return self.loadingTimer.isActive() + + def setShape(self, shape): + """设置形状 + :param shape: 0=圆形, 1=圆角矩形 + """ + self.shape = shape + + def setUrl(self, url): + """设置url,可以是本地路径,也可以是网络地址 + :param url: + """ + self.url = url + self._get(url) + + def setCacheDir(self, cacheDir=''): + """设置本地缓存路径 + :param cacheDir: + """ + self.cacheDir = cacheDir + self._initNetWork() + + def setSize(self, size): + """设置固定尺寸 + :param size: + """ + if not isinstance(size, QSize): + size = self.SizeMedium + self.setMinimumSize(size) + self.setMaximumSize(size) + self._resizePixmap() + + @pyqtProperty(int) + def angle(self): + return self._angle + + @angle.setter + def angle(self, value): + self._angle = value + self.update() + + def _resizePixmap(self): + """缩放图片 + """ + if not self._pixmap.isNull(): + self.pixmap = self._pixmap.scaled( + self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) + self.update() + + def _resizeGifPixmap(self, _): + """缩放动画图片 + """ + if self._movie: + self.pixmap = self._movie.currentPixmap().scaled( + self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) + self.update() + + def _initNetWork(self): + """初始化异步网络库 + """ + if not hasattr(qApp, '_network'): + network = QNetworkAccessManager(self.window()) + setattr(qApp, '_network', network) + # 是否需要设置缓存 + if self.cacheDir and not qApp._network.cache(): + cache = QNetworkDiskCache(self.window()) + cache.setCacheDirectory(self.cacheDir) + qApp._network.setCache(cache) + + def _get(self, url): + """设置图片或者请求网络图片 + :param url: + """ + if not url: + self.onError('') + return + if url.startswith('http') and not self.loadingTimer.isActive(): + url = QUrl(url) + request = QNetworkRequest(url) + # request.setHeader(QNetworkRequest.UserAgentHeader, b'CAvatar') + # request.setRawHeader(b'Author', b'Irony') + request.setAttribute( + QNetworkRequest.FollowRedirectsAttribute, True) + if qApp._network.cache(): + request.setAttribute( + QNetworkRequest.CacheLoadControlAttribute, QNetworkRequest.PreferNetwork) + request.setAttribute( + QNetworkRequest.CacheSaveControlAttribute, True) + reply = qApp._network.get(request) + self.pradius = 0 + self.loadingTimer.start(50) # 显示进度动画 + reply.finished.connect(self.onFinished) + reply.error.connect(self.onError) + return + self.pradius = 0 + if os.path.exists(url) and os.path.isfile(url): + if self.isGif: + self._movie = QMovie(url, parent=self) + if self._movie.isValid(): + self._movie.frameChanged.connect(self._resizeGifPixmap) + self._movie.start() + else: + self._pixmap = QPixmap(url) + self._resizePixmap() + else: + self.onError('') + + +if __name__ == '__main__': + import sys + from PyQt5.QtWidgets import QApplication + + app = QApplication(sys.argv) + w = CAvatar( + url='https://wx.qlogo.cn/mmhead/ver_1/DpDqmvTDORNWfLrMj26YicorEUREffl1G8FapawdKgINVH9g1icudfWesGrH9LqeGAz16z4PmkW9U1KAIM3btWgozZ1GaLF66bdKdxlMdazmibn2hpFeiaa4613dN6HM4Vfk/132') + w.show() + sys.exit(app.exec_()) diff --git a/app/components/__init__.py b/app/components/__init__.py index e69de29..00aed52 100644 --- a/app/components/__init__.py +++ b/app/components/__init__.py @@ -0,0 +1 @@ +from .contact_info_ui import ContactQListWidgetItem diff --git a/app/components/contact_info_ui.py b/app/components/contact_info_ui.py new file mode 100644 index 0000000..d1a0411 --- /dev/null +++ b/app/components/contact_info_ui.py @@ -0,0 +1,57 @@ +import sys + +from PyQt5.Qt import * +from PyQt5.QtCore import * +from PyQt5.QtWidgets import * + +from .CAvatar import CAvatar + + +# 自定义的item 继承自QListWidgetItem +class ContactQListWidgetItem(QListWidgetItem): + def __init__(self, name, url): + super().__init__() + # 自定义item中的widget 用来显示自定义的内容 + self.widget = QWidget() + # 用来显示name + self.nameLabel = QLabel() + self.nameLabel.setText(name) + # 用来显示avator(图像) + self.avatorLabel = CAvatar(None, shape=CAvatar.Rectangle, size=QSize(60, 60), + url=url) + # 设置布局用来对nameLabel和avatorLabel进行布局 + self.hbox = QHBoxLayout() + self.hbox.addWidget(self.avatorLabel) + self.hbox.addWidget(self.nameLabel) + self.hbox.addStretch(1) + # 设置widget的布局 + self.widget.setLayout(self.hbox) + # 设置自定义的QListWidgetItem的sizeHint,不然无法显示 + self.setSizeHint(self.widget.sizeHint()) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + + # 主窗口 + w = QWidget() + w.setWindowTitle("QListWindow") + # 新建QListWidget + listWidget = QListWidget(w) + listWidget.resize(300, 300) + + # 新建两个自定义的QListWidgetItem(customQListWidgetItem) + item1 = ContactQListWidgetItem("鲤鱼王", "liyuwang.jpg") + item2 = ContactQListWidgetItem("可达鸭", "kedaya.jpg") + + # 在listWidget中加入两个自定义的item + listWidget.addItem(item1) + listWidget.setItemWidget(item1, item1.widget) + listWidget.addItem(item2) + listWidget.setItemWidget(item2, item2.widget) + + # 绑定点击槽函数 点击显示对应item中的name + listWidget.itemClicked.connect(lambda item: print(item.nameLabel.text())) + + w.show() + sys.exit(app.exec_()) diff --git a/app/person.py b/app/person.py index 68f8e5c..07f0d91 100644 --- a/app/person.py +++ b/app/person.py @@ -1,4 +1,5 @@ import os.path +from typing import Dict from PyQt5.QtGui import QPixmap @@ -32,6 +33,18 @@ class Me(Person): class Contact(Person): def __init__(self, wxid: str): super(Contact, self).__init__(wxid) + self.smallHeadImgUrl = '' + self.bigHeadImgUrl = '' + + +class ContactPC: + def __init__(self, contact_info: Dict): + self.wxid = contact_info.get('UserName') + self.remark = contact_info.get('Remark') + # Alias,Type,Remark,NickName,PYInitial,RemarkPYInitial,ContactHeadImgUrl.smallHeadImgUrl,ContactHeadImgUrl,bigHeadImgUrl + self.alias = contact_info.get('Alias') + self.nickName = contact_info.get('NickName') + self.smallHeadImgUrl = contact_info.get('smallHeadImgUrl') class Group(Person): diff --git a/app/ui_pc/contact/__init__.py b/app/ui_pc/contact/__init__.py new file mode 100644 index 0000000..a83ae0a --- /dev/null +++ b/app/ui_pc/contact/__init__.py @@ -0,0 +1 @@ +from .contact_window import ContactWindow diff --git a/app/ui_pc/contact/contactUi.py b/app/ui_pc/contact/contactUi.py new file mode 100644 index 0000000..2fb6c1d --- /dev/null +++ b/app/ui_pc/contact/contactUi.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'contactUi.ui' +# +# Created by: PyQt5 UI code generator 5.15.7 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(840, 752) + Form.setStyleSheet("background: rgb(240, 240, 240);") + self.horizontalLayout = QtWidgets.QHBoxLayout(Form) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.lineEdit = QtWidgets.QLineEdit(Form) + self.lineEdit.setMinimumSize(QtCore.QSize(250, 30)) + self.lineEdit.setMaximumSize(QtCore.QSize(250, 16777215)) + self.lineEdit.setObjectName("lineEdit") + self.verticalLayout.addWidget(self.lineEdit) + self.listWidget = QtWidgets.QListWidget(Form) + self.listWidget.setMinimumSize(QtCore.QSize(250, 0)) + self.listWidget.setMaximumSize(QtCore.QSize(250, 16777215)) + self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.listWidget.setObjectName("listWidget") + self.verticalLayout.addWidget(self.listWidget) + self.horizontalLayout.addLayout(self.verticalLayout) + self.stackedWidget = QtWidgets.QStackedWidget(Form) + self.stackedWidget.setObjectName("stackedWidget") + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.stackedWidget.addWidget(self.page) + self.page_2 = QtWidgets.QWidget() + self.page_2.setObjectName("page_2") + self.stackedWidget.addWidget(self.page_2) + self.horizontalLayout.addWidget(self.stackedWidget) + self.horizontalLayout.setStretch(1, 1) + + self.retranslateUi(Form) + self.stackedWidget.setCurrentIndex(1) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Form")) diff --git a/app/ui_pc/contact/contactUi.ui b/app/ui_pc/contact/contactUi.ui new file mode 100644 index 0000000..4a432b1 --- /dev/null +++ b/app/ui_pc/contact/contactUi.ui @@ -0,0 +1,84 @@ + + + Form + + + + 0 + 0 + 840 + 752 + + + + Form + + + background: rgb(240, 240, 240); + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 250 + 30 + + + + + 250 + 16777215 + + + + + + + + + 250 + 0 + + + + + 250 + 16777215 + + + + Qt::ScrollBarAlwaysOff + + + + + + + + + 1 + + + + + + + + + + diff --git a/app/ui_pc/contact/contact_window.py b/app/ui_pc/contact/contact_window.py new file mode 100644 index 0000000..00050cd --- /dev/null +++ b/app/ui_pc/contact/contact_window.py @@ -0,0 +1,99 @@ +from random import randint + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QWidget, QListWidgetItem, QLabel, QMessageBox + +from app.DataBase import micro_msg +from app.components import ContactQListWidgetItem +from app.person import ContactPC +from .contactUi import Ui_Form +from ...Ui.Icon import Icon + +# 美化样式表 +Stylesheet = """ + +/*去掉item虚线边框*/ +QListWidget, QListView, QTreeWidget, QTreeView { + outline: 0px; + border:none; + background-color:rgb(240,240,240) +} +/*设置左侧选项的最小最大宽度,文字颜色和背景颜色*/ +QListWidget { + min-width: 250px; + max-width: 250px; + min-height: 80px; + max-height: 1200px; + color: black; + border:none; +} +QListWidget::item{ + height:60px; + width:250px; +} +/*被选中时的背景颜色和左边框颜色*/ +QListWidget::item:selected { + background: rgb(204, 204, 204); + border-bottom: 2px solid rgb(9, 187, 7); + border-left:none; + color: black; + font-weight: bold; +} +/*鼠标悬停颜色*/ +HistoryPanel::item:hover { + background: rgb(52, 52, 52); +} +""" + + +class ContactWindow(QWidget, Ui_Form): + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(self) + self.setStyleSheet(Stylesheet) + self.init_ui() + self.show_contacts() + + def init_ui(self): + self.listWidget.clear() + self.listWidget.currentRowChanged.connect(self.setCurrentIndex) + chat_item = QListWidgetItem(Icon.Chat_Icon, '解密', self.listWidget) + contact_item = QListWidgetItem(Icon.Contact_Icon, 'None', self.listWidget) + myinfo_item = QListWidgetItem(Icon.MyInfo_Icon, 'None', self.listWidget) + tool_item = QListWidgetItem(Icon.MyInfo_Icon, 'None', self.listWidget) + label = QLabel('我是页面', self) + label.setAlignment(Qt.AlignCenter) + # 设置label的背景颜色(这里随机) + # 这里加了一个margin边距(方便区分QStackedWidget和QLabel的颜色) + label.setStyleSheet('background: rgb(%d, %d, %d);margin: 50px;' % ( + randint(0, 255), randint(0, 255), randint(0, 255))) + self.stackedWidget.addWidget(label) + self.stackedWidget.addWidget(label) + self.stackedWidget.addWidget(label) + self.listWidget.setCurrentRow(0) + self.stackedWidget.setCurrentIndex(0) + + def show_contacts(self): + if not micro_msg.is_database_exist(): + QMessageBox.critical(self, "错误", "数据库不存在\n请先解密数据库") + return + contact_info_lists = micro_msg.get_contact() + for contact_info_list in contact_info_lists: + # UserName, Alias,Type,Remark,NickName,PYInitial,RemarkPYInitial,ContactHeadImgUrl.smallHeadImgUrl,ContactHeadImgUrl,bigHeadImgUrl + contact_info = { + 'UserName': contact_info_list[0], + 'Alias': contact_info_list[1], + 'Type': contact_info_list[2], + 'Remark': contact_info_list[3], + 'NickName': contact_info_list[4], + 'smallHeadImgUrl': contact_info_list[7] + } + contact = ContactPC(contact_info) + # pprint(contact.__dict__) + contact_item = ContactQListWidgetItem(contact.nickName, contact.smallHeadImgUrl) + self.listWidget.addItem(contact_item) + self.listWidget.setItemWidget(contact_item, contact_item.widget) + + def setCurrentIndex(self, row): + print(row) + self.stackedWidget.setCurrentIndex(row) diff --git a/app/ui_pc/mainview.py b/app/ui_pc/mainview.py index 010c90e..5d73691 100644 --- a/app/ui_pc/mainview.py +++ b/app/ui_pc/mainview.py @@ -15,6 +15,7 @@ from PyQt5.QtWidgets import * from app import config from app.Ui.Icon import Icon from . import mainwindow +from .contact import ContactWindow from .tool import ToolWindow # 美化样式表 @@ -75,22 +76,17 @@ class MainWinController(QMainWindow, mainwindow.Ui_MainWindow): contact_item = QListWidgetItem(Icon.Contact_Icon, '好友', self.listWidget) myinfo_item = QListWidgetItem(Icon.MyInfo_Icon, '我的', self.listWidget) tool_item = QListWidgetItem(Icon.MyInfo_Icon, '工具', self.listWidget) + tool_window = ToolWindow() label = QLabel('我是页面', self) label.setAlignment(Qt.AlignCenter) # 设置label的背景颜色(这里随机) # 这里加了一个margin边距(方便区分QStackedWidget和QLabel的颜色) - label.setStyleSheet('background: rgb(%d, %d, %d);margin: 50px;' % ( - randint(0, 255), randint(0, 255), randint(0, 255))) - - self.stackedWidget.addWidget(label) - label = QLabel('我是页面', self) - label.setAlignment(Qt.AlignCenter) - # 设置label的背景颜色(这里随机) - # 这里加了一个margin边距(方便区分QStackedWidget和QLabel的颜色) label.setStyleSheet('background: rgb(%d, %d, %d);margin: 50px;' % ( randint(0, 255), randint(0, 255), randint(0, 255))) self.stackedWidget.addWidget(label) + contact_window = ContactWindow() + self.stackedWidget.addWidget(contact_window) label = QLabel('我是页面', self) label.setAlignment(Qt.AlignCenter) # 设置label的背景颜色(这里随机)