二手拍卖系统设计方案【完整版】
系统概述
基于Python Tkinter + MySQL的图形化界面二手拍卖系统,提供完整的在线拍卖功能,包括用户注册登录、商品管理、竞拍交易和后台管理。
功能模块详细设计
1. 用户认证模块
1.1 用户注册
- 首次使用需提供用户名、密码、真实姓名、联系方式等基本信息
- 密码需加密存储(使用SHA-256哈希+盐值)
- 用户名唯一性验证
- 邮箱/手机号验证(可选)
1.2 用户登录
- 使用注册的用户名和密码登录
- 内置管理员账号:
admin/admin123
- 登录失败次数限制(3次失败后锁定账户15分钟)
- 记住登录状态(Cookie/Session机制)
2. 商品管理模块
2.1 商品操作
- 发布商品:上传商品图片、填写标题、描述、起拍价、拍卖截止时间
- 编辑商品:修改未开始拍卖的商品信息
- 下架商品:手动结束拍卖或标记为已售出
- 商品分类:支持多级分类(如电子产品、服装、家居等)
2.2 商品状态管理
- 上架/下架状态
- 拍卖中/已结束/已售出状态
- 违规商品标记(如虚假描述)
3. 拍卖交易模块
3.1 竞拍功能
- 出价功能:用户可对拍卖中的商品出价
- 自动出价代理:设置最高限价,系统自动出价
- 出价记录:实时显示当前最高出价和出价人
- 拍卖倒计时:显示剩余拍卖时间
3.2 交易流程
- 保证金:竞拍前需支付保证金(可配置比例)
- 成交确认:拍卖结束后自动确认最高出价者
- 支付:支持模拟支付(或集成第三方支付接口)
- 发货/收货:买卖双方确认交易状态
- 评价:交易完成后双方互评
4. 查询功能模块
4.1 商品查询
- 按关键词搜索
- 按分类筛选
- 按价格区间筛选
- 按拍卖状态筛选(进行中/即将开始/已结束)
4.2 订单查询
- 我的竞拍记录
- 我的购买记录
- 我的销售记录
- 交易状态筛选
4.3 管理员查询
- 查看所有用户
- 查看所有商品
- 查看所有交易记录
- 异常交易监控
5. 个人信息管理
- 查看和修改个人信息
- 修改登录密码(需验证原密码)
- 绑定/解绑支付方式(模拟)
- 查看个人信用评分
6. 管理员功能模块
6.1 用户管理
- 添加新用户
- 编辑用户信息
- 删除用户(需确认)
- 用户封禁/解封
6.2 商品管理
- 查看所有商品
- 强制下架违规商品
- 商品审核(防止虚假信息)
6.3 交易管理
- 查看所有交易记录
- 处理交易纠纷
- 生成交易报表
6.4 系统管理
- 系统参数配置(如佣金比例、保证金比例)
- 公告管理
- 统计报表
- 用户活跃度分析
- 商品交易量统计
- 收入统计
二手拍卖系统功能界面:
数据库添加数据:auctionSystem.sql
CREATE DATABASE IF NOT EXISTS auction_system;
USE auction_system;
-- 用户表
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20),
address TEXT,
is_admin BOOLEAN DEFAULT FALSE,
last_login VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 商品表
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
category VARCHAR(50),
base_price DECIMAL(10, 2) NOT NULL,
current_price DECIMAL(10, 2) NOT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '商品价格',
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
status ENUM('pending', 'active', 'sold', 'expired') DEFAULT 'pending',
seller_id INT NOT NULL,
image_path VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (seller_id) REFERENCES users(user_id)
);
-- 订单表
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10,2) NOT NULL,
product_id INT NOT NULL,
buyer_id INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
current_price DECIMAL(10, 2) NOT NULL,
status ENUM('pending', 'paid', 'shipped', 'completed', 'cancelled') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
payment_time TIMESTAMP NULL DEFAULT NULL COMMENT '支付时间',
shipping_time TIMESTAMP NULL DEFAULT NULL COMMENT '订单完成时间',
completion_time TIMESTAMP NULL DEFAULT NULL COMMENT '订单完成时间',
FOREIGN KEY (product_id) REFERENCES products(product_id),
FOREIGN KEY (buyer_id) REFERENCES users(user_id)
);
-- 创建订单商品表
CREATE TABLE IF NOT EXISTS order_items (
item_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL DEFAULT 1,
price DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 竞拍记录表
CREATE TABLE bids (
bid_id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL,
user_id INT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
bid_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (product_id) REFERENCES products(product_id),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
CREATE TABLE login_attempts (
attempt_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
attempt_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
success BOOLEAN NOT NULL
);
数据库连接模块: db.py
# db.py
import pymysql
from pymysql.cursors import DictCursor
class Database:
def __init__(self):
self.connection = pymysql.connect(
host='localhost',
user='root', #换成你的数据库用户
password='123456', #换成你的数据库密码
database='auction_system',
charset='utf8mb4',
cursorclass=DictCursor
)
def execute_query(self, query, params=None, fetch_one=False):
"""
执行SQL查询
:param query: SQL语句
:param params: 查询参数(tuple/list/dict)
:param fetch_one: 是否只获取一条记录
:return: 查询结果
"""
with self.connection.cursor() as cursor:
cursor.execute(query, params or ())
result = cursor.fetchone() if fetch_one else cursor.fetchall()
self.connection.commit()
return result
def close(self):
"""关闭数据库连接"""
if hasattr(self, 'connection'):
self.connection.close()
# 创建全局数据库实例
db = Database()
登录模块:login.py
from db import db # 直接使用db.execute_query()
import tkinter as tk
from tkinter import messagebox
import ttkbootstrap as tb
from ttkbootstrap.constants import *
import hashlib
class LoginWindow:
def __init__(self, root, app):
self.root = root
self.app = app
self.create_widgets()
def create_widgets(self):
# 主框架
main_frame = tb.Frame(self.root)
main_frame.pack(expand=True, fill='both', padx=50, pady=50)
# 标题
title_label = tb.Label(
main_frame,
text="\n\n\n\n用户登录系统",
font=('Helvetica', 25, 'bold'),
bootstyle=PRIMARY
)
title_label.pack(pady=20)
# 登录表单框架
form_frame = tb.Frame(main_frame)
form_frame.pack(pady=20)
# 用户名
tb.Label(form_frame, text="用户名:", bootstyle=PRIMARY).grid(row=1, column=1, padx=5, pady=5, sticky='e')
self.username_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.username_entry.grid(row=1, column=2, padx=5, pady=5)
# 密码
tb.Label(form_frame, text="密码:", bootstyle=PRIMARY).grid(row=2, column=1, padx=5, pady=5, sticky='e')
self.password_entry = tb.Entry(form_frame, show="*", bootstyle=PRIMARY)
self.password_entry.grid(row=2, column=2, padx=5, pady=5)
# 登录按钮
login_btn = tb.Button(
main_frame,
text=" 登 录 ",
bootstyle=SUCCESS,
command=self.login
)
login_btn.pack(pady=10)
# 版权信息
tb.Label(
main_frame,
text="\n\n 海南平安网络科技有限公司 \n\n版权所有©郭平安 二手拍卖系统 v1.0",
bootstyle="secondary",
font=('TkDefaultFont', 10)
).pack(side='bottom', pady=20)
# 注册链接(兼容实现)
self._create_register_link(main_frame)
def _create_register_link(self, parent):
"""创建注册链接(兼容所有版本)"""
link_frame = tb.Frame(parent)
link_frame.pack()
tb.Label(link_frame, text="没有账号?").pack(side='left')
register_link = tb.Label(
link_frame,
text="立即注册",
foreground="#1a73e8", # 蓝色
font=('TkDefaultFont', 10, 'underline'),
cursor="hand2"
)
register_link.pack(side='left', padx=5)
# 事件绑定
register_link.bind("<Button-1>", lambda e: self.app.show_register())
# 悬停效果
register_link.bind("<Enter>", lambda e: register_link.config(foreground="#0d47a1"))
register_link.bind("<Leave>", lambda e: register_link.config(foreground="#1a73e8"))
def login(self):
username = self.username_entry.get()
password = self.password_entry.get()
if not username or not password:
messagebox.showerror("错误", "用户名和密码不能为空!")
return
query = "SELECT * FROM users WHERE username = %s AND password = %s"
user = db.execute_query(query, (username, password), fetch_one=True)
if user:
self.app.current_user = user
if user['is_admin']:
self.app.show_admin_dashboard()
else:
self.app.show_user_dashboard()
else:
messagebox.showerror("错误", "用户名或密码错误!")
注册模块:register.py
from db import db # 直接使用db.execute_query()
import tkinter as tk
from tkinter import messagebox
import ttkbootstrap as tb
from ttkbootstrap.constants import *
class RegisterWindow:
def __init__(self, root, app):
self.root = root
self.app = app
self.create_widgets()
def create_widgets(self):
# 主框架
main_frame = tb.Frame(self.root)
main_frame.pack(expand=True, fill='both', padx=50, pady=50)
# 标题
title_label = tb.Label(
main_frame,
text="\n\n\n用户注册系统",
font=('Helvetica', 20, 'bold'),
bootstyle=PRIMARY
)
title_label.pack(pady=20)
# 注册表单框架
form_frame = tb.Frame(main_frame)
form_frame.pack(pady=20)
# 用户名
tb.Label(form_frame, text="用户名:", bootstyle=PRIMARY).grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.username_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.username_entry.grid(row=0, column=1, padx=5, pady=5)
# 密码
tb.Label(form_frame, text="密码:", bootstyle=PRIMARY).grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.password_entry = tb.Entry(form_frame, show="*", bootstyle=PRIMARY)
self.password_entry.grid(row=1, column=1, padx=5, pady=5)
# 确认密码
tb.Label(form_frame, text="确认密码:", bootstyle=PRIMARY).grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.confirm_password_entry = tb.Entry(form_frame, show="*", bootstyle=PRIMARY)
self.confirm_password_entry.grid(row=2, column=1, padx=5, pady=5)
# 邮箱
tb.Label(form_frame, text="邮箱:", bootstyle=PRIMARY).grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.email_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.email_entry.grid(row=3, column=1, padx=5, pady=5)
# 电话
tb.Label(form_frame, text="电话:", bootstyle=PRIMARY).grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.phone_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.phone_entry.grid(row=4, column=1, padx=5, pady=5)
# 地址
tb.Label(form_frame, text="地址:", bootstyle=PRIMARY).grid(row=5, column=0, padx=5, pady=5, sticky='e')
self.address_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.address_entry.grid(row=5, column=1, padx=5, pady=5)
# 注册按钮
register_btn = tb.Button(
main_frame,
text=" 注 册 ",
bootstyle=SUCCESS,
command=self.register
)
register_btn.pack(pady=10)
# 版权信息
tb.Label(
main_frame,
text="\n\n 海南平安网络科技有限公司 \n\n版权所有©郭平安 二手拍卖系统 v1.0",
bootstyle="secondary",
font=('TkDefaultFont', 10)
).pack(side='bottom', pady=20)
# 登录链接(兼容实现)
self._create_login_link(main_frame)
def _create_login_link(self, parent):
"""创建注册链接(兼容所有版本)"""
link_frame = tb.Frame(parent)
link_frame.pack()
tb.Label(link_frame, text="已有帐号?").pack(side='left')
login_link = tb.Label(
link_frame,
text="直接登录",
foreground="#1a73e8", # 蓝色
font=('TkDefaultFont', 10, 'underline'),
cursor="hand2"
)
login_link.pack(side='left', padx=5)
# 事件绑定
login_link.bind("<Button-1>", lambda e: self.app.show_login())
# 悬停效果
login_link.bind("<Enter>", lambda e: login_link.config(foreground="#0d47a1"))
login_link.bind("<Leave>", lambda e: login_link.config(foreground="#1a73e8"))
def register(self):
username = self.username_entry.get()
password = self.password_entry.get()
confirm_password = self.confirm_password_entry.get()
email = self.email_entry.get()
phone = self.phone_entry.get()
address = self.address_entry.get()
if not username or not password or not confirm_password or not email:
messagebox.showerror("错误", "必填字段不能为空")
return
if password != confirm_password:
messagebox.showerror("错误", "两次输入的密码不一致")
return
# 检查用户名是否已存在
query = "SELECT * FROM users WHERE username = %s"
if db.execute_query(query, (username,), fetch_one=True):
messagebox.showerror("错误", "用户名已存在")
return
# 检查邮箱是否已存在
query = "SELECT * FROM users WHERE email = %s"
if db.execute_query(query, (email,), fetch_one=True):
messagebox.showerror("错误", "邮箱已被注册")
return
# 插入新用户
query = """
INSERT INTO users (username, password, email, phone, address, is_admin)
VALUES (%s, %s, %s, %s, %s, %s)
"""
db.execute_query(query, (username, password, email, phone, address, False))
messagebox.showinfo("成功", "注册成功!请登录")
self.app.show_login()
用户模块:user.py
from typing import List, Dict
from distlib import logger
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import db
import pit
""""用户界面"""
import tkinter as tk
from tkinter import messagebox, ttk, filedialog
import ttkbootstrap as tb
from ttkbootstrap.constants import *
from datetime import datetime, timedelta
from PIL import Image, ImageTk
import os
from datetime import datetime, timedelta # 添加timedelta导入
from db import db # 从db模块导入db实例
from pytz import timezone # 需要安装:pip install pytz
from tkcalendar import DateEntry # 需要安装:pip install tkcalendar
import logging
logging.basicConfig(filename='app.log', level=logging.INFO)
class UserDashboard:
def __init__(self, root, app):
self.root = root
self.app = app
self.current_user = app.current_user
self.create_widgets()
self.load_products()
self.load_my_products()
self.load_my_orders()
self.load_my_bids()
def load_users(self):
"""加载用户数据"""
try:
query = "SELECT user_id, username, email, is_admin FROM users"
users = db.execute_query(query) # 使用全局db实例
# 清空现有数据
for item in self.user_tree.get_children():
self.user_tree.delete(item)
# 添加新数据
for user in users:
self.user_tree.insert('', 'end', values=(
user['user_id'],
user['username'],
user['email'],
'是' if user['is_admin'] else '否'
))
except Exception as e:
messagebox.showerror("数据库错误", f"加载用户失败: {str(e)}!")
def place_bid(self):
"""处理用户出价逻辑"""
selected_item = self.product_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个商品!")
return
# 获取选中商品信息
item = self.product_tree.item(selected_item[0])
product_id = item['values'][0]
current_price = float(item['values'][3].replace('¥', ''))
# 创建出价对话框
bid_window = tb.Toplevel(self.root)
bid_window.title("出价")
# 主窗口居中
self.width = 500
self.height = 400
# 获取窗口屏幕尺寸参数
self.screenwidth = self.root.winfo_screenwidth()
self.screenheight = self.root.winfo_screenheight()
self.screen_geo = "%dx%d+%d+%d" % (
self.width, self.height, (self.screenwidth - self.width) / 2, (self.screenheight - self.height) / 2)
bid_window.geometry(self.screen_geo)
# 设置主窗口不可拉伸
bid_window.resizable(False, False)
# bid_window.geometry("300x200")
# 当前价格显示
tb.Label(
bid_window,
text=f"当前价格: ¥{current_price:.2f}",
font=('Helvetica', 12)
).pack(pady=10)
# 出价输入框
bid_frame = tb.Frame(bid_window)
bid_frame.pack(pady=10)
tb.Label(bid_frame, text="您的出价:").pack(side='left')
self.bid_amount_entry = tb.Entry(bid_frame)
self.bid_amount_entry.pack(side='left', padx=5)
tb.Label(bid_frame, text="元").pack(side='left')
# 出价按钮
tb.Button(
bid_window,
text="确认出价",
bootstyle=SUCCESS,
command=lambda: self._submit_bid(product_id, current_price, bid_window)
).pack(pady=20)
def _submit_bid(self, product_id, current_price, window):
"""提交出价到数据库"""
try:
bid_amount = float(self.bid_amount_entry.get())
if bid_amount <= current_price:
messagebox.showerror("错误", "出价必须高于当前价格!")
return
# 执行数据库操作
db.execute_query(
"UPDATE products SET current_price = %s WHERE product_id = %s",
(bid_amount, product_id)
)
# 记录竞拍记录
db.execute_query(
"INSERT INTO bids (product_id, user_id, amount) VALUES (%s, %s, %s)",
(product_id, self.current_user['user_id'], bid_amount)
)
messagebox.showinfo("成功", "出价成功!")
window.destroy()
self.load_products() # 刷新商品列表
except ValueError:
messagebox.showerror("错误", "请输入有效的金额!")
except Exception as e:
messagebox.showerror("数据库错误", f"出价失败: {str(e)}!")
def create_widgets(self):
# 主框架
self.main_frame = tb.Frame(self.root)
self.main_frame.pack(fill='both', expand=True)
# 侧边栏
self.sidebar = tb.Frame(self.main_frame, bootstyle=SECONDARY, width=200)
self.sidebar.pack(side='left', fill='y')
# 顶部栏
self.topbar = tb.Frame(self.main_frame, bootstyle=PRIMARY, height=50)
self.topbar.pack(side='top', fill='x')
# 内容区域
self.content = tb.Frame(self.main_frame)
self.content.pack(side='right', fill='both', expand=True)
# 侧边栏内容
tb.Label(
self.sidebar,
text="用户面板",
font=('Helvetica', 12, 'bold'),
bootstyle=INVERSE
).pack(pady=20)
# 导航按钮
nav_buttons = [
("浏览商品", self.show_products),
("我的商品", self.show_my_products),
("我的订单", self.show_my_orders),
("我的竞拍", self.show_my_bids),
("发布商品", self.show_add_product),
("账户设置", self.show_account_settings),
("退出登录", self.logout)
]
for text, command in nav_buttons:
btn = tb.Button(
self.sidebar,
text=text,
bootstyle=(OUTLINE, LIGHT),
command=command
)
btn.pack(fill='x', padx=10, pady=5)
# 顶部栏内容
tb.Label(
self.topbar,
text=f"欢迎, {self.current_user['username']}",
font=('Helvetica', 12),
bootstyle=INVERSE
).pack(side='left', padx=20)
# 默认显示商品浏览
self.show_products()
def show_products(self):
self.clear_content()
# 操作按钮框架
button_frame = tb.Frame(self.content)
button_frame.pack(pady=10)
tb.Button(
button_frame,
text="出价",
bootstyle=SUCCESS,
command=self.place_bid # 确保使用正确的方法名
).pack(side='left', padx=5)
# 商品浏览标题
tb.Label(
self.content,
text="浏览商品",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 搜索框
search_frame = tb.Frame(self.content)
search_frame.pack(fill='x', padx=10, pady=5)
self.product_search_entry = tb.Entry(search_frame, bootstyle=PRIMARY)
self.product_search_entry.pack(side='left', padx=5, expand=True, fill='x')
tb.Button(
search_frame,
text="搜索",
bootstyle=INFO,
command=self.search_products
).pack(side='left', padx=5)
# 类别筛选
category_frame = tb.Frame(self.content)
category_frame.pack(fill='x', padx=10, pady=5)
tb.Label(category_frame, text="类别:", bootstyle=PRIMARY).pack(side='left', padx=5)
self.category_combobox = tb.Combobox(
category_frame,
values=['全部', '电子产品', '家居用品', '服装', '书籍', '其他']
)
self.category_combobox.pack(side='left', padx=5)
self.category_combobox.set('全部')
tb.Button(
category_frame,
text="筛选",
bootstyle=INFO,
command=self.filter_products
).pack(side='left', padx=5)
# 商品表格
columns = ('id', 'name', 'category', 'current_price', 'status', 'seller', 'end_time')
self.product_tree = ttk.Treeview(
self.content,
columns=columns,
show='headings',
height=15
)
# 设置列
self.product_tree.heading('id', text='ID', anchor='center')
self.product_tree.heading('name', text='商品名称', anchor='center')
self.product_tree.heading('category', text='类别', anchor='center')
self.product_tree.heading('current_price', text='当前价', anchor='center')
self.product_tree.heading('status', text='状态', anchor='center')
self.product_tree.heading('seller', text='卖家', anchor='center')
self.product_tree.heading('end_time', text='结束时间', anchor='center')
# 设置列宽
self.product_tree.column('id', width=50, anchor='center')
self.product_tree.column('name', width=120, anchor='center')
self.product_tree.column('category', width=80, anchor='center')
self.product_tree.column('current_price', width=80, anchor='center')
self.product_tree.column('status', width=80, anchor='center')
self.product_tree.column('seller', width=100, anchor='center')
self.product_tree.column('end_time', width=120, anchor='center')
self.product_tree.pack(fill='both', expand=True, padx=10, pady=5)
# 操作按钮
button_frame = tb.Frame(self.content)
button_frame.pack(pady=10)
tb.Button(
button_frame,
text="查看详情",
bootstyle=INFO,
command=self.view_product_details
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="参与竞拍",
bootstyle=SUCCESS,
command=self.place_bid
).pack(side='left', padx=5)
def show_my_products(self):
self.clear_content()
# 我的商品标题
tb.Label(
self.content,
text="我的商品",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 我的商品表格
columns = ('id', 'name', 'category', 'current_price', 'status', 'start_time', 'end_time')
self.my_product_tree = ttk.Treeview(
self.content,
columns=columns,
show='headings',
height=15
)
# 设置列
self.my_product_tree.heading('id', text='ID', anchor='center')
self.my_product_tree.heading('name', text='商品名称', anchor='center')
self.my_product_tree.heading('category', text='类别', anchor='center')
self.my_product_tree.heading('current_price', text='当前价', anchor='center')
self.my_product_tree.heading('status', text='状态', anchor='center')
self.my_product_tree.heading('start_time', text=' 开始时间 ', anchor='center')
self.my_product_tree.heading('end_time', text=' 结束时间 ')
# 设置列宽
self.my_product_tree.column('id', width=50, anchor='center')
self.my_product_tree.column('name', width=120, anchor='center')
self.my_product_tree.column('category', width=80, anchor='center')
self.my_product_tree.column('current_price', width=80, anchor='center')
self.my_product_tree.column('status', width=80, anchor='center')
self.my_product_tree.column('start_time', width=120, anchor='center')
self.my_product_tree.column('end_time', width=120, anchor='center')
self.my_product_tree.pack(fill='both', expand=True, padx=10, pady=5)
# 操作按钮
button_frame = tb.Frame(self.content)
button_frame.pack(pady=10)
# 在 show_my_products 方法中确保这样绑定
tb.Button(
button_frame,
text="查看详情",
bootstyle=INFO,
command=self.view_my_product_details # 确保方法名一致
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="编辑商品",
bootstyle=WARNING,
command=self.edit_product
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="删除商品",
bootstyle=DANGER,
command=self.delete_product
).pack(side='left', padx=5)
# 加载我的商品数据
self.load_my_products()
#删除商品
def delete_product(self):
"""删除选中的商品"""
selected_item = self.my_product_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个商品!")
return
# 获取选中商品信息
item = self.my_product_tree.item(selected_item[0])
product_id = item['values'][0]
product_name = item['values'][1]
# 确认对话框
if not messagebox.askyesno(
"确认删除",
f"确定要删除商品 [{product_name}] 吗?\n此操作不可撤销!"
):
return
try:
# 检查商品状态
product = db.execute_query(
"SELECT status FROM products WHERE product_id = %s ORDER BY product_id"
(product_id,),
fetch_one=True
)
if not product:
raise ValueError("商品不存在!")
if product['status'] == 'sold':
raise ValueError("已售出商品不能删除!")
# 执行删除操作
db.execute_query(
"DELETE FROM products WHERE product_id = %s",
(product_id,)
)
# 删除关联的竞拍记录
db.execute_query(
"DELETE FROM bids WHERE product_id = %s",
(product_id,)
)
messagebox.showinfo("成功", "商品已删除!")
self.load_my_products() # 刷新商品列表
except Exception as e:
messagebox.showerror("删除失败!", str(e))
#编辑商品
def edit_product(self):
"""编辑选中的商品"""
selected_item = self.my_product_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个商品!")
return
# 获取选中商品ID
item = self.my_product_tree.item(selected_item[0])
product_id = item['values'][0]
# 查询商品详细信息
product = db.execute_query(
"SELECT * FROM products WHERE product_id = %s",
(product_id,),
fetch_one=True
)
if not product:
messagebox.showerror("错误", "未找到商品信息!")
return
# 创建编辑窗口
self.edit_window = tb.Toplevel(self.root)
self.edit_window.title("编辑商品")
# 主窗口居中
self.width = 800
self.height = 800
# 获取窗口屏幕尺寸参数
self.screenwidth = self.root.winfo_screenwidth()
self.screenheight = self.root.winfo_screenheight()
self.screen_geo = "%dx%d+%d+%d" % (self.width, self.height, (self.screenwidth - self.width) / 2, (self.screenheight - self.height) / 2)
self.edit_window.geometry(self.screen_geo)
# 设置主窗口不可拉伸
self.edit_window.resizable(False, False)
# self.edit_window.geometry("600x700")
# 表单框架
form_frame = tb.Frame(self.edit_window)
form_frame.pack(pady=20, padx=30, fill='both', expand=True)
# 商品名称
tb.Label(form_frame, text="商品名称:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.edit_name_entry = tb.Entry(form_frame)
self.edit_name_entry.grid(row=0, column=1, padx=5, pady=5, sticky='ew')
self.edit_name_entry.insert(0, product['name'])
# 商品描述
tb.Label(form_frame, text="商品描述:").grid(row=1, column=0, padx=5, pady=5, sticky='ne')
self.edit_desc_text = tk.Text(form_frame, height=5, width=30)
self.edit_desc_text.grid(row=1, column=1, padx=5, pady=5, sticky='ew')
self.edit_desc_text.insert('1.0', product['description'])
# 商品类别
tb.Label(form_frame, text="商品类别:").grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.edit_category_combobox = tb.Combobox(
form_frame,
values=['电子产品', '家居用品', '服装', '书籍', '其他']
)
self.edit_category_combobox.grid(row=2, column=1, padx=5, pady=5, sticky='ew')
self.edit_category_combobox.set(product['category'])
# 起拍价
tb.Label(form_frame, text="起拍价:").grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.edit_base_price_entry = tb.Entry(form_frame)
self.edit_base_price_entry.grid(row=3, column=1, padx=5, pady=5, sticky='ew')
self.edit_base_price_entry.insert(0, str(product['base_price']))
# 结束时间
tb.Label(form_frame, text="结束时间:").grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.edit_end_time_entry = tb.Entry(form_frame)
self.edit_end_time_entry.grid(row=4, column=1, padx=5, pady=5, sticky='ew')
self.edit_end_time_entry.insert(0, product['end_time'].strftime('%Y-%m-%d %H:%M:%S'))
# 商品图片
tb.Label(form_frame, text="商品图片:").grid(row=5, column=0, padx=5, pady=5, sticky='e')
self.edit_image_path_entry = tb.Entry(form_frame)
self.edit_image_path_entry.grid(row=5, column=1, padx=5, pady=5, sticky='ew')
self.edit_image_path_entry.insert(0, product['image_path'] or "")
tb.Button(
form_frame,
text="选择图片",
bootstyle=INFO,
command=self._select_edit_image
).grid(row=5, column=2, padx=5, pady=5)
# 图片预览
self.edit_image_preview = tb.Label(form_frame)
self.edit_image_preview.grid(row=6, column=1, pady=5)
if product['image_path']:
self._show_image_preview(product['image_path'], self.edit_image_preview)
# 按钮框架
button_frame = tb.Frame(self.edit_window)
button_frame.pack(pady=20)
# 保存按钮
tb.Button(
button_frame,
text="保存修改",
bootstyle=SUCCESS,
command=lambda: self._save_product_changes(product_id)
).pack(side='left', padx=10)
# 取消按钮
tb.Button(
button_frame,
text="取消",
bootstyle=DANGER,
command=self.edit_window.destroy
).pack(side='left', padx=10)
def _select_edit_image(self):
"""选择新的商品图片"""
file_path = filedialog.askopenfilename(
title="选择商品图片",
filetypes=[("Image Files", "*.png *.jpg *.jpeg")]
)
if file_path:
self.edit_image_path_entry.delete(0, tk.END)
self.edit_image_path_entry.insert(0, file_path)
self._show_image_preview(file_path, self.edit_image_preview)
def _show_image_preview(self, image_path, label_widget):
"""显示图片预览"""
try:
from PIL import Image, ImageTk
image = Image.open(image_path)
image.thumbnail((200, 200))
photo = ImageTk.PhotoImage(image)
label_widget.config(image=photo)
label_widget.image = photo # 保持引用
except Exception as e:
messagebox.showerror("错误", f"无法加载图片: {str(e)}")
def _save_product_changes(self, product_id):
"""保存商品修改到数据库"""
try:
# 获取表单数据
name = self.edit_name_entry.get()
description = self.edit_desc_text.get("1.0", tk.END).strip()
category = self.edit_category_combobox.get()
base_price = float(self.edit_base_price_entry.get())
end_time = self.edit_end_time_entry.get()
image_path = self.edit_image_path_entry.get()
# 数据验证
if not all([name, description, category, base_price > 0]):
raise ValueError("必填字段不能为空且价格必须大于0")
# 更新数据库
query = """
UPDATE products
SET name = %s,
description = %s,
category = %s,
base_price = %s,
end_time = %s,
image_path = %s
WHERE product_id = %s
ORDER BY product_id
"""
db.execute_query(
query,
(name, description, category, base_price, end_time, image_path, product_id)
)
messagebox.showinfo("成功", "商品信息已更新")
self.edit_window.destroy()
self.load_my_products() # 刷新商品列表
except ValueError as e:
messagebox.showerror("输入错误", str(e))
except Exception as e:
messagebox.showerror("数据库错误", f"更新失败: {str(e)}")
"""我的竞拍界面"""
def show_my_bids(self):
self.clear_content()
# 我的竞拍标题
tb.Label(
self.content,
text="我的竞拍",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 我的竞拍表格
columns = ('id', 'product', 'amount', 'bid_time', 'status')
self.my_bid_tree = ttk.Treeview(
self.content,
columns=columns,
show='headings',
height=15
)
# 设置列
self.my_bid_tree.heading('id', text='ID', anchor='center')
self.my_bid_tree.heading('product', text='商品', anchor='center')
self.my_bid_tree.heading('amount', text='出价', anchor='center')
self.my_bid_tree.heading('bid_time', text='出价时间', anchor='center')
self.my_bid_tree.heading('status', text='状态', anchor='center')
# 设置列宽
self.my_bid_tree.column('id', width=50, anchor='center')
self.my_bid_tree.column('product', width=150, anchor='center')
self.my_bid_tree.column('amount', width=80, anchor='center')
self.my_bid_tree.column('bid_time', width=120, anchor='center')
self.my_bid_tree.column('status', width=80, anchor='center')
self.my_bid_tree.pack(fill='both', expand=True, padx=10, pady=5)
# 加载我的竞拍数据
self.load_my_bids()
def show_add_product(self):
self.clear_content()
# 添加商品标题
tb.Label(
self.content,
text="发布新商品",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 表单框架
form_frame = tb.Frame(self.content)
form_frame.pack(pady=10)
# 商品名称
tb.Label(form_frame, text="商品名称:", bootstyle=PRIMARY).grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.product_name_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.product_name_entry.grid(row=0, column=1, padx=5, pady=5)
# 商品描述
tb.Label(form_frame, text="商品描述:", bootstyle=PRIMARY).grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.product_desc_entry = tb.Text(form_frame, height=5, width=30)
self.product_desc_entry.grid(row=1, column=1, padx=5, pady=5)
# 商品类别
tb.Label(form_frame, text="商品类别:", bootstyle=PRIMARY).grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.product_category_combobox = tb.Combobox(form_frame, values=['电子产品', '家居用品', '服装', '书籍', '其他'])
self.product_category_combobox.grid(row=2, column=1, padx=5, pady=5)
# 起拍价
tb.Label(form_frame, text="起拍价:", bootstyle=PRIMARY).grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.base_price_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.base_price_entry.grid(row=3, column=1, padx=5, pady=5)
# 开始时间(当前时间)
tb.Label(form_frame, text="开始时间:").grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.start_time_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.start_time_entry.grid(row=4, column=1, padx=5, pady=5)
self.start_time_entry.insert(0, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
# 结束时间(默认7天后)
tb.Label(form_frame, text="结束时间:").grid(row=5, column=0, padx=5, pady=5, sticky='e')
self.end_time_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.end_time_entry.grid(row=5, column=1, padx=5, pady=5)
self.end_time_entry.insert(0, (datetime.now() + timedelta(days=7)).strftime('%Y-%m-%d %H:%M:%S'))
# 商品图片
tb.Label(form_frame, text="商品图片:", bootstyle=PRIMARY).grid(row=6, column=0, padx=5, pady=5, sticky='e')
self.image_path_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.image_path_entry.grid(row=6, column=1, padx=5, pady=5)
tb.Button(
form_frame,
text="选择图片",
bootstyle=INFO,
command=self.select_image
).grid(row=6, column=2, padx=5, pady=5)
# 预览图片
self.image_preview_label = tb.Label(form_frame)
self.image_preview_label.grid(row=7, column=1, pady=5)
# 提交按钮
tb.Button(
self.content,
text="发布商品",
bootstyle=SUCCESS,
command=self.add_product
).pack(pady=10)
#时区
tz = timezone('Asia/Shanghai')
now = datetime.now(tz)
# 使用统一的时间格式常量
TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
now_str = datetime.now().strftime(TIME_FORMAT)
# 定义时间格式常量
TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
def reset_times(self):
"""重置时间为默认值"""
self._set_default_times()
def _set_default_times(self):
"""设置默认的开始和结束时间"""
now = datetime.now()
# 开始时间默认为当前时间
self.start_time_entry.delete(0, 'end')
self.start_time_entry.insert(0, now.strftime(self.TIME_FORMAT))
# 结束时间默认为7天后
self.end_time_entry.delete(0, 'end')
self.end_time_entry.insert(0, (now + timedelta(days=7)).strftime(self.TIME_FORMAT))
def validate_times(self):
"""验证时间输入的有效性"""
try:
start_time = datetime.strptime(self.start_time_entry.get(), self.TIME_FORMAT)
end_time = datetime.strptime(self.end_time_entry.get(), self.TIME_FORMAT)
if end_time <= start_time:
raise ValueError("结束时间必须晚于开始时间!")
if (end_time - start_time).days > 30:
raise ValueError("拍卖持续时间不能超过30天!")
return True
except ValueError as e:
messagebox.showerror("时间错误", str(e))
return False
def show_account_settings(self):
self.clear_content()
# 账户设置标题
tb.Label(
self.content,
text="账户设置",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 表单框架
form_frame = tb.Frame(self.content)
form_frame.pack(pady=10)
# 用户名
tb.Label(form_frame, text="用户名:", bootstyle=PRIMARY).grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.username_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.username_entry.grid(row=0, column=1, padx=5, pady=5)
self.username_entry.insert(0, self.current_user['username'])
self.username_entry.config(state='readonly')
# 邮箱
tb.Label(form_frame, text="邮箱:", bootstyle=PRIMARY).grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.email_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.email_entry.grid(row=1, column=1, padx=5, pady=5)
self.email_entry.insert(0, self.current_user['email'])
# 电话
tb.Label(form_frame, text="电话:", bootstyle=PRIMARY).grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.phone_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.phone_entry.grid(row=2, column=1, padx=5, pady=5)
self.phone_entry.insert(0, self.current_user['phone'] or '')
# 地址
tb.Label(form_frame, text="地址:", bootstyle=PRIMARY).grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.address_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.address_entry.grid(row=3, column=1, padx=5, pady=5)
self.address_entry.insert(0, self.current_user['address'] or '')
# 密码修改
tb.Label(form_frame, text="新密码:", bootstyle=PRIMARY).grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.new_password_entry = tb.Entry(form_frame, show="*", bootstyle=PRIMARY)
self.new_password_entry.grid(row=4, column=1, padx=5, pady=5)
tb.Label(form_frame, text="确认密码:", bootstyle=PRIMARY).grid(row=5, column=0, padx=5, pady=5, sticky='e')
self.confirm_password_entry = tb.Entry(form_frame, show="*", bootstyle=PRIMARY)
self.confirm_password_entry.grid(row=5, column=1, padx=5, pady=5)
# 保存按钮
tb.Button(
self.content,
text="保存更改",
bootstyle=SUCCESS,
command=self.update_account
).pack(pady=10)
def select_image(self):
file_path = filedialog.askopenfilename(
title="选择商品图片",
filetypes=[("Image Files", "*.png *.jpg *.jpeg")]
)
if file_path:
self.image_path_entry.delete(0, tk.END)
self.image_path_entry.insert(0, file_path)
# 显示预览
self.show_image_preview(file_path)
def show_image_preview(self, image_path):
try:
image = Image.open(image_path)
image.thumbnail((200, 200))
photo = ImageTk.PhotoImage(image)
self.image_preview_label.config(image=photo)
self.image_preview_label.image = photo # 保持引用
except Exception as e:
messagebox.showerror("错误", f"无法加载图片: {str(e)}")
def load_products(self):
query = """
SELECT p.product_id as id, p.name, p.category, p.base_price, p.current_price, p.status,
u.username as seller, p.end_time
FROM products p
JOIN users u ON p.seller_id = u.user_id
WHERE p.status = 'active'
ORDER BY p.product_id
"""
products = db.execute_query(query)
if hasattr(self, 'product_tree'):
for item in self.product_tree.get_children():
self.product_tree.delete(item)
for product in products:
self.product_tree.insert('', 'end', values=(
product['id'],
product['name'],
product['category'],
f"¥{product['current_price']:.2f}",
self.get_status_text(product['status']),
product['seller'],
product['end_time'].strftime('%Y-%m-%d %H:%M:%S')
))
def load_my_products(self):
query = """
SELECT product_id as id, name, category, current_price, status, start_time, end_time
FROM products
WHERE seller_id = %s
ORDER BY seller_id
"""
products = db.execute_query(query, (self.current_user['user_id'],))
if hasattr(self, 'my_product_tree'):
for item in self.my_product_tree.get_children():
self.my_product_tree.delete(item)
for product in products:
self.my_product_tree.insert('', 'end', values=(
product['id'],
product['name'],
product['category'],
f"¥{product['current_price']:.2f}",
self.get_status_text(product['status']),
product['start_time'].strftime('%Y-%m-%d %H:%M:%S'),
product['end_time'].strftime('%Y-%m-%d %H:%M:%S')
))
"""订单数据函数"""
def show_my_orders(self):
self.clear_content()
# 我的商品标题
tb.Label(
self.content,
text="我的订单",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 我的商品表格
columns = ('order_id', 'product_name', 'price', 'status', 'created_at', 'payment_time', 'completion_time')
self.my_orders_tree = ttk.Treeview(
self.content,
columns=columns,
show='headings',
height=15
)
# 设置列
self.my_orders_tree.heading('order_id', text='订单ID', anchor='center')
self.my_orders_tree.heading('product_name', text='商品名称', anchor='center')
self.my_orders_tree.heading('price', text='价格', anchor='center')
self.my_orders_tree.heading('status', text='状态', anchor='center')
self.my_orders_tree.heading('created_at', text='创建时间', anchor='center')
self.my_orders_tree.heading('payment_time', text=' 支付时间 ', anchor='center')
self.my_orders_tree.heading('completion_time', text=' 完成时间 ', anchor='center')
# 设置列宽
self.my_orders_tree.column('order_id', width=50, anchor='center')
self.my_orders_tree.column('product_name', width=120, anchor='center')
self.my_orders_tree.column('price', width=80, anchor='center')
self.my_orders_tree.column('status', width=80, anchor='center')
self.my_orders_tree.column('created_at', width=80, anchor='center')
self.my_orders_tree.column('payment_time', width=120, anchor='center')
self.my_orders_tree.column('completion_time', width=120, anchor='center')
self.my_orders_tree.pack(fill='both', expand=True, padx=10, pady=5)
# 加载我的订单数据
self.load_my_orders()
def load_my_orders(self):
query = """
SELECT o.order_id, p.name as product_name, o.price, o.status,
o.created_at, o.payment_time, o.completion_time
FROM orders o
JOIN products p ON o.product_id = p.product_id
WHERE o.buyer_id = %s
ORDER BY o.created_at
"""
orders = db.execute_query(query, (self.current_user['user_id'],))
if hasattr(self, 'my_orders_tree'):
for item in self.my_orders_tree.get_children():
self.my_orders_tree.delete(item)
for order in orders:
self.my_orders_tree.insert('', 'end', values=(
order['order_id'],
order['name as product_name'],
order['price'],
# f"¥{order['current_price']:.2f}",
self.get_status_text(order['status']),
order['created_at'].strftime('%Y-%m-%d %H:%M:%S'),
order['payment_time'].strftime('%Y-%m-%d %H:%M:%S'),
order['completion_time'].strftime('%Y-%m-%d %H:%M:%S')
))
"""我的竞品"""
def load_my_bids(self):
query = """
SELECT b.bid_id as id, p.name as product, b.amount, b.bid_time,
CASE
WHEN p.status = 'sold' AND b.amount = p.current_price THEN '中标'
WHEN p.status = 'sold' THEN '未中标'
WHEN p.status = 'active' THEN '竞拍中'
ELSE '已结束'
END as status
FROM bids b
JOIN products p ON b.product_id = p.product_id
WHERE b.user_id = %s
ORDER BY b.bid_time
"""
bids = db.execute_query(query, (self.current_user['user_id'],))
if hasattr(self, 'my_bid_tree'):
for item in self.my_bid_tree.get_children():
self.my_bid_tree.delete(item)
for bid in bids:
self.my_bid_tree.insert('', 'end', values=(
bid['id'],
bid['product'],
f"¥{bid['amount']:.2f}",
bid['bid_time'].strftime('%Y-%m-%d %H:%M:%S'),
bid['status']
))
"""搜索商品"""
def search_products(self):
keyword = self.product_search_entry.get()
query = """
SELECT p.product_id as id, p.name, p.category, p.base_price,p.current_price, p.status,
u.username as seller, p.end_time
FROM products p
JOIN users u ON p.seller_id = u.user_id
WHERE p.status = 'active' AND (p.name LIKE %s OR p.category LIKE %s OR u.username LIKE %s)
"""
products = db.execute_query(query, (f"%{keyword}%", f"%{keyword}%", f"%{keyword}%"))
for item in self.product_tree.get_children():
self.product_tree.delete(item)
for product in products:
self.product_tree.insert('', 'end', values=(
product['id'],
product['name'],
product['category'],
f"¥{product['current_price']:.2f}",
self.get_status_text(product['status']),
product['seller'],
product['end_time'].strftime('%Y-%m-%d %H:%M:%S')
))
def filter_products(self):
category = self.category_combobox.get()
if category == '全部':
self.load_products()
return
query = """
SELECT p.product_id as id, p.name, p.category, p.base_price,p.current_price, p.status,
u.username as seller, p.end_time
FROM products p
JOIN users u ON p.seller_id = u.user_id
WHERE p.status = 'active' AND p.category = %s
ORDER BY u.user_id
"""
products = db.execute_query(query, (category,))
for item in self.product_tree.get_children():
self.product_tree.delete(item)
for product in products:
self.product_tree.insert('', 'end', values=(
product['id'],
product['name'],
product['category'],
f"¥{product['current_price']:.2f}",
self.get_status_text(product['status']),
product['seller'],
product['end_time'].strftime('%Y-%m-%d %H:%M:%S')
))
def view_product_details(self):
selected_item = self.product_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个商品!")
return
item = self.product_tree.item(selected_item[0])
product_id = item['values'][0]
query = """
SELECT p.*, u.username as seller_name
FROM products p
JOIN users u ON p.seller_id = u.user_id
WHERE p.product_id = %s
ORDER BY u.user_id
"""
product = db.execute_query(query, (product_id,), fetch_one=True)
if not product:
messagebox.showerror("错误", "未找到商品信息!")
return
# 创建详情窗口
detail_window = tb.Toplevel(self.root)
detail_window.title(f"商品详情 - {product['name']}")
# 主窗口居中
self.width = 800
self.height = 800
# 获取窗口屏幕尺寸参数
self.screenwidth = self.root.winfo_screenwidth()
self.screenheight = self.root.winfo_screenheight()
self.screen_geo = "%dx%d+%d+%d" % (
self.width, self.height, (self.screenwidth - self.width) / 2, (self.screenheight - self.height) / 2)
detail_window.geometry(self.screen_geo)
# 设置主窗口不可拉伸
detail_window.resizable(False, False)
# detail_window.geometry("600x600")
# 商品名称
tb.Label(
detail_window,
text=product['name'],
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 图片显示
if product['image_path'] and os.path.exists(product['image_path']):
try:
image = Image.open(product['image_path'])
image.thumbnail((300, 300))
photo = ImageTk.PhotoImage(image)
image_label = tb.Label(detail_window, image=photo)
image_label.image = photo # 保持引用
image_label.pack(pady=5)
except Exception as e:
tb.Label(detail_window, text="无法加载图片", bootstyle=DANGER).pack()
else:
tb.Label(detail_window, text="无图片", bootstyle=SECONDARY).pack()
# 商品信息
info_frame = tb.Frame(detail_window)
info_frame.pack(pady=10, padx=20, fill='x')
# 卖家信息
tb.Label(info_frame, text=f"卖家: {product['seller_name']}", bootstyle=PRIMARY).pack(anchor='w')
# 价格信息
tb.Label(info_frame, text=f"起拍价: ¥{product['base_price']:.2f}", bootstyle=PRIMARY).pack(anchor='w')
tb.Label(info_frame, text=f"当前价: ¥{product['current_price']:.2f}", bootstyle=PRIMARY).pack(anchor='w')
# 时间信息
tb.Label(info_frame, text=f"开始时间: {product['start_time'].strftime('%Y-%m-%d %H:%M:%S')}",
bootstyle=PRIMARY).pack(anchor='w')
tb.Label(info_frame, text=f"结束时间: {product['end_time'].strftime('%Y-%m-%d %H:%M:%S')}", bootstyle=PRIMARY).pack(
anchor='w')
# 状态
tb.Label(info_frame, text=f"状态: {self.get_status_text(product['status'])}", bootstyle=PRIMARY).pack(anchor='w')
# 类别
tb.Label(info_frame, text=f"类别: {product['category']}", bootstyle=PRIMARY).pack(anchor='w')
# 描述
desc_frame = tb.Frame(detail_window)
desc_frame.pack(pady=10, padx=20, fill='both', expand=True)
tb.Label(desc_frame, text="商品描述:", bootstyle=PRIMARY).pack(anchor='w')
desc_text = tk.Text(desc_frame, height=5, wrap='word')
desc_text.insert('1.0', product['description'])
desc_text.config(state='disabled')
desc_text.pack(fill='both', expand=True)
# 竞拍按钮
if product['status'] == 'active':
bid_frame = tb.Frame(detail_window)
bid_frame.pack(pady=10)
tb.Label(bid_frame, text="出价金额:", bootstyle=PRIMARY).pack(side='left', padx=5)
self.bid_amount_entry = tb.Entry(bid_frame, bootstyle=PRIMARY)
self.bid_amount_entry.pack(side='left', padx=5)
tb.Button(
bid_frame,
text="确认出价",
bootstyle=SUCCESS,
command=lambda: self.place_bid_for_product(product['product_id'])
).pack(side='left', padx=5)
def place_bid_for_product(self, product_id):
try:
amount = float(self.bid_amount_entry.get())
except ValueError:
messagebox.showerror("错误", "请输入有效的金额!")
return
query = "SELECT current_price FROM products WHERE product_id = %s ORDER BY product_id"
product = db.execute_query(query, (product_id,), fetch_one=True)
if not product:
messagebox.showerror("错误", "商品不存在!")
return
if amount <= product['current_price']:
messagebox.showerror("错误", "出价必须高于当前价格!")
return
# 更新商品当前价格
update_query = "UPDATE products SET current_price = %s WHERE product_id = %s"
db.execute_query(update_query, (amount, product_id))
# 添加竞拍记录
bid_query = "INSERT INTO bids (product_id, user_id, amount) VALUES (%s, %s, %s)"
db.execute_query(bid_query, (product_id, self.current_user['user_id'], amount))
messagebox.showinfo("成功", "出价成功!")
self.load_products()
def show_bid_history(self, product_id):
bids = db.execute_query(
"SELECT u.username, b.amount, b.bid_time "
"FROM bids b JOIN users u ON b.user_id = u.user_id "
"WHERE b.product_id = %s ORDER BY b.bid_time",
(product_id,)
)
def add_product(self):
name = self.product_name_entry.get()
description = self.product_desc_entry.get("1.0", tk.END).strip()
category = self.product_category_combobox.get()
base_price = self.base_price_entry.get()
start_time = self.start_time_entry.get()
end_time = self.end_time_entry.get()
image_path = self.image_path_entry.get()
if not name or not description or not category or not base_price:
messagebox.showerror("错误", "必填字段不能为空!")
return
try:
base_price = float(base_price)
if base_price <= 0:
raise ValueError("价格必须大于0!")
except ValueError:
messagebox.showerror("错误", "请输入有效的价格!")
return
try:
datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S')
datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S')
except ValueError:
messagebox.showerror("错误", "日期格式应为 YYYY-MM-DD HH:MM:SS")
return
# 保存图片到服务器目录
if image_path:
try:
if not os.path.exists('images'):
os.makedirs('images')
ext = os.path.splitext(image_path)[1]
new_filename = f"product_{datetime.now().strftime('%Y%m%d%H%M%S')}{ext}"
new_path = os.path.join('images', new_filename)
import shutil
shutil.copy(image_path, new_path)
image_path = new_path
except Exception as e:
messagebox.showerror("错误", f"图片保存失败: {str(e)}")
return
query = """
INSERT INTO products (name, description, category, base_price, current_price, start_time, end_time, status, seller_id, image_path)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
db.execute_query(query, (
name, description, category, base_price, base_price,
start_time, end_time, 'pending', self.current_user['user_id'], image_path
))
messagebox.showinfo("成功", "商品已提交审核!")
self.show_my_products()
def update_account(self):
email = self.email_entry.get()
phone = self.phone_entry.get()
address = self.address_entry.get()
new_password = self.new_password_entry.get()
confirm_password = self.confirm_password_entry.get()
if not email:
messagebox.showerror("错误", "邮箱不能为空!")
return
if new_password and new_password != confirm_password:
messagebox.showerror("错误", "两次输入的密码不一致!")
return
# 检查邮箱是否已被其他用户使用
query = "SELECT user_id FROM users WHERE email = %s AND user_id != %s ORDER BY user_id"
existing_user = db.execute_query(query, (email, self.current_user['user_id']), fetch_one=True)
if existing_user:
messagebox.showerror("错误", "该邮箱已被其他用户使用!")
return
# 更新用户信息
update_fields = []
update_values = []
if new_password:
update_fields.append("password = %s")
update_values.append(new_password)
update_fields.append("email = %s")
update_values.append(email)
if phone:
update_fields.append("phone = %s")
update_values.append(phone)
else:
update_fields.append("phone = NULL")
if address:
update_fields.append("address = %s")
update_values.append(address)
else:
update_fields.append("address = NULL")
update_values.append(self.current_user['user_id'])
query = f"UPDATE users SET {', '.join(update_fields)} WHERE user_id = %s"
db.execute_query(query, tuple(update_values))
# 更新当前用户信息
self.current_user['email'] = email
self.current_user['phone'] = phone or None
self.current_user['address'] = address or None
messagebox.showinfo("成功", "账户信息已更新!")
self.show_account_settings()
def get_status_text(self, status):
status_map = {
'pending': '待审核',
'active': '进行中',
'sold': '已售出',
'expired': '已过期'
}
return status_map.get(status, status)
def get_order_status_text(self, status):
status_map = {
'pending': '待支付',
'paid': '已支付',
'shipped': '已发货',
'completed': '已完成',
'cancelled': '已取消'
}
return status_map.get(status, status)
def clear_content(self):
for widget in self.content.winfo_children():
widget.destroy()
def logout(self):
self.app.current_user = None
self.app.show_login()
def view_my_product_details(self):
"""查看当前用户商品的详细信息"""
selected_item = self.my_product_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个商品!")
return
# 获取选中商品的ID
item = self.my_product_tree.item(selected_item[0])
product_id = item['values'][0]
# 查询数据库获取商品详情
query = """
SELECT p.*, u.username as seller_name
FROM products p
JOIN users u ON p.seller_id = u.user_id
WHERE p.product_id = %s
ORDER BY p.product_id
"""
product = db.execute_query(query, (product_id,), fetch_one=True)
if not product:
messagebox.showerror("错误", "未找到商品信息!")
return
# 创建详情窗口
detail_window = tb.Toplevel(self.root)
detail_window.title(f"商品详情 - {product['name']}")
# 主窗口居中
self.width = 800
self.height = 800
# 获取窗口屏幕尺寸参数
self.screenwidth = self.root.winfo_screenwidth()
self.screenheight = self.root.winfo_screenheight()
self.screen_geo = "%dx%d+%d+%d" % (self.width, self.height, (self.screenwidth - self.width) / 2, (self.screenheight - self.height) / 2)
detail_window.geometry(self.screen_geo)
# 设置主窗口不可拉伸
detail_window.resizable(False, False)
# detail_window.geometry("600x500")
# 商品名称
tb.Label(
detail_window,
text=product['name'],
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 图片显示(如果有)
if product.get('image_path'):
try:
from PIL import Image, ImageTk
image = Image.open(product['image_path'])
image.thumbnail((300, 300))
photo = ImageTk.PhotoImage(image)
image_label = tb.Label(detail_window, image=photo)
image_label.image = photo # 保持引用
image_label.pack(pady=5)
except Exception as e:
tb.Label(detail_window, text="图片加载失败", bootstyle=DANGER).pack()
# 商品信息框架
info_frame = tb.Frame(detail_window)
info_frame.pack(pady=10, padx=20, fill='x')
# 显示商品信息
info_items = [
("商品ID:", product['product_id']),
("卖家:", product['seller_name']),
("类别:", product['category']),
("起拍价:", f"¥{product['base_price']:.2f}"),
("当前价:", f"¥{product['current_price']:.2f}"),
("状态:", self._get_status_text(product['status'])),
("开始时间:", product['start_time'].strftime('%Y-%m-%d %H:%M:%S')),
("结束时间:", product['end_time'].strftime('%Y-%m-%d %H:%M:%S'))
]
for text, value in info_items:
row_frame = tb.Frame(info_frame)
row_frame.pack(fill='x', pady=2)
tb.Label(row_frame, text=text, width=10, bootstyle=PRIMARY).pack(side='left')
tb.Label(row_frame, text=value, bootstyle=SECONDARY).pack(side='left')
# 商品描述
desc_frame = tb.Frame(detail_window)
desc_frame.pack(pady=10, padx=20, fill='both', expand=True)
tb.Label(desc_frame, text="商品描述:", bootstyle=PRIMARY).pack(anchor='w')
desc_text = tk.Text(desc_frame, height=5, wrap='word')
desc_text.insert('1.0', product['description'])
desc_text.config(state='disabled')
desc_text.pack(fill='both', expand=True)
def _get_status_text(self, status):
"""转换状态码为可读文本"""
status_map = {
'pending': '待审核',
'active': '进行中',
'sold': '已售出',
'expired': '已过期'
}
return status_map.get(status, status)
def register(self, username):
try:
# 注册逻辑...
logging.info(f"新用户注册: {username}!")
except Exception as e:
logging.error(f"注册失败: {str(e)}!")
raise
管理员模块:admin.py
"""管理员界面"""
from db import db
import tkinter as tk
from tkinter import messagebox, ttk, filedialog
import ttkbootstrap as tb
from ttkbootstrap.constants import *
from datetime import datetime, timedelta
from PIL import Image, ImageTk
import os
class AdminDashboard:
def __init__(self, root, app):
self.root = root
self.app = app
self.current_user = app.current_user
self.create_widgets()
self.load_users()
self.load_orders()
# 初始化界面组件
self._init_product_tree() # 新增商品树初始化
self.load_products() # 加载商品数据
def _init_product_tree(self):
"""初始化商品列表Treeview"""
# 创建Frame容器
tree_frame = ttk.Frame(self.content)
tree_frame.pack(fill='both', expand=True, padx=10, pady=10)
# 创建Treeview
self.product_tree = ttk.Treeview(
tree_frame,
columns=('id', 'name', 'category', 'price', 'status', 'seller', 'time'),
show='headings',
height=15
)
# 配置列
columns = [
('id', 'ID', 50),
('name', '商品名称', 150),
('category', '类别', 100),
('price', '价格', 80),
('status', '状态', 80),
('seller', '卖家', 100),
('time', '发布时间', 120)
]
for col, text, width in columns:
self.product_tree.heading(col, text=text)
self.product_tree.column(col, width=width, anchor='center')
# 添加滚动条
scrollbar = ttk.Scrollbar(
tree_frame,
orient="vertical",
command=self.product_tree.yview
)
self.product_tree.configure(yscrollcommand=scrollbar.set)
# 布局
self.product_tree.pack(side='left', fill='both', expand=True)
scrollbar.pack(side='right', fill='y')
# 添加右键菜单
self.product_menu = tk.Menu(self.root, tearoff=0)
self.product_menu.add_command(label="上架商品", command=self._approve_product)
self.product_menu.add_command(label="下架商品", command=self._reject_product)
# 绑定事件
self.product_tree.bind("<Button-3>", self._show_product_menu)
def _show_product_menu(self, event):
"""显示商品操作菜单"""
item = self.product_tree.identify_row(event.y)
if item:
self.product_tree.selection_set(item)
self.product_menu.post(event.x_root, event.y_root)
def _approve_product(self):
"""上架选中的商品"""
selected = self._get_selected_product()
if not selected:
return
try:
db.execute_query(
"UPDATE products SET status = 'active' WHERE product_id = %s",
(selected['id'],)
)
messagebox.showinfo("成功", "商品已上架!")
self.refresh_products()
except Exception as e:
messagebox.showerror("错误", f"上架失败: {str(e)}!")
# 记录操作日志
db.execute_query(
"INSERT INTO product_logs (product_id, action, admin_id) VALUES (%s, %s, %s)",
(selected['id'], 'approve', self.app.current_user['user_id'])
)
def _reject_product(self):
"""下架选中的商品"""
selected = self._get_selected_product()
if not selected:
return
try:
db.execute_query(
"UPDATE products SET status = 'inactive' WHERE product_id = %s",
(selected['id'],)
)
messagebox.showinfo("成功", "商品已下架!")
self.refresh_products()
except Exception as e:
messagebox.showerror("错误", f"下架失败: {str(e)}!")
def _get_selected_product(self):
"""获取当前选中的商品"""
selected = self.product_tree.selection()
if not selected:
messagebox.showwarning("警告", "请先选择一个商品!")
return None
item = self.product_tree.item(selected[0])
return {
'id': item['values'][0],
'name': item['values'][1]
}
def create_widgets(self):
# 主框架
self.main_frame = tb.Frame(self.root)
self.main_frame.pack(fill='both', expand=True)
# 侧边栏
self.sidebar = tb.Frame(self.main_frame, bootstyle=SECONDARY, width=200)
self.sidebar.pack(side='left', fill='y')
# 顶部栏
self.topbar = tb.Frame(self.main_frame, bootstyle=PRIMARY, height=50)
self.topbar.pack(side='top', fill='x')
# 内容区域
self.content = tb.Frame(self.main_frame)
self.content.pack(side='right', fill='both', expand=True)
# 侧边栏内容
tb.Label(
self.sidebar,
text="管理员面板",
font=('Helvetica', 12, 'bold'),
bootstyle=INVERSE
).pack(pady=20)
# 导航按钮
nav_buttons = [
("用户管理", self.show_user_management),
("商品管理", self.show_product_management),
("订单管理", self.show_order_management),
("发布商品", self.show_add_product),
("退出登录", self.logout)
]
for text, command in nav_buttons:
btn = tb.Button(
self.sidebar,
text=text,
bootstyle=(OUTLINE, LIGHT),
command=command
)
btn.pack(fill='x', padx=10, pady=5)
# 顶部栏内容
tb.Label(
self.topbar,
text=f"欢迎, {self.current_user['username']} (超级管理员)",
font=('Helvetica', 12),
bootstyle=INVERSE
).pack(side='left', padx=20)
# 默认显示用户管理
self.show_user_management()
def show_user_management(self):
self.clear_content()
# 用户管理标题
tb.Label(
self.content,
text="用户管理",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 搜索框
search_frame = tb.Frame(self.content)
search_frame.pack(fill='x', padx=10, pady=5)
self.user_search_entry = tb.Entry(search_frame, bootstyle=PRIMARY)
self.user_search_entry.pack(side='left', padx=5, expand=True, fill='x')
tb.Button(
search_frame,
text="搜索",
bootstyle=INFO,
command=self.search_users
).pack(side='left', padx=5)
# 用户表格
columns = ('id', 'username', 'email', 'phone', 'is_admin', 'created_at')
self.user_tree = ttk.Treeview(
self.content,
columns=columns,
show='headings',
height=15
)
# 设置列
self.user_tree.heading('id', text='ID', anchor='center')
self.user_tree.heading('username', text='用户名', anchor='center')
self.user_tree.heading('email', text='邮箱', anchor='center')
self.user_tree.heading('phone', text='电话', anchor='center')
self.user_tree.heading('is_admin', text='管理员', anchor='center')
self.user_tree.heading('created_at', text='注册时间', anchor='center')
# 设置列宽
self.user_tree.column('id', width=50, anchor='center')
self.user_tree.column('username', width=100, anchor='center')
self.user_tree.column('email', width=150, anchor='center')
self.user_tree.column('phone', width=100, anchor='center')
self.user_tree.column('is_admin', width=80, anchor='center')
self.user_tree.column('created_at', width=120, anchor='center')
self.user_tree.pack(fill='both', expand=True, padx=10, pady=5)
# 操作按钮
button_frame = tb.Frame(self.content)
button_frame.pack(pady=10)
tb.Button(
button_frame,
text="设为管理员",
bootstyle=WARNING,
command=self.set_as_admin
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="删除用户",
bootstyle=DANGER,
command=self.delete_user
).pack(side='left', padx=5)
def show_product_management(self):
self.clear_content()
# 商品管理标题
tb.Label(
self.content,
text="商品管理",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 搜索框
search_frame = tb.Frame(self.content)
search_frame.pack(fill='x', padx=10, pady=5)
self.product_search_entry = tb.Entry(search_frame, bootstyle=PRIMARY)
self.product_search_entry.pack(side='left', padx=5, expand=True, fill='x')
tb.Button(
search_frame,
text="搜索",
bootstyle=INFO,
command=self.search_products
).pack(side='left', padx=5)
# 商品表格
columns = (
'id', 'name', 'category', 'base_price', 'current_price', 'status', 'seller', 'start_time', 'end_time')
self.product_tree = ttk.Treeview(
self.content,
columns=columns,
show='headings',
height=15
)
# 设置列
self.product_tree.heading('id', text='ID', anchor='center')
self.product_tree.heading('name', text='商品名称', anchor='center')
self.product_tree.heading('category', text='类别', anchor='center')
self.product_tree.heading('base_price', text='起拍价', anchor='center')
self.product_tree.heading('current_price', text='当前价', anchor='center')
self.product_tree.heading('status', text='状态', anchor='center')
self.product_tree.heading('seller', text='卖家', anchor='center')
self.product_tree.heading('start_time', text='开始时间', anchor='center')
self.product_tree.heading('end_time', text='结束时间', anchor='center')
# 设置列宽
self.product_tree.column('id', width=50, anchor='center')
self.product_tree.column('name', width=120, anchor='center')
self.product_tree.column('category', width=80, anchor='center')
self.product_tree.column('base_price', width=80, anchor='center')
self.product_tree.column('current_price', width=80, anchor='center')
self.product_tree.column('status', width=80, anchor='center')
self.product_tree.column('seller', width=100, anchor='center')
self.product_tree.column('start_time', width=120, anchor='center')
self.product_tree.column('end_time', width=120, anchor='center')
self.product_tree.pack(fill='both', expand=True, padx=10, pady=5)
# 操作按钮
button_frame = tb.Frame(self.content)
button_frame.pack(pady=10)
tb.Button(
button_frame,
text="上架商品",
bootstyle=SUCCESS,
command=self.approve_product
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="下架商品",
bootstyle=DANGER,
command=self.reject_product
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="查看详情",
bootstyle=INFO,
command=self.view_product_details
).pack(side='left', padx=5)
def show_order_management(self):
self.clear_content()
# 订单管理标题
tb.Label(
self.content,
text="订单管理",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 搜索框
search_frame = tb.Frame(self.content)
search_frame.pack(fill='x', padx=10, pady=5)
self.order_search_entry = tb.Entry(search_frame, bootstyle=PRIMARY)
self.order_search_entry.pack(side='left', padx=5, expand=True, fill='x')
tb.Button(
search_frame,
text="搜索",
bootstyle=INFO,
command=self.search_orders
).pack(side='left', padx=5)
# 订单表格
columns = ('id', 'product', 'buyer', 'price', 'status', 'created_at')
self.order_tree = ttk.Treeview(
self.content,
columns=columns,
show='headings',
height=15
)
# 设置列
self.order_tree.heading('id', text='ID', anchor='center')
self.order_tree.heading('product', text='商品', anchor='center')
self.order_tree.heading('buyer', text='买家', anchor='center')
self.order_tree.heading('price', text='价格', anchor='center')
self.order_tree.heading('status', text='状态', anchor='center')
self.order_tree.heading('created_at', text='创建时间', anchor='center')
# 设置列宽
self.order_tree.column('id', width=50, anchor='center')
self.order_tree.column('product', width=150, anchor='center')
self.order_tree.column('buyer', width=100, anchor='center')
self.order_tree.column('price', width=80, anchor='center')
self.order_tree.column('status', width=80, anchor='center')
self.order_tree.column('created_at', width=120, anchor='center')
self.order_tree.pack(fill='both', expand=True, padx=10, pady=5)
# 操作按钮
button_frame = tb.Frame(self.content)
button_frame.pack(pady=10)
tb.Button(
button_frame,
text="标记为已支付",
bootstyle=SUCCESS,
command=lambda: self.update_order_status('paid')
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="标记为已发货",
bootstyle=INFO,
command=lambda: self.update_order_status('shipped')
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="标记为已完成",
bootstyle=PRIMARY,
command=lambda: self.update_order_status('completed')
).pack(side='left', padx=5)
tb.Button(
button_frame,
text="取消订单",
bootstyle=DANGER,
command=lambda: self.update_order_status('cancelled')
).pack(side='left', padx=5)
def show_add_product(self):
self.clear_content()
# 添加商品标题
tb.Label(
self.content,
text="发布新商品",
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 表单框架
form_frame = tb.Frame(self.content)
form_frame.pack(pady=10)
# 商品名称
tb.Label(form_frame, text="商品名称:", bootstyle=PRIMARY).grid(row=0, column=0, padx=5, pady=5, sticky='e')
self.product_name_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.product_name_entry.grid(row=0, column=1, padx=5, pady=5)
# 商品描述
tb.Label(form_frame, text="商品描述:", bootstyle=PRIMARY).grid(row=1, column=0, padx=5, pady=5, sticky='e')
self.product_desc_entry = tb.Text(form_frame, height=5, width=30)
self.product_desc_entry.grid(row=1, column=1, padx=5, pady=5)
# 商品类别
tb.Label(form_frame, text="商品类别:", bootstyle=PRIMARY).grid(row=2, column=0, padx=5, pady=5, sticky='e')
self.product_category_combobox = tb.Combobox(form_frame, values=['电子产品', '家居用品', '服装', '书籍', '其他'])
self.product_category_combobox.grid(row=2, column=1, padx=5, pady=5)
# 起拍价
tb.Label(form_frame, text="起拍价:", bootstyle=PRIMARY).grid(row=3, column=0, padx=5, pady=5, sticky='e')
self.base_price_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.base_price_entry.grid(row=3, column=1, padx=5, pady=5)
# 开始时间
tb.Label(form_frame, text="开始时间:", bootstyle=PRIMARY).grid(row=4, column=0, padx=5, pady=5, sticky='e')
self.start_time_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.start_time_entry.grid(row=4, column=1, padx=5, pady=5)
self.start_time_entry.insert(0, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
# 结束时间
tb.Label(form_frame, text="结束时间:", bootstyle=PRIMARY).grid(row=5, column=0, padx=5, pady=5, sticky='e')
self.end_time_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.end_time_entry.grid(row=5, column=1, padx=5, pady=5)
self.end_time_entry.insert(0, (datetime.now() + timedelta(days=7)).strftime('%Y-%m-%d %H:%M:%S'))
# 商品图片
tb.Label(form_frame, text="商品图片:", bootstyle=PRIMARY).grid(row=6, column=0, padx=5, pady=5, sticky='e')
self.image_path_entry = tb.Entry(form_frame, bootstyle=PRIMARY)
self.image_path_entry.grid(row=6, column=1, padx=5, pady=5)
tb.Button(
form_frame,
text="选择图片",
bootstyle=INFO,
command=self.select_image
).grid(row=6, column=2, padx=5, pady=5)
# 预览图片
self.image_preview_label = tb.Label(form_frame)
self.image_preview_label.grid(row=7, column=1, pady=5)
# 提交按钮
tb.Button(
self.content,
text="发布商品",
bootstyle=SUCCESS,
command=self.add_product
).pack(pady=10)
def select_image(self):
file_path = filedialog.askopenfilename(
title="选择商品图片",
filetypes=[("Image Files", "*.png *.jpg *.jpeg")]
)
if file_path:
self.image_path_entry.delete(0, tk.END)
self.image_path_entry.insert(0, file_path)
# 显示预览
self.show_image_preview(file_path)
def show_image_preview(self, image_path):
try:
image = Image.open(image_path)
image.thumbnail((200, 200))
photo = ImageTk.PhotoImage(image)
self.image_preview_label.config(image=photo)
self.image_preview_label.image = photo # 保持引用
except Exception as e:
messagebox.showerror("错误", f"无法加载图片: {str(e)}")
def add_product(self):
name = self.product_name_entry.get()
description = self.product_desc_entry.get("1.0", tk.END).strip()
category = self.product_category_combobox.get()
base_price = self.base_price_entry.get()
start_time = self.start_time_entry.get()
end_time = self.end_time_entry.get()
image_path = self.image_path_entry.get()
if not name or not description or not category or not base_price:
messagebox.showerror("错误", "必填字段不能为空!")
return
try:
base_price = float(base_price)
if base_price <= 0:
raise ValueError("价格必须大于0!")
except ValueError:
messagebox.showerror("错误", "请输入有效的价格")
return
try:
datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S')
datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S')
except ValueError:
messagebox.showerror("错误", "日期格式应为 YYYY-MM-DD HH:MM:SS")
return
# 保存图片到服务器目录
if image_path:
try:
# 创建images目录如果不存在
if not os.path.exists('images'):
os.makedirs('images')
# 生成唯一文件名
ext = os.path.splitext(image_path)[1]
new_filename = f"product_{datetime.now().strftime('%Y%m%d%H%M%S')}{ext}"
new_path = os.path.join('images', new_filename)
# 复制文件
import shutil
shutil.copy(image_path, new_path)
image_path = new_path
except Exception as e:
messagebox.showerror("错误", f"图片保存失败: {str(e)}!")
return
query = """
INSERT INTO products (name, description, category, base_price, current_price, start_time, end_time, status, seller_id, image_path)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
db.execute_query(query, (
name, description, category, base_price, base_price,
start_time, end_time, 'active', self.current_user['user_id'], image_path
))
messagebox.showinfo("成功", "商品发布成功!")
self.show_product_management()
def load_users(self):
"""加载并显示用户列表(优化版)"""
try:
# 1. 执行查询获取用户数据
query = """
SELECT
user_id AS id,
username,
email,
phone,
is_admin,
created_at,
last_login
FROM users
ORDER BY user_id
"""
users = db.execute_query(query)
# 2. 检查Treeview组件是否存在
if not hasattr(self, 'user_tree'):
self._init_user_tree()
# 3. 清空现有数据
self._clear_user_tree()
# 4. 填充数据
for user in users:
self.user_tree.insert('', 'end', values=(
user['id'],
user['username'],
user['email'],
user['phone'] or '未填写',
'✅' if user['is_admin'] else '❌',
user['created_at'].strftime('%Y-%m-%d %H:%M'),
user['last_login'].strftime('%Y-%m-%d %H:%M') if user['last_login'] else '从未登录'
))
# 5. 更新状态栏
self._update_status_bar(f"加载完成,共 {len(users)} 位用户")
except Exception as e:
self._show_error_message(f"加载用户失败: {str(e)}!")
def _init_user_tree(self):
"""初始化用户列表Treeview"""
self.user_tree = ttk.Treeview(
self.content_frame,
columns=('id', 'username', 'email', 'phone', 'is_admin', 'created_at', 'last_login'),
show='headings',
height=15,
selectmode='browse'
)
# 配置列
columns_config = [
('id', 'ID', 50),
('username', '用户名', 120),
('email', '邮箱', 180),
('phone', '电话', 100),
('is_admin', '超级管理员', 80),
('created_at', '注册时间', 150),
('last_login', '最后登录', 150)
]
for col, text, width in columns_config:
self.user_tree.heading(col, text=text)
self.user_tree.column(col, width=width, anchor='center')
# 添加滚动条
scrollbar = ttk.Scrollbar(
self.content_frame,
orient="vertical",
command=self.user_tree.yview
)
self.user_tree.configure(yscrollcommand=scrollbar.set)
# 布局
self.user_tree.pack(side='left', fill='both', expand=True)
scrollbar.pack(side='right', fill='y')
def _clear_user_tree(self):
"""清空用户列表"""
for item in self.user_tree.get_children():
self.user_tree.delete(item)
def _update_status_bar(self, message):
"""更新状态栏信息"""
if hasattr(self, 'status_bar'):
self.status_bar.config(text=message)
def _show_error_message(self, message):
"""显示错误信息"""
messagebox.showerror("错误", message)
if hasattr(self, 'status_bar'):
self.status_bar.config(text=message)
def load_products(self):
"""加载商品数据到Treeview"""
try:
# 确保product_tree存在
if not hasattr(self, 'product_tree'):
self._init_product_tree()
# 清空现有数据
for item in self.product_tree.get_children():
self.product_tree.delete(item)
# 查询商品数据
query = """
SELECT
p.product_id as id,
p.name,
p.category,
p.current_price AS price, # 使用正确的字段名
# p.current_price,
p.status,
u.username as seller,
p.created_at as time
FROM products p
JOIN users u ON p.seller_id = u.user_id
"""
products = db.execute_query(query)
# 添加数据到Treeview
for product in products:
self.product_tree.insert('', 'end', values=(
product['id'],
product['name'],
product['category'],
f"¥{product['price']:.2f}",
# f"¥{product['current_price']:.2f}",
self._get_product_status(product['status']),
product['seller'],
product['time'].strftime('%Y-%m-%d %H:%M')
))
except Exception as e:
messagebox.showerror("错误", f"加载商品失败: {str(e)}")
def _get_product_status(self, status):
"""转换商品状态为可读文本"""
status_map = {
'pending': '待审核',
'active': '进行中',
'sold': '已售出',
'expired': '已过期'
}
return status_map.get(status, status)
def load_orders(self):
query = """
SELECT o.order_id as id, p.name as product, u.username as buyer, o.price, o.status, o.created_at
FROM orders o
JOIN products p ON o.product_id = p.product_id
JOIN users u ON o.buyer_id = u.user_id
ORDER BY p.product_id
"""
orders = db.execute_query(query)
if hasattr(self, 'order_tree'):
for item in self.order_tree.get_children():
self.order_tree.delete(item)
for order in orders:
self.order_tree.insert('', 'end', values=(
order['id'],
order['product'],
order['buyer'],
f"¥{order['price']:.2f}",
self.get_order_status_text(order['status']),
order['created_at'].strftime('%Y-%m-%d %H:%M:%S')
))
def search_users(self):
keyword = self.user_search_entry.get()
query = """
SELECT user_id as id, username, email, phone, is_admin, created_at
FROM users
WHERE username LIKE %s OR email LIKE %s OR phone LIKE %s
ORDER BY created_at
"""
users = db.execute_query(query, (f"%{keyword}%", f"%{keyword}%", f"%{keyword}%"))
for item in self.user_tree.get_children():
self.user_tree.delete(item)
for user in users:
self.user_tree.insert('', 'end', values=(
user['id'],
user['username'],
user['email'],
user['phone'],
'是' if user['is_admin'] else '否',
user['created_at'].strftime('%Y-%m-%d %H:%M:%S')
))
def search_products(self):
keyword = self.product_search_entry.get()
query = """
SELECT p.product_id as id, p.name, p.category, p.base_price, p.current_price, p.status,
u.username as seller, p.start_time, p.end_time
FROM products p
JOIN users u ON p.seller_id = u.user_id
WHERE p.name LIKE %s OR p.category LIKE %s OR u.username LIKE %s
ORDER BY id
"""
products = db.execute_query(query, (f"%{keyword}%", f"%{keyword}%", f"%{keyword}%"))
for item in self.product_tree.get_children():
self.product_tree.delete(item)
for product in products:
self.product_tree.insert('', 'end', values=(
product['id'],
product['name'],
product['category'],
f"¥{product['base_price']:.2f}",
f"¥{product['current_price']:.2f}",
self.get_status_text(product['status']),
product['seller'],
product['start_time'].strftime('%Y-%m-%d %H:%M:%S'),
product['end_time'].strftime('%Y-%m-%d %H:%M:%S')
))
def search_orders(self):
keyword = self.order_search_entry.get()
query = """
SELECT o.order_id as id, p.name as product, u.username as buyer, o.price, o.status, o.created_at
FROM orders o
JOIN products p ON o.product_id = p.product_id
JOIN users u ON o.buyer_id = u.user_id
WHERE p.name LIKE %s OR u.username LIKE %s
ORDER BY o.created_at
"""
orders = db.execute_query(query, (f"%{keyword}%", f"%{keyword}%"))
for item in self.order_tree.get_children():
self.order_tree.delete(item)
for order in orders:
self.order_tree.insert('', 'end', values=(
order['id'],
order['product'],
order['buyer'],
f"¥{order['price']:.2f}",
self.get_order_status_text(order['status']),
order['created_at'].strftime('%Y-%m-%d %H:%M:%S')
))
def set_as_admin(self):
selected_item = self.user_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个用户!")
return
item = self.user_tree.item(selected_item[0])
user_id = item['values'][0]
query = "UPDATE users SET is_admin = NOT is_admin WHERE user_id = %s"
db.execute_query(query, (user_id,))
messagebox.showinfo("成功", "用户权限已更新!")
self.load_users()
def delete_user(self):
selected_item = self.user_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个用户!")
return
item = self.user_tree.item(selected_item[0])
user_id = item['values'][0]
username = item['values'][1]
if messagebox.askyesno("确认", f"确定要删除用户 {username} 吗?"):
query = "DELETE FROM users WHERE user_id = %s"
db.execute_query(query, (user_id,))
messagebox.showinfo("成功", "用户已删除!")
self.load_users()
def approve_product(self):
selected_item = self.product_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个商品!")
return
item = self.product_tree.item(selected_item[0])
product_id = item['values'][0]
query = "UPDATE products SET status = 'active' WHERE product_id = %s"
db.execute_query(query, (product_id,))
messagebox.showinfo("成功", "商品已上架!")
self.load_products()
def reject_product(self):
selected_item = self.product_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个商品!")
return
item = self.product_tree.item(selected_item[0])
product_id = item['values'][0]
query = "UPDATE products SET status = 'expired' WHERE product_id = %s"
db.execute_query(query, (product_id,))
messagebox.showinfo("成功", "商品已下架!")
self.load_products()
def view_product_details(self):
selected_item = self.product_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个商品!")
return
item = self.product_tree.item(selected_item[0])
product_id = item['values'][0]
query = """
SELECT p.*, u.username as seller_name
FROM products p
JOIN users u ON p.seller_id = u.user_id
WHERE p.product_id = %s
ORDER BY p.product_id
"""
product = db.execute_query(query, (product_id,), fetch_one=True)
if not product:
messagebox.showerror("错误", "未找到商品信息!")
return
# 创建详情窗口
detail_window = tb.Toplevel(self.root)
detail_window.title(f"商品详情 - {product['name']}")
# 主窗口居中
self.width = 1000
self.height = 900
# 获取窗口屏幕尺寸参数
self.screenwidth = self.root.winfo_screenwidth()
self.screenheight = self.root.winfo_screenheight()
self.screen_geo = "%dx%d+%d+%d" % (self.width, self.height, (self.screenwidth - self.width) / 2, (self.screenheight - self.height) / 2)
detail_window.geometry(self.screen_geo)
# 设置主窗口不可拉伸
detail_window.resizable(False, False)
# detail_window.geometry("600x500")
# 商品名称
tb.Label(
detail_window,
text=product['name'],
font=('Helvetica', 16, 'bold'),
bootstyle=PRIMARY
).pack(pady=10)
# 图片显示
if product['image_path'] and os.path.exists(product['image_path']):
try:
image = Image.open(product['image_path'])
image.thumbnail((300, 300))
photo = ImageTk.PhotoImage(image)
image_label = tb.Label(detail_window, image=photo)
image_label.image = photo # 保持引用
image_label.pack(pady=5)
except Exception as e:
tb.Label(detail_window, text="无法加载图片", bootstyle=DANGER).pack()
else:
tb.Label(detail_window, text="无图片", bootstyle=SECONDARY).pack()
# 商品信息
info_frame = tb.Frame(detail_window)
info_frame.pack(pady=10, padx=20, fill='x')
# 卖家信息
tb.Label(info_frame, text=f"卖家: {product['seller_name']}", bootstyle=PRIMARY).pack(anchor='w')
# 价格信息
tb.Label(info_frame, text=f"起拍价: ¥{product['base_price']:.2f}", bootstyle=PRIMARY).pack(anchor='w')
tb.Label(info_frame, text=f"当前价: ¥{product['current_price']:.2f}", bootstyle=PRIMARY).pack(anchor='w')
# 时间信息
tb.Label(info_frame, text=f"开始时间: {product['start_time'].strftime('%Y-%m-%d %H:%M:%S')}",
bootstyle=PRIMARY).pack(anchor='w')
tb.Label(info_frame, text=f"结束时间: {product['end_time'].strftime('%Y-%m-%d %H:%M:%S')}", bootstyle=PRIMARY).pack(
anchor='w')
# 状态
tb.Label(info_frame, text=f"状态: {self.get_status_text(product['status'])}", bootstyle=PRIMARY).pack(anchor='w')
# 类别
tb.Label(info_frame, text=f"类别: {product['category']}", bootstyle=PRIMARY).pack(anchor='w')
# 描述
desc_frame = tb.Frame(detail_window)
desc_frame.pack(pady=10, padx=20, fill='both', expand=True)
tb.Label(desc_frame, text="商品描述:", bootstyle=PRIMARY).pack(anchor='w')
desc_text = tk.Text(desc_frame, height=5, wrap='word')
desc_text.insert('1.0', product['description'])
desc_text.config(state='disabled')
desc_text.pack(fill='both', expand=True)
def update_order_status(self, status):
selected_item = self.order_tree.selection()
if not selected_item:
messagebox.showwarning("警告", "请先选择一个订单!")
return
item = self.order_tree.item(selected_item[0])
order_id = item['values'][0]
query = "UPDATE orders SET status = %s WHERE order_id = %s ORDER BY order_id"
db.execute_query(query, (status, order_id))
messagebox.showinfo("成功", "订单状态已更新!")
self.load_orders()
def get_status_text(self, status):
status_map = {
'pending': '待审核',
'active': '进行中',
'sold': '已售出',
'expired': '已过期'
}
return status_map.get(status, status)
def get_order_status_text(self, status):
status_map = {
'pending': '待支付',
'paid': '已支付',
'shipped': '已发货',
'completed': '已完成',
'cancelled': '已取消'
}
return status_map.get(status, status)
def clear_content(self):
for widget in self.content.winfo_children():
widget.destroy()
def logout(self):
self.app.current_user = None
self.app.show_login()
系统主程序入口:
"""二手拍卖商城系统"""
from db import db
import tkinter as tk
from tkinter import ttk
import ttkbootstrap as tb
from ttkbootstrap.constants import *
from db import db
from login import LoginWindow
from register import RegisterWindow
from admin import AdminDashboard
from user import UserDashboard
class AuctionSystemApp:
def __init__(self):
self.root = tb.Window(themename="minty")
self.root.title("二手拍卖系统")
# 主窗口居中
self.width = 1920
self.height = 1280
# 获取窗口屏幕尺寸参数
self.screenwidth = self.root.winfo_screenwidth()
self.screenheight = self.root.winfo_screenheight()
self.screen_geo = "%dx%d+%d+%d" % (
self.width, self.height, (self.screenwidth - self.width) / 2, (self.screenheight - self.height) / 2)
self.root.geometry(self.screen_geo)
# 设置主窗口不可拉伸
self.root.resizable(False, False)
# self.root.geometry("1000x700")
self.current_user = None
self.show_login()
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
def show_login(self):
self.clear_window()
LoginWindow(self.root, self)
def show_register(self):
self.clear_window()
RegisterWindow(self.root, self)
def show_admin_dashboard(self):
self.clear_window()
AdminDashboard(self.root, self)
def show_user_dashboard(self):
self.clear_window()
UserDashboard(self.root, self)
def clear_window(self):
for widget in self.root.winfo_children():
widget.destroy()
def on_close(self):
db.close()
self.root.destroy()
if __name__ == "__main__":
app = AuctionSystemApp()
app.root.mainloop()
项目打包:
# 打包说明 """ 1. 安装PyInstaller: pip install pyinstaller 2. 创建打包命令: pyinstaller --onefile --windowed --icon=auction.ico app.py 3. 将images文件夹、数据库配置文件和图标文件放入dist目录 4. 测试运行生成的exe文件 """
若项目启动和项目打包有什么问题,可在评论区留言!!!