基于Python、tkinter、sqlite3 和matplotlib的校园书店管理系统

写一个小例子练习一下python语言。一个基于Python的校园书店管理系统,使用了tkinter库构建图形用户界面(GUI),sqlite3 进行数据库管理,matplotlib用于统计分析可视化。系统支持用户登录、书籍管理、客户管理、员工管理、采购管理、销售管理、统计分析和系统设置等功能。

一、运行环境

该系统是一个使用Python和Tkinter构建的校园书店管理系统,运行该程序需要以下环境:

(一) Python版本

使用Python 3.x版本,建议使用最新版,当前使用的是3.12版,python官网:www.python.org。因为我们在代码中要使用到Python 3的语法和标准库特性。例如,代码中需要使用os.path.join等Python 3支持的路径处理方式。

(二) 第三方库

程序中使用了一些第三方库,需要提前安装:

1. tkinter

这是Python的标准GUI库,用于创建图形用户界面。在大多数Python 3安装中,tkinter 已经默认包含,无需额外安装。

2. sqlite3

这是Python的标准数据库接口,用于操作SQLite数据库,同样无需额外安装。但是数据库管理工具SQLite-Web需要安装。

SQLite-Web是一个基于Web浏览器的轻量级SQLite数据库管理工具。它基于Python开发,免费开源,无需复杂的安装或配置,适合快速搭建本地或内网的SQLite管理和开发环境。SQLite-Web支持常见的SQLite数据库管理和开发任务,包括:

  1. 管理和操作已有的SQLite数据库,或者新建数据库;
  2. 支持表、字段以及索引的创建、修改和删除;
  3. 支持导入/导出CSV、JSON格式的数据文件;
  4. 支持数据的增删改查操作;
  5. 支持书签功能,保存常用脚本;
  6. 支持URL分享查询语句;
  7. 提供常见SQL语句帮助。

SQLite-Web的安装非常简单,但是它的依赖项很多,如果是不先安装依赖项,则SQLite-Web无法安装成功,以下是安装依赖项的命令如下:

复制代码
pip install Flask Peewee Pygments

Python环境中使用pip进行SQLite-Web安装的命令如下:

复制代码
pip install sqlite-web

建好数据库之后,就可以执行以下命令运行SQLite-Web,来管理数据库了:

复制代码
sqlite_web /path/to/database.db

这里最好指定绝对路径。如:

复制代码
sqlite_web C:/Users/lzm07/Desktop/BookstoreApp/bookstore.db

运行成功后在浏览器中输入以下地址进行访问:http://127.0.0.1:8080/

会在页面显示数据表的结构(Structure),包括字段、索引、触发器以及外键;同时可以修改表的字段和索引等信息。

3. matplotlib

用于绘制统计图表。可以使用以下命令进行安装:

复制代码
pip install matplotlib

(四) 数据库文件

程序使用SQLite数据库bookstore.db来存储数据,确保该数据库文件存在于与BookstoreApp.py相同的目录下。如果文件不存在,程序会自动创建所需的表结构,并添加默认的管理员账户,账号密码都是admin。

(五) 字体支持

程序中设置了 matplotlib 支持中文显示,需要系统中安装有相应的中文字体,如 SimHei(黑体)。如果系统中没有该字体,可能会导致中文显示异常。

(六) 运行步骤总结

(1)确保已经安装了 Python 3.x 版本。

(2)使用 pip 安装 matplotlib 库。

(3)确保 config.ini 文件存在且格式正确(后面说明如何创建)。

(4)确保 bookstore.db 文件存在或程序可以创建该文件。

(5)CMD中运行 BookstoreApp.py 文件:

复制代码
python BookstoreApp.py

通过以上步骤,你应该能够成功运行该校园书店管理系统。

、系统整体概述

接下来详细说明如何构建整个系统,该系统使用了tkinter库构建图形用户界面(GUI),sqlite3 进行数据库管理,matplotlib用于统计分析可视化。系统支持用户登录、书籍管理、客户管理、员工管理、采购管理、销售管理、统计分析和系统设置等功能。

(一)代码树形结构

整个项目的代码树形结构如下:

BookstoreApp/

├── config.ini

├── bookstore.db(自动创建)

└── BookstoreApp.py

其中BookstoreApp是项目根目录。里面包含了三个文件。

(二)程序组成部分

(1)配置文件模块:config.ini 用于存储系统的基本设置,如系统名称、库存警告值、每页记录数等。

(2)主程序模块BookstoreApp.py 包含了系统的主要业务逻辑和GUI界面。

(3)数据库管理:使用 sqlite3 进行数据库的连接、表的创建和数据的操作。

(4)用户界面:使用 tkinter 库创建登录界面、主界面、导航栏和各个管理页面。内容都包含在BookstoreApp.py文件中。

(5)统计分析:使用 matplotlib 进行数据可视化,目前代码中未完整实现可视化部分,但预留了相关接口。内容都包含在BookstoreApp.py文件中。

系统的主要功能包括:

  1. 用户登录与权限控制
  2. 书籍信息的增删改查
  3. 客户信息的管理
  4. 员工信息的管理
  5. 采购流程的管理
  6. 销售流程的管理
  7. 数据统计与分析
  8. 系统设置与数据库备份恢复

BookstoreApp.py 简要说明

1. BookstoreApp.py文件树形结构

BookstoreApp.py

├── 导入模块

│ ├── tkinter 及其子模块

│ ├── sqlite3

│ ├── datetime

│ ├── os

│ ├── matplotlib 及其子模块

│ ├── configparser

├── 全局配置

│ ├── 读取配置文件 config.ini

│ ├── 设置 matplotlib 中文字体

├── BookstoreApp 类

│ ├── init(self, root)

│ ├── load_config(self)

│ ├── save_config(self)

│ ├── create_tables(self)

│ ├── create_main_window(self)

│ ├── create_navbar(self)

│ ├── show_login_page(self)

│ │ └── login(self)

│ ├── setup_navigation(self)

│ ├── show_welcome_page(self)

│ ├── show_book_management(self)

│ │ ├── search_books(self)

│ │ ├── open_add_book_dialog(self)

│ │ │ └── add_book(self)

│ │ ├── open_edit_book_dialog(self)

│ │ │ └── update_book(self)

│ │ └── delete_book(self)

│ ├── load_books(self, keyword="")

│ ├── show_customer_management(self)

│ │ ├── search_customers(self)

│ │ ├── open_add_customer_dialog(self)

│ │ │ └── add_customer(self)

│ │ ├── open_edit_customer_dialog(self)

│ │ │ └── update_customer(self)

│ │ └── delete_customer(self)

│ ├── load_customers(self, keyword="")

│ ├── show_employee_management(self)

│ │ ├── search_employees(self)

│ │ ├── open_add_employee_dialog(self)

│ │ │ └── add_employee(self)

│ │ ├── open_edit_employee_dialog(self)

│ │ │ └── update_employee(self)

│ │ └── delete_employee(self)

│ ├── load_employees(self, keyword="")

│ ├── show_purchase_management(self)

│ ├── show_sale_management(self)

│ ├── show_statistics(self)

│ └── show_system_settings(self)

└── 主程序入口

└── if name == "main":

2. 各方法主要功能说明

1 )类外部分

1导入模块

  1. 导入tkinter及其子模块用于GUI开发
  2. 导入sqlite3用于数据库操作
  3. 导入datetime用于日期处理
  4. 导入os用于文件路径操作
  5. 导入matplotlib及其子模块用于数据可视化
  6. 导入configparser用于读取配置文件

2全局配置

  1. 读取config.ini文件获取系统名称、库存警告值等配置
  2. 设置matplotlib支持中文显示

2 )BookstoreApp类方法

1初始化与配置

  1. init(self, root):初始化应用程序,设置窗口属性,连接数据库,创建主界面
  2. load_config(self):从配置文件读取系统设置
  3. save_config(self):将当前设置保存到配置文件
  4. create_tables(self):创建数据库表结构(书籍、客户、员工、采购、销售、用户)

2界面构建

  1. create_main_window(self):创建主窗口框架
  2. create_navbar(self):创建顶部导航栏
  3. setup_navigation(self):根据用户角色设置导航项

3用户认证

  1. show_login_page(self):显示登录页面
  2. login(self):处理用户登录验证

4主页面与功能模块

  1. show_welcome_page(self):显示欢迎页面,展示系统概览和最近销售记录
  2. show_book_management(self):显示书籍管理页面,支持搜索、添加、编辑、删除书籍
  3. show_customer_management(self):显示客户管理页面,支持客户信息管理
  4. show_employee_management(self):显示员工管理页面(管理员权限)
  5. show_purchase_management(self):显示采购管理页面
  6. show_sale_management(self):显示销售管理页面
  7. show_statistics(self):显示统计分析页面
  8. show_system_settings(self):显示系统设置页面

5数据加载与操作

  1. load_books(self, keyword):加载书籍数据,支持关键词搜索
  2. load_customers(self, keyword):加载客户数据,支持关键词搜索
  3. load_employees(self, keyword):加载员工数据,支持关键词搜索

6对话框与操作

  1. open_add_book_dialog(self):打开添加书籍对话框
  2. add_book(self):执行添加书籍操作
  3. open_edit_book_dialog(self):打开编辑书籍对话框
  4. update_book(self):执行更新书籍操作
  5. delete_book(self):执行删除书籍操作
  6. 类似的客户和员工管理方法:open_add_customer_dialog、update_customer、delete_customer 等

3. 方法功能总结

这个书店管理系统采用了典型的MVC(模型 - 视图 - 控制器)架构:

  1. 模型层:通过sqlite3操作数据库表
  2. 视图层:使用tkinter创建各种GUI界面
  3. 控制层:通过类方法处理用户交互和业务逻辑

系统实现了完整的CRUD(创建、读取、更新、删除)操作,支持用户认证、多角色权限管理、数据统计和系统配置等功能。配置文件和数据库均采用UTF-8编码,确保中文显示正常。

系统 代码详细说明

config.ini文件

程序依赖于config.ini文件来读取系统配置信息,确保该文件存在于与BookstoreApp.py相同的目录下,并且文件内容格式正确。这个地方要求文件后缀是ini文件,文件编码一定是utf-8编码方式,否则系统运行出错。

复制代码
[settings]

system_name = bobo校园书店管理系统

stock_warning_value = 10

records_per_page = 20

在代码开发工具中创建好文件保存后,可以通过用"记事本"打开,再"另存为"的方式修改编码方式。

(1)原理:INI文件是一种常见的配置文件格式,由多个节(section)组成,每个节包含多个键值对(key-value pair)。[settings] 是节的名称,下面的system_name、stock_warning_value和records_per_page是键,对应的值分别是系统名称、库存警告值和每页记录数。

(2)所需依赖包:无,Python 标准库中的 configparser 模块可用于读取和写入 INI 文件。

(3)运行环境:ini是文本文件,不依赖特定运行环境。

BookstoreApp.py文件

BookstoreApp.py 包含了系统的主要业务逻辑和GUI界面,是整个程序的主模块。

