纯html - web网页
QWebEngineWidget + Html :
(搜索)
PyQt5
和 html
双向通信
python负责网络通信和API(html没有python照样可以)
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>
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
html+css+js+python(QtWebEngineWidgets) 实现微信聊天界面-包括时间,文件,纯文本等