前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >html+css+js+python(QtWebEngineWidgets) 实现微信聊天界面-包括时间,文件,纯文本等

html+css+js+python(QtWebEngineWidgets) 实现微信聊天界面-包括时间,文件,纯文本等

作者头像
zmh-program
发布2023-02-06 10:13:16
1.4K0
发布2023-02-06 10:13:16
举报
文章被收录于专栏:信息技术博客

文章目录

展示

纯html - web网页

QWebEngineWidget + Html :

参考文章

(搜索)

  1. 聊天界面html+css+javascript -https://blog.csdn.net/lutrra/article/details/120390780
  2. html 自动包裹内容,CSS 实现div宽度根据内容自适应 -https://blog.csdn.net/weixin_32052253/article/details/117725804
  3. HTML/CSS float 属性 -https://www.w3school.com.cn/cssref/pr_class_float.asp
  4. Vue input textarea自动滚动到最底部 -https://blog.csdn.net/weixin_42776111/article/details/109194393?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_ecpm_v1~rank_v31_ecpm-6-109194393-null-null.pc_agg_new_rank&utm_term=textarea%E6%BB%9A%E5%8A%A8%E8%87%B3%E5%BA%95%E9%83%A8&spm=1000.2123.3001.4430
  5. keyframes_CSS淡入淡出 -https://blog.csdn.net/culuo8053/article/details/107910312
  6. CSS3实现毛玻璃完美效果 -https://www.cnblogs.com/ivan5277/p/10007273.html

PyQt5html 双向通信 python负责网络通信和API(html没有python照样可以)

html + js + css

display: inline-block 可以解决父div包裹div问题, 避免出现多个消息出现在一行

chat.html

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat</title>
</head>
<style>
    *{
        padding: 0;
        margin: 0;
        font-family: Consolas,Microsoft YaHei UI,serif;
        font-size: 22px;
    }
    @keyframes
    fadeIn{
        0% {opacity:0}
        100% {opacity:1}
    }
    .clearfix::after{
        content: "";
        display: block;
        clear: both;
        width: 0;
        height: 0;
        line-height: 0;
        visibility: hidden;
    }

    .chat_middle{
        width: 100%;
        height: 400px;
        position: relative;
        box-sizing: border-box;
        overflow: auto;
        border-width: 0;
    }

    .chat_left{
        width: 100%;
        height: auto;
        min-height: 100px;
        margin-top: 20px;
        margin-bottom: 20px;
        animation-name: fadeIn;
        animation-duration: 1.5s;
        zoom:1;
        display: inline-block;
    }
    img.chat_left_img{
        width: 50px;
        height: 50px;
        float: left;
        margin-top: 10px;
        margin-left: 10px;
        margin-right: 10px;
        border-radius: 25px;
    }
    .chat_left_item{
        width: auto;
        max-width: calc(100% - 70px - 15px);
        height: auto;
        float: left;
        margin-top: 10px;
    }
    .chat_left_item .chat_left_chat{
        float: left;
    }
    .chat_left_item .chat_left_content{
        padding: 15px; /* changed */
        margin-top: 10px;
        background-color: #f4f5f7;
        color: black;
        display: inline-block;
        overflow: auto;
        border-radius: 0 10px 10px 10px;
        word-wrap:break-word;
		word-break:break-all;
        position: relative;
        box-shadow: 0 5px 15px rgba(20, 20, 20, 0.8);
        align-items: center;
    }

    .chat_right{
        width: 100%;
        height: auto;
        min-height: 100px;
        margin-top: 20px;
        margin-bottom: 20px;
        animation-name: fadeIn;
        animation-duration: 1.5s;
        zoom:1;
        display: inline-block;
    }
    img.chat_right_img{
        width: 50px;
        height: 50px;
        float: right;
        margin-top: 10px;
        margin-left: 10px;
        margin-right: 10px;
        border-radius: 25px;
    }
    .chat_right_item{
        width: auto;
        max-width: calc(100% - 70px - 15px);
        height: auto;
        float: right;
        margin-top: 10px;
    }
    .chat_time{
        width: 100%;
        text-align: center;
        color: gray;
    }
    .chat_right_name{
        color: darkgray;
        text-align: right;
    }
    .chat_left_name{
        color: darkgray;
        text-align: left;
    }
    .chat_right_content{
        float: right;
        padding: 15px; /* changed */
        margin-top: 10px;
        border-radius: 10px 0 10px 10px;
        background: linear-gradient(rgba(0, 255, 12, 255) 0%, rgba(16, 211, 22, 255) 100%);
        color: black;
        word-wrap:break-word;
		word-break:break-all;
        position: relative;
        box-shadow: 0 5px 15px rgba(20, 20, 20, 0.8);
        display: flex;
        align-items: center;
    }

    .split{
        width: 100%;
        height: 1px;
        visibility: hidden;
    }

	.file{
		width:100%;
		height:auto;
        min-height: 50px;
		padding:10px; 
		background-color: #f4f5f7;
		border: 1px;
        display: inline-block;
    }
	.fileinfo{ 
		float:left;
	}
	.fileicon{ 
		float:right; 
		width:50px;
        height: 50px;
	}
	.filename{
		word-wrap:break-word;
		word-break:break-all;
		overflow: hidden;
        color: black;
        display: inline-block;
	}
	.filesize{
		width:100px;
        height: auto;
		font-size:12px;
		color: rgb(153, 153, 153);
        text-align: end;
	}