1. 导入依赖包

程序需要多个依赖包,所在在程序开头,先导入python的依赖包。

复制代码
import tkinter as tk

from tkinter import ttk, messagebox

import sqlite3

import datetime

import osimport matplotlib.pyplot as plt

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

from tkinter import filedialog

import configparser

原理说明:导入所需的Python库和模块,tkinter用于创建GUI,sqlite3用于数据库操作,datetime用于处理日期时间,os用于操作系统相关操作,matplotlib用于数据可视化,configparser用于读取和写入配置文件。

2. 读取配置文件

要实现全局调用保存好的系统名称、库存预警值和每页显示记录数,我们可以将这些配置信息存储在一个配置文件中,然后在代码里全局引用这些配置。为了持久化配置信息,本系统使用了ini文件来存储配置信息,自然在加载程序之前,就要先加载配置信息,因此,在导入依赖包之后,就开始读取配置文件。

复制代码
# 读取配置文件,使用绝对路径

config = configparser.ConfigParser()

config_file_path = os.path.join(os.path.dirname(__file__), 'config.ini')

# 指定编码为 UTF-8 读取配置文件

config.read(config_file_path, encoding='utf-8')

try:

    # 获取配置信息

system_name = config.get('settings', 'system_name')

    stock_warning_value = int(config.get('settings', 'stock_warning_value'))

    records_per_page = int(config.get('settings', 'records_per_page'))except configparser.NoSectionError:

    messagebox.showerror("配置文件错误", "未找到 'settings' 节,请检查配置文件。")

    raise

(1)原理:使用configparser 模块读取 config.ini文件中的配置信息。如果配置文件中缺少settings节,会弹出错误消息框。

(2)所需依赖包:configparser(Python标准库)。

(3)配置文件路径问题

  1. 程序是在当前工作目录下读取config.ini文件,要是当前工作目录并非BookstoreApp所在目录,就会找不到该文件。
  2. 使用绝对路径读取配置文件:借助os.path.join(os.path.dirname(file), 'config.ini')来获取配置文件的绝对路径。os.path.dirname(file)这个读取的是BookstoreApp.py所在的目录。
  3. 添加异常处理:运用try-except语句捕获configparser.NoSectionError异常,并且弹出错误提示框。

通过这些修改,程序就能准确读取配置文件,避免因路径问题而引发的错误。

(4)config.ini文件中文显示乱码的问题

config.ini文件中文显示乱码通常是由于文件编码问题导致的。在读取和写入配置文件时,需要指定正确的编码格式,一般使用UTF-8编码。

  1. 读取配置文件:在 config.read方法中指定encoding='utf-8'来读取配置文件。
  2. 保存配置文件:在open函数中指定encoding='utf-8'来写入配置文件。
  3. 这也要求配置文件config.ini是UTF-8编码的。

3. 设置matplotlib支持中文显示

由于在系统中添加了图形可视化,而图形中有很多中文字符,所以需要支持中文。在读取配置文件之后添加以下代码:

复制代码
plt.rcParams["font.family"] = ["SimHei"]

plt.rcParams["axes.unicode_minus"] = False   # 解决负号显示问题

(1)原理:设置 matplotlib 的字体为黑体,解决中文显示问题,同时解决负号显示问题。

(2)所需依赖包:matplotlib。

4. BookstoreApp类

BookstoreApp 类是系统的核心类,init 方法用于初始化系统,包括创建主窗口、加载配置文件、连接数据库、创建数据库表和显示登录页面。

复制代码
class BookstoreApp:
	def __init__(self, root):
		self.root = root
		# 读取配置文件
		self.config_file = os.path.join(os.path.dirname(__file__), 'config.ini')
		self.load_config()
		
		self.root.title(f"{system_name}")
		self.root.geometry("1024x768")
		self.root.minsize(800, 600)
		
		# 数据库连接
		self.db_path = os.path.join(os.path.dirname(__file__), "bookstore.db")  # 构建数据库的绝对路径
		# 使用绝对路径连接数据库
		self.conn = sqlite3.connect(self.db_path)
		self.create_tables()
		
		# 当前用户信息
		self.current_user = None
		
		# 创建主界面
		self.create_main_window()

所需依赖包:tkinter、sqlite3、configparser。

5. load_config方法

要实现将"系统设置"保存到config.ini并全局应用,就需要先实现对配置文件内容的加载和保存,并在程序启动时检查并创建配置文件。load_config方法用于加载配置,save_config方法用于保存配置。

复制代码
def load_config(self):
	try:
		config = configparser.ConfigParser()
		# 指定编码为 UTF-8 读取配置文件
		config.read(self.config_file, encoding='utf-8')
		self.system_name = config.get('settings', 'system_name', fallback="bobo校园书店管理系统")
		self.stock_warning_value = int(config.get('settings', 'stock_warning_value', fallback=10))
		self.records_per_page = int(config.get('settings', 'records_per_page', fallback=20))
		self.root.title(self.system_name)  # 设置窗口标题
	except Exception as e:
		messagebox.showerror("配置错误", f"加载配置失败: {str(e)}")
		# 使用默认值
		self.system_name = "bobo校园书店管理系统"
		self.stock_warning_value = 10
		self.records_per_page = 20

(1)代码说明:读取配置文件中的配置信息,如果读取失败,使用默认值。

(2)所需依赖包:configparser、tkinter。

