You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
9.1 KiB
245 lines
9.1 KiB
"""
|
|
聊天记录搜索窗口(优化版 — 连接服务器搜索)
|
|
"""
|
|
from PyQt5.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
QLineEdit, QPushButton, QListWidget, QListWidgetItem,
|
|
QTextEdit, QSplitter, QFrame
|
|
)
|
|
from PyQt5.QtCore import Qt, pyqtSignal
|
|
from PyQt5.QtGui import QColor
|
|
import html
|
|
|
|
|
|
class SearchResultItem(QListWidgetItem):
|
|
def __init__(self, message_data, keyword=''):
|
|
super().__init__()
|
|
self.message_data = message_data
|
|
self._update_display(keyword)
|
|
|
|
def _update_display(self, keyword=''):
|
|
sender = self.message_data.get('sender', '未知')
|
|
content = self.message_data.get('content', '')
|
|
timestamp = self.message_data.get('timestamp', '')
|
|
msg_type = self.message_data.get('msg_type', 'text')
|
|
if msg_type == 'image':
|
|
preview = '[图片]'
|
|
else:
|
|
preview = content[:60] + '...' if len(content) > 60 else content
|
|
display_text = f"{sender} {timestamp}\n{preview}"
|
|
self.setText(display_text)
|
|
self.setData(Qt.UserRole, self.message_data)
|
|
|
|
def highlight_keyword(self, keyword):
|
|
"""高亮关键词(未来可扩展富文本显示)"""
|
|
pass
|
|
|
|
|
|
class SearchWindow(QWidget):
|
|
search_requested = pyqtSignal(str, str, str) # chat_type, target_id, keyword
|
|
|
|
def __init__(self, chat_type, target_id, target_name, parent=None):
|
|
super().__init__(parent)
|
|
self.chat_type = chat_type
|
|
self.target_id = target_id
|
|
self.target_name = target_name
|
|
self.search_results = []
|
|
self._init_ui()
|
|
|
|
def _init_ui(self):
|
|
self.setWindowTitle(f"搜索聊天记录 - {self.target_name}")
|
|
self.setGeometry(200, 200, 820, 600)
|
|
self.setMinimumSize(600, 400)
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setSpacing(0)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
# 搜索栏
|
|
search_frame = QFrame()
|
|
search_frame.setStyleSheet("""
|
|
QFrame {
|
|
background: #f8fafc; border-bottom: 1px solid #e2e8f0;
|
|
}
|
|
""")
|
|
search_layout = QHBoxLayout(search_frame)
|
|
search_layout.setContentsMargins(16, 10, 16, 10)
|
|
search_layout.setSpacing(8)
|
|
|
|
search_icon = QLabel("🔍")
|
|
search_icon.setStyleSheet("font-size: 16px; border: none;")
|
|
search_layout.addWidget(search_icon)
|
|
|
|
self.search_input = QLineEdit()
|
|
self.search_input.setPlaceholderText("输入关键词搜索聊天记录,支持中英文…")
|
|
self.search_input.setStyleSheet("""
|
|
QLineEdit {
|
|
border: 1px solid #ddd; border-radius: 8px;
|
|
background: white; font-size: 14px; padding: 8px 12px;
|
|
}
|
|
QLineEdit:focus { border: 1px solid #6366f1; }
|
|
""")
|
|
self.search_input.returnPressed.connect(self._perform_search)
|
|
search_layout.addWidget(self.search_input)
|
|
|
|
search_btn = QPushButton("搜索")
|
|
search_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: #6366f1; color: white; border: none;
|
|
border-radius: 8px; padding: 8px 20px;
|
|
font-size: 13px; font-weight: bold;
|
|
}
|
|
QPushButton:hover { background: #4f46e5; }
|
|
""")
|
|
search_btn.clicked.connect(self._perform_search)
|
|
search_layout.addWidget(search_btn)
|
|
|
|
layout.addWidget(search_frame)
|
|
|
|
# 信息栏
|
|
info_frame = QFrame()
|
|
info_frame.setStyleSheet("background: #f1f5f9; border-bottom: 1px solid #e0e0e0;")
|
|
info_layout = QHBoxLayout(info_frame)
|
|
info_layout.setContentsMargins(16, 6, 16, 6)
|
|
chat_type_text = "私聊" if self.chat_type == 'private' else "群聊"
|
|
info_lbl = QLabel(f"搜索范围:{chat_type_text}「{self.target_name}」")
|
|
info_lbl.setStyleSheet("font-size: 12px; color: #888;")
|
|
info_layout.addWidget(info_lbl)
|
|
layout.addWidget(info_frame)
|
|
|
|
# 分割器
|
|
splitter = QSplitter(Qt.Horizontal)
|
|
self._build_results_panel(splitter)
|
|
self._build_detail_panel(splitter)
|
|
splitter.setSizes([320, 500])
|
|
layout.addWidget(splitter)
|
|
|
|
def _build_results_panel(self, parent_splitter):
|
|
results_frame = QFrame()
|
|
results_frame.setStyleSheet("QFrame { background: white; }")
|
|
|
|
results_layout = QVBoxLayout(results_frame)
|
|
results_layout.setContentsMargins(0, 0, 0, 0)
|
|
results_layout.setSpacing(0)
|
|
|
|
# 结果标题
|
|
header = QWidget()
|
|
header.setFixedHeight(36)
|
|
header.setStyleSheet("background: #fafafa; border-bottom: 1px solid #eee;")
|
|
hl = QHBoxLayout(header)
|
|
hl.setContentsMargins(12, 0, 12, 0)
|
|
self.results_title = QLabel("搜索结果")
|
|
self.results_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #555;")
|
|
hl.addWidget(self.results_title)
|
|
results_layout.addWidget(header)
|
|
|
|
self.results_list = QListWidget()
|
|
self.results_list.setStyleSheet("""
|
|
QListWidget {
|
|
border: none; background: white; outline: none;
|
|
}
|
|
QListWidget::item {
|
|
padding: 10px 14px; border-bottom: 1px solid #f1f5f9;
|
|
font-size: 12px; line-height: 1.4;
|
|
}
|
|
QListWidget::item:hover { background: #f8fafc; }
|
|
QListWidget::item:selected { background: #eef2ff; }
|
|
""")
|
|
self.results_list.itemClicked.connect(self._show_message_detail)
|
|
results_layout.addWidget(self.results_list)
|
|
|
|
parent_splitter.addWidget(results_frame)
|
|
|
|
def _build_detail_panel(self, parent_splitter):
|
|
detail_frame = QFrame()
|
|
detail_frame.setStyleSheet("QFrame { background: #f8fafc; }")
|
|
|
|
detail_layout = QVBoxLayout(detail_frame)
|
|
detail_layout.setContentsMargins(0, 0, 0, 0)
|
|
detail_layout.setSpacing(0)
|
|
|
|
# 详情标题
|
|
header = QWidget()
|
|
header.setFixedHeight(36)
|
|
header.setStyleSheet("background: #fafafa; border-bottom: 1px solid #eee;")
|
|
hl = QHBoxLayout(header)
|
|
hl.setContentsMargins(12, 0, 12, 0)
|
|
detail_title = QLabel("消息详情")
|
|
detail_title.setStyleSheet("font-size: 13px; font-weight: bold; color: #555;")
|
|
hl.addWidget(detail_title)
|
|
detail_layout.addWidget(header)
|
|
|
|
self.detail_text = QTextEdit()
|
|
self.detail_text.setReadOnly(True)
|
|
self.detail_text.setStyleSheet("""
|
|
QTextEdit {
|
|
border: none; background: #f8fafc;
|
|
font-size: 13px; line-height: 1.6;
|
|
padding: 16px;
|
|
}
|
|
""")
|
|
self.detail_text.setPlaceholderText("选择左侧一条搜索结果查看消息详情…")
|
|
detail_layout.addWidget(self.detail_text)
|
|
|
|
parent_splitter.addWidget(detail_frame)
|
|
|
|
def _perform_search(self):
|
|
keyword = self.search_input.text().strip()
|
|
if not keyword:
|
|
return
|
|
|
|
self.results_list.clear()
|
|
self.detail_text.clear()
|
|
self.results_title.setText("搜索中…")
|
|
self.search_requested.emit(self.chat_type, str(self.target_id), keyword)
|
|
|
|
def _show_message_detail(self, item):
|
|
message_data = item.data(Qt.UserRole)
|
|
if not message_data:
|
|
return
|
|
|
|
sender = message_data.get('sender', '未知')
|
|
content = message_data.get('content', '')
|
|
timestamp = message_data.get('timestamp', '')
|
|
msg_type = message_data.get('msg_type', 'text')
|
|
|
|
if msg_type == 'image':
|
|
content_display = '<div style="font-size: 14px; color: #6366f1;">[图片消息]</div>'
|
|
else:
|
|
content_display = f'<div style="font-size: 14px; line-height: 1.7; color: #333;">{html.escape(content).replace(chr(92)+"n", "<br>")}</div>'
|
|
|
|
detail_html = f"""
|
|
<div style="font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;">
|
|
<div style="background: #eef2ff; padding: 10px 14px; border-radius: 8px; margin-bottom: 14px;">
|
|
<strong style="color: #6366f1;">发送者:</strong>{html.escape(sender)}<br>
|
|
<strong style="color: #6366f1;">时间:</strong>{html.escape(timestamp)}
|
|
</div>
|
|
<div style="background: white; padding: 14px; border-radius: 8px; border: 1px solid #e0e0e0;">
|
|
<strong style="color: #2c3e50;">消息内容:</strong><br><br>
|
|
{content_display}
|
|
</div>
|
|
</div>
|
|
"""
|
|
self.detail_text.setHtml(detail_html)
|
|
|
|
def update_search_results(self, results, keyword):
|
|
self.search_results = results
|
|
self.results_list.clear()
|
|
|
|
count = len(results)
|
|
self.results_title.setText(f"搜索结果({count} 条)")
|
|
|
|
if not results:
|
|
no_result = QListWidgetItem("未找到包含关键词的消息")
|
|
no_result.setForeground(QColor('#94a3b8'))
|
|
self.results_list.addItem(no_result)
|
|
return
|
|
|
|
for message in results:
|
|
item = SearchResultItem(message, keyword)
|
|
self.results_list.addItem(item)
|
|
|
|
def closeEvent(self, event):
|
|
self.search_results.clear()
|
|
event.accept()
|