</style>
<body>
    <div class="chat_middle" id="chat_middle_item"></div>
    <script>
        // 成功发送
        const send_message = document.getElementById("chat_middle_item");
        let _link = -1;
        function server_message(content){
            const ans = '<img class="chat_left_img clearfix" src="images/server.png">' +
                '<div class="chat_left_item">' +
                '<div class="chat_left_name clearfix">Server</div>' +
                '<span class="chat_left_content clearfix">' + content + '</span>'
                + '</div>';
            const oLi = document.createElement("div");

            oLi.setAttribute("class","chat_left");
            oLi.innerHTML=ans;
            const _split = document.createElement("div");
            _split.setAttribute("class", "split");

            send_message.append(oLi);
            send_message.append(_split);
            send_message.scrollTop = send_message.scrollHeight;
        }

        function user_message(name, content, is_self){
            let ans;
            if (is_self) {
                ans = '<img class="chat_right_img clearfix" src="images/chat_user.png">' +
                    '<div class="chat_right_item">' +
                    '<div class="chat_right_name clearfix">' + name + '</div>' +
                    '<span class="chat_right_content clearfix">' + content + '</span>'
                    + '</div>';
            } else {
                ans = '<img class="chat_left_img clearfix" src="images/chat_user.png">' +
                    '<div class="chat_left_item">' +
                    '<div class="chat_left_name clearfix">' + name + '</div>' +
                    '<span class="chat_left_content clearfix">' + content + '</span>' +
                    '<div class="split"></div>'
                    + '</div>';
            }

            const oLi = document.createElement("div");

            oLi.setAttribute("class","chat_right");
            oLi.innerHTML=ans;
            send_message.append(oLi);
            send_message.scrollTop = send_message.scrollHeight;
        }

        function file(filename, _size, ico_path, link, username, is_self) {
            const data = "<div class='file' οnclick='set_anchor(" + link + ");'>" +
               "<div class='fileinfo'>" +
               "<span class='filename'>" + filename + "</span>" +
               "<br>" +
               "<span class='filesize'>" + _size + "</span> " +
               "</div>" +
               "<img class='fileicon' src='" + ico_path + "'>" +
               "</div>"
            user_message(username, data, is_self)
        }

        function time(time_str){
            const time = document.createElement("div")
            time.innerHTML = "<div class='chat_time'>"+time_str+"</div>"
            send_message.append(time)
        }

        function height_changed(height) {
            send_message.style.height = height + "px";
        }

        function set_anchor( index ) {
            _link = index
        }
        function reset_anchor() {
            set_anchor(-1);
        }

        function get_anchor() {
            return _link;
        }
    </script>
</body>
</html>

python

PyQt5.QtWebEngineWidgets.QWebEngineView.load(QtCore.QUrl(QtCore.QFileInfo(>> file string <<).absoluteFilePath()))可以解决相对路径无法读取问题

代码语言:javascript
复制
import os
import sys
import logging
import time

from PyQt5 import QtWebEngineWidgets, QtCore, QtWidgets, QtGui


def convert(byte, fine=False):
    if not isinstance(byte, (int, float)):
        byte = len(byte)
    DEI = f"{byte} bytes"
    units = ["b",
             "Kb",
             "Mb",
             "Gb",
             "Tb",
             "Pb",
             "Eb"]
    index = 0
    while True:
        if byte < 1024 or index + 1 >= len(units):
            break
        byte /= 1024
        index += 1

    if index == 0:
        return DEI
    else:
        if fine:
            return "%0.1f%s(%s)" % (byte, units[index], DEI)
        else:
            return "%0.1f%s" % (byte, units[index])


def to_logging(command):
    def logs(*args, **kwargs):
        try:
            _result = command(*args, **kwargs)
            if _result is None:
                return True
            return _result
        except:
            logging.exception("")

    return logs


with open("chat.html", "r", encoding="utf-8") as f:
    html = f.read()


def omit(string: str, max_length: int, d_text: str = "...") -> str:
    if len(d_text) > max_length:
        d_text = d_text[: max_length]

    if len(string) > max_length:
        return string[:max_length - len(d_text)] + d_text
    return string


class ImageLoader:
    path = "images/filetype"
    unkown = os.path.join(path, "unknown.png").replace("\\", "/")
    filedict = {}

    def __init__(self):
        for filename in os.listdir(self.path):
            filepath = self.join(filename)
            filetype, _ = os.path.splitext(filename)
            self.filedict[filetype.lower()] = filepath

    def join(self, filename):
        return os.path.join(self.path, filename).replace("\\", "/")

    def get_suffix_img(self, suf):
        return self.filedict.get(suf, self.unkown)

    @staticmethod
    def get_suf(filename):
        _, suf = os.path.splitext(filename)
        return suf.lstrip(".").lower()

    def get(self, filename):
        return self.get_suffix_img(self.get_suf(filename))


class QChatWidget(QtWebEngineWidgets.QWebEngineView):
    img = ImageLoader()
    anchorClicked = QtCore.pyqtSignal(int)
    user_message = QtCore.pyqtSignal(bool, str, str)
    server_message = QtCore.pyqtSignal(str)
    add_file = QtCore.pyqtSignal(str, str, bool, str, int)
    boundary_time = 60 * 5

    def __init__(self, parent=None):
        super(QChatWidget, self).__init__(parent)
        self.setWindowTitle('chat')
        self.setGeometry(5, 30, 468, 662)
        self.load(QtCore.QUrl(QtCore.QFileInfo("chat.html").absoluteFilePath()))
        self.startTimer(100)
        self.anchor = -1
        self.user_message.connect(self._user_message)
        self.server_message.connect(self._server_message)
        self.add_file.connect(self._add_file)
        self.record_time = 0

    def timerEvent(self, *args) -> None:
        self.JavaScript(f"height_changed({self.size().height()});")
        self.JavaScript("get_anchor();", self.checkAnchor)

    def check_time(self):
        if time.time() - self.record_time > self.boundary_time:
            self.record_time = time.time()
            self.JavaScript(f"time({repr(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.record_time)))})")

    def checkAnchor(self, index: int):
        """
        function set_anchor( index ) {
            _link = index
        }
        function reset_anchor() {
            set_anchor(-1);
        }

        function get_anchor() {
            return _link;
        }
        """
        if isinstance(index, int) and index != self.anchor:
            self.anchorClicked.emit(index)
            self.JavaScript("reset_anchor();")

    def JavaScript(self, *args, **kwargs):
        self.page().runJavaScript(*args, **kwargs)

    def _user_message(self, _is_self: bool, content: str, name: str):
        """ function user_message(name, content, is_self); """
        self.check_time()
        self.JavaScript(f"user_message({repr(name)}, {repr(omit(content, 400))}, {str(_is_self).lower()});")

    def _server_message(self, content: str):
        """ function server_message(content); """
        self.check_time()
        self.JavaScript(f"server_message({repr(content)});")

    def _add_file(self, filename: str, size: str, _is_self: bool, name: str, index: int):
        """ function file(filename, _size, ico_path, link, username, is_self); """
        ico_path = self.img.get(filename)
        filename = omit(filename, 50)
        self.check_time()
        self.JavaScript(
            f"file({repr(filename)}, {repr(size)}, {repr(ico_path)}, {index}, {repr(name)}, {str(_is_self).lower()});")

    def contextMenuEvent(self, a0: QtGui.QContextMenuEvent) -> None:
        pass