需要涉及到的关键部分包括:

  1. 配置文件管理:添加load_config和save_config方法
  2. 系统设置保存:更新show_system_settings中的保存逻辑
  3. 系统标题:在load_config中设置窗口标题
  4. 分页功能:在所有列表页面使用self.records_per_page
  5. 库存预警:在统计分析中使用self.stock_warning_value
  6. 配置文件初始化:在创建表时检查并创建配置文件

这些修改将确保系统设置能够正确保存到配置文件,并在整个系统中全局应用。

6. save_config方法

将当前的配置信息写入 config.ini 文件。

复制代码
def save_config(self):
	config = configparser.ConfigParser()
	config['settings'] = {
		'system_name': self.system_name,
		'stock_warning_value': self.stock_warning_value,
		'records_per_page': self.records_per_page
	}
	try:
		# 指定编码为 UTF-8 写入配置文件
		with open(self.config_file, 'w', encoding='utf-8') as f:
			config.write(f)
		messagebox.showinfo("成功", "配置已保存!")
	except Exception as e:
		messagebox.showerror("错误", f"保存配置失败: {str(e)}")

7. create_tables方法

在设计之初,希望减轻用户的开发难度,省略数据库的设计。所以你可以直接运行这个程序来使用校园书店管理系统,首次运行会自动创建数据库并添加默认管理员账户(用户名:admin,密码:admin)。自动创建数据库并添加默认管理员账户的功能就由create_tables方法实现。

复制代码
def create_tables(self):
	# 检查配置文件是否存在,不存在则创建
	if not os.path.exists(self.config_file):
		self.save_config()
	# 创建书籍表
	self.conn.execute('''
	CREATE TABLE IF NOT EXISTS books (
		isbn TEXT PRIMARY KEY,
		title TEXT NOT NULL,
		author TEXT,
		publisher TEXT,
		price REAL,
		stock INTEGER
	)
	''')
	
	# 创建客户表
	self.conn.execute('''
	CREATE TABLE IF NOT EXISTS customers (
		customer_id INTEGER PRIMARY KEY AUTOINCREMENT,
		name TEXT NOT NULL,
		contact TEXT,
		membership TEXT DEFAULT '普通会员',
		points INTEGER DEFAULT 0
	)
	''')
	
	# 创建员工表
	self.conn.execute('''
	CREATE TABLE IF NOT EXISTS employees (
		employee_id INTEGER PRIMARY KEY AUTOINCREMENT,
		name TEXT NOT NULL,
		position TEXT,
		salary REAL,
		hire_date TEXT
	)
	''')
	
	# 创建采购表
	self.conn.execute('''
	CREATE TABLE IF NOT EXISTS purchases (
		purchase_id INTEGER PRIMARY KEY AUTOINCREMENT,
		isbn TEXT NOT NULL,
		quantity INTEGER NOT NULL,
		price REAL NOT NULL,
		supplier TEXT,
		purchase_date TEXT,
		FOREIGN KEY (isbn) REFERENCES books(isbn)
	)
	''')
	
	# 创建销售表
	self.conn.execute('''
	CREATE TABLE IF NOT EXISTS sales (
		sale_id INTEGER PRIMARY KEY AUTOINCREMENT,
		isbn TEXT NOT NULL,
		customer_id INTEGER,
		quantity INTEGER NOT NULL,
		price REAL NOT NULL,
		sale_date TEXT,
		FOREIGN KEY (isbn) REFERENCES books(isbn),
		FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
	)
	''')
	
	# 创建用户表(用于登录)
	self.conn.execute('''
	CREATE TABLE IF NOT EXISTS users (
		username TEXT PRIMARY KEY,
		password TEXT NOT NULL,
		role TEXT NOT NULL,
		employee_id INTEGER,
		FOREIGN KEY (employee_id) REFERENCES employees(employee_id)
	)
	''')
	
	# 添加默认管理员账户(如果不存在)
	cursor = self.conn.cursor()
	cursor.execute("SELECT COUNT(*) FROM users WHERE username = 'admin'")
	if cursor.fetchone()[0] == 0:
		self.conn.execute("INSERT INTO users (username, password, role) VALUES ('admin', 'admin', '管理员')")
		self.conn.commit()
		
	self.conn.commit()

(1)代码说明:检查配置文件是否存在,如果不存在则保存默认配置。创建数据库中的各个表,包括书籍表、客户表、员工表、采购表、销售表和用户表。如果表已经存在,则不进行创建。添加默认管理员账户(如果不存在)。

(2)所需依赖包:sqlite3、os。

当创建好数据库之后,可以按前文方法浏览器打开SQLite-Web数据库管理工具,可以看到如下界面:

8. create_main_window方法

create_main_window方法创建程序主窗口的方法,创建窗口之后,创建顶部导航栏和主框架,初始显示登录页面。

复制代码
def create_main_window(self):
	# 创建顶部导航栏
	self.create_navbar()
	
	# 创建主框架
	self.main_frame = ttk.Frame(self.root)
	self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
	
	# 初始显示登录页面
	self.show_login_page()

所需依赖包:tkinter。

运行代码之后的,首先调用show_login_page()方法,显示登录界面,效果如下:

9. create_navbar方法

在create_main_window方法中要创建顶部导航栏。

复制代码
def create_navbar(self):
	# 顶部导航栏
	self.navbar = ttk.Frame(self.root, height=40)
	self.navbar.pack(fill=tk.X)
	
	# 品牌标识
	self.brand_label = ttk.Label(self.navbar, text=f"{system_name}", font=("微软雅黑", 12, "bold"))
	self.brand_label.pack(side=tk.LEFT, padx=10, pady=10)
	
	# 导航按钮(初始隐藏,登录后显示)
	self.nav_buttons = {}
	self.nav_frame = ttk.Frame(self.navbar)
	
	# 状态标签
	self.status_label = ttk.Label(self.navbar, text="未登录", font=("微软雅黑", 10))
	self.status_label.pack(side=tk.RIGHT, padx=10, pady=10)

