检查代码<import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext, PhotoImage
import sqlite3
import threading
import time
import random
import os
import json
from PIL import Image, ImageTk
import requests
from io import BytesIO
class WeChatClone:
def __init__(self, root):
self.root = root
self.root.title("微信复刻版 - Python实现")
self.root.geometry("1000x700")
self.root.resizable(True, True)
self.root.configure(bg='#f0f0f0')
# 创建数据库和表
self.create_database()
# 当前用户
self.current_user = None
# 创建主界面框架
self.create_login_frame()
# 在线用户
self.online_users = {}
self.simulate_online_users()
# 消息队列
self.message_queue = []
self.message_thread = threading.Thread(target=self.process_messages, daemon=True)
self.message_thread.start()
# 字体
self.title_font = ("Arial", 20, "bold")
self.text_font = ("Arial", 12)
self.small_font = ("Arial", 10)
def create_database(self):
"""创建数据库和表"""
self.conn = sqlite3.connect('wechat_clone.db')
self.cursor = self.conn.cursor()
# 用户表
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
password TEXT,
avatar TEXT,
nickname TEXT,
phone TEXT,
email TEXT,
region TEXT
)
''')
# 联系人表
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS contacts (
user_id INTEGER,
contact_id INTEGER,
remark TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id),
FOREIGN KEY(contact_id) REFERENCES users(id),
PRIMARY KEY (user_id, contact_id)
)
''')
# 消息表
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender_id INTEGER,
receiver_id INTEGER,
content TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
is_read INTEGER DEFAULT 0,
msg_type TEXT DEFAULT 'text' -- text, image, voice
)
''')
# 朋友圈表
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS moments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
content TEXT,
image_url TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
likes INTEGER DEFAULT 0,
comments TEXT DEFAULT '[]'
)
''')
# 添加测试数据
self.add_test_data()
self.conn.commit()
def add_test_data(self):
"""添加测试数据"""
# 添加用户
users = [
('zhangsan', '123456', '😀', '张三', '13800138000', 'zhangsan@example.com', '北京市海淀区'),
('lisi', '123456', '😊', '李四', '13900139000', 'lisi@example.com', '上海市浦东新区'),
('wangwu', '123456', '😎', '王五', '13700137000', 'wangwu@example.com', '广州市天河区'),
('zhaoliu', '123456', '🥰', '赵六', '13600136000', 'zhaoliu@example.com', '深圳市南山区'),
('admin', 'admin', '👑', '管理员', '13500135000', 'admin@example.com', '杭州市西湖区')
]
try:
self.cursor.executemany(
"INSERT OR IGNORE INTO users (username, password, avatar, nickname, phone, email, region) VALUES (?, ?, ?, ?, ?, ?, ?)",
users
)
# 添加联系人关系
contacts = [
(1, 2, '同事李四'),
(1, 3, '同学王五'),
(1, 4, '朋友赵六'),
(2, 1, '同事张三'),
(3, 1, '同学张三'),
(4, 1, '朋友张三'),
(5, 1, '张三'),
(5, 2, '李四'),
(5, 3, '王五'),
(5, 4, '赵六')
]
self.cursor.executemany(
"INSERT OR IGNORE INTO contacts (user_id, contact_id, remark) VALUES (?, ?, ?)",
contacts
)
# 添加朋友圈
moments = [
(1, '今天天气真好,适合出去玩!', 'sky.jpg'),
(2, '新项目终于上线了,开心!', 'project.jpg'),
(3, '分享一首好听的歌曲给大家', 'music.jpg'),
(4, '美食探店,这家餐厅太棒了!', 'food.jpg'),
(5, '欢迎使用微信复刻版!', 'logo.png')
]
self.cursor.executemany(
"INSERT OR IGNORE INTO moments (user_id, content, image_url) VALUES (?, ?, ?)",
moments
)
# 添加一些消息
messages = [
(2, 1, '张三,今晚一起吃饭吗?'),
(1, 2, '好啊,老地方见?'),
(3, 1, '同学聚会下周举行,记得来参加'),
(4, 1, '周末一起去爬山怎么样?'),
(5, 1, '系统提示:欢迎使用微信复刻版')
]
self.cursor.executemany(
"INSERT INTO messages (sender_id, receiver_id, content) VALUES (?, ?, ?)",
messages
)
# 添加点赞和评论数据
comments = json.dumps([
{'user_id': 2, 'content': '真不错!', 'timestamp': '2023-05-01 10:30:00'},
{'user_id': 3, 'content': '玩的开心!', 'timestamp': '2023-05-01 11:15:00'}
])
self.cursor.execute(
"UPDATE moments SET likes = 5, comments = ? WHERE id = 1",
(comments,)
)
self.conn.commit()
except sqlite3.IntegrityError:
pass # 数据已存在
def create_login_frame(self):
"""创建登录界面"""
self.login_frame = tk.Frame(self.root, bg='#f0f0f0')
self.login_frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER, width=400, height=500)
# 标题
title = tk.Label(
self.login_frame,
text="微信复刻版",
font=self.title_font,
fg="#07C160",
bg='#f0f0f0'
)
title.pack(pady=50)
# 表单容器
form_frame = tk.Frame(self.login_frame, bg='#f0f0f0')
form_frame.pack(pady=20)
# 用户名
tk.Label(form_frame, text="用户名:", bg='#f0f0f0', font=self.text_font).grid(row=0, column=0, padx=5, pady=10, sticky='e')
self.username_entry = tk.Entry(form_frame, width=25, font=self.text_font, relief=tk.FLAT, highlightthickness=1, highlightbackground="#cccccc")
self.username_entry.grid(row=0, column=1, padx=5, pady=10, ipady=5)
self.username_entry.insert(0, "zhangsan") # 测试用户名
# 密码
tk.Label(form_frame, text="密码:", bg='#f0f0f0', font=self.text_font).grid(row=1, column=0, padx=5, pady=10, sticky='e')
self.password_entry = tk.Entry(form_frame, width=25, show="*", font=self.text_font, relief=tk.FLAT, highlightthickness=1, highlightbackground="#cccccc")
self.password_entry.grid(row=1, column=1, padx=5, pady=10, ipady=5)
self.password_entry.insert(0, "123456") # 测试密码
# 记住密码
self.remember_var = tk.BooleanVar(value=True)
remember_check = tk.Checkbutton(
form_frame,
text="记住密码",
variable=self.remember_var,
bg='#f0f0f0',
font=self.small_font,
activebackground='#f0f0f0'
)
remember_check.grid(row=2, column=1, sticky='w', padx=5, pady=5)
# 登录按钮
login_btn = tk.Button(
self.login_frame,
text="登录",
bg="#07C160",
fg="white",
width=15,
height=1,
font=self.text_font,
relief=tk.FLAT,
activebackground="#05A85E",
command=self.login
)
login_btn.pack(pady=20)
# 注册按钮
register_btn = tk.Button(
self.login_frame,
text="注册新账号",
fg="#07C160",
bg='#f0f0f0',
borderwidth=0,
font=self.small_font,
activebackground='#f0f0f0',
activeforeground="#05A85E",
command=self.show_register_frame
)
register_btn.pack(pady=5)
def show_register_frame(self):
"""显示注册界面"""
self.login_frame.destroy()
self.register_frame = tk.Frame(self.root, bg='#f0f0f0')
self.register_frame.place(relx=0.5, rely=0.5, anchor=tk.CENTER, width=500, height=600)
# 标题
title = tk.Label(
self.register_frame,
text="注册新账号",
font=self.title_font,
fg="#07C160",
bg='#f0f0f0'
)
title.pack(pady=30)
# 表单容器
form_frame = tk.Frame(self.register_frame, bg='#f0f0f0')
form_frame.pack(pady=10)
# 用户名
tk.Label(form_frame, text="用户名:", bg='#f0f0f0', font=self.text_font).grid(row=0, column=0, padx=5, pady=8, sticky='e')
self.reg_username_entry = tk.Entry(form_frame, width=30, font=self.text_font, relief=tk.FLAT, highlightthickness=1, highlightbackground="#cccccc")
self.reg_username_entry.grid(row=0, column=1, padx=5, pady=8, ipady=5)
# 密码
tk.Label(form_frame, text="密码:", bg='#f0f0f0', font=self.text_font).grid(row=1, column=0, padx=5, pady=8, sticky='e')
self.reg_password_entry = tk.Entry(form_frame, width=30, show="*", font=self.text_font, relief=tk.FLAT, highlightthickness=1, highlightbackground="#cccccc")
self.reg_password_entry.grid(row=1, column=1, padx=5, pady=8, ipady=5)
# 确认密码
tk.Label(form_frame, text="确认密码:", bg='#f0f0f0', font=self.text_font).grid(row=2, column=0, padx=5, pady=8, sticky='e')
self.reg_confirm_entry = tk.Entry(form_frame, width=30, show="*", font=self.text_font, relief=tk.FLAT, highlightthickness=1, highlightbackground="#cccccc")
self.reg_confirm_entry.grid(row=2, column=1, padx=5, pady=8, ipady=5)
# 昵称
tk.Label(form_frame, text="昵称:", bg='#f0f0f0', font=self.text_font).grid(row=3, column=0, padx=5, pady=8, sticky='e')
self.nickname_entry = tk.Entry(form_frame, width=30, font=self.text_font, relief=tk.FLAT, highlightthickness=1, highlightbackground="#cccccc")
self.nickname_entry.grid(row=3, column=1, padx=5, pady=8, ipady=5)
# 手机号
tk.Label(form_frame, text="手机号:", bg='#f0f0f0', font=self.text_font).grid(row=4, column=0, padx=5, pady=8, sticky='e')
self.phone_entry = tk.Entry(form_frame, width=30, font=self.text_font, relief=tk.FLAT, highlightthickness=1, highlightbackground="#cccccc")
self.phone_entry.grid(row=4, column=1, padx=5, pady=8, ipady=5)
# 邮箱
tk.Label(form_frame, text="邮箱:", bg='#f0f0f0', font=self.text_font).grid(row=5, column=0, padx=5, pady=8, sticky='e')
self.email_entry = tk.Entry(form_frame, width=30, font=self.text_font, relief=tk.FLAT, highlightthickness=1, highlightbackground="#cccccc")
self.email_entry.grid(row=5, column=1, padx=5, pady=8, ipady=5)
# 地区
tk.Label(form_frame, text="地区:", bg='#f0f0f0', font=self.text_font).grid(row=6, column=0, padx=5, pady=8, sticky='e')
self.region_var = tk.StringVar(value="北京市")
region_menu = ttk.Combobox(
form_frame,
textvariable=self.region_var,
values=["北京市", "上海市", "广州市", "深圳市", "杭州市", "南京市", "成都市"],
width=28,
font=self.text_font,
state="readonly"
)
region_menu.grid(row=6, column=1, padx=5, pady=8, ipady=5)
# 注册按钮
register_btn = tk.Button(
self.register_frame,
text="注册",
bg="#07C160",
fg="white",
width=15,
height=1,
font=self.text_font,
relief=tk.FLAT,
activebackground="#05A85E",
command=self.register
)
register_btn.pack(pady=20)
# 返回登录按钮
back_btn = tk.Button(
self.register_frame,
text="返回登录",
fg="#07C160",
bg='#f0f0f0',
borderwidth=0,
font=self.small_font,
activebackground='#f0f0f0',
activeforeground="#05A85E",
command=self.back_to_login
)
back_btn.pack(pady=5)
def back_to_login(self):
"""返回登录界面"""
self.register_frame.destroy()
self.create_login_frame()
def register(self):
"""注册新账号"""
username = self.reg_username_entry.get()
password = self.reg_password_entry.get()
confirm = self.reg_confirm_entry.get()
nickname = self.nickname_entry.get()
phone = self.phone_entry.get()
email = self.email_entry.get()
region = self.region_var.get()
if not username or not password or not nickname or not phone or not email:
messagebox.showerror("错误", "所有字段都必须填写")
return
if password != confirm:
messagebox.showerror("错误", "两次输入的密码不一致")
return
try:
self.cursor.execute(
"INSERT INTO users (username, password, nickname, avatar, phone, email, region) VALUES (?, ?, ?, ?, ?, ?, ?)",
(username, password, nickname, random.choice(['😀', '😊', '😎', '🥰']), phone, email, region)
)
self.conn.commit()
messagebox.showinfo("成功", "注册成功!")
self.back_to_login()
except sqlite3.IntegrityError:
messagebox.showerror("错误", "用户名已存在")
def login(self):
"""用户登录"""
username = self.username_entry.get()
password = self.password_entry.get()
self.cursor.execute(
"SELECT id, nickname, avatar FROM users WHERE username = ? AND password = ?",
(username, password)
)
user = self.cursor.fetchone()
if user:
self.current_user = {
'id': user[0],
'nickname': user[1],
'avatar': user[2],
'username': username
}
self.login_frame.destroy()
self.create_main_frame()
# 将用户添加到在线列表
self.add_online_user(self.current_user['id'])
else:
messagebox.showerror("错误", "用户名或密码错误")
def create_main_frame(self):
"""创建主界面"""
self.main_frame = tk.Frame(self.root, bg="#F5F5F5")
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 左侧菜单
left_frame = tk.Frame(self.main_frame, width=200, bg='#2E2E2E')
left_frame.pack(side=tk.LEFT, fill=tk.Y)
# 用户信息
user_info_frame = tk.Frame(left_frame, bg='#232323', height=80)
user_info_frame.pack(fill=tk.X, pady=(0, 10))
# 用户头像和昵称
avatar_frame = tk.Frame(user_info_frame, bg='#232323')
avatar_frame.pack(side=tk.LEFT, padx=(15, 10), pady=10)
# 使用Canvas创建圆形头像
avatar_canvas = tk.Canvas(avatar_frame, width=40, height=40, bg='#232323', highlightthickness=0)
avatar_canvas.pack()
avatar_canvas.create_oval(2, 2, 38, 38, fill="#444444", outline="#07C160", width=2)
avatar_canvas.create_text(20, 20, text=self.current_user['avatar'], font=("Arial", 14), fill="white")
# 昵称和状态
info_frame = tk.Frame(user_info_frame, bg='#232323')
info_frame.pack(side=tk.LEFT, padx=(0, 15))
nickname_label = tk.Label(
info_frame,
text=self.current_user['nickname'],
font=("Arial", 11, "bold"),
bg='#232323',
fg='white'
)
nickname_label.pack(anchor='w', pady=(5, 2))
status_label = tk.Label(
info_frame,
text="在线",
font=("Arial", 9),
bg='#232323',
fg='#07C160'
)
status_label.pack(anchor='w')
# 菜单按钮
menu_buttons = [
("聊天", self.show_chat_frame, "#07C160"),
("联系人", self.show_contacts_frame, "#5D9CEC"),
("朋友圈", self.show_moments_frame, "#FFCE54"),
("设置", self.show_settings_frame, "#A0D468")
]
for text, command, color in menu_buttons:
btn = tk.Button(
left_frame,
text=text,
bg='#2E2E2E',
fg='white',
anchor='w',
padx=20,
width=20,
relief=tk.FLAT,
activebackground=color,
activeforeground="white",
font=("Arial", 11),
command=command
)
btn.pack(fill=tk.X, pady=2, ipady=8)
# 添加分隔线
separator = tk.Frame(left_frame, height=1, bg='#444444')
separator.pack(fill=tk.X, padx=10, pady=0)
# 右侧内容区域
self.content_frame = tk.Frame(self.main_frame, bg='#F5F5F5')
self.content_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# 显示默认页面 - 聊天
self.show_chat_frame()
# 右下角悬浮按钮
self.floating_btn = tk.Button(
self.main_frame,
text="+",
bg="#07C160",
fg="white",
font=("Arial", 20, "bold"),
width=2,
height=1,
relief=tk.FLAT,
command=self.show_floating_menu
)
self.floating_btn.place(relx=0.95, rely=0.9, anchor=tk.CENTER)
# 悬浮菜单(初始隐藏)
self.floating_menu = None
def show_chat_frame(self):
"""显示聊天界面"""
self.clear_content_frame()
self.update_window_title("微信")
# 标题栏
header_frame = tk.Frame(self.content_frame, bg="#F5F5F5", height=60)
header_frame.pack(fill=tk.X, padx=20, pady=10)
title = tk.Label(
header_frame,
text="微信",
font=("Arial", 18, "bold"),
bg="#F5F5F5"
)
title.pack(side=tk.LEFT)
# 搜索框
search_frame = tk.Frame(header_frame, bg="#F5F5F5")
search_frame.pack(side=tk.RIGHT)
search_icon = tk.Label(search_frame, text="🔍", font=("Arial", 12), bg="#F5F5F5")
search_icon.pack(side=tk.LEFT, padx=(0, 5))
search_entry = tk.Entry(
search_frame,
width=25,
relief=tk.FLAT,
highlightthickness=1,
highlightbackground="#cccccc",
highlightcolor="#07C160",
font=("Arial", 10)
)
search_entry.pack(side=tk.LEFT, ipady=3)
search_entry.insert(0, "搜索")
# 联系人列表容器
contact_list_frame = tk.Frame(self.content_frame, bg="#F5F5F5")
contact_list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 10))
# 获取联系人列表
self.cursor.execute('''
SELECT u.id, u.nickname, u.avatar, c.remark
FROM contacts c
JOIN users u ON c.contact_id = u.id
WHERE c.user_id = ?
''', (self.current_user['id'],))
contacts = self.cursor.fetchall()
# 最近联系人分组
recent_frame = tk.LabelFrame(
contact_list_frame,
text="最近联系人",
bg="#F5F5F5",
fg="#666666",
font=("Arial", 10, "bold"),
borderwidth=1,
relief=tk.FLAT
)
recent_frame.pack(fill=tk.X, pady=(0, 15))
# 联系人列表
all_contacts_frame = tk.LabelFrame(
contact_list_frame,
text="所有联系人",
bg="#F5F5F5",
fg="#666666",
font=("Arial", 10, "bold"),
borderwidth=1,
relief=tk.FLAT
)
all_contacts_frame.pack(fill=tk.BOTH, expand=True)
# 添加最近联系人(取前3个)
for contact in contacts[:3]:
contact_id = contact[0]
nickname = contact[3] if contact[3] else contact[1]
avatar = contact[2]
# 获取最后一条消息
self.cursor.execute('''
SELECT content, timestamp
FROM messages
WHERE (sender_id = ? AND receiver_id = ?)
OR (sender_id = ? AND receiver_id = ?)
ORDER BY timestamp DESC
LIMIT 1
''', (self.current_user['id'], contact_id, contact_id, self.current_user['id']))
last_msg = self.cursor.fetchone()
last_msg_text = last_msg[0][:20] + "..." if last_msg and len(last_msg[0]) > 20 else last_msg[0] if last_msg else ""
last_msg_time = last_msg[1].split()[0] if last_msg else ""
contact_frame = self.create_contact_item(
recent_frame,
avatar,
nickname,
last_msg_text,
last_msg_time,
contact_id
)
contact_frame.pack(fill=tk.X, pady=5)
# 添加所有联系人
contact_canvas = tk.Canvas(all_contacts_frame, bg="#F5F5F5", highlightthickness=0)
scrollbar = ttk.Scrollbar(all_contacts_frame, orient="vertical", command=contact_canvas.yview)
scrollable_frame = tk.Frame(contact_canvas, bg="#F5F5F5")
scrollable_frame.bind(
"<Configure>",
lambda e: contact_canvas.configure(scrollregion=contact_canvas.bbox("all"))
)
contact_canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
contact_canvas.configure(yscrollcommand=scrollbar.set)
contact_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 按字母分组联系人
grouped_contacts = {}
for contact in contacts:
first_char = contact[1][0].upper() if contact[1] else "?"
if first_char not in grouped_contacts:
grouped_contacts[first_char] = []
grouped_contacts[first_char].append(contact)
# 按字母顺序排序
for char in sorted(grouped_contacts.keys()):
# 添加字母分组标题
char_frame = tk.Frame(scrollable_frame, bg="#F5F5F5")
char_frame.pack(fill=tk.X, pady=(10, 5))
char_label = tk.Label(
char_frame,
text=char,
font=("Arial", 10, "bold"),
fg="#888888",
bg="#F5F5F5",
width=5
)
char_label.pack(side=tk.LEFT, padx=10)
separator = tk.Frame(char_frame, height=1, bg="#E0E0E0")
separator.pack(side=tk.LEFT, fill=tk.X, expand=True, pady=10)
# 添加该分组下的联系人
for contact in grouped_contacts[char]:
contact_id = contact[0]
nickname = contact[3] if contact[3] else contact[1]
avatar = contact[2]
contact_frame = self.create_contact_item(
scrollable_frame,
avatar,
nickname,
"",
"",
contact_id
)
contact_frame.pack(fill=tk.X, pady=3)
def create_contact_item(self, parent, avatar, nickname, last_msg, last_time, contact_id):
"""创建联系人列表项"""
contact_frame = tk.Frame(parent, bg="#FFFFFF", height=60, cursor="hand2")
contact_frame.bind("<Button-1>", lambda e, cid=contact_id: self.open_chat_window_from_id(cid, nickname))
# 头像
avatar_frame = tk.Frame(contact_frame, bg="#FFFFFF")
avatar_frame.pack(side=tk.LEFT, padx=10)
avatar_canvas = tk.Canvas(avatar_frame, width=40, height=40, bg="#FFFFFF", highlightthickness=0)
avatar_canvas.pack()
avatar_canvas.create_oval(2, 2, 38, 38, fill="#EEEEEE", outline="#E0E0E0", width=1)
avatar_canvas.create_text(20, 20, text=avatar, font=("Arial", 14))
# 联系人信息
info_frame = tk.Frame(contact_frame, bg="#FFFFFF")
info_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
# 顶部名称和时间
top_frame = tk.Frame(info_frame, bg="#FFFFFF")
top_frame.pack(fill=tk.X, pady=(8, 0))
name_label = tk.Label(
top_frame,
text=nickname,
font=("Arial", 12, "bold"),
fg="#000000",
bg="#FFFFFF",
anchor="w"
)
name_label.pack(side=tk.LEFT)
if last_time:
time_label = tk.Label(
top_frame,
text=last_time,
font=("Arial", 9),
fg="#888888",
bg="#FFFFFF",
anchor="e"
)
time_label.pack(side=tk.RIGHT)
# 底部最后一条消息
if last_msg:
msg_frame = tk.Frame(info_frame, bg="#FFFFFF")
msg_frame.pack(fill=tk.X, pady=(0, 8))
msg_label = tk.Label(
msg_frame,
text=last_msg,
font=("Arial", 10),
fg="#666666",
bg="#FFFFFF",
anchor="w"
)
msg_label.pack(fill=tk.X)
# 底部分隔线
separator = tk.Frame(contact_frame, height=1, bg="#F0F0F0")
separator.pack(side=tk.BOTTOM, fill=tk.X, padx=10)
return contact_frame
def open_chat_window_from_id(self, contact_id, nickname):
"""通过ID打开聊天窗口"""
# 创建聊天窗口
chat_win = tk.Toplevel(self.root)
chat_win.title(f"与 {nickname} 聊天")
chat_win.geometry("500x600")
chat_win.resizable(False, False)
# 设置窗口图标
try:
chat_win.iconbitmap("chat_icon.ico")
except:
pass
# 标题栏
title_frame = tk.Frame(chat_win, bg="#07C160", height=50)
title_frame.pack(fill=tk.X)
# 返回按钮
back_btn = tk.Button(
title_frame,
text="←",
font=("Arial", 12),
bg="#07C160",
fg="white",
relief=tk.FLAT,
command=chat_win.destroy
)
back_btn.pack(side=tk.LEFT, padx=10)
# 联系人名称
name_label = tk.Label(
title_frame,
text=nickname,
font=("Arial", 14, "bold"),
fg="white",
bg="#07C160"
)
name_label.pack(side=tk.LEFT, padx=5)
# 在线状态
status = "在线" if contact_id in list(self.online_users.keys()) else "离线"
status_label = tk.Label(
title_frame,
text=status,
font=("Arial", 9),
fg="#CCFFCC" if status == "在线" else "#CCCCCC",
bg="#07C160"
)
status_label.pack(side=tk.LEFT, padx=2)
# 消息显示区域
msg_frame = tk.Frame(chat_win, bg="#EDEDED")
msg_frame.pack(fill=tk.BOTH, expand=True)
# 使用Canvas实现可滚动的消息区域
msg_canvas = tk.Canvas(msg_frame, bg="#EDEDED", highlightthickness=0)
scrollbar = ttk.Scrollbar(msg_frame, orient="vertical", command=msg_canvas.yview)
scrollable_frame = tk.Frame(msg_canvas, bg="#EDEDED")
scrollable_frame.bind(
"<Configure>",
lambda e: msg_canvas.configure(scrollregion=msg_canvas.bbox("all"))
)
msg_canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
msg_canvas.configure(yscrollcommand=scrollbar.set)
msg_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 输入区域
input_frame = tk.Frame(chat_win, bg="#F0F0F0")
input_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
# 表情按钮
emoji_btn = tk.Button(
input_frame,
text="😀",
font=("Arial", 12),
bg="#F0F0F0",
relief=tk.FLAT
)
emoji_btn.pack(side=tk.LEFT, padx=(0, 5))
input_entry = tk.Text(
input_frame,
height=3,
font=("Arial", 11),
bg="white",
relief=tk.SOLID,
borderwidth=1
)
input_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
# 发送按钮
send_btn = tk.Button(
input_frame,
text="发送",
bg="#07C160",
fg="white",
width=8,
height=2,
command=lambda: self.send_message_func(
input_entry, scrollable_frame,
msg_canvas, contact_id, nickname
)
)
send_btn.pack(side=tk.RIGHT)
# 加载历史消息
self.load_chat_history(scrollable_frame, contact_id, nickname)
# 滚动到底部
msg_canvas.yview_moveto(1.0)
# 自动回复线程
self.start_auto_reply_thread(contact_id, scrollable_frame, msg_canvas)
def load_chat_history(self, parent, contact_id, nickname):
"""加载聊天历史"""
# 获取历史消息
self.cursor.execute('''
SELECT sender_id, content, timestamp
FROM messages
WHERE (sender_id = ? AND receiver_id = ?)
OR (sender_id = ? AND receiver_id = ?)
ORDER BY timestamp ASC
''', (self.current_user['id'], contact_id, contact_id, self.current_user['id']))
messages = self.cursor.fetchall()
# 创建日期分组字典
grouped_messages = {}
for msg in messages:
sender_id, content, timestamp = msg
date_str = timestamp.split()[0] # 提取日期部分
if date_str not in grouped_messages:
grouped_messages[date_str] = []
grouped_messages[date_str].append(msg)
# 显示消息
for date_str, msgs in grouped_messages.items():
# 添加日期分隔线
date_frame = tk.Frame(parent, bg="#EDEDED")
date_frame.pack(fill=tk.X, pady=10)
date_label = tk.Label(
date_frame,
text=date_str,
font=("Arial", 9),
fg="#888888",
bg="#D0D0D0",
padx=10,
pady=2
)
date_label.pack()
# 显示当天的消息
for msg in msgs:
sender_id, content, timestamp = msg
time_str = timestamp.split()[1][:5] # 提取时间部分
# 决定消息位置(左或右)
align = "right" if sender_id == self.current_user['id'] else "left"
# 创建消息气泡
bubble_frame = tk.Frame(parent, bg="#EDEDED")
bubble_frame.pack(fill=tk.X, padx=10, pady=3)
if align == "right":
# 空白占位
tk.Frame(bubble_frame, bg="#EDEDED", width=100).pack(side=tk.LEFT, fill=tk.X, expand=True)
# 时间标签
time_label = tk.Label(
bubble_frame,
text=time_str,
font=("Arial", 8),
fg="#888888",
bg="#EDEDED"
)
time_label.pack(side=tk.RIGHT, padx=(0, 5))
# 消息气泡
bubble = tk.Label(
bubble_frame,
text=content,
font=("Arial", 11),
bg="#95EC69", # 自己发送的气泡颜色
fg="#000000",
wraplength=300,
padx=12,
pady=8,
justify=tk.LEFT
)
bubble.pack(side=tk.RIGHT)
else:
# 联系人头像
self.cursor.execute("SELECT avatar FROM users WHERE id = ?", (contact_id,))
avatar = self.cursor.fetchone()[0]
avatar_label = tk.Label(
bubble_frame,
text=avatar,
font=("Arial", 14),
bg="#EDEDED",
padx=(0, 5)
)
avatar_label.pack(side=tk.LEFT)
# 消息气泡
bubble = tk.Label(
bubble_frame,
text=content,
font=("Arial", 11),
bg="#FFFFFF", # 对方发送的气泡颜色
fg="#000000",
wraplength=300,
padx=12,
pady=8,
justify=tk.LEFT
)
bubble.pack(side=tk.LEFT, fill=tk.X, expand=True)
# 时间标签
time_label = tk.Label(
bubble_frame,
text=time_str,
font=("Arial", 8),
fg="#888888",
bg="#EDEDED",
padx=(5, 0)
)
time_label.pack(side=tk.LEFT)
# 空白占位
tk.Frame(bubble_frame, bg="#EDEDED", width=100).pack(side=tk.LEFT, fill=tk.X, expand=True)
def send_message_func(self, input_entry, parent, msg_canvas, contact_id, nickname):
"""发送消息处理函数"""
content = input_entry.get("1.0", tk.END).strip()
if not content:
return
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
# 保存到数据库
self.cursor.execute('''
INSERT INTO messages (sender_id, receiver_id, content, timestamp)
VALUES (?, ?, ?, ?)
''', (self.current_user['id'], contact_id, content, timestamp))
self.conn.commit()
# 更新消息显示
bubble_frame = tk.Frame(parent, bg="#EDEDED")
bubble_frame.pack(fill=tk.X, padx=10, pady=3)
# 空白占位
tk.Frame(bubble_frame, bg="#EDEDED", width=100).pack(side=tk.LEFT, fill=tk.X, expand=True)
# 时间标签
time_str = timestamp.split()[1][:5]
time_label = tk.Label(
bubble_frame,
text=time_str,
font=("Arial", 8),
fg="#888888",
bg="#EDEDED"
)
time_label.pack(side=tk.RIGHT, padx=(0, 5))
# 消息气泡(右侧显示)
bubble = tk.Label(
bubble_frame,
text=content,
font=("Arial", 11),
bg="#95EC69", # 自己发送的气泡颜色
fg="#000000",
wraplength=300,
padx=12,
pady=8,
justify=tk.LEFT
)
bubble.pack(side=tk.RIGHT)
# 滚动到底部
msg_canvas.yview_moveto(1.0)
# 清空输入框
input_entry.delete("1.0", tk.END)
# 触发自动回复
threading.Thread(
target=self.simulate_reply,
args=(contact_id, nickname, content, parent, msg_canvas),
daemon=True
).start()
def simulate_reply(self, contact_id, nickname, received_msg, parent, msg_canvas):
"""模拟对方回复消息"""
time.sleep(random.uniform(1.0, 3.0)) # 随机延迟回复
# 智能回复逻辑
keywords = {
"你好": ["你好!", "很高兴见到你!", "你好呀!"],
"在吗": ["在的,有什么事吗?", "在的,请说~", "在呢"],
"谢谢": ["不客气!", "应该的!", "随时为你服务"],
"吃饭": ["还没呢", "刚吃过", "准备去吃"],
"周末": ["周末有空", "周末可能有事", "周末可以安排"]
}
reply = ""
for word, replies in keywords.items():
if word in received_msg:
reply = random.choice(replies)
break
if not reply:
replies = [
"好的,我知道了",
"谢谢你的消息",
"稍后回复你",
"这个问题我需要考虑一下",
"明天见面聊吧",
"收到!",
"嗯,明白了",
"这个想法不错"
]
reply = random.choice(replies)
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
# 在主线程更新UI
self.root.after(0, lambda: self.show_reply(
contact_id, nickname, reply, timestamp, parent, msg_canvas
))
# 保存到数据库
self.cursor.execute('''
INSERT INTO messages (sender_id, receiver_id, content, timestamp)
VALUES (?, ?, ?, ?)
''', (contact_id, self.current_user['id'], reply, timestamp))
self.conn.commit()
def show_reply(self, contact_id, nickname, content, timestamp, parent, msg_canvas):
"""显示对方回复消息"""
bubble_frame = tk.Frame(parent, bg="#EDEDED")
bubble_frame.pack(fill=tk.X, padx=10, pady=3)
# 联系人头像
self.cursor.execute("SELECT avatar FROM users WHERE id = ?", (contact_id,))
avatar = self.cursor.fetchone()[0]
avatar_label = tk.Label(
bubble_frame,
text=avatar,
font=("Arial", 14),
bg="#EDEDED",
padx=(0, 5)
)
avatar_label.pack(side=tk.LEFT)
# 消息气泡
bubble = tk.Label(
bubble_frame,
text=content,
font=("Arial", 11),
bg="#FFFFFF", # 对方发送的气泡颜色
fg="#000000",
wraplength=300,
padx=12,
pady=8,
justify=tk.LEFT
)
bubble.pack(side=tk.LEFT, fill=tk.X, expand=True)
# 时间标签
time_str = timestamp.split()[1][:5]
time_label = tk.Label(
bubble_frame,
text=time_str,
font=("Arial", 8),
fg="#888888",
bg="#EDEDED",
padx=(5, 0)
)
time_label.pack(side=tk.LEFT)
# 空白占位
tk.Frame(bubble_frame, bg="#EDEDED", width=100).pack(side=tk.LEFT, fill=tk.X, expand=True)
# 滚动到底部
msg_canvas.yview_moveto(1.0)
# ... 其他方法保持不变 ...
>
最新发布