class QChat(QtWidgets.QWidget):
    def __init__(self, parent=None, username=""):
        super(QChat, self).__init__(parent)
        self.setObjectName("Form")
        self.username = username
        self.resize(591, 670)
        self.gridLayout = QtWidgets.QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.web = QChatWidget(self)
        font = QtGui.QFont()
        font.setFamily("Consolas")
        font.setPointSize(12)
        self.web.setFont(font)
        self.web.setObjectName("web")
        self.gridLayout.addWidget(self.web, 2, 1, 2, 1)
        self.line_2 = QtWidgets.QFrame(self)
        self.line_2.setFrameShape(QtWidgets.QFrame.HLine)
        self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line_2.setObjectName("line_2")
        self.gridLayout.addWidget(self.line_2, 1, 1, 1, 1)
        self.textEdit = QtWidgets.QTextEdit(self)
        self.textEdit.setObjectName("textEdit")
        font = QtGui.QFont()
        font.setFamily("Consolas")
        font.setPointSize(13)
        self.textEdit.setFont(font)
        self.gridLayout.addWidget(self.textEdit, 4, 1, 1, 1)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.uploadButton = QtWidgets.QPushButton(self)
        font = QtGui.QFont()
        font.setFamily("Consolas")
        font.setPointSize(10)
        self.uploadButton.setFont(font)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("images/upload.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.uploadButton.setIcon(icon)
        self.uploadButton.setObjectName("uploadButton")
        self.horizontalLayout.addWidget(self.uploadButton)
        self.sendButton = QtWidgets.QPushButton(self)
        self.sendButton.setEnabled(False)
        self.sendButton.setStyleSheet("""
QPushButton{
    background:#fffff;
    border-size: 0;
}
QPushButton:hover{
    background: rgb(205, 205, 205);
}""")
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("images/send.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.sendButton.setIcon(icon1)
        self.sendButton.setObjectName("sendButton")
        self.horizontalLayout.addWidget(self.sendButton)
        self.gridLayout.addLayout(self.horizontalLayout, 5, 1, 1, 1)
        self.line = QtWidgets.QFrame(self)
        self.line.setFrameShape(QtWidgets.QFrame.HLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line.setObjectName("line")
        self.gridLayout.addWidget(self.line, 3, 1, 1, 1)
        self.label = QtWidgets.QLabel(self)
        font = QtGui.QFont()
        font.setFamily("Consolas")
        font.setPointSize(20)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 1, 1, 1)

        self.retranslateUi()
        QtCore.QMetaObject.connectSlotsByName(self)
        self.textEdit.textChanged.connect(self.textChanged)
        self.sendButton.clicked.connect(self.send)
        self.uploadButton.clicked.connect(self.sendfile)
        self.sendButton.setStyleSheet("""
    QPushButton
    {
    border-size: 10px solid rgb(200, 200, 200);
    border-radius: 15px;
    padding: 5px 10px 5px 10px;
    border: 2px groove gray;border-style: outset;
    }
    QPushButton:hover{background:rgb(220, 220, 220);}
    QPushButton:pressed{background:rgb(210, 210, 210);}
    """)
        self.uploadButton.setStyleSheet("""
    QPushButton
    {
    border-size: 10px solid rgb(200, 200, 200);
    border-radius: 15px;
    padding: 5px 10px 5px 10px;
    border: 2px groove gray;
    border-style: outset;
    }
    QPushButton:hover{background:rgb(220, 220, 220);}
    QPushButton:pressed{background:rgb(210, 210, 210);}
    """)

    def user_message(self, _is_self: bool, content: str, name: str):
        self.web.user_message.emit(_is_self, content, name)

    def server_message(self, content: str):
        self.web.server_message.emit(content)

    def add_file(self, filename: str, size: str, _is_self: bool, name: str, index: int):
        self.web.add_file.emit(filename, size, _is_self, name, index)

    def getText(self) -> str:
        return self.textEdit.toPlainText().strip()

    def send(self) -> None:
        self.user_message(True, self.getText(), self.username)  ##
        self.textEdit.clear()

    def textChanged(self, *args) -> None:
        self.sendButton.setEnabled(0 < len(self.getText()) < 400)

    def setTitle(self, title: str) -> None:
        self.label.setText(QtCore.QCoreApplication.translate("Form", title))

    def retranslateUi(self):
        _translate = QtCore.QCoreApplication.translate
        self.setWindowTitle(_translate("Form", "Form"))
        self.sendButton.setText(_translate("Form", "发送"))
        self.uploadButton.setText(_translate("Form", "上传"))
        self.setTitle("ZServer - Chat")

    def sendfile(self, *args):
        for file in QtWidgets.QFileDialog.getOpenFileNames(self, "上传文件")[0]:
            if os.path.isfile(file):  ##
                path, filename = os.path.split(file)
                self.add_file(filename, convert(os.path.getsize(file)), True, self.username, 0)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = QChat(username="user1")
    win.show()
    app.exit(app.exec_())

代码地址

gitcode - https://gitcode.net/m0_60394896/python

user目录下的 chat.py为主页面, 图片都在user/images/filetype下面

相关资源

html+css+js+python(QtWebEngineWidgets) 实现微信聊天界面-包括时间,文件,纯文本等

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 展示
  • 参考文章
  • html + js + css
  • python
  • 代码地址
  • 相关资源
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档