创建顶部导航栏,包括品牌标识、导航按钮(初始隐藏)和状态标签。

当登录主界面之后,看到的效果如下:

10. show_login_page方法

show_login_page方法,用于显示登录页面,包括用户名和密码输入框、登录按钮。用户输入用户名和密码后,点击登录按钮或按下回车键,系统会在数据库中查询用户信息,如果匹配则登录成功,显示欢迎页面并设置导航栏;否则弹出登录失败的消息框。

复制代码
def show_login_page(self):
	# 清空主框架
	for widget in self.main_frame.winfo_children():
		widget.destroy()

	# 隐藏导航按钮
	if hasattr(self, 'nav_frame'):
		self.nav_frame.pack_forget()

	# 登录框架
	login_frame = ttk.LabelFrame(self.main_frame, text="用户登录", padding=(20, 10))
	login_frame.pack(fill=tk.BOTH, expand=True, padx=200, pady=100, anchor=tk.CENTER)

	# 用户名
	username_label = ttk.Label(login_frame, text="用户名:")
	username_label.pack(pady=10, anchor=tk.CENTER)
	username_entry = ttk.Entry(login_frame, width=30)
	username_entry.pack(pady=5, anchor=tk.CENTER)

	# 密码
	password_label = ttk.Label(login_frame, text="密码:")
	password_label.pack(pady=10, anchor=tk.CENTER)
	password_entry = ttk.Entry(login_frame, width=30, show="*")
	password_entry.pack(pady=5, anchor=tk.CENTER)

	# 登录按钮
	def login():
		username = username_entry.get()
		password = password_entry.get()

		cursor = self.conn.cursor()
		cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password))
		user = cursor.fetchone()

		if user:
			self.current_user = {
				'username': user[0],
				'role': user[2]
			}
			self.status_label.config(text=f"当前用户: {username} ({user[2]})")
			messagebox.showinfo("登录成功", f"欢迎回来,{username}!")
			self.show_welcome_page()
			self.setup_navigation()
		else:
			messagebox.showerror("登录失败", "用户名或密码错误")

	login_button = ttk.Button(login_frame, text="登录", command=login)
	login_button.pack(pady=20, anchor=tk.CENTER)

	# 绑定回车事件到登录按钮
	username_entry.bind("<Return>", lambda event: login())
	password_entry.bind("<Return>", lambda event: login())

	# 设置焦点
	username_entry.focus()

show_login_page方法方法中又嵌套了login()方法。

11. setup_navigation方法

系统划分了两个角色,分别是管理员和普通员工。根据用户角色显示不同的导航项,管理员可以访问所有功能,普通员工只能访问部分功能。添加退出登录按钮,点击后返回登录页面。

复制代码
def setup_navigation(self):
	# 清空现有导航按钮
	for widget in self.nav_frame.winfo_children():
		widget.destroy()
	
	# 根据用户角色显示不同的导航项
	if self.current_user['role'] == '管理员':
		nav_items = [
			("首页", self.show_welcome_page),
			("书籍管理", self.show_book_management),
			("客户管理", self.show_customer_management),
			("员工管理", self.show_employee_management),
			("采购管理", self.show_purchase_management),
			("销售管理", self.show_sale_management),
			("统计分析", self.show_statistics),
			("系统设置", self.show_system_settings)
		]
	else:  # 普通员工
		nav_items = [
			("首页", self.show_welcome_page),
			("书籍管理", self.show_book_management),
			("客户管理", self.show_customer_management),
			("销售管理", self.show_sale_management)
		]
	
	# 添加导航按钮
	for text, command in nav_items:
		btn = ttk.Button(self.nav_frame, text=text, command=command)
		btn.pack(side=tk.LEFT, padx=5, pady=5)
	
	# 添加退出按钮
	ttk.Button(self.nav_frame, text="退出登录", command=self.show_login_page).pack(side=tk.RIGHT, padx=5, pady=5)
	
	# 显示导航框架
	self.nav_frame.pack(side=tk.LEFT, padx=10, pady=5)

管理员的界面效果如下:

当在管理员账号下,添加了员工之后,可以使用员工的账号密码登录,登录的界面效果如下:

两种角色可以管理的功能是不同的。

12. show_welcome_page方法

显示欢迎页面,包括系统概览(书籍总数、客户总数、今日销售额、库存预警)和最近销售记录。从数据库中查询相关数据并显示在界面上。

