分享一下我的数据库课设。







(一)概述
1.1 选题
在本次数据库原理实践中,本小组的选题为书店管理系统。该题目要求如下:
书店管理系统是适合于中小型书店,书城销售的最佳管理软件。可进行图书登记、查找、编辑。
会员登记功能:记录了会员的日常必备的联系项目,软件会自动统计出会员的总数。
销售管理:记录一天所售出的图书量,并记录您当天的销售额。
统计功能:软件会统计出图书的总数,总价格,会员的总数,销售图书数量,销售额等必备数据供管理员查看。
丰富的查找系统:可供管理员查找相关资料数据。
1.2项目背景
本系统的目标主要是实现书店对图书信息、会员信息以及销售信息的管理,实现对图书和会员的基本信息的添加,查询,删除等功能,同时结合书店的销售系统,录入销售信息,实现对销售信息的管理和统计,较大程度地实现书店的信息化管理。
1.3编写目的
该课程设计报告的编写目的主要为对该书店管理系统进行系统性的总结,并描述本项目的实现方法和功能。
1.4 内容组织安排
本次课程设计报告总共分为9个部分。本文首先从书店管理系统的选题、背景、编写目的等方面进行介绍。其次,从系统的需求分析、概念结构设计、逻辑结构设计、数据库物理设计与实施、应用系统设计与实施、数据库测试等方面进行介绍与说明。最后,是本次课程设计的总结、收获与体会。
第一部分概述,从选题、项目背景、课程设计报告编写目的、内容组织安排等方面进行较为详细地介绍。
第二部分需求分析,提出了系统所要实现的功能需求、管理系统的各个业务流程,并分析了系统的安全性需求等。
第三部分概念结构设计,分别对各个模块的功能进行更为详细地描述,并指出
各模块中所包含的实体,画出了各模块的E-R图以及整个系统的E-R图。
第四部分逻辑结构设计,介绍了对各个关系模式所进行的优化。
第五部分数据库物理设计与实施,分别从数据库应用的硬件、软件环境、物理结构设计、物理结构优化、数据库实施等方面进行详细介绍。
第六部分应用系统设计与实施,交代了各模块各个界面的设计并给出了关键实现代码。
第七部分数据库测试,对所设计的数据库系统进行全方位的测试。其中,包括了数据测试与各个模块功能的测试。
第八部分收获和体会,总结了我们本次课程设计的经验与深入的体会。
第九部分参考文献,罗列出了本次系统设计所参考的所有文献。
(二)需求分析
2.1 系统角色需求
2.1.1 店长需求
(1) 图书管理:实现对图书信息的添加、查看、查询、修改、删除等功能。
录入图书信息:添加新图书的信息,包括书名、类别、库存等信息。查找图书信息:系统管理员能够查找图书信息,支持精确查找和模糊查找,如搜索所有书名包含“人工智能”四个字的图书信息;以及其他模糊查询等。查看图书信息:查看全部图书的信息,能根据类别查看图书信息;能查看图书总数;能查看各类别图书总数。编辑图书信息:修改/更新图书的信息,删除图书信息。
(2) 会员管理:实现对会员信息的添加、查询、修改、删除、统计会员总数等功能。
会员添加:添加新会员的信息,包括姓名、年龄、生日等信息。会员查询:查询会员信息,支持模糊查询,如搜索所有姓名包含“唐”这个字的会员信息。会员编辑(修改、删除):修改/更新会员的信息,删除会员信息。会员总数:按查询得出会员的总数,支持模糊查询和范围查询。
(3) 销售管理:实现录入销售信息、销售记录和数量统计的功能。
录入销售信息:书店售出图书结算时,将对应的销售信息录入系统中。销售记录:记录所售出图书的图书信息,并记录当天的销售额与每日的销售额。统计功能:统计出书店的销售总额以及每日的销售额,销售图书总数,订单数等数据。
(1) 图书管理:实现对图书信息的添加、查看、查询、修改、删除等功能。
录入图书信息:添加新图书的信息,包括书名、类别、库存等信息。查找图书信息:系统管理员能够查找图书信息,支持精确查找和模糊查找,如搜索所有书名包含“人工智能”四个字的图书信息;以及其他模糊查询等。查看图书信息:查看全部图书的信息,能根据类别查看图书信息;能查看图书总数;能查看各类别图书总数。编辑图书信息:修改/更新图书的信息,删除图书信息。
(2) 会员管理:实现对会员信息的添加、查询、修改、删除、统计会员总数等功能。
会员添加:添加新会员的信息,包括姓名、年龄、生日等信息。会员查询:查询会员信息,支持模糊查询,如搜索所有姓名包含“唐”这个字的会员信息。会员编辑(修改、删除):修改/更新会员的信息,删除会员信息。会员总数:按查询得出会员的总数,支持模糊查询和范围查询。
2.2 系统功能模块总述
2-1 系统功能模块图
书店管理系统功能模块图如2-1所示。根据需求,分为3个模块,只有登录后才能进入系统,登录系统分为店长登录和店员登录2种登录模式,店长身份登录具有系统的所有使用权限,包括图书管理、会员管理、销售管理功能。店员身份登录可以使用会员管理功能。
2.3 系统的数据流图
图2-2 系统数据流图
书店管理系统的数据流图如图2-2所示。会员购买图书产生订单,结算后完成购买,订单记录流向销售记录。会员购买图书产生的订单会对图书库存产生影响,图书的库存会产生库存更新。
2.4 系统的数据字典
2.4.1 数据项
(1) 用户账号:
数据项名:用户账号
含义说明:唯一标识每个用户
别名:无
类型:定长字符型
长度:8
取值范围:仅可取8位数字
取值含义:无
与其他数据项的逻辑关系:用户账号的值确定了与用户有关的其他数据项的值
(2) 用户密码:
数据项名:用户密码
含义说明:无
别名:无
类型:不定长字符型
长度:255
取值范围:无特殊限制
取值含义:无
与其他数据项的逻辑关系:无
(3) 会员号:
数据项名:会员号
含义说明:唯一标识每个会员
别名:无
类型:整型
长度:无
取值范围:无
取值含义:无
与其他数据项的逻辑关系:会员号的值确定了与会员有关的其他数据项的值
(4) 会员姓名:
数据项名:会员姓名
含义说明:无
别名:无
类型:不定长字符型
长度:255
取值范围:无特殊限制
取值含义:无
与其他数据项的逻辑关系:无
(5) 会员性别:
数据项名:会员性别
含义说明:无
别名:无
类型:字符型
长度:2
取值范围:仅能填男或女
取值含义:无
与其他数据项的逻辑关系:无
(6) 会员电话:
数据项名:会员电话
含义说明:无
别名:无
类型:字符型
长度:11
取值范围:仅可取11位数字
取值含义:无
与其他数据项的逻辑关系:无
(7) 会员生日:
数据项名:会员生日
含义说明:无
别名:无
类型:日期
长度:
取值范围:无特殊限制
取值含义:无
与其他数据项的逻辑关系:无
(8) 会员积分:
数据项名:会员积分
含义说明:无
别名:无
类型:整型
长度:无
取值范围:无
取值含义:无
与其他数据项的逻辑关系:无
(9) 图书号:
数据项名:图书号
含义说明:唯一标识每种图书
别名:无
类型:整型
长度:
取值范围:无
取值含义:无
与其他数据项的逻辑关系:图书号的值确定了与图书有关的其他数据项的值
(10) 图书名称:
数据项名:图书名称
含义说明:无
别名:无
类型:不定长字符型
长度:255
取值范围:无特殊限制
取值含义:无
与其他数据项的逻辑关系:无
(11) 图书类别:
数据项名:图书类别
含义说明:无
别名:无
类型:不定长字符型
长度:255
取值范围:无特殊限制
取值含义:无
与其他数据项的逻辑关系:无
(12) 图书作者:
数据项名:图书作者
含义说明:无
别名:无
类型:不定长字符型
长度:255
取值范围:无特殊限制
取值含义:无
与其他数据项的逻辑关系:无
(13) 图书价格:
数据项名:图书价格
含义说明:无
别名:无
类型:decimal
长度:10
取值范围:保留两位小数,不能为负数
取值含义:无
与其他数据项的逻辑关系:无
(14) 图书库存:
数据项名:图书库存
含义说明:无
别名:无
类型:int
长度:
取值范围:不能为负数
取值含义:无
与其他数据项的逻辑关系:无
(15) 图书出版社:
数据项名:图书出版社
含义说明:无
别名:无
类型:不定长字符型
长度:255
取值范围:无特殊限制
取值含义:无
与其他数据项的逻辑关系:无
(16) 订单号:
数据项名:订单号
含义说明:唯一标识每个订单
别名:无
类型:整型
长度:
取值范围:无
取值含义:无
与其他数据项的逻辑关系:订单号的值确定了与订单有关的其他数据项的值
(17) 订单总额:
数据项名:订单总额
含义说明:无
别名:无
类型:decimal
长度:10
取值范围:保留两位位小数,不能为负数
取值含义:无
(18) 订单日期:
数据项名:订单日期
含义说明:无
别名:无
类型:日期
长度:
取值范围:无特殊限制
取值含义:无
(19) 购买数量:
数据项名:购买数量
含义说明:无
别名:无
类型:int
长度:
取值范围:不能为负数
取值含义:无
(20) 订单会员号:
数据项名:订单会员号
含义说明:标识订单对应的会员
别名:无
类型:int
长度:
取值范围:
取值含义:无
(21) 订单详情记录号:
数据项名:订单详情记录号
含义说明:唯一标识每个订单详情的记录
别名:无
类型:int
长度:
取值范围:无特殊限制
取值含义:无
(22) 图书售出数量:
数据项名:图书售出数量
含义说明:记录订单中某本图书的售出数量
别名:无
类型:int
长度:
取值范围:不能为负数
取值含义:无
(23) 订单详情销售额:
数据项名:订单详情销售额
含义说明:记录订单中某本图书的销售额
别名:无
类型:int
长度:
取值范围:不能为负数
取值含义:无
(24) 用户级别:
数据项名:用户级别
含义说明:标识用户的权限等级
别名:无
类型:smallint
长度:
取值范围:无特殊限制
取值含义:无
2.4.2 数据结构
(1) 用户:
数据结构名:用户
含义说明:书店管理系统的主体数据结构,定义了一个用户的有关信息
组成:用户账号,用户密码,用户级别
(2) 会员:
数据结构名:会员
含义说明:书店管理系统的主体数据结构,定义了一个会员的有关信息
组成:会员号,会员姓名,会员性别,会员电话,会员生日,会员积分
(3) 图书:
数据结构名:图书
含义说明:书店管理系统的主体数据结构,定义了一个图书的有关信息
组成:图书号,图书名称,图书类别,图书作者,图书价格,图书库存,出版社
(4) 销售记录:
数据结构名:销售记录
含义说明:书店管理系统的主体数据结构,定义了一个销售记录的有关信息
组成:订单号,会员号,图书号,购买数量,订单总额,订单日期
(4) 销售详情记录:
数据结构名:销售详情记录
含义说明:书店管理系统的主体数据结构,定义了一个销售详情记录的有关信息
组成:订单详情记录号,订单号,图书号,图书名称,图书价格,图书售出数量,订单详情销售额
2.4.3 数据流
(1) 订单:
数据流名:订单
数据流来源:结算
数据流去向:销售记录,订单生成
含义说明:会员购买图书时生成的信息
组成:订单号,会员号,图书号,购买数量,订单总额,订单日期
平均流量:每天100
高峰期流量:每天150
(2) 订单详情:
数据流名:订单详情
数据流来源:订单生成
数据流去向:销售详情记录
含义说明:订单生成时对应的详情信息
组成:订单详情记录号,订单号,图书号,图书名称,图书价格,图书售出数量,订单详情销售额
平均流量:每天300
高峰期流量:每天450
(3) 会员信息:
数据流名:会员信息
数据流来源:添加会员
数据流去向:会员记录
含义说明:会员入会时录入会员的相关信息
组成:会员号,会员姓名,会员性别,会员电话,会员生日,会员积分
平均流量:每天20
高峰期流量:每天60
(4) 图书信息:
数据流名:图书信息
数据流来源:订单生成,添加图书
数据流去向:图书记录
含义说明:用户向系统中添加的图书,订单生成时对应图书库存减少
组成:图书号,图书名称,图书类别,图书作者,图书价格,图书库存,出版社
平均流量:每天300
高峰期流量:每天500
(5) 用户信息:
数据流名:用户信息
数据流来源:添加用户
数据流去向:用户表
含义说明:系统管理员向用户表中添加用户
组成:用户账号,用户密码,用户级别
2.4.4 数据存储
数据存储名:订单
说明:会员购买图书时生成的信息
流入数据流:每天100
流出数据流:每天100
组成:订单号,会员号,图书号,购买数量,订单总额,订单日期
数据量:每天200
存取方式:随机存取
(2) 订单详情:
数据存储名:订单详情
说明:订单生成时对应的详情信息
流入数据流:每天300
流出数据流:每天300
组成:订单详情记录号,订单号,图书号,图书名称,图书价格,图书售出数量,订单详情销售额
数据量:每天600
存取方式:随机存取
(3) 会员信息:
数据存储名:会员信息
说明:会员入会时录入会员的相关信息
流入数据流:每天20
流出数据流:每天200
组成:会员号,会员姓名,会员性别,会员电话,会员生日,会员积分
数据量:每天220
(4) 图书信息:
数据存储名:图书信息
说明:用户向系统中添加的图书,订单生成时对应图书库存减少
流入数据流:每天300
流出数据流:每天300
组成:图书号,图书名称,图书类别,图书作者,图书价格,图书库存,出版社
数据量:每天600
(5) 用户信息:
数据存储名:用户信息
流入数据流:无特殊时间规律
流出数据流:无特殊时间规律
说明:系统管理员向用户表中添加用户
组成:用户账号,用户密码,用户级别
数据量:无特殊时间规律
2.4.5 处理过程
(1) 结算
处理过程名:结算
说明:会员购买图书时需要进行订单结算
输入:会员,图书信息
输出:订单
处理:会员购买图书时产生结算信息
(2) 库存更新
处理过程名:库存更新
说明:会员购买图书或书店增添图书时导致图书库存发生更新
输入:图书信息
输出:图书信息
处理:会员购买图书时减少图书库存,书店增添图书时更新图书库存
(3) 添加会员
处理过程名:添加会员
说明:会员入会导致会员记录发生更新
输入:会员信息
输出:会员记录
处理:会员入会后更新会员记录
(三)概念结构设计
3.1 销售管理模块
3.1.1 模块功能描述
销售管理模块需要实现的功能如下:
- 录入销售信息:书店售出图书结算时,将对应的销售信息录入系统中。
- 记录销售信息:记录销售订单的相关信息,如会员号,售出图书的数量,售出图书的信息,每单的销售额等。
- 统计功能:统计出书店的销售总额以及每日的销售额,销售图书总数,订单数等数据。
本模块设计的实体有会员、订单和订单详情。
本模块中各实体之间的联系为:一名会员可以进行多次购买行为并结算,每次结算产生一个订单,每个订单中包含多本图书,每本图书的销售信息对应一条订单详情,因此会员与订单之间、订单与订单详情之间为一对多的关系。
3.1.2 模块分E-R图
销售管理模块的分E-R图如图3-1所示:

