目 录
摘 要 I
Abstract II
1 前 言 1
1.1 项目开发背景 1
1.2 项目开发意义 1
1.3 主要工作 1
2 系统分析 2
2. 1 需求分析 2
2. 2 可行性分析 2
2.2.1 经济可行性 2
2.2.2 技术可行性 2
2.2.3 操作可行性 2
2.2.4 社会可行性 2
3 开发环境 3
3.1 开发工具 3
3.1.1 操作系统 3
3.1.2 数据库 3
3.1.3 开发平台 3
3.1.4 语言框架 3
3.2 系统开发环境 4
4 系统总体设计 5
4.1 系统架构设计 5
4.2 数据结构设计 5
4.2.1 ER图 5
4.2.2 数据描述 9
4.2.3 数据库表设计 9
4. 3 系统功能模块设计 12
4.3.1 系统登陆模块 12
4.3.2 账号管理模块 13
4.3.3 商品分类管理 13
4.3.4 商品管理模块 14
4.3.5 库存管理模块 14
4.3.6 库存日志模块 15
4.3.7 销售管理模块 15
5 系统详细设计 17
5.1 登录 17
5.2 主界面 17
5.2.1 首页 17
5.2.2 个人信息 19
5.3 账号管理 19
5.4 商品分类管理 20
5.4.1 商品分类列表 20
5.4.2 商品分类新增/修改 20
5.5 商品管理 21
5.5.1商品列表 21
5.5.2商品新增/修改 22
5.6 库存管理 22
5.7 库存日志 24
5.8 销售管理 25
5.8.1 商品销售情况 25
5.8.2 报表导出Excel 25
5.9 供应商管理 27
5.9.1 供应商列表 27
5.9.2 供应商新增/编辑 27
6 系统测试 28
6.1 系统测试的目的及意义 28
6.2 测试过程 28
6.2.1 系统登录测试 28
6.2.2 用户新增/编辑、禁用/启用测试 29
6.2.3 商品分类新增/编辑、删除测试 29
6.3 测试结果 29
7 总结 31
参考文献 32
致谢 33
1.2 项目开发意义
本项目旨在为中小型超市开发一款安全高效的管理系统,通过对项目的思考、构架以及项目的实施过程中,可以切身体会到一个项目的流程步骤,为今后的项目设计和开发打下扎实的根底。本项目采用面向对象的思想,在项目代码的编写过程中,通过不断地思考和优化,可以使开发者从面向程序的思维中跳出来,站在更高的角度来思考问题。项目中涉及到大量的数据库操作,可以使开发者熟悉和掌握大量的SQL语言,也让其对数据结构有一个更加深刻的认识和理解。项目前端的界面渲染则要求开发者学习一定的CSS+JS+DIV的页面布局和部分动态网页技巧。
通过本次项目,开发者学习到的不仅仅是独立分析问题、解决问题的技巧,更多的是一种全局观的思维逻辑。
1.3 主要工作
1.网上查找thinkPHP5框架手册,搭建好系统和服务器环境;
2.针对本系统,通过网络和线下调查用户需求;
3.规划系统主体模块,绘出系统模块结构图;
4.对模块进行细化,确定系统数据库结构;
5.创建数据库,实现系统功能;
6.调试系统,整理相关资料,完成论文。
3 开发环境
3.1 开发工具
3.1.1 操作系统
本次项目开发选择了Windows 10家庭版操作系统。Windows 10是美国微软公司于2015年7月份正式推出的一款操作系统。Win10在保留了之前操作系统的优秀特点的基础上,在界面、功能和性能上做出了很大的提升和优化,是一款优秀的操作系统,其独特的任务视图和多虚拟桌面为开发者提供了很大的便利,非常适合开发者。
3.1.2 数据库
数据库(Database)是一种按照数据结构来组织、存储和管理数据的建立在计算机存储设备上的仓库[1]。
严格来说,数据库是长期存储在计算机内、有组织的、可共享的数据集合;数据库中的数据指的是以一定的数据模型组织、描述和存储在一起、具有尽可能小的冗余度、较高的数据独立性和易扩展性的特点并可在一定的范围内为多个用户共享[2]。
这种数据集合具备以下几个方面的特点:尽可能不出现重复,以最优的存储方式和最小的查询代价为某个特定组织的多种应用提供数据存储服务,其数据结构应独立于使用它的应用程序,并且应该尽量只作为存储层,不参与任何逻辑,对数据的增、删、改、查由统一软件进行管理和控制。
数据库的出现,大大地满足了各类型企业对数据维护的需求。如今,数据库已经成为企业日常管理中必不可少的工具。
本系统采用了MySQL数据库,MySQL是一个关系型数据库管理系统,是如今最流行的数据库管理系统之一,灵活性高,处理速度快,非常适用于web应用。
3.1.3 开发平台
系统是在NetBeans IDE+ WampServer的环境下进行开发的。
3.1.3.1 IDE
在软件开发上,IDE一般指集成开发环境(Integrated Development Environment),是用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面等工具[3]。
NetBeans IDE是由Sum公司于2000年创立,是一个开源软件开发集成环境,是一个可扩展的开发平台,使用方便,功能强大。本文转载自http://www.biyezuopin.vip/onews.asp?id=15615
3.1.3.2 WampServer
WampServer是一款集成了Apache、MySQL和PHP的服务器软件,拥有简单的图形菜单,可以快捷方便配置开发环境,免去了繁琐的开发环境配置过程。WampServer还同时支持PHPmyadmin,可以通过它来轻松地管理MySQL数据库。
3.1.4 语言框架
在语言框架上,本系统采用了基于PHP语言的ThinkPHP5框架。
3.1.4.1 PHP语言
PHP(PHP:Hypertext Preprocessor)又名“超文本预处理器”,是一种被广泛应用的面向对象的开源脚本语言,它的语法结合了C语言、Perl和Java等语言的特点[4]。PHP可以嵌入到HTML中,非常适合web开发。本文转载自
3.1.4.2 ThinkPHP框架
ThinkPHP是一个免费开源的,简单、快速的,面向对象的轻量级PHP开发框架,遵循Apache2开源协议而发布,是为了敏捷WEB应用开发和简化企业应用开发而诞生的,性能稳定,代码简洁[5]。
3.2 系统开发环境
B/S结构模式,又称“浏览器/服务器模式”是如今web开发中最经典的一种模式,是通过web浏览器作为中转来实现客户端与服务器的互动,将系统的关键部分集中放在服务器上,从而提高系统的稳定性,方便开发和维护。对用户而言,无需任何其他软件,只需安装一个web浏览器即可使用本系统。
<?php
/**
* tpAdmin [a web admin based ThinkPHP5]
*
* @author yuan1994 <tianpian0805@gmail.com>
* @link http://tpadmin.yuan1994.com/
* @copyright 2016 yuan1994 all rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0
*/
namespace app\index;
use think\Model;
use think\View;
use think\Request;
use think\Session;
use think\Db;
use think\Config;
use think\Loader;
use think\Exception;
use think\exception\HttpException;
use app\index\logic\Pub as PubLogic;
class Controller
{
/**
* @var View 视图类实例
*/
protected $view;
/**
* @var Request Request实例
*/
protected $request;
/**
* @var array 黑名单方法,禁止访问某些方法
*/
protected static $blacklist = [];
/**
* @var array 白名单方法,如果设置会覆盖黑名单方法,只允许白名单方法能正常访问
*/
protected static $allowList = [];
/**
* @var int 是否删除标志,0-正常|1-删除|false-不包含该字段
*/
protected static $isdelete = 0;
/**
* @var string 假删除字段
*/
protected $fieldIsDelete = 'isdelete';
/**
* @var string 状态禁用、恢复字段
*/
protected $fieldStatus = 'status';
public function __construct()
{
if (null === $this->view) {
$this->view = View::instance(Config::get('template'), Config::get('view_replace_str'));
}
if (null === $this->request) {
$this->request = Request::instance();
}
// 白名单/黑名单方法
if ($this::$allowList && !in_array($this->request->action(), $this::$allowList)) {
throw new HttpException(404, 'method not exists:' . $this->request->controller() . '->' . $this->request->action());
} elseif ($this::$blacklist && in_array($this->request->action(), $this::$blacklist)) {
throw new HttpException(404, 'method not exists:' . $this->request->controller() . '->' . $this->request->action());
}
// 用户ID
defined('UID') or define('UID', Session::get(Config::get('rbac.user_auth_key')));
// 是否是管理员
defined('ADMIN') or define('ADMIN', true === Session::get(Config::get('rbac.admin_auth_key')));
// 检查认证识别号
if (null === UID) {
$this->notLogin();
} else {
$this->auth();
}
// 前置方法
$beforeAction = "before" . $this->request->action();
if (method_exists($this, $beforeAction)) {
$this->$beforeAction();
}
}
/**
* 自动搜索查询字段,给模型字段过滤
*/
protected function search($model, $param = [])
{
$map = [];
$table_info = $model->getTableInfo();
$param = array_merge($this->request->param(), $param);
foreach ($param as $key => $val) {
if ($val !== "" && in_array($key, $table_info['fields'])) {
$map[$key] = $val;
}
}
return $map;
}
/**
* 获取模型
*
* @param string $controller
* @param bool $type 是否返回模型的类型
*
* @return \think\db\Query|\think\Model|array
*/
protected function getModel($controller = '', $type = false)
{
$module = Config::get('app.model_path');
if (!$controller) {
$controller = $this->request->controller();
}
if (
class_exists($modelClass = Loader::parseClass($module, 'model', $this->parseCamelCase($controller)))
|| class_exists($modelClass = Loader::parseClass($module, 'model', $controller))
) {
$model = new $modelClass();
$modelType = 'model';
} else {
$model = Db::name($this->parseTable($controller));
$modelType = 'db';
}
return $type ? ['type' => $modelType, 'model' => $model] : $model;
}
/**
* 获取实际的控制器名称(应用于多层控制器的场景)
*
* @param $controller
*
* @return mixed
*/
protected function getRealController($controller = '')
{
if (!$controller) {
$controller = $this->request->controller();
}
$controllers = explode(".", $controller);
$controller = array_pop($controllers);
return $controller;
}
/**
* 默认更新字段方法
*
* @param string $field 更新的字段
* @param string|int $value 更新的值
* @param string $msg 操作成功提示信息
* @param string $pk 主键,默认为主键
* @param string $input 接收参数,默认为主键
*/
protected function updateField($field, $value, $msg = "操作成功", $pk = "", $input = "")
{
$model = $this->getModel();
if (!$pk) {
$pk = $model->getPk();
}
if (!$input) {
$input = $model->getPk();
}
$ids = $this->request->param($input);
$where[$pk] = ["in", $ids];
if (false === $model->where($where)->update([$field => $value])) {
return ajax_return_adv_error($model->getError());
}
return ajax_return_adv($msg, '');
}
/**
* 格式化表名,将 /. 转为 _ ,支持多级控制器
*
* @param string $name
*
* @return mixed
*/
protected function parseTable($name = '')
{
if (!$name) {
$name = $this->request->controller();
}
return str_replace(['/', '.'], '_', $name);
}
/**
* 格式化类名,将 /. 转为 \\
* 已废弃,请使用Loader::parseClass()
*
* @param string $name
*
* @return mixed
*/
protected function parseClass($name = '')
{
if (!$name) {
$name = $this->request->controller();
}
return str_replace(['/', '.'], '\\', $name);
}
/**
* 未登录处理
*/
protected function notLogin()
{
PubLogic::notLogin();
}
/**
* 权限校验
*/
protected function auth()
{
// 用户权限检查
if (
Config::get('rbac.user_auth_on') &&
!in_array($this->request->module(), explode(',', Config::get('rbac.not_auth_module')))
) {
if (!\Rbac::AccessCheck()) {
throw new HttpException(403, "没有权限");
}
}
}
/**
* 过滤禁止操作某些主键
*
* @param $filterData
* @param string $error
* @param string $method
* @param string $key
*/
protected function filterId($filterData, $error = '该记录不能执行此操作', $method = 'in_array', $key = 'id')
{
$data = $this->request->param();
if (!isset($data[$key])) {
throw new HttpException(404, '缺少必要参数');
}
$ids = is_array($data[$key]) ? $data[$key] : explode(",", $data[$key]);
foreach ($ids as $id) {
switch ($method) {
case '<':
case 'lt':
$ret = $id < $filterData;
break;
case '>':
case 'gt':
$ret = $id < $filterData;
break;
case '=':
case 'eq':
$ret = $id == $filterData;
break;
case '!=':
case 'neq':
$ret = $id != $filterData;
break;
default:
$ret = call_user_func_array($method, [$id, $filterData]);
break;
}
if ($ret) {
throw new Exception($error);
}
}
}
/**
* 根据表单生成查询条件
* 进行列表过滤
*
* 过滤条件
* $map['_table'] 可强制设置表名前缀
* $map['_relation'] 可强制设置关联模型预载入(需在模型里定义)
* $map['_field'] 可强制设置字段
* $map['_order_by'] 可强制设置排序字段(field asc|desc[,filed2 asc|desc...]或者false)
* $map['_paginate'] 是否开启分页,传入false可以关闭分页
* $map['_model'] 可强制指定模型
* $map['_func'] 匿名函数,可以给模型设置属性,比如关联,alias,function ($model) {$model->alias('table')->join(...)}
*
* @param Model|Db $model 数据对象
* @param array $map 过滤条件
* @param string $field 查询的字段
* @param string $sortBy 排序
* @param boolean $asc 是否正序
* @param boolean $return 是否返回数据,返回数据时返回paginate对象,不返回时直接模板赋值
* @param boolean $paginate 是否开启分页
*/
protected function datalist($model, $map, $field = null, $sortBy = '', $asc = false, $return = false, $paginate = true)
{
// 私有字段,指定特殊条件,查询时要删除
$protectField = ['_table', '_relation', '_field', '_order_by', '_paginate', '_model', '_func'];
// 通过过滤器指定模型
if (isset($map['_model'])) {
$model = $map['_model'];
}
if (isset($map['_func']) && ($map['_func'] instanceof \Closure)) {
call_user_func_array($map['_func'], [$model]);
}
// 排序字段 默认为主键名
$order = $this->request->param('_order') ?: (empty($sortBy) ? $model->getPk() : $sortBy);
// 接受 sort参数 0 表示倒序 非0都 表示正序
$sort = null !== $this->request->param('_sort')
? ($this->request->param('_sort') == 'asc' ? 'asc' : 'desc')
: ($asc ? 'asc' : 'desc');
// 设置关联预载入
if (isset($map['_relation'])) {
$model = $model::with($map['_relation']);
}
// 设置字段
if (isset($map['_field'])) {
$field = $map['_field'];
}
// 设置有$map['_controller']表示存在关联模型
if (isset($map['_table'])) {
// 给排序字段强制加上表名前缀
if (strpos($order, ".") === false) {
$order = $map['_table'] . "." . $order;
}
// 给字段强制加上表名前缀
$_field = is_array($field) ? $field : explode(",", $field);
foreach ($_field as &$v) {
if (strpos($v, ".") === false) {
$v = preg_replace("/([^\s\(\)]*[a-z0-9\*])/", $map['_table'] . '.$1', $v, 1);
}
}
$field = implode(",", $_field);
// 给查询条件强制加上表名前缀
foreach ($map as $k => $v) {
if (!in_array($k, $protectField) && strpos($k, ".") === false) {
$map[$map['_table'] . '.' . $k] = $v;
unset($map[$k]);
}
}
}
// 设置排序字段 防止表无主键报错
$order_by = $order ? "{$order} {$sort}" : false;
if (isset($map['_order_by'])) {
$order_by = $map['_order_by'];
}
// 是否开启分页
$paginate = isset($map['_paginate']) ? boolval($map['_paginate']) : $paginate;
// 删除设置属性的字段
foreach ($protectField as $v) {
unset($map[$v]);
}
if ($paginate) {
// 分页查询
// 每页数据数量
$listRows = $this->request->param('numPerPage') ?: Config::get("paginate.list_rows");
$list = $model
->field($field)
->where($map)
->order($order_by)
->paginate($listRows, false, ['query' => $this->request->get()]);
if ($return) {
// 返回值
return $list;
} else {
// 模板赋值显示
$this->view->assign('list', $list);
$this->view->assign("count", $list->total());
$this->view->assign("page", $list->render());
$this->view->assign('numPerPage', $list->listRows());
}
} else {
// 不开启分页查询
$list = $model
->field($field)
->where($map)
->order($order_by)
->select();
if ($return) {
// 返回值
return $list;
} else {
// 模板赋值显示
$this->view->assign('list', $list);
$this->view->assign("count", count($list));
$this->view->assign("page", '');
$this->view->assign('numPerPage', 0);
}
}
}
/**
* 将abc.def.Gh转为AbcDefGh
*
* @param $string
*
* @return mixed
*/
protected function parseCamelCase($string)
{
return preg_replace_callback('/(\.|^)([a-zA-Z])/', function ($match) {
return ucfirst($match[2]);
}, $string);
}
}