复制代码
def show_welcome_page(self):
	# 清空主框架
	for widget in self.main_frame.winfo_children():
		widget.destroy()
	
	# 创建欢迎界面
	welcome_label = ttk.Label(self.main_frame, text=f"欢迎使用{system_name}", font=("微软雅黑", 24))
	welcome_label.pack(pady=50)
	
	# 显示系统信息
	info_frame = ttk.LabelFrame(self.main_frame, text="系统概览", padding=(10, 5))
	info_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
	
	# 获取统计数据
	cursor = self.conn.cursor()
	
	# 书籍总数
	cursor.execute("SELECT COUNT(*) FROM books")
	book_count = cursor.fetchone()[0]
	
	# 客户总数
	cursor.execute("SELECT COUNT(*) FROM customers")
	customer_count = cursor.fetchone()[0]
	
	# 今日销售额
	today = datetime.date.today().strftime("%Y-%m-%d")
	cursor.execute("SELECT SUM(price * quantity) FROM sales WHERE sale_date = ?", (today,))
	today_sales = cursor.fetchone()[0] or 0
	
	# 库存预警
	cursor.execute(f"SELECT COUNT(*) FROM books WHERE stock < {self.stock_warning_value}")
	low_stock_count = cursor.fetchone()[0]
	
	# 显示统计数据
	stats = [
		("书籍总数", book_count),
		("客户总数", customer_count),
		("今日销售额", f"¥{today_sales:.2f}"),
		("库存预警", low_stock_count)
	]
	
	for i, (label, value) in enumerate(stats):
		frame = ttk.Frame(info_frame)
		frame.pack(fill=tk.X, padx=10, pady=5)
		
		ttk.Label(frame, text=label, font=("微软雅黑", 12)).pack(side=tk.LEFT, padx=10)
		ttk.Label(frame, text=str(value), font=("微软雅黑", 16, "bold")).pack(side=tk.RIGHT, padx=10)
	
	# 最近销售记录
	recent_frame = ttk.LabelFrame(self.main_frame, text="最近销售记录", padding=(10, 5))
	recent_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
	
	# 创建表格
	columns = ("sale_id", "isbn", "title", "customer", "quantity", "price", "date")
	self.recent_tree = ttk.Treeview(recent_frame, columns=columns, show="headings", height=5)
	
	# 设置列标题
	self.recent_tree.heading("sale_id", text="销售ID")
	self.recent_tree.heading("isbn", text="ISBN")
	self.recent_tree.heading("title", text="书名")
	self.recent_tree.heading("customer", text="客户")
	self.recent_tree.heading("quantity", text="数量")
	self.recent_tree.heading("price", text="金额")
	self.recent_tree.heading("date", text="日期")
	
	# 设置列宽
	self.recent_tree.column("sale_id", width=80)
	self.recent_tree.column("isbn", width=120)
	self.recent_tree.column("title", width=200)
	self.recent_tree.column("customer", width=100)
	self.recent_tree.column("quantity", width=80)
	self.recent_tree.column("price", width=80)
	self.recent_tree.column("date", width=120)
	
	self.recent_tree.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
	
	# 获取最近10条销售记录
	cursor.execute('''
	SELECT s.sale_id, s.isbn, b.title, c.name, s.quantity, s.price, s.sale_date
	FROM sales s
	LEFT JOIN books b ON s.isbn = b.isbn
	LEFT JOIN customers c ON s.customer_id = c.customer_id
	ORDER BY s.sale_date DESC
	LIMIT 10
	''')
	
	for row in cursor.fetchall():
		self.recent_tree.insert("", tk.END, values=row)

所需依赖包:tkinter、sqlite3、datetime。

13. show_book_management方法

显示书籍管理页面,包括搜索框、添加书籍按钮、书籍列表表格、编辑按钮和删除按钮。用户可以搜索书籍、添加新书籍、编辑现有书籍信息,以及操作之后对数据列表进行刷新。

