Skip to content

前端分层开发

分层开发需求分析

前端页面的JS和html都放在一起,并不利于后期的维护。可以在前端代码中也运用MVC的设计模式,将代码进行分离,提高程序的可维护性。

前端页面mvc分层

前端基础层

  • 在pinyougou-manager-web工程js目录下创建base.js
js
/* 定义基础模块(不带分布模块) */
var app = angular.module('pinyougou', []);
  • 再创建base-pagination.js
js
/* 定义基础模块(带分页模块) */
var app = angular.module('pinyougou', ['pagination']);
  • 说明:一个用于不需要分页功能的页面,一个用于需要分页功能的页面

前端服务层

  • 在pinyougou-manager-web工程js文件夹下创建service文件夹,创建baseService.js
js
/* 定义基础服务层,公用的js代码 */
app.service('baseService', function ($http) {
    /* 定义发送get请求方法(带请求参数或不带请求参数) */
    this.sendGet = function (url, data) {
        /* 判断传入的数据是否有效 */
        if (data) {
            url = url + "?" + data;
        }

        /* 返回发送get请求 */
        return $http.get(url);
    };

    /* 定义发送post请求方法(带请求参数或不带请求参数) */
    this.sendPost = function (url, data) {
        /* 判断传入的数据是否有效 */
        if (data) {
            /* 带请求参数 */
            return $http.post(url, data);
        } else {
            /* 不带请求参数 */
            return $http.post(url);
        }
    };

    /* 定义根据id查询方法 */
    this.findOne = function (url, id) {
        return this.sendGet(url, "id=" + id);
    };

    /* 分页查询(带查询条件或不带查询条件) */
    this.findByPage = function (url, page, row, data) {
        /* 定义分页url */
        url += "?page=" + page + "&rows=" + row;

        /* 判断是否有查询条件 */
        if (data && JSON.stringify(data) !== "{}") {
            // 使用get发送查询请求
            return $http({
                method: 'get',
                url: url,
                params: data
            });
        } else {
            // 无查询条件
            return this.sendGet(url);
        }
    };

    /* 定义删除或者批量删除的方法 */
    this.deleteById = function (url, ids) {
        /* 判断ids是否为数组 */
        if (ids instanceof Array) {
            return this.sendGet(url, "ids=" + ids);
        } else {
            return this.sendGet(url, "id=" + ids);
        }
    };
});
  • 创建品牌服务层brandService.js,用于封装处理品牌的专用js方法(如果有的话)
js
/* 定义品牌服务层,用于封装处理品牌的专用js方法 */
app.service('brandService', function ($http) {});

控制器继承

  • 有些功能是每个页面都有可能用到的,比如分页,复选等等,如果再开发另一个功能,还需要重复编写。通过继承的方式来实现让这些通用的功能只写一次。
  • 语法格式:$controller("baseController",{$scope:$scope});
  • 说明:$controller也是angular提供的一个服务,可以实现伪继承,实际上就是与BaseController共享$scope

前端控制层-创建父控制器

  • 在pinyougou-manager-web的js/controller目录下建立baseController.js。
  • 将定义分页配置信息对象、reload()方法、定义ids数组封装删除的id放到父控制器中
js
/* 定义基础的控制器层 */
app.controller('baseController', function ($scope) {

    // 定义初始化分页组件需要的配置信息对象(对象对应传递tm-pagination组件的conf属性)
    $scope.paginationConf = {
        currentPage : 1,    // 当前页码
        totalItems : 0,     // 总记录数
        itemsPerPage : 10,  // 每页显示的记录数
        perPageOptions : [10, 20 ,30], // 页码下拉列表框
        onChange : function(){ // 当页码改变监听事件
            /** 重新加载数据 */
            $scope.reload();
        }
    };

    /* 定义监听页码改变后事件,重新加载数据的方法 */
    $scope.reload = function () {
        // 传入当前页码与每页大小,重新调用分页查询方法即可
        $scope.search($scope.paginationConf.currentPage,
            $scope.paginationConf.itemsPerPage);
    };

    /* 定义选中的id数组 */
    $scope.ids = new Array();
    /* 定义checkbox点击事件方法,获取选取id数组 */
    $scope.updateSelection = function ($event, id) {
        // 判断当前checkbox是否选中
        if ($event.target.checked) {
            // 选中复选框,将id添加到数组中
            $scope.ids.push(id);
        } else {
            // 取消选择,则根据id的值获取对应数组中的索引
            var index = $scope.ids.indexOf(id);

            // 根据元素的索引号从数组中移除
            $scope.ids.splice(index, 1);
        }

        /* 判断全选是否选中 */
        $scope.ck_all = $scope.ids.length == $scope.dataList.length;

        // 测试使用(待删)
        console.log("选中的id===" + JSON.stringify($scope.ids));
    };

    /* 为全选checkbox绑定点击事件 */
    $scope.checkAll = function() {
        // 判断全选checkbox是选中还中没选中
        if ($scope.ck) {
            // 注意,如果之前有选中的话,要选将数组清空,再增加,不然会出现重复的id
            $scope.ids = [];
            // 循环请求后端返回的数据集合dataList
            for (var i = 0; i < $scope.dataList.length; i++) {
                $scope.ids.push($scope.dataList[i].id);
            }
        } else {
            // 全选复选框没有选中,则将选择的id数组清空
            $scope.ids = [];
        }
        alert("点击全选====" + $scope.ids);
    };

});

前端控制器层(例:品牌模块)

修改brandController.js,指定继承baseController.js

js
/* 定义品牌控制器层 */
app.controller('brandController', function ($scope, $controller, baseService) {

    /* 指定继承的baseController,将baseController的scope赋值给此controller的scope */
    $controller('baseController', {$scope: $scope});


    // 获取所有品牌数据(不分页查询,可删)
    $scope.findAll = function () {
        // 调用服务层,发送异步get请求查询后台
        baseService.sendGet('/brand/findAll').then(function (response) {
            $scope.dataList = response.data;
        }, function (response) {
            alert("获取品牌列表数据失败!");
        })
    };

    /* 定义封装查询条件对象 */
    $scope.searchEntity = {};
    /* 定义分页查询的方法 */
    $scope.search = function (page, rows) {
        /* 调用服务层,送异步请求分页查询品牌数据 */
        baseService.findByPage('/brand/findByPage', page, rows, $scope.searchEntity)
            .then(function (response) {
            // 请求成功回调函数.获取响应数据,赋值给dataList
            $scope.dataList = response.data.rows;
            // 更新分页组件中的总记录数
            $scope.paginationConf.totalItems = response.data.total;
        }, function (response) {
            alert("获取品牌列表分页数据失败!");
        });
    };

    /* 定义添加或修改的方法 */
    $scope.saveOrUpdate = function () {
        // 定义默认url
        var url = 'save';

        // 判断是新增还是修改
        if ($scope.entity.id) {
            // 如果发送到后端的对象id属性不为空,则是修改操作
            url = 'update';
        }

        // 调用服务层,使用post方法发送异步请求发送对象到后台
        baseService.sendPost('/brand/' + url, $scope.entity)
            .then(function (response) {
                // 判断返回的结果,后端返回的是boolean
                if (response.data) {
                    // 成功新增后,重新刷新页面
                    $scope.reload();
                } else {
                    alert("操作失败")
                }
            })
    };

    /* 定义点击修改按钮显示对象数据的方法 */
    $scope.show = function (entity) {
        // 把entity对象解析成一个新的对象,否则修改此对象就是修改列表的该数据
        $scope.entity = JSON.parse(JSON.stringify(entity));
    };

    /* 定义批量删除品牌的方法 */
    $scope.delete = function () {
        // 判断是否有选中复选框
        if ($scope.ids.length > 0) {
            // 调用业务层删除方法,发送get异步请求
            baseService.deleteById('/brand/delete', $scope.ids)
                .then(function (response) {
                    if (response.data) {
                        // 删除成功,重新加载页面
                        $scope.reload();
                    } else {
                        alert("操作失败");
                    }
                });
            // 删除操作后,无论成功与否,都清空id数组
            $scope.ids = [];
            // 使用所有复选框不选中
            $scope.ck = false;
            $scope.ck_all = false;
        } else {
            // 提示至少选择1个品牌
            alert("请至少选择1个要删除的品牌!");
        }
    };
});

修改原来brand.html页面

将brand.html原来js代码删除,直接导入所有相关的js文件即可

html
<!-- 导入angular框架mvc分层后的js文件start -->
<script src="/js/base-pagination.js"></script>
<script src="/js/service/baseService.js"></script>
<script src="/js/controller/baseController.js"></script>
<script src="/js/controller/brandController.js"></script>
<!-- 导入angular框架mvc分层后的js文件end -->

规格管理模块功能

将商家商品服务相关代码【资料\准备代码】拷贝到工程

  1. 拷贝服务接口

1

  1. 拷贝服务实现类

2

  1. 拷贝控制器

3

  1. 拷贝JS

4

需求及表格构分析

实现规格管理功能

tb_specification 规格表

字段类型长度含义
idbigint主键
spec_namevarchar255规格名称

tb_specification_option 规格选项表

字段类型长度含义
idbigint主键
option_namevarchar200规格选项名称
spec_idbigint30规格ID
ordersint11排序

规格列表显示-前端

  • 修改pinyougou-manager-web工程的specification.html,引入js,复制品牌管理模块html修改代码
html
<!-- 导入angular前端框架 -->
<script src="/plugins/angularjs/angular.min.js"></script>
<!-- 导入分页组件start -->
<script src="/plugins/angularjs/pagination.js"></script>
<link rel="stylesheet" href="/plugins/angularjs/pagination.css"/>
<!-- 导入分页组件end -->
<!-- 导入angular框架mvc分层后的js文件start -->
<script src="/js/base-pagination.js"></script>
<script src="/js/service/baseService.js"></script>
<script src="/js/controller/baseController.js"></script>
<script src="/js/controller/specificationController.js"></script>
<!-- 导入angular框架mvc分层后的js文件end -->
  • 在table后面放置分页组件
html
<!-- 分页组件 -->
<tm-pagination conf="paginationConf"></tm-pagination>
  • 在body元素指定模块名和控制器名
html
<body class="hold-transition skin-red sidebar-mini"
        ng-app="pinyougou" ng-controller="specificationController">
  • 循环获取表格行数据
html
<tbody>
	<tr ng-repeat="entity in dataList">
		<td><input type="checkbox"
                   ng-checked="ck"
                   ng-click="updateSelection($event, entity.id);"></td>
		<td>&#123;&#123; entity.id &#125;&#125;</td>
		<td>&#123;&#123; entity.specName &#125;&#125;</td>
		<td class="text-center">
			<button type="button" class="btn bg-olive btn-xs"
				data-toggle="modal" data-target="#editModal">修改</button>
		</td>
	</tr>
</tbody>
  • 修改全选按钮,用于全选删除
html
<thead>
	<tr>
		<th class="" style="padding-right: 0px">
            <input id="selall" type="checkbox" class="icheckbox_square-blue"
                   ng-model="ck" ng-checked="ck_all" ng-click="checkAll();">
        </th>
		<th class="sorting_asc">规格ID</th>
		<th class="sorting">规格名称</th>
		<th class="text-center">操作</th>
	</tr>
</thead>
  • 查询条件,添加点击事件和绑定参数
html
<div class="has-feedback">
	规格名称:<input ng-model="searchEntity.specName">
	<button class="btn btn-default" ng-click="reload();">查询</button>
</div>

规格列表显示-后端

  • 根据品牌后台代码修改,规格管理控制层SpecificationController
java
/**
 * 分页查询规格
 *
 * @param specification 规格查询条件
 * @param page          当前页记录
 * @param rows          每页显示大小
 * @return 封装分页数据
 */
@GetMapping("/findByPage")
public PageResult<Specification> findByPage(Specification specification,
                                            @RequestParam("page") Integer page,
                                            @RequestParam("rows") Integer rows) {
    try {
        // 调用服务接口分页查询方法
        PageResult<Specification> pageResult = specificationService
                .findByPage(specification, page, rows);
        return pageResult;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
  • 规格管理服务类接口SpecificationService与实现类SpecificationServiceImpl
java
/**
 * 分页条件查询规格
 *
 * @param specification 规格查询条件
 * @param page          当前页记录
 * @param rows          每页显示大小
 * @return 封装分页数据
 */
PageResult<Specification> findByPage(Specification specification, Integer page, Integer rows);
java
/**
 * 注入数据访问层代理对象
 */
@Autowired
private SpecificationMapper specificationMapper;
@Autowired
private SpecificationOptionMapper specificationOptionMapper;

/**
 * 分页条件查询规格
 *
 * @param specification 规格查询条件
 * @param page          当前页记录
 * @param rows          每页显示大小
 * @return 封装分页数据
 */
@Override
public PageResult<Specification> findByPage(Specification specification, Integer page, Integer rows) {
    try {
        // 使用分页助手开启分页,获取pageInfo对象
        PageInfo<Specification> pageInfo = PageHelper.startPage(page, rows)
                .doSelectPageInfo(new ISelect() {
                    @Override
                    public void doSelect() {
                        // 使用自定义动态条件查询方法
                        specificationMapper.findByWhere(specification);
                    }
                });

        // 创建PageResult对象
        PageResult<Specification> pageResult = new PageResult<Specification>();

        pageResult.setTotal(pageInfo.getTotal());
        pageResult.setRows(pageInfo.getList());

        return pageResult;

    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
  • mapper接口与映射文件
java
/**
 * 分页条件查询规格数据
 *
 * @param specification 规格条件查询对象
 * @return 返回规格对象集合
 */
List<Specification> findByWhere(@Param("specification") Specification specification);
xml
<!-- 动态sql多条件查询规格 -->
<select id="findByWhere" resultType="Specification">
    select * from tb_specification
    <where>
        <!-- 先判断输入的对象是否为空 -->
        <if test="specification != null">
            <!-- 判断查询名称的条件 -->
            <if test="specification.specName != null and specification.specName != ''">
                <!-- 使用concat函数拼接字符 -->
                `spec_name` like concat('%', #{specification.specName} ,'%')
            </if>
        </if>
    </where>
    order by id asc
</select>

新增规格

增加页面,新增行实现

  • 给specification.html “新增规格选项” 按钮增加点击事件
html
<!-- 规格选项 -->
<div class="btn-group">
  <button type="button" class="btn btn-default" title="新建"
          ng-click="addTableRow();">
      <i class="fa fa-file-o"></i> 新增规格选项</button>
</div>
  • 在循环列表行,绑定表格内的编辑框的值
html
<tbody>
  <tr ng-repeat="specificationOption in entity.specificationOptions">
        <td>
        	<input ng-model="specificationOption.optionName" class="form-control" placeholder="规格选项">
        </td>
        <td>
        	<input ng-model="specificationOption.orders" class="form-control" placeholder="排序">
        </td>
		<td>
			<button type="button" class="btn btn-default" title="删除" ><i class="fa fa-trash-o"></i> 删除</button>
		</td>
  </tr>
</tbody>
  • 修改specificationController.js 新增点击新增行方法
js
/* 为新增规格选项按钮绑定事件(新增一行) */
$scope.addTableRow = function () {
    /* 点击后给对象中specificationOptions属性增加一个空对象 */
    $scope.entity.specificationOptions.push({});
};
  • 修改specification.html “新建”按钮
html
<button type="button" class="btn btn-default" title="新建"
	    data-toggle="modal" data-target="#editModal"
        ng-click="entity = {'specificationOptions':[]}">
	<i class="fa fa-file-o"></i> 新建
</button>

注意:要修改specification.html “新建”按钮,弹出窗口时对entity进行初始化,否则向集合添加数据时会报错!

增加页面,删除行实现

  • 实现思路:在每一行将索引值传递给集合,在集合中删除。
  • 注意:$index 用于获取ng-repeat指令循环中的索引。
  • 修改specification.html每行的删除按钮,增加点击事件
html
<td>
    <button type="button" class="btn btn-default" title="删除"
            ng-click="deleteTableRow($index);">
    <i class="fa fa-trash-o"></i> 删除</button>
</td>
  • 修改specificationController.js 新增删除方法
js
/* 根据传入的索引,删除行 */
$scope.deleteTableRow = function (index) {
    $scope.entity.specificationOptions.splice(index, 1);
}

增加页面,提交保存

实现思路:将规格和规格选项数据合并成一个对象来传递,在业务逻辑中,得到对象中的规格和规格选项列表,插入规格返回规格ID,然后循环插入规格选项

  • 绑定规格名称
html
<tr>
	<td>规格名称</td>
	<td><input  ng-model="entity.specName"
                class="form-control" placeholder="规格名称"></td>
</tr>
  • 点击保存事件
js
<button ng-click="saveOrUpdate();" class="btn btn-success"
        data-dismiss="modal" aria-hidden="true">保存</button>

保存后台实现

  • 修改pinyougou-pojo工程中Specification.java类,增加specificationOptions的List集合属性
java
/** 规格属性集合 */
@Transient
private List<SpecificationOption> specificationOptions;
  • 修改控制层SpecificationController
java
/**
 * 添加规格
 *
 * @param specification 规格实体
 * @return 添加成功/失败标识符
 */
@PostMapping("/save")
public boolean save(@RequestBody Specification specification) {
    try {
        // 调用服务层新增方法
        specificationService.saveSpecification(specification);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    // 出现异常,更新失败
    return false;
}
  • 修改SpecificationService、SpecificationServiceImpl,增加添加规格的方法
java
/**
 * 新增规格
 *
 * @param specification 规格数据实体类
 */
void saveSpecification(Specification specification);

/**
 * 新增规格
 *
 * @param specification 规格数据实体类
 */
@Override
public void saveSpecification(Specification specification) {
    try {
        // 1.调用规格数据访问层保存规格
        specificationMapper.insertSelective(specification);
        // 2.调用规格选项数据访问层保存对应的规格选项
        if (specification.getSpecificationOptions() != null
                && specification.getSpecificationOptions().size() > 0) {
            // 当有规格选项才去新增
            specificationOptionMapper.save(specification);
        }
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • 修改SpecificationOptionMapper与映射文件,新增void save(Specification specification);方法
xml
<!-- 新增规格选项 -->
<insert id="save">
    insert into tb_specification_option(option_name, spec_id, orders)
    values
    <foreach collection="specificationOptions" item="so" separator=",">
        (#{so.optionName}, #{id}, #{so.orders})
    </foreach>
</insert>

修改规格(不完善,修改时是批量删除所有规格再新增本次数据)

前端页面

  • 修改specification.html,给修改按钮绑定点击事件
html
<td class="text-center">
    <button type="button" class="btn bg-olive btn-xs"
            data-toggle="modal" data-target="#editModal"
            ng-click="show(entity)">修改</button>
</td>
  • 修改specificationController.js,调用服务层根据id查询对象的js方法,查询规格下包含的所有规格选项数据
js
/** 显示修改 */
$scope.show = function(entity){
    // 把entity对象解析成一个新的对象
    $scope.entity = JSON.parse(JSON.stringify(entity));

    // 用服务层根据id查询规格选项的数据
    baseService.findOne("/specification/findOne", entity.id)
        .then(function (response) {
            $scope.entity.specificationOptions = response.data;
    });
};

后端-获取规格数据

  • 实现思路:通过规格ID,到后端查询规格选项列表,然后返回结果
  • 控制层SpecificationController添加根据id查询规格,返回当前规格下的所有规格选项集合
java
/**
 * 根据规格id查询规格选项
 *
 * @param id 规格id
 * @return List<SpecificationOption> 返回当前规格下的所有规格选项集合
 */
@GetMapping("/findOne")
public List<SpecificationOption> findOne(@RequestParam("id") Long id) {
    // 调用服务层根据规格id查询方法
    return specificationService.findOne(id);
}
  • 实现层SpecificationServiceImpl添加根据id查询规格包含的所有规格选项
java
/**
 * 根据规格id查询规格选项
 *
 * @param id 规格id
 * @return 返回当前规格下的所有规格选项集合
 */
List<SpecificationOption> findOne(Long id);

/**
 * 根据规格id查询规格选项
 *
 * @param id 规格id
 * @return 返回当前规格下的所有规格选项集合
 */
@Override
public List<SpecificationOption> findOne(Long id) {
    try {
        // 调用规格选项数据访问层根据规格id查询所有规格选项
        return specificationOptionMapper.findBySpecId(id);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • SpecificationOptionMapper接口,使用注解方式写sql语句(尝试不同的写法)
java
/**
 * 根据规格id查询规格选项
 *
 * @param id 规格id
 * @return 返回当前规格下的所有规格选项集合
 */
// 使用注解方式写sql语句(配置mapper.xml也能实现)
@Select("select * from tb_specification_option where spec_id = #{id} order by orders asc")
List<SpecificationOption> findBySpecId(Long id);

保存修改后的规格数据

  • 控制层SpecificationController添加保存修改的方法
java
/**
 * 修改规格
 *
 * @param specification 规格实体类
 * @return 成功/失败标识
 */
@PostMapping("/update")
public boolean update(@RequestBody Specification specification) {
    try {
        // 调用服务层修改方法
        specificationService.updateSpecification(specification);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 服务层SpecificationService修改方法
java
/**
 * 修改规格
 *
 * @param specification 规格实体类
 */
void updateSpecification(Specification specification);


/**
 * 修改规格
 *
 * @param specification 规格实体类
 */
@Override
public void updateSpecification(Specification specification) {
    try {
        // 1.调用规格数据访问层修改规格,根据主键更新属性不为null的值
        specificationMapper.updateByPrimaryKeySelective(specification);

        // 2.使用暴力方式实现功能,将规格下所有选项删除,再新增本次所有修改内容(有时间再优化)
        // 2.1调用规格选项mapper根据规格id删除所有(为了不多写方法,直接使用删除多个规格选项时使用的sql)
        Long[] ids = {specification.getId()};
        specificationOptionMapper.deleteBySpecIds(ids);

        // 2.2调用规格选项数据访问层批量新增方法
        if (specification.getSpecificationOptions() != null
                && specification.getSpecificationOptions().size() > 0) {
            // 当有规格选项才去新增
            specificationOptionMapper.save(specification);
        }
    } catch (Exception e) {
        // 更新出现异常
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • SpecificationOptionMapper接口与映射文件增加根据规格id批量删除的方法
java
/**
 * 根据规格id批量删除规格选项
 *
 * @param ids 规格id数组
 */
void deleteBySpecIds(@Param("ids") Long[] ids);
xml
<!-- 根据规格id批量删除规格选项 -->
<delete id="deleteBySpecIds">
    delete from tb_specification_option where spec_id in
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

注:为了兼容后面批量删除的功能,定义入参为id数组

删除规格

实现思路:要删除规格的同时,还要记得将关联的规格选项删除掉

后端部分

  • 控制层SpecificationController添加批量删除方法
java
/**
 * 批量删除规格
 *
 * @param ids 规格id数组
 * @return 成功/失败标识
 */
@GetMapping("/delete")
public boolean delete(Long[] ids) {
    try {
        // 调用服务层批量删除方法
        specificationService.deleteSpecification(ids);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 服务端SpecificationService接口与现实类编写批量删除方法
java
/**
 * 批量删除规格
 *
 * @param ids 规格id数组
 */
void deleteSpecification(Long[] ids);

/**
 * 批量删除规格
 *
 * @param ids 规格id数组
 */
@Override
public void deleteSpecification(Long[] ids) {
    try {
        // 使用自定义sql删除,注意先删除从表
        specificationOptionMapper.deleteBySpecIds(ids);
        specificationMapper.deleteSpecification(ids);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • 规格mapper接口与映射文件
java
/**
 * 批量删除规格
 *
 * @param ids 规格id数组
 */
void deleteSpecification(@Param("ids") Long[] ids);
xml
<!-- 根据id批量删除规格 -->
<delete id="deleteSpecification">
    delete from tb_specification where id in
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>
  • 规格选项mapper接口与映射文件(在修改规格时已编写好)

前端页面

  • 修改pinyougou-manager-web的specification.html,列表的复选框
html
<td><input type="checkbox"
           ng-checked="ck"
           ng-click="updateSelection($event, entity.id);"></td>
  • 删除按钮点击事件
html
<button type="button" class="btn btn-default" title="删除"
        ng-click="delete();">
	<i class="fa fa-trash-o"></i> 删除
</button>

模版管理模块功能

需求及表结构分析

需要理解模板的作用。模板主要有两个:

  1. 是用于关联品牌与规格
  2. 定义扩充属性

tb_type_template 模板表

字段类型长度含义
idbigint主键
namevarchar80模板名称
spec_idsvarchar1000关联规格(json格式)
brand_idsvarchar1000关联品牌(json格式)
custom_attribute_itemsvarchar2000扩展属性(json格式)

模板列表显示

引入模板列表与规格模块代码一致

模板列表查询显示-前端

  • type_template.html引入select2的JS与angular、controller
html
<!-- 导入angular前端框架 -->
<script src="/plugins/angularjs/angular.min.js"></script>
<!-- 导入分页组件start -->
<script src="/plugins/angularjs/pagination.js"></script>
<link rel="stylesheet" href="/plugins/angularjs/pagination.css"/>
<!-- 导入分页组件end -->
<!-- 导入angular框架mvc分层后的js文件start -->
<script src="/js/base-pagination.js"></script>
<script src="/js/service/baseService.js"></script>
<script src="/js/controller/baseController.js"></script>
<script src="/js/controller/typeTemplateController.js"></script>
<!-- 导入angular框架mvc分层后的js文件end -->
  • 放置分页组件、初始化指令与页面元素
html
<!-- 分页组件 -->
<tm-pagination conf="paginationConf"></tm-pagination>
html
<body class="hold-transition skin-red sidebar-mini"
      ng-app="pinyougou" ng-controller="typeTemplateController">
html
<tbody>
    <tr ng-repeat="entity in dataList">
    	<td><input type="checkbox"></td>
    	<td>&#123;&#123; entity.id &#125;&#125;</td>
    	<td>&#123;&#123; entity.name &#125;&#125;</td>
    	<td>&#123;&#123; entity.brandIds &#125;&#125;</td>
    	<td>&#123;&#123; entity.specIds &#125;&#125;</td>
    	<td>&#123;&#123; entity.customAttributeItems &#125;&#125;</td>
    	<td class="text-center">
    		<button type="button" class="btn bg-olive btn-xs"
    			data-toggle="modal" data-target="#editModal">修改</button>
    	</td>
    </tr>
</tbody>
  • 修改typeTemplateController.js与type_template.html,增加条件查询代码
js
/** 定义搜索对象 */
$scope.searchEntity = {};
/** 分页查询 */
$scope.search = function(page, rows){
    baseService.findByPage("/typeTemplate/findByPage", page,
		rows, $scope.searchEntity)
        .then(function(response){
            $scope.dataList = response.data.rows;
            /** 更新总记录数 */
            $scope.paginationConf.totalItems = response.data.total;
        });
};
html
<div class="has-feedback">
	分类模板名称:<input type="text" ng-model="searchEntity.name"
	                   placeholder="分类模板名称">
	<button class="btn btn-default" ng-click="reload();">查询</button>
</div>

模板列表查询-后端

  • 修改TypeTemplateController,增加条件分页查询方法
java
/**
 * 分页查询类型模版
 *
 * @param typeTemplate  模板查询条件typeTemplate
 * @param page          当前页记录
 * @param rows          每页显示大小
 * @return 封装分页数据
 */
@GetMapping("/findByPage")
public PageResult<TypeTemplate> findByPage(TypeTemplate typeTemplate,
                                            @RequestParam("page") Integer page,
                                            @RequestParam("rows") Integer rows) {
    try {
        // 调用服务接口分页查询方法
        PageResult<TypeTemplate> pageResult = typeTemplateService
                .findByPage(typeTemplate, page, rows);
        return pageResult;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
  • 修改模板接口TypeTemplateService与实现类TypeTemplateServiceImpl,增加查询方法
java
/**
 * 分页查询类型模版
 *
 * @param typeTemplate 模板查询条件typeTemplate
 * @param page         当前页记录
 * @param rows         每页显示大小
 * @return 封装分页数据
 */
PageResult<TypeTemplate> findByPage(TypeTemplate typeTemplate, Integer page, Integer rows);

/**
 * 分页查询类型模版
 *
 * @param typeTemplate 模板查询条件typeTemplate
 * @param page         当前页记录
 * @param rows         每页显示大小
 * @return 封装分页数据
 */
@Override
public PageResult<TypeTemplate> findByPage(TypeTemplate typeTemplate, Integer page, Integer rows) {

    try {
        // 使用分页助手开启分页,获取pageInfo对象
        PageInfo<TypeTemplate> pageInfo = PageHelper.startPage(page, rows)
                .doSelectPageInfo(new ISelect() {
                    @Override
                    public void doSelect() {
                        // 使用自定义动态条件查询方法
                        typeTemplateMapper.findByWhere(typeTemplate);
                    }
                });

        // 创建PageResult对象
        PageResult<TypeTemplate> pageResult = new PageResult<TypeTemplate>();

        pageResult.setTotal(pageInfo.getTotal());
        pageResult.setRows(pageInfo.getList());

        return pageResult;

    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
  • 修改TypeTemplateMapper接口与配置文件,增加动态条件查询方法
java
/**
 * 分页条件查询模版数据
 *
 * @param typeTemplate 模板条件查询对象
 * @return 返回模版对象集合
 */
List<TypeTemplate> findByWhere(@Param("typeTemplate") TypeTemplate typeTemplate);
xml
<!-- 动态sql多条件查询规格 -->
<select id="findByWhere" resultType="TypeTemplate">
    select * from tb_type_template
    <where>
        <!-- 先判断输入的对象是否为空 -->
        <if test="typeTemplate != null">
            <!-- 判断查询名称的条件 -->
            <if test="typeTemplate.name != null and typeTemplate.name != ''">
                <!-- 使用concat函数拼接字符 -->
                `name` like concat('%', #{typeTemplate.name} ,'%')
            </if>
        </if>
    </where>
    order by id asc
</select>

优化模板列表的显示

  • 现在完成的列表中都是以JSON格式显示的,不利于用户的查询。需要将信息以更友好的方式展现出来

优化显示1

优化显示2

  • 将一个json字符串中某个属性的值提取出来,用逗号拼接成一个新的字符串。这样的功能比较常用,所以我们将方法写到baseController.js
js
/** 提取数组中json某个属性,返回拼接字符串,以逗号分隔(使用箭头函数) */
$scope.json2String = (jsonStr, key) => {
    // 将json字符串转换为json对象(或者json数组)
    var json = JSON.parse(jsonStr);
    var resultArray = new Array();

    // 循环数组
    for (let i = 0; i < json.length; i++) {
        // 从数组中取一个元素(json对象)
        resultArray.push(json[i][key]);
    }

    return resultArray.join(", ");
};
  • 在type_template.html页面上使用该函数进行转换
html
<tr ng-repeat="entity in dataList">
	<td><input type="checkbox"></td>
	<td>&#123;&#123; entity.id &#125;&#125;</td>
	<td>&#123;&#123; entity.name &#125;&#125;</td>
	<td>&#123;&#123; json2String(entity.brandIds, 'text') &#125;&#125;</td>
	<td>&#123;&#123; json2String(entity.specIds, 'text') &#125;&#125;</td>
	<td>&#123;&#123; json2String(entity.customAttributeItems, 'text') &#125;&#125;</td>
	<td class="text-center">
		<button type="button" class="btn bg-olive btn-xs"
			data-toggle="modal" data-target="#editModal">修改</button>
	</td>
</tr>

新增模板-品牌下拉列表

在弹出窗口中有个品牌下拉列表,要求品牌是可以选择多个,这与之前的单选的下拉列表是不同的。实现这个功能,需要使用select2组件来完成

select2 组件效果

select2 组件使用相关配置

  • select2需要的数据格式

select2组件配置1

  • 相关属性

    • multiple:表示可多选
    • config:用于配置数据来源
    • select2-model:用于指定用户选择后提交的变量
  • type_template.html加入显示品牌下拉列表(静态):

    • 修改 type_template.html引入JS
    html
    <!-- select2组件 start -->
    <link rel="stylesheet" href="/plugins/select2/select2.css" />
    <link rel="stylesheet" href="/plugins/select2/select2-bootstrap.css" />
    <script src="/plugins/select2/select2.min.js"></script>
    <script src="/plugins/select2/angular-select2.js"></script>
    <!-- select2组件 end -->
    • 注:select2组件js文件必须放在最后,如果放在angular分层之后,组件将读取不到数据。因为html文件是从上往下顺序解析
    • 修改typeTemplateController.js,定义品牌列表数据
    js
    /** 品牌列表 */
    $scope.brandList = {data:[{id:1,text:'联想'},{id:2,text:'华为'},{id:3,text:'小米'}]};
    • 在type_template.html 用select2组件实现多选下拉框
    html
    <input  select2  select2-model="entity.brandIds"
            config="brandList" multiple placeholder="选择品牌(可多选)"
            class="form-control" type="text"/>
  • 最终实现效果如下:

select2组件配置12

显示品牌下拉列表-后端返回数据

查询品牌下拉数据-后端代码
  • 让下拉列表的数据从数据库中提取,修改后端pinyougou-manager-web工程的BrandController中增加查询所有品牌的方法
java
/**
 * 查询所有品牌(用于页面下拉框)
 *
 * @return 所有品牌的id与name
 */
@GetMapping("/selectBrandList")
public List<Map<String, Object>> selectBrandList() {
    try {
        // 调用服务层查询方法
        return brandService.findAllByIdAnName();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
  • 修改pinyougou-sellergoods-interface 的BrandService与pinyougou-sellergoods-service的BrandServiceImpl,增加查询方法
java
/**
 * 查询所有品牌(用于页面下拉框)
 *
 * @return 所有品牌的id与name
 */
List<Map<String, Object>> findAllByIdAnName();


/**
 * 查询所有品牌(用于页面下拉框)
 *
 * @return 所有品牌的id与name
 */
@Override
public List<Map<String, Object>> findAllByIdAnName() {
    try {
        // 调用数据访问层查询方法
        return brandMapper.findAllByIdAnName();
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • 在pinyougou-mapper工程,在BrandMapper中添加查询方法
java
/**
 * 查询所有品牌(用于页面下拉框)
 *
 * @return 所有品牌的id与name
 */
@Select("SELECT t.id,t.`name` AS 'text' FROM tb_brand t ORDER BY t.id ASC")
List<Map<String, Object>> findAllByIdAnName();
查询品牌下拉数据-前端代码
  • 修改pinyougou-manager-web的typeTemplateController.js,增加查询品牌请求方法
js
/** 定义查询品牌方法 */
$scope.findBrandList = function () {
    // 响应部分使用箭头函数,当函数参数只有一个时,可以省略()
    baseService.sendGet("/brand/selectBrandList").then(response => {
        // 组装成select2组件需要的格式
        $scope.brandList = {data : response.data};
    })
};
  • 修改type_template.html ,添加初始化查询品牌方法
html
<body class="hold-transition skin-red sidebar-mini"
      ng-app="pinyougou" ng-controller="typeTemplateController"
      ng-init="findBrandList();">

规格下拉列表

参照品牌下拉列表的实现步骤

查询规格下拉列表-前端代码
js
/** 定义查询规格方法 */
$scope.findSpecList = function () {
    baseService.sendGet("/specification/selectSpecList").then(response => {
        $scope.specList = {data : response.data};
    })
};
html
<body class="hold-transition skin-red sidebar-mini"
      ng-app="pinyougou" ng-controller="typeTemplateController"
      ng-init="findBrandList();findSpecList();">

<td>关联规格</td>
<td>
<input select2  select2-model="entity.specIds"
       config="specList" multiple placeholder="选择规格(可多选)"
       class="form-control" type="text"/>
</td>
查询规格下拉列表-后端代码
java
/**
 * 查询所有规格(用于页面下拉框)
 *
 * @return 所有规格的id与name
 */
@GetMapping("/selectSpecList")
public List<Map<String, Object>> selectSpecList() {
    try {
        // 调用服务层查询方法
        return specificationService.findAllByIdAnName();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
java
/**
 * 查询所有规格(用于页面下拉框)
 *
 * @return 所有规格的id与name
 */
List<Map<String, Object>> findAllByIdAnName();

/**
 * 查询所有规格(用于页面下拉框)
 *
 * @return 所有规格的id与name
 */
@Override
public List<Map<String, Object>> findAllByIdAnName() {
    try {
        // 调用数据访问层查询方法
        return specificationMapper.findAllByIdAnName();
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
java
/**
 * 查询所有规格(用于页面下拉框)
 *
 * @return 所有规格的id与name
 */
@Select("SELECT id,spec_name AS 'text' FROM `tb_specification` ORDER BY id ASC")
List<Map<String, Object>> findAllByIdAnName();

扩展属性

新增行

  • 在typeTemplateController.js中新增addTableRow()方法
js
/** 新增扩展属性行(使用箭头函数) */
$scope.addTableRow = () => {
    // 给customAttributeItems数组增加一个空的对象
    $scope.entity.customAttributeItems.push({});
};
  • 在type_template.html中的“新建”按钮,执行实体的初始化操作,给customAttributeItems初始化数组
html
<button type="button" class="btn btn-default" title="新建"
        data-toggle="modal" data-target="#editModal"
        ng-click="entity = {'customAttributeItems' : []}">
	<i class="fa fa-file-o"></i> 新建
</button>
  • 修改“新增扩展属性按钮”,绑定点击事件
html
<button type="button" class="btn btn-default" title="新增扩展属性"
        ng-click="addTableRow();">
    <i class="fa fa-file-o"></i> 新增扩展属性</button>
  • 循环表格
html
<tr ng-repeat="item in entity.customAttributeItems">
	<td>
        <input class="form-control" placeholder="属性名称"
               ng-model="item.text">
    </td>
	<td>
        <button type="button" class="btn btn-default" title="删除">
            <i class="fa fa-trash-o"></i> 删除</button>
    </td>
</tr>

删除行

  • 实现思路:在每一行将索引值传递给集合,在集合中删除。
  • 修改typeTemplateController.js新增删除行的方法
js
/* 根据传入的索引,删除扩展属性行 */
$scope.deleteTableRow = index => {
    $scope.entity.customAttributeItems.splice(index, 1);
};
  • 修改type_template.html每行的删除按钮,绑定点击事件
html
<tr ng-repeat="item in entity.customAttributeItems">
	<td>
        <input class="form-control" placeholder="属性名称"
               ng-model="item.text">
    </td>
	<td>
        <button type="button" class="btn btn-default" title="删除"
                ng-click="deleteTableRow($index);">
            <i class="fa fa-trash-o"></i> 删除</button>
    </td>
</tr>

说明:$index 用于获取ng-repeat指令循环中的索引。

新增模板

  • 修改type_template.html,绑定文本框
html
<tr>
    <td>模版名称</td>
    <td><input  class="form-control"
                placeholder="模版名称"
                ng-model="entity.name"></td>
</tr>
  • 保存按钮绑定点击事件
html
<button class="btn btn-success" data-dismiss="modal"
        aria-hidden="true"
        ng-click="saveOrUpdate();">保存</button>
  • 修改TypeTemplateController,增加save()方法
java
/**
 * 添加类型模版
 *
 * @param typeTemplate 模版实体类
 * @return 成功/失败标识
 */
@PostMapping("/save")
public boolean save(@RequestBody TypeTemplate typeTemplate) {
    try {
        // 调用服务层保存模版方法
        typeTemplateService.saveTypeTemplate(typeTemplate);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 修改TypeTemplateService接口与TypeTemplateServiceImpl现实类,增加saveTypeTemplate()方法
java
/**
 * 添加类型模版
 *
 * @param typeTemplate 模版实体类
 */
void saveTypeTemplate(TypeTemplate typeTemplate);

/**
 * 添加类型模版
 *
 * @param typeTemplate 模版实体类
 */
@Override
public void saveTypeTemplate(TypeTemplate typeTemplate) {
    try {
        // 调用通用mapper新增方法
        typeTemplateMapper.insertSelective(typeTemplate);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

修改模板

  • 修改typeTemplateController.js,回显当前行数据
js
/** 显示修改 */
$scope.show = function(entity){
    // 把entity对象解析成一个新的对象
    $scope.entity = JSON.parse(JSON.stringify(entity));
    // 把品牌json字符串转化成json数组
    $scope.entity.brandIds = JSON.parse($scope.entity.brandIds);
    // 把规格json字符串转化成json数组
    $scope.entity.specIds = JSON.parse($scope.entity.specIds);
    // 把扩展属性json字符串转化成json数组
    $scope.entity.customAttributeItems =
        JSON.parse($scope.entity.customAttributeItems);
};
  • 修改type_template.html,给修改按钮绑定点击事件
html
<button type="button" class="btn bg-olive btn-xs"
        data-toggle="modal" data-target="#editModal"
        ng-click="show(entity);">修改</button>
  • 修改TypeTemplateController,增加update()方法
java
/**
 * 修改类型模版
 *
 * @param typeTemplate 模版实体类
 * @return 成功/失败标识
 */
@PostMapping("/update")
public boolean update(@RequestBody TypeTemplate typeTemplate) {
    try {
        // 调用服务层修改方法
        typeTemplateService.updateTypeTemplate(typeTemplate);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 修改TypeTemplateService接口与TypeTemplateServiceImpl现实类,增加updateTypeTemplate()方法
java
/**
 * 修改类型模版
 *
 * @param typeTemplate 模版实体类
 */
void updateTypeTemplate(TypeTemplate typeTemplate);

/**
 * 修改类型模版
 *
 * @param typeTemplate 模版实体类
 */
@Override
public void updateTypeTemplate(TypeTemplate typeTemplate) {
    try {
        // 调用通用mapper修改方法
        typeTemplateMapper.updateByPrimaryKeySelective(typeTemplate);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

删除模板(全选勾选功能未完成)

  • 修改type_template.html,表格中的复选框绑定点击事件
html
<td><input type="checkbox"
           ng-click="updateSelection($event, entity.id)"></td>
  • 给删除按钮绑定点击事件
html
<button type="button" class="btn btn-default"
        title="删除" ng-click="delete();">
	<i class="fa fa-trash-o"></i> 删除
</button>
  • 修改TypeTemplateController,增加delete()方法
java
/**
 * 批量删除模版
 *
 * @param ids 模版id数组
 * @return 成功/失败标识
 */
@GetMapping("/delete")
public boolean delete(Long[] ids) {
    try {
        // 调用服务层批量删除方法
        typeTemplateService.deleteTypeTemplate(ids);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 修改TypeTemplateService接口与TypeTemplateServiceImpl现实类,增加deleteTypeTemplate()方法
java
/**
 * 批量删除模版
 *
 * @param ids 模版id数组
 */
void deleteTypeTemplate(Long[] ids);

/**
 * 批量删除模版
 *
 * @param ids 模版id数组
 */
@Override
public void deleteTypeTemplate(Long[] ids) {
    try {
        // 调用mapper自定义批量删除方法
        typeTemplateMapper.deleteAll(ids);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • 修改TypeTemplateMapper与配置文件,增加批量删除模版方法与sql
java
/**
 * 批量删除模版
 *
 * @param ids 模版id数组
 */
void deleteAll(@Param("ids") Long[] ids);
xml
<!-- 批量删除模版 -->
<delete id="deleteAll">
    delete from tb_type_template where id in
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

I know where dry desert ends, green grass grows · MooN