在设计物联网平台的时候,涉及到一个五级联动的问题,操作顺序如下:依次选择 所属省份,所属市县,所属地区,所属公司,设备名称。在使用Jquery时代,做好这个其实很容易,但是稍显繁琐,并且得很好的处理上一级下拉列表选中,然后触发下一级下拉列表加载数据的问题。相比使用Angularjs而言,代码量大而且繁琐,并且还得处理好顺序加载的问题。本节我们就看看Angularjs能给我们带来怎么样的体验吧。本文成文仓促,讲解难免会有谬误,还请见谅。
五级联动的设计
由于省份,市县,地区,公司四张表,在数据库中的设计基本一致,我们就专门为这四种下拉列表设计一个公共的模板出来。
1 2 3 4 5 6 7 8 9 10 | app.directive( 'sectorPart' , [ function () { return { restrict: 'AE' , replace: true , scope: { options: "=" }, template: '<select class="form-control" ng-options="item as item.name for item in options" name="state" style="width:220px;height:33px;">' + '<option value="">请选择</option>' + '</select>' }; }]); |
由于设备表中的数据字段类型不太一致,所以这里我们专门为设备设计一种下拉列表模板:
1 2 3 4 5 6 7 8 9 10 | app.directive( 'sectorMachine' , [ function () { return { restrict: 'AE' , replace: true , scope: { options: "=" }, template: '<select class="form-control" ng-options="item as item.machine_name for item in options" name="state" style="width:220px;height:33px;">' + '<option value="">请选择</option>' + '</select>' }; }]); |
好了,模板都设计完毕了,我们接下来将模板(Directive)应用到页面中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | < div class="searchcontainer"> < div class="row"> < div class="col-md-4"> < div class="input-group"> < span class="input-group-addon" id="basic-addon1">所属省份:</ span > < sector-part options="ProvinceData" ng-model="selectedProvince" ng-change="GetCityList()"></ sector-part > </ div > </ div > < div class="col-md-4"> < div class="input-group"> < span class="input-group-addon" id="basic-addon1">所属市县:</ span > < sector-part options="CityData" ng-model="selectedCity" ng-change="GetDistrictList()"></ sector-part > </ div > </ div > < div class="col-md-4"> < div class="input-group"> < span class="input-group-addon" id="basic-addon1">所属地区:</ span > < sector-part options="DistrictData" ng-model="selectedDistrict" ng-change="GetCompanyList()"></ sector-part > </ div > </ div > </ div > < div class="row"> < div class="col-md-4"> < div class="input-group"> < span class="input-group-addon" id="basic-addon1">所属公司:</ span > < sector-part options="CompanyData" ng-model="selectedCompany" ng-change="GetMachineList()"></ sector-part > </ div > </ div > < div class="col-md-4"> < div class="input-group"> < span class="input-group-addon" id="basic-addon1">设备名称:</ span > < sector-machine options="MachineData" ng-model="selectedMachine" ng-change="RefreshPage()"></ sector-machine > </ div > </ div > < div class="col-md-4"> < div class="input-group"> < button class="btn btn-success" ng-click="LoadControllerList()" style="height:33px;line-height:16px;width:315px;">点击加载</ button > </ div > </ div > </ div > </ div > |
从HTML模板中,我们可以看到以下几个关键点:options是我传入到下拉列表的数据,下拉列表会根据options的值来加载列表并显示;ng-model是我选中的项,一旦有列表被选中,就会将数据传递到这个model中;ng-change是下拉列表被选中的时候触发的事件。
最后让我们来看看Controller中的写法吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | app.controller( 'collectorController' , [ '$scope' , '$cookies' , '$timeout' , '$compile' , 'baseService' , 'collectorService' , 'uiGridConstants' , function ($scope, $cookies,$timeout,$compile, baseService, collectorService, uiGridConstants) { var self = this ; $scope.selectedProvince = null ; $scope.selectedCity = null ; $scope.selectedDistrict = null ; $scope.selectedCompany = null ; $scope.selectedMachine = null ; //省份绑定 collectorService.GetProvinceData().then( function (data) { var flag = data.data.success; if (flag) { $scope.ProvinceData = data.data.data; } }, null ); $scope.GetCityList = function () { var selectedProvinceId; if ($scope.selectedProvince != undefined) selectedProvinceId = $scope.selectedProvince.id; else return ; //市区绑定 collectorService.GetCityData(selectedProvinceId).then( function (data) { var flag = data.data.success; if (flag) { $scope.CityData = data.data.data; } }, null ); } $scope.GetDistrictList = function () { var selectedCityId; if ($scope.selectedCity != undefined) selectedCityId = $scope.selectedCity.id; else return ; //区县绑定 collectorService.GetDistrictData(selectedCityId).then( function (data) { var flag = data.data.success; if (flag) { $scope.DistrictData = data.data.data; } }, null ); } $scope.GetCompanyList = function () { var selectedDistrictId; if ($scope.selectedDistrict != undefined) else return ; //公司绑定 collectorService.GetCompanyData(selectedDistrictId).then( function (data) { var flag = data.data.success; if (flag) { $scope.CompanyData = data.data.data; } }, null ); } $scope.GetMachineList = function () { var selectedCompanyId; if ($scope.selectedCompany != undefined) selectedCompanyId = $scope.selectedCompany.id; else return ; //设备绑定 collectorService.GetMachineList(selectedCompanyId).then( function (data) { var flag = data.data.success; if (flag) { $scope.MachineData = data.data.data; } }, null ); } }]); |
看上去是不是挺简洁的。反正这些方法的触发都是根据ng-change来进行的。然后ng-model由于是mvvm模式,所以一旦选中项改变,会自动被传到controller中来。至于collectorService就是一个简单的获取数据的service而已,这里不是主要的,我简单的贴一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | app.service( 'collectorService' , [ '$http' , function ($http) { var provinceData = function () { return $http({ method: 'POST' , url: '/Process/GetLocationList?provinceid=-1&cityid=-1' }); } var cityData = function (provinceId) { return $http({ method: 'POST' , url: '/Process/GetLocationList?provinceid=' + provinceId + '&cityid=-1' }); } var districtData = function (cityId) { return $http({ method: 'POST' , url: '/Process/GetLocationList?provinceid=-1&cityid=' + cityId }); } var companyData = function (districtId) { return $http({ method: 'POST' , url: '/BaseData/GetSchoolByDistrictId?districtId=' + districtId }); } var machineList = function (companyId) { return $http({ method: 'POST' , url: '/Machine/GetMachineBy?companyId=' + companyId + '&isController=0' }); } return { GetProvinceData: provinceData, GetCityData: cityData, GetDistrictData: districtData, GetCompanyData: companyData, GetMachineList: machineList }; }]); |
好了,以上就是五级联动了。我们可以一级一级的点下去了。是不是很方便呢?操作图展示:
五级联动记忆恢复
在实际使用的时候,有的用户反映,五级联动选择很麻烦,需要一个一个的点,如果能记忆上次的选择项就好了。这样就可以不用费力的每次都点击了。基于此种用户需求,我决定利用cookie来记忆用户上次的选择内容,然后在下次浏览的时候,来载入之前的浏览记录。下面我们来分析一下如何能够做到自动的根据已有记录逐级加载下来列表并选中。
首先,我们需要将数据保存到cookie中。
其次,我们需要监视列表选项的变化,这里我们可以用$watchcollection方法。
最后,列表选项变化后,我们就需要依据选中内容,加载下级列表,所以这里我们需要多selectedProvice,selectedCity等变量赋值。
明白了这些步骤,下面我们进行改造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | app.controller( 'collectorController' , [ '$scope' , '$cookies' , '$timeout' , '$compile' , 'baseService' , 'collectorService' , 'uiGridConstants' , function ($scope, $cookies,$timeout,$compile, baseService, collectorService, uiGridConstants) { var self = this ; $scope.ProvinceData = null ; $scope.CityData = null ; $scope.DistrictData = null ; $scope.CompanyData = null ; $scope.MachineData = null ; $scope.RealTimeData = null ; $scope.HistoryData = null ; $scope.selectedProvince = null ; $scope.selectedCity = null ; $scope.selectedDistrict = null ; $scope.selectedCompany = null ; $scope.selectedMachine = null ; //监测省份的变化,如果发生了变化,则加载城市列表 $scope.$watchCollection( 'selectedProvince' , function (oldval, newval) { $scope.GetCityList(); }); //监测城市变化,如果发生了变化,则加载地区列表 $scope.$watchCollection( 'selectedCity' , function (oldval, newval) { $scope.GetDistrictList(); }); //监测地区变化,如果发生了变化,则加载公司列表 $scope.$watchCollection( 'selectedDistrict' , function (oldval, newval) { $scope.GetCompanyList(); }); //监测公司变化,如果发生了变化,则加载机器列表 $scope.$watchCollection( 'selectedCompany' , function (oldval, newval) { $scope.GetMachineList(); }); //省份绑定 collectorService.GetProvinceData().then( function (data) { var flag = data.data.success; if (flag) { $scope.ProvinceData = data.data.data; $scope.selectedProvince = baseService.getSelectedDataMapper($scope.ProvinceData, 'province' ); } }, null ); $scope.GetCityList = function () { var selectedProvinceId; if ($scope.selectedProvince != undefined) selectedProvinceId = $scope.selectedProvince.id; else return ; //市区绑定 collectorService.GetCityData(selectedProvinceId).then( function (data) { var flag = data.data.success; if (flag) { $scope.CityData = data.data.data; $scope.selectedCity = baseService.getSelectedDataMapper($scope.CityData, 'city' ); } }, null ); } $scope.GetDistrictList = function () { var selectedCityId; if ($scope.selectedCity != undefined) selectedCityId = $scope.selectedCity.id; else return ; //区县绑定 collectorService.GetDistrictData(selectedCityId).then( function (data) { var flag = data.data.success; if (flag) { $scope.DistrictData = data.data.data; $scope.selectedDistrict = baseService.getSelectedDataMapper($scope.DistrictData, 'district' ); } }, null ); } $scope.GetCompanyList = function () { var selectedDistrictId; if ($scope.selectedDistrict != undefined) selectedDistrictId = $scope.selectedDistrict.id; else return ; //公司绑定 collectorService.GetCompanyData(selectedDistrictId).then( function (data) { var flag = data.data.success; if (flag) { $scope.CompanyData = data.data.data; $scope.selectedCompany = baseService.getSelectedDataMapper($scope.CompanyData, 'company' ); } }, null ); } $scope.GetMachineList = function () { var selectedCompanyId; if ($scope.selectedCompany != undefined) selectedCompanyId = $scope.selectedCompany.id; else return ; //设备绑定 collectorService.GetMachineList(selectedCompanyId).then( function (data) { var flag = data.data.success; if (flag) { $scope.MachineData = data.data.data; $scope.selectedMachine = baseService.getSelectedDataMapper($scope.MachineData, 'machine' ); } }, null ); } //获取实时数据 $scope.GetRealTimeDataByMachine = function () { //将级联列表项放到cookie中,以便于之后的操作简易化 var expireDate = new Date(); expireDate.setDate(expireDate.getDate() + 7); delete $cookies[ 'frontselection' ]; var cookieData = JSON.stringify({ province: $scope.selectedProvince, city: $scope.selectedCity, district: $scope.selectedDistrict, company: $scope.selectedCompany, machine: $scope.selectedMachine }); $cookies.put( 'frontselection' , cookieData, { 'expires' : expireDate }); } }]); |
可以看出,我们在点击最后的加载按钮的时候,把cookie存储到了浏览器中。然后在页面进入的时候,我们先加载了省份列表,省份列表加载成功后,我们马上从cookie中拿出上次选中的省份,然后给selectedProvice赋值,这个赋值动作会马上触发$watchcollection中的动作,然后进行市县的绑定。一级一级的触发,直到最后。
有人会问,那个baseService是干什么的。由于我将$scope.selectedProvince,$scope.selectedCity等对象直接保存到了cookie中,在取出来的时候,我发现这些entity已经变了,entity内部会自动多出来一个_hashcode的属性,导致和原来的entity有差别,这就会导致angularjs无法通过验证,我必须通过baseService的方法,来还原出原来的selectedProvice等对象才行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | app.service( 'baseService' , [ '$cookies' , function ($cookies) { //获取比对数据并赋值 //type: province,city,district,company,machine var selectionMapper = function (sourceData, type) { //从cookie获取值 var collectorSelectionFromCookie = $cookies.get( 'frontselection' ); if (collectorSelectionFromCookie != undefined) { //如果cookie存在,则进行解析 collectorSelectionFromCookie = JSON.parse($cookies.get( 'frontselection' )); for ( var i = 0; i < sourceData.length; i++) { var current = sourceData[i]; if (type == "province" ) { if (current.id == collectorSelectionFromCookie.province.id) { return current; } } if (type == "city" ) { if (current.id == collectorSelectionFromCookie.city.id) { return current; } } if (type == "district" ) { if (current.id == collectorSelectionFromCookie.district.id) { return current; } } if (type == "company" ) { if (current.id == collectorSelectionFromCookie.company.id) { return current; } } if (type == "machine" ) { if (current.machine_id == collectorSelectionFromCookie.machine.machine_id) { return current; } } if (type == "monitor" ) { if (current.id == collectorSelectionFromCookie.monitor.id) { return current; } } } } return null ; } return { getSelectedDataMapper: selectionMapper }; }]); |
最后我们打开页面,看看加载的结果吧:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!