程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

html+css+js+python(QtWebEngineWidgets) 實現微信聊天界面-包括時間,文件,純文本等

編輯:Python

文章目錄

  • 展示
  • 參考文章
  • html + js + css
  • python
  • 代碼地址
          • user目錄下的 chat.py為主頁面, 圖片都在user/images/filetype下面
  • 相關資源

展示

純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) 實現微信聊天界面-包括時間,文件,純文本等


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved