整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

基于SpringBoot2的开源管理后台系统springboot-plus

一个基于SpringBoot 2 的管理后台系统,有数十个基于此的商业应用,包含了用户管理,组织机构管理,角色管理,功能点管理,菜单管理,权限分配,数据权限分配,代码生成等功能 相比其他开源的后台开发平台脚手架,SpringBoot-Plus 使用简单,可以轻易完成中型,大型系统开发。同时技术栈较为简单

如何判断一个开源开发平台适合自己

  • 要明白单体系统,系统拆分,微服务三个不同构建开发平台方式,plus支持单体和系统拆分,一般而言,后台管理系统适合单体和系统拆分。微服务并不适合系统管理,以我知道的互联网大厂,央企后台管理系统,还是以前俩个为多
  • 你需要的是技术框架还是开发平台,技术框架就是技术堆砌,开发平台必须具备一定复杂基础业务功能
  • 看权限模型,支持功能权限和数据权限。plus具备强大的功能权限和数据权限,且可以扩展n种数据权限
  • 看用户是否能属于多个部门,用户兼职情况很常见
  • 看数据字典是否支持级联,数据字典级联太常见了,平台需要提供数据和前端的支持。puls系统支持
  • 看代码生成是否支持预览,为什么要预览,因为生成会覆盖,预览可以修改已经生成的代码

Plus系统是一个使用简单,功能较为复杂的开源系统,已经数十家商业公司采用

系统基于Spring Boot2.1技术,前端采用了Layui2.4。数据库以MySQL/Oracle/Postgres/SQLServer为实例,理论上是跨数据库平台.

1 使用说明

1.1 安装说明

建议在彻底熟悉plus系统之前,先暂时不要修改其他配置选项,免得系统无法访问

本系统基于Spring Boot 2 ,因此请务必使用JDK8,且打开编译选项parameters(点击了解parameters), 并重新编译工程,如果你没有使用Java8的 parameters 特性,系统不能正常使用

从Git上获取代码后,通过IDE导入此Maven工程,包含俩个子工程

  • admin-core ,核心包,包含了缓存,数据权限,公用的JS和HTML页面。
  • admin-console, 系统管理功能,包含了用户,组织机构,角色,权限,数据权限,代码生成等管理功能

com.ibeetl.admin.CosonleApplication 是系统启动类,在admin-console包下,在运行这个之前,还需要初始化数据库,位于doc/starter-mysql.sql,目前只提供mysql, oracle, postgresql脚本。理论上支持所有数据库

还需要修改SpringBoot配置文件application.properties,修改你的数据库地址和访问用户

spring.datasource.baseDataSource.url=jdbc:mysql://127.0.0.1:3306/starter?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&useInformationSchema=true

spring.datasource.baseDataSource.username=root

spring.datasource.baseDataSource.password=123456

spring.datasource.baseDataSource.driver-class-name=com.mysql.cj.jdbc.Driver

运行CosonleApplication,然后访问http://127.0.0.1:8080/ 输入admin/123456 则可以直接登录进入管理系统

1.2 创建子系统

SpringBoot-plus 是一个适合大系统拆分成小系统的架构,或者是一个微服务系统,因此,如果你需要创建自己的业务系统,比如,一个CMS子系统,建议你不要在SpringBoot-Plus 添加代码,应该是新建立一个maven工程,依赖admin-core,或者依赖admin-console(如果你有后台管理需求,通常都有,但不是必须的)

创建子系统,可以进入代码生成>子系统生成, 输入maven项目路径,还有包名,就可以直接生成一个可运行的基于SpringBoot-Plus 的子系统,所有代码可以在个项目里些完成,直接运行MainApplication,

@SpringBootApplication @EnableCaching @ComponentScan(basePackages= {"com.corp.xxx","com.ibeetl.admin"}) public class MainApplication extends SpringBootServletInitializer implements WebApplicationInitializer { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } } 

子系统包含了admin-core和admin-console, 因此你可以直接在子系统里使用core和console提供的所有功能,通过子系统的console功能的代码生成来完成进一步开发

子系统可以单独运行和维护,也可以集成到nginx后构成一个庞大的企业应用系统

1.2.1 配置子系统

子系统不需要做任何配置即可在IDE里直接运行,如果你想打包jar方式运行,则需要添加

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>

如果你想打包成war放到tomcat下运行,需要修改maven打包为war

<packaging>war</packaging>

1.2.2 菜单系统

系统默认提供三种类型菜单

  • 系统级菜单,出现在页面顶部,表示一个子系统
  • 导航菜单,出现在页面左边,点击导航将打开其下所有菜单
  • 菜单,点开菜单将定位到页面,菜单必须关联到一个功能点。

建议新建立一个子系统来放置新功能

SpringPlus-Boot 并非以菜单或者按钮来组织整个系统,而是以功能点来组织整个系统提供的功能。如果要使得菜单生效,你必须要先常见一个功能点并且功能点有一个访问地址,然后将此菜单关联到这个功能点

SpringBoot-Plus 先建立功能点是个好习惯,功能点被组织成一颗树,代表了系统应该提供功能的功能,我们看代码就会看到,功能点跟菜单,跟权限,和数据权限都有密切关系

1.2.2 添加代码

可以参考1.3业务代码生成生成初始化的代码,业务代码生成了14个文件,包含前后端所有代码,可以通过生成来了解代码习作规范

1.3 业务代码生成

在介绍如何利用Plus开发系统之前,先介绍代码生成功能,此功能可以生成前后端代码总计14个文件,你可以通过预览功能了解如何开发这个系统

代码生成针对表进行代码生成,包括JS,JAVA,SQL和HTML,可以通过预览功能直接预览。在生成代码到本地前,有些参数需要修改,否则,代码生成后显示的都是英文

  • 显示字段 : 当此实体显示在任何地方的时候,能代表此实体的名称,比如用户名,组织机构名
  • 变量名:可以自己设定一个较短的名字,此变量名会用于前后端的变量
  • urlBase:你规划的子系统,最后访问路径是urlBase+变量名字
  • system: 存放sql目录的的名称

其他修改的地方有

是否包含导入导出,如果选择,则会生成导入导出的代码,导入导出模板则需要参考已有功能(比如数据字典)来完成

是否包含附件管理,如果选择,则业务对象可以关联一组附件,比如客户关联一组附件,或者申请信息关联一组附件。

字段信息的显示名字,这个用于前端列表,表单的显示,应当输入中文名字

作为搜索,可以勾选几个搜索条件,系统自动生成一个搜索配置类

如果字段关联数据字典,那么设置一个数据字典,这样,生成的界面将会变成一个下拉列表

1.3.1 前端代码

前端代码采用了layui的JS框架,使用了按需加载的方式,文档参考 http://www.layui.com/doc/base/infrastructure.html.

  • index.js: 系统入口JS,包含了查询和表格
  • add.js : 新增操作的所有JS
  • edit.js: 编辑操作的所有JS
  • del.js: 删除操作的所有JS

基础JS

  • Common.js: 封装了通常JS功能,如jquery的post方法,layui的窗口方法
  • Lib.js 封装了业务相关方法,如submitForm,loadOrgPanel等方法

1.3.2 HTML代码

页面采用layui,文档参考 http://www.layui.com/demo/

模板语言了使用Beetl,文档参考ibeetl.com

  • index.html: 功能首页
  • add.html: 新增首页
  • edit.html: 编辑操作首页

采用layui的好处是自带了页面和组件还有JS的管理,能完成大多数业务需求

基础UI组件:

  • orgInput.tag.html 组织机构输入框
  • simpleDictSelect.tag.html 字典下拉列表
  • simpleDataSelect.tag 包含key-value的下拉列表
  • searchForm.tag.html 通用搜索表单
  • submitButtons.tag.html 提交按钮
  • accessButton.tag.html 普通按钮(含权限)
  • attachment.tag.html 附件管理组件
  • ....

2 单体系统,系统拆分和微服务

plus是一个适合单体系统,系统拆分的java快速开发平台,也可以经过改造成微服务平台(以前做一个版本,但觉得plus应该聚焦系统核心,而不是简单堆砌功能,所以放弃了)

以下是单体系统,小系统,和微服务的区别

单体系统是一种常见系统设计方式,也是这十几年年来最主要的设计方式。单体系统的所有功能都在一个工程里,打成一个war包,部署。这样有如下明显好处

  • 单体系统开发方式简单,我们从刚开始学习编程,就是完成的单体系统,开发人员只要集中精力开发当前工程
  • 容易修改,如果需要修改任何功能,都非常方便,只需要修改一个工程范围的代码
  • 测试简单,单体系统测试不需要考虑别的系统,避免本书下册要提到的各种REST,MQ调用
  • 部署也很容易:不需要考虑跟别的系统关系,直接打war包部署到Web服务器即可
  • 性能容易扩展,可以通过Nginx,把一个应用部署到多个服务器上。

随着业务发展,重构,单体系统越来越多,在开发一个庞大的单体系统的时候,就会有如下弊病

  • 单体系统庞大,越来越难理解单体系统,微小的改动牵涉面广泛导致开发小组小心谨慎,开发速度会越来越慢。另外,启动一个庞大的单体系统,可能需要3分钟,或者更多时间
  • 多个功能在同一个单体系统上开发,导致测试越来越慢,比如,测试必须排期,串行测试
  • 单体系统如果想对技术进行更新换代,那代价非常大,如果是个小系统构成,则可以选取一个小系统先做尝试。单体大系统是几乎不可能做技术升级的
  • 单体系统的所有功能运行在同一个JVM里,功能会互相影响,比如一个统计上传word文档的页码的功能由于非常消耗CPU,因此,会因为调用统计功能,导致整个系统短暂都不可用,出现假死的现象

因此,越来越多的架构师在设计系统的时候,会考虑系统拆分成多个单体小系统甚至是微服务。对于传统企业应用,拆成小系统更合适,对互联网系统,使用微服务个更合适,这是因为

  • 传统IT系统本质上还是会用一个数据库,而微服务提倡的是一个服务一个数据库
  • 传统IT系统很少需要调用其他模块服务。传统IT系统通过工作流来串联其他子系统。而电商类的微服务则是通过RPC等方式进行交互,是一个轻量级协议。传统IT系统也可以通过SOA,JMS跟其他系统(非子系统)交互,采用重量级协议
  • 微服务对系统的基础设施要求很高,比如微服务治理,弹性库等等,只要电商系统才有人力物力去做这种事情,而传统IT系统,及时财大气粗,也暂时不具备微服务那样的IT基础设置

因此,对于大多数传统IT应用来说,单体拆分小系统在技术上没有风险,是一个可以立即实施的架构。如下是一个单体系统拆分后的物理架构

对于用户来说,访问不同的菜单功能,讲定位到不同得子系统,提供服务。

一篇文章讲解“重定向”,本篇文章讲解“视图-模板渲染”。


因为新版的控制器可以无需继承任何的基础类,因此在控制器中如何使用视图取决于你怎么定义控制器。

渲染模板最常用的是控制器类在继承系统控制器基类(\think\Controller)后调用fetch方法,调用格式:

模板文件的写法支持下面几种:


1. 基本使用

下面是一个最典型的用法,不带任何参数:

①新建Index控制器,并在控制器中新建index方法

注意:

1. 不带任何参数,表示系统会按照默认规则自动定位模板文件,其规则是:

当前模块/view/当前控制器名(小写)/当前操作(小写).html

②在index/view/index/下新建index.html模板文件

预览:


2. 修改模板引擎的view_depr(模板文件名分隔符)设置

①修改模板引擎的view_depr为“_”

位置:配置项文件config.php的template参数下。

注意:

1. 如果有更改模板引擎的view_depr设置(假设'view_depr'=>'_')的话,则上面的自动定位规则变成:

当前模块/view/当前控制器(小写)_当前操作(小写).html

②在index/view下新建index_index.html模板文件

访问Index控制器下的index方法,预览:


3. 如果没有按照模板定义规则来定义模板文件(或者需要调用其他控制器下面的某个模板)

1)没有按照模板定义规则来定义模板文件

【例】在Index控制器的add方法中要调用同控制器下的edit.html模板

①Index控制器的add方法

注意:

1. $this->fetch(‘edit’);表示调用当前控制器的edit模板。

②在index/view/index/下新建edit.html模板(view_depr设置为默认)。

访问add方法,预览:

2)调用其他控制器下的某个模板

【例】在Index控制器的edit方法下,调用News控制器下的add模板。

①Index控制器下的edit方法:

②在index/view/news下新建add.html模板。

访问Index控制器的edit方法,预览:


4. 跨模块渲染模板

【例】在index模块的Index控制器的del方法中,调用admin模块User控制器下的index.html模板。

①index模块的Index控制器的del方法

②admin模块的User控制器下的index.html模板

访问index模块的Index的del方法,预览:

注意:

1. 渲染输出不需要写模板文件的路径和后缀。

2. 这里面的控制器和操作并不一定需要有实际对应的控制器和操作,只是一个目录名称和文件名称而已。

【例2】你的项目里面可能根本没有Public控制器,更没有Public控制器的menu操作,但是一样可以使用。

①在Index控制器中新建pub方法,调用Public控制器的menu模板

②在index/view/public下新建menu.html模板

访问Index控制器的pub方法,预览:

5. 从视图根目录开始读取模板

支持从视图根目录开始读取模板,即从view文件夹下读取。

①在Index控制器中新建viewFloder方法,调用视图根目录下的模板。

②在index/view下新建folder.html模板

访问Index控制器的viewFloder方法,预览:

注意:

1. 如果需要调用视图类(think\View)的其它方法,可以直接使用$this->view对象。

【例】调用视图类的config方法(配置模板引擎),设置模板后缀。

视图类(think\View)的config方法:

默认的模板后缀:

在Index方法中,新建viewTest方法

访问viewTest方法,预览:

注意:

1. 在视图根目录下有folder.html模板,当设置模板后缀为.htm时,那么将不会访问folder.html模板,将会访问folder.htm模板。


6. 自定义模板文件位置

如果你的模板文件位置比较特殊或者需要自定义模板文件的位置,可以采用下面的方式处理。

①在Index控制器中新建Custom方法,调用自定义的模板文件位置

../template/index/是相当于当前项目入口文件的位置。

②在项目下创建template/index文件夹,并新建custom.html模板

预览:

注意:

1. 这种方式需要带模板路径和后缀指定一个完整的模板文件位置,这里的../template/index目录是相对于当前项目入口文件位置。

2. 如果是其他的后缀文件,也支持直接输出,例如:

return $this->fetch('../template/public/menu.tpl');

只要../template/index/menu.tpl是一个实际存在的模板文件。

3. 要注意模板文件位置是相对于应用的入口文件,而不是模板目录。

ThinkPHP5连载为卓象程序员原创,转载请联系卓象程序员

关注卓象程序员,定期发布技术文章

下一篇讲解“视图-助手函数+渲染内容”

程目标

目标1:完成选择商品分类功能

目标2:完成品牌选择功能

目标3:完成扩展属性功能

目标4:完成规格选择功能

目标5:完成SKU商品信息功能

目标6:完成是否启用规格功能

1.商品录入【选择商品分类】

1.1需求分析

在商品录入界面实现商品分类的选择(三级分类)效果如下:

当用户选择一级分类后,二级分类列表要相应更新,当用户选择二级分类后,三级列表要相应更新。

1.2准备工作

(1)在pinyougou-shop-web工程中创建ItemCatController.(可拷贝运营商后台的代码)

(2)创建item_catService.js (可拷贝运营商后台的代码)

(3)修改goodsController.js,引入itemCatService

(4)修改goods_edit.html,添加引用

<script type="text/javascript" src="../js/base.js"></script>
<script type="text/javascript" src="../js/service/goodsService.js"></script>
<script type="text/javascript" src="../js/service/itemCatService.js"></script>
<script type="text/javascript" src="../js/controller/baseController.js"></script>
<script type="text/javascript" src="../js/controller/goodsController.js"></script>

1.3代码实现

1.3.1一级分类下拉选择框

在goodsController增加代码

//读取一级分类
$scope.selectItemCat1List=function(){
 itemCatService.findByParentId(0).success(
 		 function(response){
 			 $scope.itemCat1List=response; 
 		 }
 );
}

页面加载调用该方法

<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="selectItemCat1List()">

修改goods_edit.html一级分类下拉选择框

<select class="form-control" ng-model="entity.goods.category1Id" ng-options="item.id as item.name for item in itemCat1List"></select>

ng-options属性可以在表达式中使用数组或对象来自动生成一个select中的option列表。ng-options与ng-repeat很相似,很多时候可以用ng-repeat来代替ng-options。但是ng-options提供了一些好处,例如减少内存提高速度,以及提供选择框的选项来让用户选择。

运行效果如下:

1.3.2二级分类下拉选择框

在goodsController增加代码:

//读取二级分类
$scope.$watch('entity.goods.category1Id', function(newValue, oldValue) { 
 	//根据选择的值,查询二级分类
 	itemCatService.findByParentId(newValue).success(
 		function(response){
 			$scope.itemCat2List=response; 	 			
 		}
 	); 	
}); 

$watch方法用于监控某个变量的值,当被监控的值发生变化,就自动执行相应的函数。

修改goods_edit.html中二级分类下拉框

<select class="form-control select-sm" ng-model="entity.goods.category2Id" ng-options="item.id as item.name for item in itemCat2List"></select>

1.3.3三级分类下拉选择框

在goodsController增加代码:

//读取三级分类
$scope.$watch('entity.goods.category2Id', function(newValue, oldValue) { 
 	//根据选择的值,查询二级分类
 	itemCatService.findByParentId(newValue).success(
 		function(response){
 			$scope.itemCat3List=response; 	 			
 		}
 	); 	
 });

修改goods_edit.html中三级分类下拉框

<select class="form-control select-sm" ng-model="entity.goods.category3Id" ng-options="item.id as item.name for item in itemCat3List"></select>

1.3.4读取模板ID

在goodsController增加代码:

 //三级分类选择后 读取模板ID
 $scope.$watch('entity.goods.category3Id', function(newValue, oldValue) { 
 	itemCatService.findOne(newValue).success(
 		 function(response){
 			 $scope.entity.goods.typeTemplateId=response.typeId; //更新模板ID 
 		 }
 ); 
 }); 

在goods_edit.html显示模板ID

模板ID:{{entity.goods.typeTemplateId}}

2.商品录入【品牌选择】

2.1需求分析

在用户选择商品分类后,品牌列表要根据用户所选择的分类进行更新。具体的逻辑是根据用户选择的三级分类找到对应的商品类型模板,商品类型模板中存储了品牌的列表json数据。

2.2代码实现

(1)在pinyougou-shop-web工程创建TypeTemplateController (可从运营商后台拷贝)

(2)在pinyougou-shop-web工程创建typeTemplateService.js (可从运营商后台拷贝)

(3)在goodsController引入typeTemplateService 并新增代码

//模板ID选择后 更新品牌列表
$scope.$watch('entity.goods.typeTemplateId', function(newValue, oldValue) { 
 	typeTemplateService.findOne(newValue).success(
 		function(response){
 			 $scope.typeTemplate=response;//获取类型模板
 			 $scope.typeTemplate.brandIds= JSON.parse( $scope.typeTemplate.brandIds);//品牌列表
 		}
 ); 
}); 

在页面goods_edit.html 引入js

<script type="text/javascript" src="../js/service/typeTemplateService.js"> </script>

添加品牌选择框

<select class="form-control" ng-model="entity.goods.brandId" ng-options="item.id as item.text for item in typeTemplate.brandIds"></select>

3.商品录入【扩展属性】

3.1需求分析

在商品录入实现扩展属性的录入。

3.2代码实现