复制代码
def show_book_management(self):
	# 清空主框架
	for widget in self.main_frame.winfo_children():
		widget.destroy()
	
	# 创建管理界面
	manage_frame = ttk.LabelFrame(self.main_frame, text="书籍管理", padding=(10, 5))
	manage_frame.pack(fill=tk.BOTH, expand=True)
	
	# 上部操作区
	top_frame = ttk.Frame(manage_frame)
	top_frame.pack(fill=tk.X, padx=10, pady=10)
	
	# 搜索框
	ttk.Label(top_frame, text="搜索:").pack(side=tk.LEFT, padx=5)
	search_entry = ttk.Entry(top_frame, width=30)
	search_entry.pack(side=tk.LEFT, padx=5)
	
	def search_books():
		keyword = search_entry.get()
		self.load_books(keyword)
	
	ttk.Button(top_frame, text="搜索", command=search_books).pack(side=tk.LEFT, padx=5)
	
	# 添加按钮
	def open_add_book_dialog():
		dialog = tk.Toplevel(self.root)
		dialog.title("添加书籍")
		dialog.geometry("500x400")
		dialog.resizable(True, True)
		dialog.transient(self.root)
		dialog.grab_set()
		
		# 表单框架
		form_frame = ttk.Frame(dialog, padding=(20, 10))
		form_frame.pack(fill=tk.BOTH, expand=True)
		
		# ISBN
		ttk.Label(form_frame, text="ISBN:").grid(row=0, column=0, sticky=tk.W, pady=10)
		isbn_entry = ttk.Entry(form_frame, width=30)
		isbn_entry.grid(row=0, column=1, pady=10)
		
		# 书名
		ttk.Label(form_frame, text="书名:").grid(row=1, column=0, sticky=tk.W, pady=10)
		title_entry = ttk.Entry(form_frame, width=30)
		title_entry.grid(row=1, column=1, pady=10)
		
		# 作者
		ttk.Label(form_frame, text="作者:").grid(row=2, column=0, sticky=tk.W, pady=10)
		author_entry = ttk.Entry(form_frame, width=30)
		author_entry.grid(row=2, column=1, pady=10)
		
		# 出版社
		ttk.Label(form_frame, text="出版社:").grid(row=3, column=0, sticky=tk.W, pady=10)
		publisher_entry = ttk.Entry(form_frame, width=30)
		publisher_entry.grid(row=3, column=1, pady=10)
		
		# 价格
		ttk.Label(form_frame, text="价格:").grid(row=4, column=0, sticky=tk.W, pady=10)
		price_entry = ttk.Entry(form_frame, width=30)
		price_entry.grid(row=4, column=1, pady=10)
		
		# 库存
		ttk.Label(form_frame, text="库存:").grid(row=5, column=0, sticky=tk.W, pady=10)
		stock_entry = ttk.Entry(form_frame, width=30)
		stock_entry.grid(row=5, column=1, pady=10)
		
		# 添加按钮
		def add_book():
			isbn = isbn_entry.get()
			title = title_entry.get()
			author = author_entry.get()
			publisher = publisher_entry.get()
			
			try:
				price = float(price_entry.get())
				stock = int(stock_entry.get())
			except ValueError:
				messagebox.showerror("错误", "价格和库存必须是数字!")
				return
			
			if not isbn or not title:
				messagebox.showerror("错误", "ISBN和书名不能为空!")
				return
			
			try:
				self.conn.execute(
					"INSERT INTO books VALUES (?, ?, ?, ?, ?, ?)",
					(isbn, title, author, publisher, price, stock)
				)
				self.conn.commit()
				messagebox.showinfo("成功", "书籍添加成功!")
				dialog.destroy()
				self.load_books()
			except sqlite3.IntegrityError:
				messagebox.showerror("错误", "ISBN已存在!")
			except Exception as e:
				messagebox.showerror("错误", f"添加书籍失败: {str(e)}")
		
		ttk.Button(form_frame, text="添加", command=add_book).grid(row=6, column=0, columnspan=2, pady=20)
	
	ttk.Button(top_frame, text="添加书籍", command=open_add_book_dialog).pack(side=tk.RIGHT, padx=5)
	
	# 表格区域
	table_frame = ttk.Frame(manage_frame)
	table_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
	
	# 创建表格
	columns = ("isbn", "title", "author", "publisher", "price", "stock")
	self.book_tree = ttk.Treeview(table_frame, columns=columns, show="headings", height=self.records_per_page)
	
	# 设置列标题
	self.book_tree.heading("isbn", text="ISBN")
	self.book_tree.heading("title", text="书名")
	self.book_tree.heading("author", text="作者")
	self.book_tree.heading("publisher", text="出版社")
	self.book_tree.heading("price", text="价格")
	self.book_tree.heading("stock", text="库存")
	
	# 设置列宽
	self.book_tree.column("isbn", width=150)
	self.book_tree.column("title", width=200)
	self.book_tree.column("author", width=150)
	self.book_tree.column("publisher", width=150)
	self.book_tree.column("price", width=80, anchor=tk.E)
	self.book_tree.column("stock", width=80, anchor=tk.E)
	
	self.book_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
	
	# 添加滚动条
	scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.book_tree.yview)
	self.book_tree.configure(yscroll=scrollbar.set)
	scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
	
	# 底部按钮区域
	bottom_frame = ttk.Frame(manage_frame)
	bottom_frame.pack(fill=tk.X, padx=10, pady=10)
	
	# 编辑按钮
	def open_edit_book_dialog():
		selected_item = self.book_tree.selection()
		if not selected_item:
			messagebox.showinfo("提示", "请先选择一本书!")
			return
		
		item = self.book_tree.item(selected_item[0])
		values = item['values']
		
		dialog = tk.Toplevel(self.root)
		dialog.title("编辑书籍")
		dialog.geometry("500x400")
		dialog.resizable(True, True)
		dialog.transient(self.root)
		dialog.grab_set()
		
		# 表单框架
		form_frame = ttk.Frame(dialog, padding=(20, 10))
		form_frame.pack(fill=tk.BOTH, expand=True)
		
		# ISBN (只读)
		ttk.Label(form_frame, text="ISBN:").grid(row=0, column=0, sticky=tk.W, pady=10)
		isbn_entry = ttk.Entry(form_frame, width=30)
		isbn_entry.grid(row=0, column=1, pady=10)
		isbn_entry.insert(0, values[0])
		isbn_entry.configure(state='readonly')
		
		# 书名
		ttk.Label(form_frame, text="书名:").grid(row=1, column=0, sticky=tk.W, pady=10)
		title_entry = ttk.Entry(form_frame, width=30)
		title_entry.grid(row=1, column=1, pady=10)
		title_entry.insert(0, values[1])
		
		# 作者
		ttk.Label(form_frame, text="作者:").grid(row=2, column=0, sticky=tk.W, pady=10)
		author_entry = ttk.Entry(form_frame, width=30)
		author_entry.grid(row=2, column=1, pady=10)
		author_entry.insert(0, values[2])
		
		# 出版社
		ttk.Label(form_frame, text="出版社:").grid(row=3, column=0, sticky=tk.W, pady=10)
		publisher_entry = ttk.Entry(form_frame, width=30)
		publisher_entry.grid(row=3, column=1, pady=10)
		publisher_entry.insert(0, values[3])
		
		# 价格
		ttk.Label(form_frame, text="价格:").grid(row=4, column=0, sticky=tk.W, pady=10)
		price_entry = ttk.Entry(form_frame, width=30)
		price_entry.grid(row=4, column=1, pady=10)
		price_entry.insert(0, values[4])
		
		# 库存
		ttk.Label(form_frame, text="库存:").grid(row=5, column=0, sticky=tk.W, pady=10)
		stock_entry = ttk.Entry(form_frame, width=30)
		stock_entry.grid(row=5, column=1, pady=10)
		stock_entry.insert(0, values[5])
		
		# 更新按钮
		def update_book():
			isbn = isbn_entry.get()
			title = title_entry.get()
			author = author_entry.get()
			publisher = publisher_entry.get()
			
			try:
				price = float(price_entry.get())
				stock = int(stock_entry.get())
			except ValueError:
				messagebox.showerror("错误", "价格和库存必须是数字!")
				return
			
			if not isbn or not title:
				messagebox.showerror("错误", "ISBN和书名不能为空!")
				return
			
			try:
				self.conn.execute(
					"UPDATE books SET title=?, author=?, publisher=?, price=?, stock=? WHERE isbn=?",
					(title, author, publisher, price, stock, isbn)
				)
				self.conn.commit()
				messagebox.showinfo("成功", "书籍更新成功!")
				dialog.destroy()
				self.load_books()
			except Exception as e:
				messagebox.showerror("错误", f"更新书籍失败: {str(e)}")
		
		ttk.Button(form_frame, text="更新", command=update_book).grid(row=6, column=0, columnspan=2, pady=20)
	
	ttk.Button(bottom_frame, text="编辑", command=open_edit_book_dialog).pack(side=tk.LEFT, padx=5)
	
	# 删除按钮
	def delete_book():
		selected_item = self.book_tree.selection()
		if not selected_item:
			messagebox.showinfo("提示", "请先选择一本书!")
			return
		
		item = self.book_tree.item(selected_item[0])
		isbn = item['values'][0]
		title = item['values'][1]
		
		if messagebox.askyesno("确认", f"确定要删除《{title}》吗?"):
			try:
				# 先检查是否有销售记录
				cursor = self.conn.cursor()
				cursor.execute("SELECT COUNT(*) FROM sales WHERE isbn = ?", (isbn,))
				count = cursor.fetchone()[0]
				
				if count > 0:
					messagebox.showerror("错误", "该书存在销售记录,不能删除!")
					return
				
				self.conn.execute("DELETE FROM books WHERE isbn = ?", (isbn,))
				self.conn.commit()
				messagebox.showinfo("成功", "书籍删除成功!")
				self.load_books()
			except Exception as e:
				messagebox.showerror("错误", f"删除书籍失败: {str(e)}")
	
	ttk.Button(bottom_frame, text="删除", command=delete_book).pack(side=tk.LEFT, padx=5)
	
	# 刷新按钮
	ttk.Button(bottom_frame, text="刷新", command=lambda: self.load_books()).pack(side=tk.RIGHT, padx=5)
	
	# 加载书籍数据
	self.load_books()

所需依赖包:tkinter、sqlite3。

书籍管理界面的效果如下:

14. load_books 方法

show_book_management方法中,需要加载书籍数据,使用load_books方法。

复制代码
def load_books(self, keyword=""):
	# 清空表格
	for item in self.book_tree.get_children():
		self.book_tree.delete(item)
	
	cursor = self.conn.cursor()
	
	if keyword:
		# 搜索查询
		query = """
		SELECT isbn, title, author, publisher, price, stock 
		FROM books 
		WHERE isbn LIKE ? OR title LIKE ? OR author LIKE ?
		"""
		param = f"%{keyword}%"
		cursor.execute(query, (param, param, param))
	else:
		# 获取所有书籍
		cursor.execute("SELECT isbn, title, author, publisher, price, stock FROM books")
	
	# 填充表格
	for row in cursor.fetchall():
		self.book_tree.insert("", tk.END, values=row)

15. show_customer_management 方法

显示客户管理页面,支持客户信息管理。

load_customers()方法加载客户列表。

添加客户信息的界面如下:

16. show_employee_management 方法

显示员工管理页面(管理员权限)

load_employees()方法加载员工列表

添加员工信息的界面如下:

17. show_purchase_management 方法

显示采购管理页面

load_purchases()方法加载采购记录列表

在添加采购记录弹窗上,get_book_info()方法获取书籍信息。

18. show_sale_management 方法

显示销售管理页面

load_sales()方法加载销售记录列表

在添加销售记录弹窗上,get_book_info()方法获取书籍信息。get_customer_info()方法获取客户信息。

19. show_statistics 方法

显示统计分析页面

update_statistics()方法用于当选择新的参数时,更新统计数据。

20. show_system_settings 方法

显示系统设置页面,包括了常规设置、数据库设置、备份与恢复、用户设置。

四、系统完整代码

可以在我的CSDN博客上下载。地址:https://download.csdn.net/download/lzm12278828/91092825

总结

该校园书店管理系统通过配置文件和数据库实现了基本的书店管理功能,使用GUI界面提供了良好的用户体验。系统的主要依赖包为 tkinter、sqlite3 和 matplotlib,运行环境为 Python 3.x。

相关推荐
cylat几秒前
Day38 Dataset和Dataloader类
人工智能·python·深度学习·神经网络·机器学习
aiguangyuan1 小时前
Python元组常用操作方法
python·后端开发
闯闯桑1 小时前
Pyspark中的int
大数据·python·spark·pandas
berryyan2 小时前
Windows 环境下通过 WSL2 成功集成 Claude Code 与 PyCharm 的完整指南
人工智能·python
精灵vector2 小时前
Agent的记忆详细实现机制
python·langchain·llm
小王学python2 小时前
Python语法、注释之数据类型
后端·python
安全系统学习2 小时前
【网络安全】文件上传型XSS攻击解析
开发语言·python·算法·安全·web安全
棱镜研途2 小时前
思辨场域丨AR技术如何重塑未来学术会议体验?
人工智能·计算机视觉·信息可视化·ar·虚拟现实
尤物程序猿3 小时前
深入理解链表数据结构:从Java LinkedList到自定义实现
开发语言·python
DanceDonkey3 小时前
泛型方法调用需要显示指定泛型类型的场景
开发语言·windows·python