zl程序教程

您现在的位置是:首页 >  APP

当前栏目

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

2023-03-07 09:02:17 时间

文章目录

展示

纯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

<!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()))可以解决相对路径无法读取问题

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) 实现微信聊天界面-包括时间,文件,纯文本等