修改goodsController.js ,在用户更新模板ID时,读取模板中的扩展属性赋给商品的扩展属性。

 //模板ID选择后 更新模板对象
 $scope.$watch('entity.goods.typeTemplateId', function(newValue, oldValue) { 
 	typeTemplateService.findOne(newValue).success(
 		 function(response){
 			 $scope.typeTemplate=response;//获取类型模板
 			 $scope.typeTemplate.brandIds= JSON.parse( $scope.typeTemplate.brandIds);//品牌列表
$scope.entity.goodsDesc.customAttributeItems=JSON.parse( $scope.typeTemplate.customAttributeItems);//扩展属性
 		 }
 ); 
 });

修改goods_edit.html

 <!--扩展属性-->
<div class="tab-pane" id="customAttribute">
 <div class="row data-type"> 
	 <div ng-repeat="pojo in entity.goodsDesc.customAttributeItems">
		 <div class="col-md-2 title">{{pojo.text}}</div>
		 <div class="col-md-10 data">
	<input class="form-control" ng-model="pojo.value" placeholder="{{pojo.text}}">	 </div>
	 </div> 				
</div>
</div>

4.商品录入【规格选择】

4.1需求分析

显示规格及选项列表(复选框)如下图,并保存用户选择的结果

4.2代码实现

4.2.1 显示规格选项列表

由于我们的模板中只记录了规格名称,而我们除了显示规格名称还是显示规格下的规格选项,所以我们需要在后端扩充方法。

(1)在pinyougou-sellergoods-interface的TypeTemplateService.java新增方法定义

	/**
	 * 返回规格列表
	 * @return
	 */
	public List<Map> findSpecList(Long id);

(2)在pinyougou-sellergoods-service的TypeTemplateServiceImpl.java新增方法

	@Autowired
	private TbSpecificationOptionMapper specificationOptionMapper;
		
	@Override
	public List<Map> findSpecList(Long id) {
		//查询模板
		TbTypeTemplate typeTemplate = typeTemplateMapper.selectByPrimaryKey(id);
		
		List<Map> list = JSON.parseArray(typeTemplate.getSpecIds(), Map.class) ;
		for(Map map:list){
			//查询规格选项列表
			TbSpecificationOptionExample example=new TbSpecificationOptionExample();
			com.pinyougou.pojo.TbSpecificationOptionExample.Criteria criteria = example.createCriteria();
			criteria.andSpecIdEqualTo( new Long( (Integer)map.get("id") ) );
			List<TbSpecificationOption> options = specificationOptionMapper.selectByExample(example);
			map.put("options", options);
		}		
		return list;
	}

(3)在pinyougou-shop-web的TypeTemplateController.java新增方法

	@RequestMapping("/findSpecList")
	public List<Map> findSpecList(Long id){
		return typeTemplateService.findSpecList(id);
	}

测试后端代码:

(4)前端代码:修改pinyougou-shop-web的typeTemplateService.js

	//查询规格列表
	this.findSpecList=function(id){
		return $http.get('../typeTemplate/findSpecList.do?id='+id);
	}

(5)修改pinyougou-shop-web的goodsController.js

 //模板ID选择后 更新模板对象
 $scope.$watch('entity.goods.typeTemplateId', function(newValue, oldValue) { 
 	typeTemplateService.findOne(newValue).success(
 		 function(response){
 			 $scope.typeTemplate=response;//获取类型模板
 			 $scope.typeTemplate.brandIds= JSON.parse( $scope.typeTemplate.brandIds);//品牌列表
$scope.entity.goodsDesc.customAttributeItems=JSON.parse( $scope.typeTemplate.customAttributeItems);//扩展属性
 		 }
 ); 
 	//查询规格列表
 	typeTemplateService.findSpecList(newValue).success(
 		 function(response){
 			 $scope.specList=response;
 		 }
 	); 	
}); 

(6)修改goods_edit.html页面

<div ng-repeat="pojo in specList">
 <div class="col-md-2 title">{{pojo.text}}</div>
 <div class="col-md-10 data"> 
 <span ng-repeat="option in pojo.options">
 	<input type="checkbox" >{{option.optionName}}	 
 </span> 
 </div>