图3-1 销售管理模块分E-R图
3.2 会员管理模块
3.2.1 模块功能描述
会员管理模块需要实现的功能如下:
- 会员添加:添加新会员的信息,包括姓名、年龄、生日等信息。
- 会员查询:查询会员信息,支持模糊查询,如搜索所有姓名包含“唐”这个字的会员信息。
- 会员编辑(修改、删除):修改/更新会员的信息,删除会员信息。
- 会员总数:统计出会员的总数。
本模块设计的实体包含用户、会员。
本模块中各实体之间的联系为:用户(店长、店员)可以为顾客录入会员信息,即添加会员,用户可以查询和编辑会员信息、查看会员总数,因为用户可以管理多个会员,因此用户与会员之间为一对多的关系。
3.2.2 模块分E-R图
图3-2 会员管理模块分E-R图
3.3 图书管理模块
3.3.1 模块功能描述
图书管理模块需要实现的功能如下:
(1)录入图书信息:添加新图书的信息,包括书名、类别、库存等信息。
(2)查找图书信息:系统管理员能够查找图书信息,支持精确查找和模糊查找,如根据书号精确查找某本图书,搜索所有书名包含“人工智能”四个字的图书信息;以及其他模糊查询等。
(3)查看图书信息:查看全部图书的信息,能根据类别查看图书信息;能查看图书总数;能查看各类别图书总数。
(4)编辑图书信息:修改/更新图书的信息,删除图书信息。
本模块设计的实体包含用户、图书。
本模块中各实体之间的联系为:用户(店长、店员)可以为对图书信息进行管理,如录入图书信息,查询和编辑图书信息、查看图书总数,因为用户可以管理所有图书,因此用户与图书之间为一对多的关系。
3.3.2 模块分E-R图
图3-3 图书管理模块分E-R图
3.4总体E-R图