</div> 

4.2.2 保存选中规格选项

我们需要将用户选中的选项保存在tb_goods_desc表的specification_items字段中,定义json格式如下:

[{“attributeName”:”规格名称”,”attributeValue”:[“规格选项1”,“规格选项2”.... ] } , .... ]

(1)在baseController.js增加代码

	//从集合中按照key查询对象
	$scope.searchObjectByKey=function(list,key,keyValue){
		for(var i=0;i<list.length;i++){
			if(list[i][key]==keyValue){
				return list[i];
			}			
		}		
		return null;
	}

(2)在goodsController.js增加代码

$scope.entity={ goodsDesc:{itemImages:[],specificationItems:[]} };
$scope.updateSpecAttribute=function($event,name,value){
	var object= $scope.searchObjectByKey(
$scope.entity.goodsDesc.specificationItems ,'attributeName', name);		
		if(object!=null){	
			if($event.target.checked ){
				object.attributeValue.push(value);		
			}else{//取消勾选				object.attributeValue.splice( object.attributeValue.indexOf(value ) ,1);//移除选项
				//如果选项都取消了,将此条记录移除
				if(object.attributeValue.length==0){
					$scope.entity.goodsDesc.specificationItems.splice(
	$scope.entity.goodsDesc.specificationItems.indexOf(object),1);
				}				
			}
		}else{				
$scope.entity.goodsDesc.specificationItems.push(
{"attributeName":name,"attributeValue":[value]});
		}		
	}

(3)在goods_edit.html调用方法

<div ng-repeat="pojo in specList">
		<div class="col-md-2 title">{{pojo.text}}</div>
		<div class="col-md-10 data">
		<span ng-repeat="option in pojo.options">
		<input type="checkbox" ng-click="updateSpecAttribute($event,pojo.text,option.optionName)">{{option.optionName}}					 				 	 </span> 																 </div>
</div> 

为了方便测试,我们可以在页面上某个区域临时添加表达式,以便观测测试结果

{{entity.goodsDesc.specificationItems}}

5.商品录入【SKU商品信息】

5.1需求分析

基于上一步我们完成的规格选择,根据选择的规格录入商品的SKU信息,当用户选择相应的规格,下面的SKU列表就会自动生成,如下图:

实现思路:实现思路:

(1)我们先定义一个初始的不带规格名称的集合,只有一条记录。

(2)循环用户选择的规格,根据规格名称和已选择的规格选项对原集合进行扩充,添加规格名称和值,新增的记录数与选择的规格选项个数相同

生成的顺序如下图:

5.2前端代码

5.2.1 生成SKU列表(深克隆)

(1)在goodsController.js实现创建sku列表的方法

//创建SKU列表
$scope.createItemList=function(){	
	$scope.entity.itemList=[{spec:{},price:0,num:99999,status:'0',isDefault:'0' } ];//初始
	var items= $scope.entity.goodsDesc.specificationItems;	
	for(var i=0;i< items.length;i++){
		$scope.entity.itemList = addColumn( $scope.entity.itemList,items[i].attributeName,items[i].attributeValue ); 
	}	
}
//添加列值 
addColumn=function(list,columnName,conlumnValues){
	var newList=[];//新的集合
	for(var i=0;i<list.length;i++){
		var oldRow= list[i];
		for(var j=0;j<conlumnValues.length;j++){
			var newRow= JSON.parse( JSON.stringify( oldRow ) );//深克隆
			newRow.spec[columnName]=conlumnValues[j];
			newList.push(newRow);
		} 		 
	} 		
	return newList;
}

(2)在更新规格属性后调用生成SKU列表的方法

<input type="checkbox" ng-click="updateSpecAttribute($event,pojo.text,option.optionName);createItemList()">{{option.optionName}}	

(3)在页面上添加表达式,进行测试

 {{entity.itemList}}

显示效果如下:

5.2.2 显示SKU列表

goods_edit.html页面上绑定SKU列表

<table class="table table-bordered table-striped table-hover dataTable">
 <thead>
 <tr>					 
		 <th class="sorting" ng-repeat="item in entity.goodsDesc.specificationItems">{{item.attributeName}}</th>
		 <th class="sorting">价格</th>
		 <th class="sorting">库存</th>
		 <th class="sorting">是否启用</th>
		 <th class="sorting">是否默认</th>
	 </tr>
 </thead>
 <tbody>
 <tr ng-repeat="pojo in entity.itemList">					 
 <td ng-repeat="item in entity.goodsDesc.specificationItems">
 	{{pojo.spec[item.attributeName]}}
 </td>													
 <td>
 		<input class="form-control" ng-model="pojo.price" placeholder="价格">
 </td>
 <td>
 	<input class="form-control" ng-model="pojo.num" placeholder="库存数量">
 </td>
 <td>
 	<input type="checkbox" ng-model="pojo.status" ng-true-value="1" ng-false-value="0" >
 </td>
 <td>
 <input type="checkbox" ng-model="pojo.isDefault" ng-true-value="1" ng-false-value="0">									 	
 </td>
 </tr>
 </tbody>
</table>

删除掉原来的测试用的表达式

5.3后端代码

(1)在GoodsServiceImpl添加属性

	@Autowired
	private TbItemMapper itemMapper;
	
	@Autowired
	private TbBrandMapper brandMapper;
	
	@Autowired
	private TbItemCatMapper itemCatMapper;
	
	@Autowired
	private TbSellerMapper sellerMapper;

(2)修改GoodsServiceImpl的add方法,增加代码,实现对SKU商品信息的保存

/**
 * 增加
 */
@Override
public void add(Goods goods) {
	goods.getGoods().setAuditStatus("0");		
	goodsMapper.insert(goods.getGoods());	//插入商品表
	goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());
	goodsDescMapper.insert(goods.getGoodsDesc());//插入商品扩展数据
	for(TbItem item :goods.getItemList()){
		//标题
		String title= goods.getGoods().getGoodsName();
		Map<String,Object> specMap = JSON.parseObject(item.getSpec());
		for(String key:specMap.keySet()){
			title+=" "+ specMap.get(key);
		}
		item.setTitle(title);		
		item.setGoodsId(goods.getGoods().getId());//商品SPU编号
		item.setSellerId(goods.getGoods().getSellerId());//商家编号
		item.setCategoryid(goods.getGoods().getCategory3Id());//商品分类编号(3级)
		item.setCreateTime(new Date());//创建日期
		item.setUpdateTime(new Date());//修改日期 
		//品牌名称
		TbBrand brand = brandMapper.selectByPrimaryKey(goods.getGoods().getBrandId());
		item.setBrand(brand.getName());
		//分类名称
		TbItemCat itemCat = itemCatMapper.selectByPrimaryKey(goods.getGoods().getCategory3Id());
		item.setCategory(itemCat.getName());		
		//商家名称
		TbSeller seller = sellerMapper.selectByPrimaryKey(goods.getGoods().getSellerId());
		item.setSeller(seller.getNickName());		
		//图片地址(取spu的第一个图片)
		List<Map> imageList = JSON.parseArray(goods.getGoodsDesc().getItemImages(), Map.class) ;
		if(imageList.size()>0){
			item.setImage ( (String)imageList.get(0).get("url"));
		}		
		itemMapper.insert(item);
	}		
}

6.商品录入【是否启用规格】

6.1需求分析

在规格面板添加是否启用规格,当用户没有选择该项,将原来的规格面板和SKU列表隐藏,用户保存商品后只生成一个SKU.

6.2前端代码

goods_add.html添加复选框

<div class="row data-type">
	 <div class="col-md-2 title">是否启用规格</div>
			<div class="col-md-10 data">
			<input type="checkbox" ng-model="entity.goods.isEnableSpec" ng-true-value="1" ng-false-value="0">
			</div>
 </div>

用if指令控制规格面板与SKU列表的显示与隐藏

<div ng-if="entity.goods.isEnableSpec==1">
......SKU表格部分
</div>

6.3后端代码

修改GoodsServiceImpl的add方法