图3-4 总体E-R图
系统整体E-R图如图3-4所示。
各实体属性如下:
●用户:属性有用户账号、密码、级别;
●图书:属性有书号、书名、作者、类别、单价、出版社、库存;
●会员:属性有会员号、姓名、性别、生日、电话、积分;
●订单: 属性有订单号、会员号、图书数量、日期、销售额;
●订单详情:属性有记录号、订单号、书号、书名、单价、数量、总价;
(四)逻辑结构设计
4.1 转换为关系数据模型
基于图3-4所示的系统整体E-R图可以转换为如下关系模型:
●图书:属性有书号、书名、作者、类别、单价、出版社、库存;
●会员:属性有会员号、姓名、性别、生日、电话、积分;
●订单: 属性有订单号、会员号、图书数量、日期、销售额;
●订单详情:属性有记录号、订单号、书号、书名、单价、数量、总价;
4.2 关系模式的优化
对于书店管理系统:
(1)用户关系模式的规范化:
对于系统管理员关系模式,其函数依赖集F用户={用户账号→密码,用户账号→级别},主键是用户账号,由于不存在任何属性对用户账号的部分函数依赖或传递函数依赖,且用户账号是唯一的决定因素,因而用户关系模式已满足BCNF。
(2)会员关系模式的规范化:
对于会员关系模式,其函数依赖集F会员={会员号→姓名,会员号→性别,会员号→电话,会员号→积分,会员号→生日},主键是会员号,由于不存在任何属性对会员号的部分函数依赖或传递函数依赖,且会员号是唯一的决定因素,因而会员关系模式已满足BCNF。
(3)图书关系模式的规范化:
对于图书关系模式,其函数依赖集F图书={书号→书名,书号→类别,书号→作者,书号→单价,书号→库存,书号→出版社},主键是书号,由于不存在任何属性对图书编号的部分函数依赖或传递函数依赖,且书号是唯一的决定因素,因而图书关系模式已满足BCNF。
(4)订单关系模式的规范化:
对于订单关系模式,其函数依赖集F销售记录={单号→会员号,单号→图书数量,单号→销售额,单号→日期},主键是单号,由于不存在任何属性对单号的部分函数依赖或传递函数依赖,且单号是唯一的决定因素,因而订单关系模式已满足BCNF。
(5)销售详情关系模式的规范化:
对于订单详情关系模式,其函数依赖集F订单={记录号→会员号,记录号→书号,记录号→书名,记录号→购买数量,记录号→单价,记录号→总价,主键是记录号,由于不存在任何属性对记录号的部分函数依赖或传递函数依赖,且记录号是唯一的决定因素,因而销售详情关系模式已满足BCNF。
4.3 视图设计
由于本系统采用web开发的形式完成,数据库视图的安全性功能和简化功能主要通过web系统的后端代码实现,因此本系统没有定义视图,而是通过前端html页面和后端js函数,后台处理等方式代替视图满足安全性等需求。
5.1 数据库应用的硬件、软件环境介绍
5.1.1 硬件环境介绍
CPU:3.0G以上
内存:1GB以上
硬盘:80G以上
5.1.1 软件环境介绍
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型和大型网站的开发都选择 MySQL 作为网站数据库。
“Navicat”是一套可创建多个连接的数据库管理工具,用以方便管理 MySQL、Oracle、PostgreSQL、SQLite、SQL Server、MariaDB 和 MongoDB 等不同类型的数据库,它与阿里云、腾讯云、华为云、Amazon RDS、Amazon Aurora、Amazon Redshift、Microsoft Azure、Oracle Cloud 和 MongoDB Atlas等云数据库兼容。你可以创建、管理和维护数据库。Navicat 的功能足以满足专业开发人员的所有需求,但是对数据库服务器初学者来说又简单易操作。Navicat 的用户界面 (GUI) 设计良好,让你以安全且简单的方法创建、组织、访问和共享信息。
PyCharm是一种Python IDE(Integrated Development Environment,集成开发环境),带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外,该IDE提供了一些高级功能,以用于支持Django框架下的专业Web开发。
5.2 物理结构设计
(1)用户表
系统管理员表的表名为user,用于保存用户的相关信息,用户表的各字段设计与解释如下表5-1所示:
表5-1 用户表各字段设计
| 字段名称 | 字段意义 | 字段类型 | 字段长度 | 允许为空 | 完整性约束 |
| id | 用户账号 | CHAR | 8 | 否 | 主键 |
| password | 密码 | VARCHAR | 255 | 否 | |
| grade | 用户级别 | SMALLINT | 否 |
(2)会员表
会员表的表名为membership,用于保存会员的相关信息,会员表的各字段设计与解释如下表5-2所示:
表5-2 会员表各字段设计
| 字段名称 | 字段意义 | 字段类型 | 字段长度 | 允许为空 | 完整性约束 |
| mid | 会员号 | INT | 否 | 主键 | |
| mname | 姓名 | VARCHAR | 255 | 是 | |
| sex | 性别 | CHAR | 2 | 是 | 仅能填男或女 |
| phone | 电话 | VARCHAR | 11 | 否 | |
| points | 积分 | INT | 是 | ||
| birth | 生日 | DATE | 是 |
(3)图书表
图书表的表名为book,用于保存图书的相关信息,图书表的各字段设计与解释如下表5-3所示:
表5-3 图书表各字段设计
| 字段名称 | 字段意义 | 字段类型 | 字段长度 | 允许为空 | 完整性约束 |
| bid | 图书号 | INT | 否 | 主键 | |
| bname | 书名 | VARCHAR | 255 | 否 | |
| category | 类别 | VARCHAR | 255 | 否 | |
| author | 作者 | VARCHAR | 255 | 否 | |
| price | 单价 | DECIMAL | 10 | 否 | 保留两位小数,不能为负数 |
| quantity | 库存 | INT | 10 | 否 | 不能为负数 |
| publishment | 出版社 | VARCHAR | 255 | 否 |
(4)订单表
订单表的表名为sale,用于保存订单的相关信息,订单表的各字段设计与解释如下表5-4所示:
表5-4 订单详情表各字段设计
| 字段名称 | 字段意义 | 字段类型 | 字段长度 | 允许为空 | 完整性约束 |
| did | 记录号 | INT | 否 | 主键 | |
| oid | 单号 | INT | 否 | 外键 | |
| bid | 书号 | INT | 否 | 外键 | |
| bname | 书名 | VARCHAR | 255 | 否 | 外键 |
| price | 单价 | DECIMAL | 10 | 否 | 保留两位小数,不能为负数 |
| sum | 总价 | DECIMAL | 10 | 否 | 保留两位小数,不能为负数 |
| number | 数量 | INT | 10 | 否 | 不能为负数 |
(5)订单详情表
订单表的表名为sale_detail,用于保存订单的详细信息,订单详情表的各字段设计与解释如下表5-5所示:
表5-5 订单详情表各字段设计
| 字段名称 | 字段意义 | 字段类型 | 字段长度 | 允许为空 | 完整性约束 |
| oid | 单号 | INT | 否 | 主键 | |
| mid | 会员号 | INT | 否 | 外键 | |
| sales | 总额 | DECIMAL | 10 | 否 | 保留两位小数,不能为负数 |
| date | 日期 | DATE | 否 | ||
| bnumber | 图书数量 | INT | 10 | 否 | 不能为负数 |
5.3 物理结构优化
5.3.1索引设计
在基本表中,设定主码时自动创建聚簇索引。在用户表中,用户账号为主码,自动创建聚簇索引。由于用户信息很少用到,故可不用构建非聚簇索引。在订单表中,订单号为主码,自动创建聚簇索引。在图书表中,数据库根据书名字段自动创建聚簇索引。
在销售详情表中,有参照于订单表的外键oid,参照于图书表的外键bid,参照于图书表的外键bname,由于参照的主表可能有删操作,为了避免子表出现死锁进行全表扫描,需要在外键列上建立索引。
INDEX `bname`(`bname` ASC) USING BTREE
INDEX `mid`(`mid` ASC) USING BTREE
INDEX `oid`(`oid` ASC) USING BTREE,
INDEX `bid`(`bid` ASC) USING BTREE,
INDEX `bname`(`bname` ASC) USING BTREE
5.4 数据库实施
CREATE TABLE `book` (
`bid` int NOT NULL,
`bname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`quantity` int NOT NULL,
`category` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`publishment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`author` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`price` decimal(10, 2) NOT NULL,
PRIMARY KEY (`bid`) USING BTREE,
INDEX `bname`(`bname` ASC) USING BTREE
)
SET FOREIGN_KEY_CHECKS = 1;
CREATE TABLE `membership` (
`mid` int NOT NULL AUTO_INCREMENT,
`mname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`sex` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`birth` date NULL DEFAULT NULL,
`points` int NULL DEFAULT NULL,
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
PRIMARY KEY (`mid`) USING BTREE
)
SET FOREIGN_KEY_CHECKS = 1;
CREATE TABLE `sale` (
`oid` int NOT NULL AUTO_INCREMENT,
`mid` int NOT NULL,
`bnumber` int NOT NULL,
`date` date NOT NULL,
`sales` decimal(10, 2) NOT NULL,
PRIMARY KEY (`oid`) USING BTREE,
INDEX `mid`(`mid` ASC) USING BTREE,
CONSTRAINT `sale_ibfk_1` FOREIGN KEY (`mid`) REFERENCES `membership` (`mid`) ON DELETE CASCADE ON UPDATE CASCADE
)
SET FOREIGN_KEY_CHECKS = 1;
CREATE TABLE `sale_detail` (
`did` int NOT NULL AUTO_INCREMENT,
`oid` int NOT NULL,
`bid` int NOT NULL,
`bname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`price` decimal(10, 2) NOT NULL,
`number` int NOT NULL,
`sum` decimal(10, 2) NOT NULL,
PRIMARY KEY (`did`) USING BTREE,
INDEX `oid`(`oid` ASC) USING BTREE,
INDEX `bid`(`bid` ASC) USING BTREE,
INDEX `bname`(`bname` ASC) USING BTREE,
CONSTRAINT `sale_detail_ibfk_1` FOREIGN KEY (`oid`) REFERENCES `sale` (`oid`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `sale_detail_ibfk_2` FOREIGN KEY (`bid`) REFERENCES `book` (`bid`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `sale_detail_ibfk_3` FOREIGN KEY (`bname`) REFERENCES `book` (`bname`) ON DELETE CASCADE ON UPDATE CASCADE
)
SET FOREIGN_KEY_CHECKS = 1;
CREATE TABLE `user` (
`id` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`grade` smallint NOT NULL,
PRIMARY KEY (`id`) USING BTREE
)
SET FOREIGN_KEY_CHECKS = 1;
6.1用户界面设计部分代码
用户界面设计以登录页、主页、销售管理页为例:
6.1.1登录页
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<script src="/static/js/jquery-3.7.0.min.js"></script>
<title>欢迎使用书店管理系统</title>
<style>
body {
background-color: #8ea4f8;
background-image: url(/static/image/login_back.png);
background-repeat: no-repeat;
background-size: cover; //背景图适应屏幕
}
.login_box {
width: 400px;
height: 400px;
background-color: #fff;
margin: auto;
margin-top: 150px;
border-radius: 5px;
overflow: hidden;//.title设置margin-top无效,给父元素设置overflow:hidden;
}
.title {
text-align: center;
font-size: 28px;
color: #000;
margin-top: 80px;
margin-bottom: 10px;
letter-spacing: 3px;
font-family: sans-serif;
}
.input_box {
width: 300px;
height: 40px;
padding-left: 10px;
margin-top: 20px;
margin-left: 50px;
}
.button_box {
width: 120px;
height: 40px;
margin-top: 20px;
margin-left: 140px;
border-radius: 5px;
border-color: white;
background-color: #5ac45a;
cursor: pointer;
color: white;
}
</style>
</head>
<body>
<div class="login_box">
<form action="/login" method="post">
<h1 class="title">欢迎使用书店管理系统</h1>
<input class="input_box" type="text" name="id" placeholder="用户名">
<input class="input_box" type="password" name="password" placeholder="密码">
<input class="button_box" type="submit" value="登录">
</form>
</div>
</body>
</html>
6.1.2主页
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<script src="/static/js/jquery-3.7.0.min.js"></script>
<style>
* {
box-sizing: border-box;
}
body {
font-family: Arial;
padding-left: 20px;
padding-right: 20px;
background: #cff5cf;
background-image: url(/static/image/login_back.png);
}
/* 页眉/Blog 标题 */
.header {
padding: 10px;
text-align: center;
background: rgba(207, 245, 207, 0);
}
.header h1 {
font-size: 35px;
color: #186e33;
}
/* 设置上导航栏的样式 */
.topnav {
overflow: hidden;
background-color: #2db758;
}
/* 设置导航栏 topnav 链接的样式 */
.topnav a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
/* 改变鼠标悬停时的颜色 */
.topnav a:hover {
background-color: #63dc63;
color: black;
}
/* 创建两个不相等的彼此并排的浮动列 */
/* 左列 */
.leftcolumn {
float: left;
width: 30%;
}
/* 右列 */
.rightcolumn {
float: left;
width: 70%;
background-color: #f1f1f1;
}
/* 按钮 */
.button {
width: 100%;
height: 100%;
background-color: #4CAF50; /* Green */
border: none;
color: white;
padding: 16px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
transition-duration: 0.4s;
cursor: pointer;
}
.button1 {
background-color: rgba(255, 255, 255, 0.92);
color: black;
border: 1px solid #4CAF50;
}
.button1:hover {
background-color: #5ac45a;
color: white;
}
.button2 {
background-color: rgba(255, 255, 255, 0.92);
color: black;
border: 1px solid #4CAF50;
}
.button2:hover {
background-color: #5ac45a;
color: white;
}
/* 表格 */
input[type=text] {
border:0;
outline:0;
padding:10px;
width:150px;
font-size:16px;
}
table {
border-collapse: collapse;
width: 100%;
}
th, td {
padding: 5px;
text-align: left;
border: 1px solid #ddd;
height: 40px;
font-size:16px;
}
/* 为文章添加卡片效果 */
.card {
background-color: rgb(255, 255, 255);
padding: 20px;
margin-top: 20px;
}
/* 清除列之后的浮动 */
.row:after {
content: "";
display: table;
clear: both;
}
</style>
</head>
<body>
<div class="header">
<h1>书店管理系统</h1>
</div>
<div class="topnav">
<a href="/sale">销售管理</a>
<a href="/membership">会员管理</a>
<a href="/book">图书管理</a>
<a href="/" style="float:right">退出系统</a>
<a href="/index" style="float:right">主页</a>
</div>
<form action="/sale_check" method="post">
<div class="row">
<div class="card">
<span>会员号:</span>
<input type="text" name="mid">
<span>图书数量</span>
<input type="text" name="bnumber" id="number">
<span>总额</span>
<input type="text" name="sales" id="sales">
</div>
<div class="card">
<table id="test">
<tr>
<th>书号</th>
<th>书名</th>
<th>单价</th>
<th>数量</th>
<th>总价</th>
</tr>
<tr>
<td><input type="text" name="bid1" id="bid1"></td>
<td><input type="text" name="bname1" id="bname1"></td>
<td><input type="text" name="price1" id="price1"></td>
<td><input type="text" name="number1" id="number1"></td>
<td><input type="text" name="sum1" id="sum1"></td>
</tr>
<tr>
<td><input type="text" name="bid2"></td>
<td><input type="text" name="bname2"></td>
<td><input type="text" name="price2" id="price2"></td>
<td><input type="text" name="number2" id="number2"></td>
<td><input type="text" name="sum2" id="sum2"></td>
</tr>
<tr>
<td><input type="text" name="bid3"></td>
<td><input type="text" name="bname3"></td>
<td><input type="text" name="price3" id="price3"></td>
<td><input type="text" name="number3" id="number3"></td>
<td><input type="text" name="sum3" id="sum3"></td>
</tr>
<tr>
<td><input type="text" name="bid4"></td>
<td><input type="text" name="bname4"></td>
<td><input type="text" name="price4" id="price4"></td>
<td><input type="text" name="number4" id="number4"></td>
<td><input type="text" name="sum4" id="sum4"></td>
</tr>
</table>
</div>
</div>
<div class="leftcolumn">
<a href="javascript:sum()"><input type="button" class="button button1" value="计算总价"></a>
</div>
<div class="rightcolumn">
<input type="submit" class="button button2" value="结算">
</div>
</form>
<script>
function sum() {
var number1 = document.getElementById("number1");
number1 = change(number1.value);
number1=getNum(number1);
var price1 = document.getElementById("price1");
var number2 = document.getElementById("number2");
number2 = change(number2.value);
number2=getNum(number2);
var price2 = document.getElementById("price2");
var number3 = document.getElementById("number3");
number3 = change(number3.value);
number3=getNum(number3);
var price3 = document.getElementById("price3");
var number4 = document.getElementById("number4");
number4 = change(number4.value);
number4 = getNum(number4);
var price4 = document.getElementById("price4");
sum1 = number1*change(price1.value);
sum1 = getNum(sum1);
sum2 = number2*change(price2.value);
sum2 = getNum(sum2);
sum3 = number3*change(price3.value);
sum3 = getNum(sum3);
sum4 = number4*change(price4.value);
sum4 = getNum(sum4);
sales = (sum1+sum2+sum3+sum4).toFixed(2);
number = number1+number2+number3+number4;
var td1 = document.getElementById('sum1');//返回节点
var td2 = document.getElementById('sum2');//返回节点
var td3 = document.getElementById('sum3');//返回节点
var td4 = document.getElementById('sum4');//返回节点
var s=document.getElementById("sales");
var x=document.getElementById("number");
x.value = number;
s.value = sales;
td1.value = sum1.toFixed(2);//动态修改表格的内容
td2.value = sum2.toFixed(2);//动态修改表格的内容
td3.value = sum3.toFixed(2);//动态修改表格的内容
td4.value = sum4.toFixed(2);//动态修改表格的内容
}
//因为在文本框中获取的数据是文本格式要转换成数字格式,利用自带函数paseFloat
function change(x) {
return parseFloat(x);
}
//将NaN空值转为0
function getNum(val) {
if (isNaN(val)) {
return 0;
}
return val;
}
</script>
</body>
</html>
6.1.3销售管理页
<!DOCTYPE html>
<html lang="zh">
<meta charset="UTF-8">
<head>
<script src="/static/js/jquery-3.7.0.min.js"></script>
<style>
body {
font-family: Arial;
padding-left: 20px;
padding-right: 20px;
background: #cff5cf;
background-image: url(/static/image/login_back.png);
}
/* 页眉/Blog 标题 */
.header {
padding: 10px;
text-align: center;
background: rgba(207, 245, 207, 0);
}
.header h1 {
font-size: 35px;
color: #186e33;
}
/* 设置上导航栏的样式 */
.topnav {
overflow: hidden;
background-color: #2db758;
}
/* 设置导航栏 topnav 链接的样式 */
.topnav a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
/* 改变鼠标悬停时的颜色 */
.topnav a:hover {
background-color: #63dc63;
color: black;
}
/* 使导航栏对应颜色改变 */
.topnav #user{
background-color: #38883b;
color: #f1f3f3;
}
/* 搜索框 */
.select{
width:80px;/*宽400*/
height:44px;/*高38(因为文本框内外边框要占用1像素所以总体高度减2,其他盒子同理)*/
outline:none;
font-size:15px;
margin-top:2px;
border:2px solid #4CAF50;
border-right: none;
}
.box{
width:350px;
height:40px;
border:2px solid #4CAF50;
border-left: none;
margin:auto;
margin-top:2px;
}
.input{
float:left;
width:250px;/*宽400*/
height:38px;/*高38(因为文本框内外边框要占用1像素所以总体高度减2,其他盒子同理)*/
outline:none;
border:none;/*取消文本框内外边框*/
font-size:15px
}
.button{
float:left;
width:96px;
height:40px;
color:white;
background-color: rgba(129, 199, 133, 0.85);;/*将按钮背景设置为红色,字体设置为白*/
border:none;
outline:none;/*取消边框和外边框将按钮边框去掉*/
font-size:15px
}
.button:hover {
background-color: #45a049;
}
/* 创建两个不相等的彼此并排的浮动列 */
/* 左列 */
.leftcolumn {
float: left;
width: 20%;
}
/* 右列 */
.rightcolumn {
float: left;
width: 75%;
background-color: rgba(241, 241, 241, 0);
padding-left: 20px;
}
/* 左侧导航栏 */
ul {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #cff5cf;
}
li a {
display: block;
color: #000;
padding: 8px 16px;
text-decoration: none;
}
li a.active {
background-color: #4CAF50;
color: white;
}
li a:hover:not(.active) {
background-color: #2db758;
color: white;
}
/* 添加卡片效果 */
.card {
background-color: #ffffff;
padding: 20px;
margin-top: 20px;
margin-bottom: 20px;
}
/* 表格 */
table {
border-collapse:collapse;
width: 100%;
}
th, td {
border: 1px solid #aaa;
text-align: center;
padding: 10px;
}
tr{
background-color: rgba(129, 199, 133, 0.14);
}
th {
background-color: #2db758;
color: white;
}
</style>
</head>
<body>
<div class="header">
<h1>书店管理系统</h1>
</div>
<div class="topnav">
<a href="/sale" id="user">销售管理</a>
<a href="/membership">会员管理</a>
<a href="/book">图书管理</a>
<div style="float:right">
<div class="" style="float:left">
<select id="search" class="select" style="float:left">
<option value="0">单号</option>
<option value="1">会员号</option>
</select>
<div class="box" style="float:right">
<input type="text" class="input" id="myInput" placeholder="请输入要搜索的内容">
<!--placeholder是文本框中的提示文字-->
<input type="button" value="搜索" class="button" onclick="javascript:myFunction()">
</div>
</div>
<a href="/" style="float:right">退出系统</a>
<a href="/index" style="float:right">主页</a>
</div>
</div>
<div class="leftcolumn">
<div class="card">
<ul>
<li><a href="/sale_all">销售信息总览</a></li>
</ul>
</div>
</div>
<div class="rightcolumn">
<div class="card">
<label>今日销售额:{{ sales_sum }}</label>
<label>售出图书数量:{{ books_sum }}</label>
<label>订单数:{{ orders_sum }}</label>
</div>
<div class="card">
<table id="myTable">
<tr>
<th>单号</th>
<th>会员号</th>
<th>图书数量</th>
<th>日期</th>
<th>总额</th>
<th>详情</th>
</tr>
{% for sale in sales %}
<tr>
<td>{{ sale.oid }}</td>
<td>{{ sale.mid }}</td>
<td>{{ sale.bnumber }}</td>
<td>{{ sale.date }}</td>
<td>{{ sale.sales }}</td>
<td>
<a href="javascript:updateProduct({{ sale.oid }})" id="detail" value="{{ sale.oid }}">详情</a>
</td>
</tr>
{% endfor %}
</table>
<div>
<button type="button" id="up" class="btnShow" onclick="up()">上一页</button>
<span id="page"> </span>
<button type="button" id="down" class="btnShow " onclick="down()">下一页</button>
</div>
</div>
</div>
<script>
//详情
function updateProduct(oid){
var id=[];
id[0]=oid;
//
location.href="/sale_detail?id="+id;
}
//分页
var nowPage = 0, //当前页
count = 10, //每页显示多少条消息
pageAll = 0; //总页数
table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");
page = document.getElementById("page");
pageAll = (table.rows.length) / count; //计算总页数
//全部隐藏
for (var i = 0; i < table.rows.length; i++) {
tr[i].style.display = "none";
}
//显示第一页的内容
for (var i = 0; i < count+1; i++) {
tr[i].style.display = "";
}
page.innerHTML = nowPage+1 +"/"+Math.ceil (pageAll);
function setTable(nowPage) { //数据渲染表格
tr[0].style.display = "";
for (var i = 1; i < table.rows.length; i++) {
//显示当前页
if(( i > nowPage * count && i<=(nowPage + 1) * count) && i<table.rows.length ){
tr[i].style.display = "";
}
else {//隐藏
tr[i].style.display = "none";
}
}
page.innerHTML = nowPage+1 +"/"+Math.ceil (pageAll);
}
function up() { //上一页
if (nowPage === 0) //当前页数是第一页则返回
return
nowPage--;
setTable(nowPage);
}
function down() {//下一页
if (nowPage >= pageAll-1) //当前页数是最后一页则返回 这么写是因为总页数不一定是整数
return
nowPage++;
setTable(nowPage);
}
function myFunction() {
// 声明变量
var input, filter, table, tr, td,td1,td2,td3,td4, i;
var num = 0;
//获取select对象
var myselect=document.getElementById("search");
//获取select索引
var index=myselect.selectedIndex ;
//拿到选中项options的valued
var svalue=Number(myselect.options[index].value) ;
input = document.getElementById("myInput");
filter = input.value.toUpperCase(); //toUpperCase()是不区分大小写
table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");
// 循环表格每一行,查找匹配项
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[0]; //这个0时要搜索第1列的内容,以此类推
td1 = tr[i].getElementsByTagName("td")[1];
td2 = tr[i].getElementsByTagName("td")[2];
td3 = tr[i].getElementsByTagName("td")[3];
td4 = tr[i].getElementsByTagName("td")[4];
if(svalue === 0){
if(td){
if(td.innerHTML === filter){
tr[i].style.display = "";
}else {
tr[i].style.display = "none";
}
}
}else if(svalue === 1){
if(td1){
if(td1.innerHTML.toUpperCase().indexOf(filter) > -1){
tr[i].style.display = "";
}else {
tr[i].style.display = "none";
}
}
}
}
}
</script>
</body>
</html>
6.2访问数据库的有关代码
6.2.1数据库连接
# 盛放所有的配置信息
import pymysql
from flask_sqlalchemy import *
# 创建flask-sqlalchemy的实例对象
db = SQLAlchemy()
pymysql.install_as_MySQLdb()
class Config:
# 开启调试模式
DEBUG = False
# 配饰flask-sqlalchemy数据库的链接地址
# '数据库的类型://用户名:密码@数据库的地址:端口号/数据库的名字' 注意点 这里使用的标点符号都是英文的
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@127.0.0.1:3306/dbs'
# 压制警告信息
SQLALCHEMY_TRACK_MODIFICATIONS = True
6.2.2数据库类的定义
# coding: utf-8
from settings import db
from sqlalchemy import Table
metadata = db.metadata
class Book(db.Model):
__tablename__ = 'book'
bid = db.Column(db.Integer, primary_key=True)
bname = db.Column(db.String(255), nullable=False, index=True)
quantity = db.Column(db.Integer, nullable=False)
category = db.Column(db.VARCHAR(255), nullable=False)
publishment = db.Column(db.VARCHAR(255), nullable=False)
author = db.Column(db.VARCHAR(255), nullable=False)
price = db.Column(db.DECIMAL(10, 2), nullable=False)
class Membership(db.Model):
__tablename__ = 'membership'
mid = db.Column(db.Integer, primary_key=True, autoincrement=True)
mname = db.Column(db.String(255))
sex = db.Column(db.CHAR(2))
birth = db.Column(db.Date)
points = db.Column(db.Integer)
phone = db.Column(db.String(11), nullable=False)
class Sale(db.Model):
__tablename__ = 'sale'
oid = db.Column(db.Integer, primary_key=True, autoincrement=True)
mid = db.Column(db.ForeignKey('membership.mid', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, index=True)
bnumber = db.Column(db.Integer, nullable=False)
date = db.Column(db.Date, nullable=False)
sales = db.Column(db.DECIMAL(10, 2), nullable=False)
membership = db.relationship('Membership' ,foreign_keys=mid)
class SaleDetail(db.Model):
__tablename__ = 'sale_detail'
did = db.Column(db.Integer, primary_key=True, autoincrement=True)
oid = db.Column(db.ForeignKey('sale.oid', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, index=True)
bid = db.Column(db.ForeignKey('book.bid', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, index=True)
bname = db.Column(db.ForeignKey('book.bname', ondelete='CASCADE', onupdate='CASCADE'), nullable=False, index=True)
price = db.Column(db.DECIMAL(10, 2), nullable=False)
number = db.Column(db.Integer, nullable=False)
sum = db.Column(db.DECIMAL(10, 2), nullable=False)
book = db.relationship('Book', foreign_keys=bid)
sale = db.relationship('Sale', foreign_keys=oid)
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.CHAR(8), primary_key=True)
password = db.Column(db.String(255), nullable=False)
grade = db.Column(db.SmallInteger, nullable=False)
6.2.3 对数据库的增删改查操作
import sqlalchemy
from flask import *
from models import Membership # 引用要用的models
from settings import db
# 创建蓝图对象
membership_page = Blueprint('membership_page', __name__)
@membership_page.route('/trytable', methods=['get'])
def membership_trytable():
memberships = Membership.query.all()
return jsonify(memberships)
@membership_page.route('/try')
def membership_try():
return render_template('try.html')
# 展示所有会员信息 会员管理主页
@membership_page.route('/membership')
def membership():
memberships = Membership().query.all()
total = db.session.query(sqlalchemy.func.count(Membership.mid)).scalar()
return render_template('membership.html', memberships=memberships, total=total)
# 添加会员信息 页面渲染
@membership_page.route('/membership_add')
def membership_add():
return render_template('membership_add.html')
# 添加会员信息 功能实现
@membership_page.route('/mem_add',methods=['POST'])
def mem_add():
# 获取表单数据
data = request.form
#
membership = Membership()
# 添加数据库数据
membership.mname = data['mname']
membership.sex = data['sex']
membership.birth = data['birth']
membership.phone = data['phone']
membership.points = data['points']
# 提交
db.session.add(membership)
db.session.commit()
return redirect(url_for('membership_page.membership'))
# 删除会员信息 页面渲染
@membership_page.route('/membership_delete')
def membership_delete():
memberships = Membership().query.all()
return render_template('membership_delete.html', memberships=memberships)
# 删除会员信息 功能实现
@membership_page.route('/membership_del/')
def membership_del():
id = request.args['id']
membership = Membership().query.get(id)
if membership:
db.session.delete(membership)
db.session.commit()
return redirect(url_for('membership_page.membership_delete'))
# 修改会员信息 页面渲染
@membership_page.route('/membership_update')
def membership_update():
memberships = Membership().query.all()
return render_template('membership_update.html', memberships=memberships)
# 修改会员信息 跳转到修改信息页
@membership_page.route('/membership_upd/')
def membership_upd():
id = request.args['uid']
membership = Membership().query.get(id)
return render_template('membership_upd.html', membership=membership)
# 修改会员信息 功能实现
@membership_page.route('/mem_upd/<mid>',methods=['POST'])
def mem_upd(mid):
# 获取表单数据
data = request.form
#读取对应记录
membership = Membership().query.get(mid)
# 修改数据库数据
membership.mname = data['mname']
membership.sex = data['sex']
membership.birth = data['birth']
membership.phone = data['phone']
membership.points = data['points']
# 提交
db.session.commit()
return redirect(url_for('membership_page.membership'))
import sqlalchemy
from flask import *
from models import Book # 引用要用的models
from settings import db
# 创建蓝图对象
book_page = Blueprint('book_page', __name__)
# 展示所有图书信息 图书管理主页
@book_page.route('/book')
def book():
books = Book().query.all()
total = db.session.query(sqlalchemy.func.count(Book.bid)).scalar()
return render_template('book.html', books=books, total=total)
# 添加图书信息 页面渲染
@book_page.route('/book_add')
def book_add():
return render_template('book_add.html')
# 添加图书信息 功能实现
@book_page.route('/b_add',methods=['POST'])
def b_add():
# 获取表单数据
data = request.form
#
book = Book()
# 添加数据库数据
book.bid = data["bid"]
book.bname = data["bname"]
book.quantity = data["quantity"]
book.category = data["category"]
book.publishment = data["publishment"]
book.author = data["author"]
book.price = data["price"]
# 提交
db.session.add(book)
db.session.commit()
return redirect(url_for('book_page.book'))
# 删除图书信息 页面渲染
@book_page.route('/book_delete')
def book_delete():
books = Book().query.all()
return render_template('book_delete.html', books=books)
# 删除图书信息 功能实现
@book_page.route('/book_del/')
def book_del():
id = request.args['id']
book = Book().query.get(id)
if book:
db.session.delete(book)
db.session.commit()
return redirect(url_for('book_page.book_delete'))
# 修改图书信息 页面渲染
@book_page.route('/book_update')
def book_update():
books = Book().query.all()
return render_template('book_update.html', books=books)
# 修改图书信息 跳转到修改信息页
@book_page.route('/book_upd/')
def book_upd():
id = request.args['uid']
book = Book().query.get(id)
return render_template('book_upd.html', book=book)
# 修改图书信息 功能实现
@book_page.route('/b_upd/<bid>',methods=['POST'])
def b_upd(bid):
# 获取表单数据
data = request.form
#读取对应记录
book = Book().query.get(bid)
# 修改数据库数据
book.bid = data["bid"]
book.bname = data["bname"]
book.quantity = data["quantity"]
book.category = data["category"]
book.publishment = data["publishment"]
book.author = data["author"]
book.price = data["price"]
# 提交
db.session.commit()
return redirect(url_for('book_page.book'))
from datetime import date, datetime
import datetime
import sqlalchemy
from flask import *
from sqlalchemy import extract, func
from models import Sale,SaleDetail,Membership,Book # 引用要用的models
from settings import db
# 创建蓝图对象
sale_page = Blueprint('sale_page', __name__)
# 确认订单 功能实现 **与主页确认连接
@sale_page.route('/sale_add',methods=['POST'])
def sale_add():
# 获取表单数据
data = request.form
# 计算每条记录总价和订单总额
sum1 = float(data["price1"]) * int(data["number1"])
sum2 = float(data["price2"]) * int(data["number2"])
sales = sum1 + sum2
return render_template('index.html', sum1=sum1, sum2=sum2, sales=sales, )
# 结算 功能实现 for i in range(3): **与主页结算连接
@sale_page.route('/sale_check', methods=['POST'])
def sale_check():
# 获取表单数据
data = request.form
# sql类
sale = Sale()
saledetail1 = SaleDetail()
saledetail2 = SaleDetail()
saledetail3 = SaleDetail()
saledetail4 = SaleDetail()
# 添加数据库数据
# 增加会员积分
membership = Membership().query.get(data["mid"])
points = db.session.query(Membership.points).filter(Membership.mid == data["mid"]).scalar()
print(points)
membership.points = points + int(float(data["sales"]))
# 将订单写入sale表
sale.mid = data["mid"]
sale.bnumber = data["bnumber"]
sale.date = date.today()
sale.sales = data["sales"]
db.session.add(sale)
# 提交
db.session.commit()
# 查询sale表中的最大oid
noid = db.session.query(func.max(Sale.oid)).scalar()
print(noid)
noid = int(noid)
print(noid)
# 写入sale_detail表
if(data["bid1"]):
saledetail1.oid = noid
saledetail1.bid = data["bid1"]
saledetail1.bname = data["bname1"]
saledetail1.price = data["price1"]
saledetail1.number = data["number1"]
saledetail1.sum = data["sum1"]
db.session.add(saledetail1)
db.session.commit()
if (data["bid2"]):
saledetail2.oid = noid
saledetail2.bid = data["bid2"]
saledetail2.bname = data["bname2"]
saledetail2.price = data["price2"]
saledetail2.number = data["number2"]
saledetail2.sum = data["sum2"]
db.session.add(saledetail2)
db.session.commit()
if (data["bid3"]):
saledetail3.oid = noid
saledetail3.bid = data["bid3"]
saledetail3.bname = data["bname3"]
saledetail3.price = data["price3"]
saledetail3.number = data["number3"]
saledetail3.sum = data["sum3"]
db.session.add(saledetail3)
db.session.commit()
if (data["bid4"]):
saledetail4.oid = noid
saledetail4.bid = data["bid4"]
saledetail4.bname = data["bname4"]
saledetail4.price = data["price4"]
saledetail4.number = data["number4"]
saledetail4.sum = data["sum4"]
db.session.add(saledetail4)
db.session.commit()
# 减少图书库存
if (data["bid1"]):
book1 = Book().query.get(data["bid1"])
quantity1 = db.session.query(Book.quantity).filter(Book.bid == data["bid1"]).scalar()
print(quantity1)
book1.quantity = quantity1 - int(data["number1"])
if (data["bid2"]):
book2 = Book().query.get(data["bid2"])
quantity2 = db.session.query(Book.quantity).filter(Book.bid == data["bid2"]).scalar()
print(quantity2)
book2.quantity = quantity2 - int(data["number2"])
if (data["bid3"]):
book3 = Book().query.get(data["bid3"])
quantity3 = db.session.query(Book.quantity).filter(Book.bid == data["bid3"]).scalar()
print(quantity3)
book3.quantity = quantity3 - int(data["number3"])
if (data["bid4"]):
book4 = Book().query.get(data["bid4"])
quantity4 = db.session.query(Book.quantity).filter(Book.bid == data["bid4"]).scalar()
print(quantity4)
book4.quantity = quantity4 - int(data["number4"])
# 提交
db.session.commit()
return render_template('index.html')
# 展示当日销售信息 销售管理主页
@sale_page.route('/sale')
def sale():
sdate = date.today()
print(sdate)
sales = db.session.query(Sale).filter(extract('day', Sale.date) == sdate.day)\
.filter(extract('month', Sale.date) == sdate.month)\
.filter(extract('year', Sale.date) == sdate.year).all()
sales_sum = db.session.query(func.sum(Sale.sales)).filter(extract('day', Sale.date) == sdate.day) \
.filter(extract('month', Sale.date) == sdate.month) \
.filter(extract('year', Sale.date) == sdate.year).scalar()
books_sum = db.session.query(func.sum(Sale.bnumber)).filter(extract('day', Sale.date) == sdate.day) \
.filter(extract('month', Sale.date) == sdate.month) \
.filter(extract('year', Sale.date) == sdate.year).scalar()
orders_sum = db.session.query(sqlalchemy.func.count(Sale.oid)).filter(extract('day', Sale.date) == sdate.day) \
.filter(extract('month', Sale.date) == sdate.month) \
.filter(extract('year', Sale.date) == sdate.year).scalar()
return render_template('sale.html', sales=sales, orders_sum=orders_sum, sdate=sdate, sales_sum=sales_sum, books_sum=books_sum)
# 展示所有销售信息 销售信息总览 **完成按日期查找
@sale_page.route('/sale_all')
def sale_all():
sales = Sale().query.all()
sales_sum = db.session.query(func.sum(Sale.sales)).scalar()
books_sum = db.session.query(func.sum(Sale.bnumber)).scalar()
orders_sum = db.session.query(sqlalchemy.func.count(Sale.oid)).scalar()
return render_template('sale_all.html', sales=sales, orders_sum=orders_sum, sales_sum=sales_sum, books_sum=books_sum)
# 展示指定日期的销售信息 销售信息总览
@sale_page.route('/sale_all_date', methods=['POST'])
def sale_all_date():
data = request.form
date_str = data["date"]
print(date_str)
sdate = datetime.date(*map(int, date_str.split('-')))
print(sdate)
sales = db.session.query(Sale).filter(extract('day', Sale.date) == sdate.day) \
.filter(extract('month', Sale.date) == sdate.month) \
.filter(extract('year', Sale.date) == sdate.year).all()
sales_sum = db.session.query(func.sum(Sale.sales)).filter(extract('day', Sale.date) == sdate.day) \
.filter(extract('month', Sale.date) == sdate.month) \
.filter(extract('year', Sale.date) == sdate.year).scalar()
books_sum = db.session.query(func.sum(Sale.bnumber)).filter(extract('day', Sale.date) == sdate.day) \
.filter(extract('month', Sale.date) == sdate.month) \
.filter(extract('year', Sale.date) == sdate.year).scalar()
orders_sum = db.session.query(sqlalchemy.func.count(Sale.oid)).filter(extract('day', Sale.date) == sdate.day) \
.filter(extract('month', Sale.date) == sdate.month) \
.filter(extract('year', Sale.date) == sdate.year).scalar()
return render_template('sale_all.html', sales=sales, orders_sum=orders_sum, sdate=sdate, sales_sum=sales_sum, books_sum=books_sum)
# 查看销售信息详情 跳转到销售详情页
@sale_page.route('/sale_detail/')
def sale_detail():
id = request.args['id']
print(id)
sale = Sale().query.get(id)
sale_details = SaleDetail().query.filter(SaleDetail.oid == id)
return render_template('sale_detail.html', sale=sale, sale_details=sale_details)
(七)数据库测试
7.1加载数据库数据
7.2 功能测试
7.2.1 销售管理模块功能测试
销售管理模块的功能测试结果如下表7-1所示。
表7-1 销售管理模块测试结果表
| 测试功能 | 详细操作 | 预期效果 | 实际效果 |
| 录入销售信息 | 在主页填写订单信息并计算总价后点击结算 | 进入销售管理页面,显示添加的销售信息 | 和预期效果相符合 |
| 查看当日销售信息 | 点击导航栏的销售管理 | 进入销售管理页面,看到当日的销售信息 | 和预期效果相符合 |
| 查看全部销售信息 | 通过导航栏进入销售管理页面后点击销售信息总览 | 进入销售信息总览页面,看到全部销售信息 | 和预期效果相符 |
| 根据日期查找销售信息 | 进入销售信息总览页面,通过日期选择器选定日期并查询 | 页面中展示对应日期的销售信息 | 和预期效果相符合 |
| 查看销售信息详情 | 在销售管理页或销售信息总览页,点击表格每行的详情 | 弹出详情页面,展示对应订单的销售详情 | 和预期效果相符合 |
| 统计销售信息 | 打开销售管理页或销售信息总览页 | 页面展示对应的订单数,销售额等 | 和预期效果相符合 |
| 查询销售信息 | 在销售管理页或销售信息总览页,通过导航栏的搜索框搜索 | 根据筛选条件在页面上显示结果 | 和预期效果相符合 |
根据表7-1对销售管理模块的测试结果可知,销售管理模块的基本功能都已满足需求。
7.2.2 图书管理模块功能测试
图书管理模块的功能测试结果如下表7-2所示。
表7-2 图书管理模块测试结果表
| 测试功能 | 详细操作 | 预期效果 | 实际效果 |
| 查看图书信息 | 点击导航栏的图书管理 | 进入图书管理页面,显示所有图书信息和图书总数 | 和预期效果相符合 |
| 添加图书信息 | 点击图书管理页的添加图书信息,添加后点击图书管理页查看结果 | 进入添加图书信息表单页面,能在表单中输入要添加的信息并能在图书管理页看到添加的信息 | 和预期效果相符合 |
| 修改图书信息 | 在修改图书信息页,点击表格每行的修改 | 跳转到修改图书信息表单页面,确认修改后成功修改对应记录 | 和预期效果相符合 |
| 删除图书信息 | 在删除图书信息页,点击表格每行的删除 | 弹出提示框,确认删除后成功删除对应记录 | 和预期效果相符合 |
| 查询图书信息 | 在图书管理模块的相关页面,通过导航栏的搜索框搜索 | 根据筛选条件在页面上显示结果,并统计出对应的图书数量 | 和预期效果相符合 |
根据表7-2对图书管理模块的测试结果可知,图书管理模块的基本需求都已实现。
7.2.3 会员管理模块功能测试
会员管理模块的功能测试结果如下表7-3所示。
表7-3会员管理模块测试结果表
| 测试功能 | 详细操作 | 预期效果 | 实际效果 |
| 查看会员信息 | 点击导航栏的会员管理 | 进入图书管理页面,显示所有会员信息和会员总数 | 和预期效果相符合 |
| 添加会员信息 | 点击会员管理页的添加会员信息,添加后点击会员管理页查看结果 | 进入添加会员信息表单页面,能在表单中输入要添加的信息并能在会员管理页看到添加的信息 | 和预期效果相符合 |
| 修改会员信息 | 在修改会员信息页,点击表格每行的修改 | 跳转到修改会员信息表单页面,确认修改后成功修改对应记录 | 和预期效果相符合 |
| 删除会员信息 | 在删除会员信息页,点击表格每行的删除 | 弹出提示框,确认删除后成功删除对应记录 | 和预期效果相符合 |
| 查询会员信息 | 在会员管理模块的相关页面,通过导航栏的搜索框搜索 | 根据筛选条件在页面上显示结果 | 和预期效果相符合 |
根据表7-3对会员管理模块的测试结果可知,会员管理模块的基本需求都已实现。

942

被折叠的 条评论
为什么被折叠?



