13、angular1组件之pass-word密码、single-select|both-select消长、on-off开关、simple-dialog多层弹窗(含插槽ng-transclude)、3种search-select搜索下拉、input-select树结构 、dir-tree树结构(3150行)
一、pass-word组件(自定义密码输入框,可预览) 使用场景:修改密码时,对密码进行多方位验证,并显示验证结果。 <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>passWord</title> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> </head> <body> <div ng-controller="thisCtrl"> <pass-word label-width="150" is-must-fill="true" label-text="原密码" this-type="password" placeholder="原密码" is-disabled="false" self-show='pastShow' is-past-input='true' self-data="oldPassword" verify="verify" ></pass-word> <pass-word label-width="150" is-must-fill="true" label-text="输入新密码" this-type="password" placeholder="输入新密码" is-disabled="false" is-first-input='true' key-of-reg-exp="simplePassword" self-data="params.newPassword" sibling-data="newPasswordSecond" one-tip="oneTip" many-tips="manyTips" self-show='selfShow' sibling-show='siblingShow' ></pass-word> <pass-word label-width="150" is-must-fill="true" label-text="再次输入新密码" this-type="password" placeholder="再次输入新密码" is-disabled="false" key-of-reg-exp="simplePassword" self-data="newPasswordSecond" sibling-data="params.newPassword" one-tip="oneTip" many-tips="manyTips" self-show='siblingShow' sibling-show='selfShow' ></pass-word> </div> </body> </html> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope) { $scope.params = {};//发给后台 $scope.pastShow = { isShowOneTip:false, isGreenOneTip:false, isShowManyTips:false, }; $scope.selfShow = { isShowOneTip:false, isGreenOneTip:false, isShowManyTips:false, }; $scope.siblingShow = { isShowOneTip:false, isGreenOneTip:false, isShowManyTips:false, }; $scope.oneTip = {//由当前页传至本组件 success: '自定义_输入符合要求!', empty: '自定义_该项不能为空!', different: '自定义_两次密码输入不一致!', formatError: '自定义_输入不符合要求,请输入以字母开头的4-24个字符!' }; $scope.manyTips= [ '密码要求:', '1.必须包含:数字', '2.必须包含:字母', '3.必须包含:小写字母', '4.必须包含:大写字母', '5.必须包含:下划线', ]; $scope.oldPassword= ''; $scope.params.newPassword=''; $scope.newPasswordSecond= ''; $scope.verify = function() { //把原密码告诉后台,根据后台判断结果,决定是否继续验证 return true } }); app.directive('passWord', function () { var html = ` <div style="display:flex;align-items:center"> <div ng-style="{width:(labelWidth||130)+'px'}" style="text-align:right;margin-right:10px;"> <span ng-show="isMustFill" style="color:red;">*</span> <label ng-bind="labelText||'密码'"></label> </div> <div style="padding:10px 0; position:relative;"> <input type="{{ thisType ? thisType : 'text' }}" ng-model="selfData" placeholder="{{placeholder||''}}" ng-disabled="isDisabled" ng-focus="focus()" ng-change="changeOrBlur()" ng-blur="changeOrBlur()" ng-trim="false" style="width:290px;height:30px" > <img src="{{servicePicture.eyeClose}}" ng-click="isShowEyeOpen=true;thisType='text'" ng-show="!isShowEyeOpen" style="width:14px;height:14px;position:absolute;right:10px;top:20px;cursor:pointer;padding:5px;" > <img src="{{servicePicture.eyeOpen}}" ng-click="isShowEyeOpen=false;thisType='password'" ng-show="isShowEyeOpen" style="width:14px;height:14px;position:absolute;right:10px;top:20px;cursor:pointer;padding:5px;" > </div> <div style="position:relative"> <div ng-show="selfShow.isShowOneTip"> <img src="{{selfShow.isGreenOneTip ? servicePicture.correct : servicePicture.error}}" style="width:14px;height:14px;position:relative;left:5px;"> <span ng-bind="singleTipText" ng-style="selfShow.isGreenOneTip?{ \'color\':\'green\'}:{\'color\':\'red\'}" style="position:relative;left:5px;"> ></span> </div> <div ng-show="selfShow.isShowManyTips && manyTips.length>0" style="position:absolute;top:0;display:flex"> <img src="{{servicePicture.triangle}}" style="width:14px;height:14px;position:relative;z-index:3;left:5px;" > <div style="width:200px;border:1px solid #DCDCDC;position:relative;top:-5px;left:3px;padding:5px;"> <div ng-repeat="li in manyTips" ng-bind="li"></div> </div> </div> </div> </div> `; return { restrict: 'E', template: html, scope: { //以下是16种配置项。这是输入框组件,适用于密码输入、普通输入(isPastInput/isFirstInput:true)、旁边提示和弹窗提示。 labelWidth: '@labelWidth',//标签宽度 labelText: '@labelText',//标签文字 placeholder: '@placeholder',//输入框内提示文字 thisType: '@thisType',//输入框类型(文字或密码) keyOfRegExp: '@keyOfRegExp',//会用到的正则 isMustFill: '=isMustFill',//该项是否必填 isDisabled: '=isDisabled',//该项是否禁用 isPastInput: '=isPastInput',//该项是不是原密码xxxxxxxxxxx isFirstInput: '=isFirstInput',//该项是不是第一次输入新密码xxxxxxxxxxxx selfData: '=selfData',//自身数据============ siblingData: '=siblingData',//兄弟数据=============== oneTip: '=oneTip',//单条提示 manyTips: '=manyTips',//多条提示 selfShow: '=selfShow',//自身的提示是否显示出来============ siblingShow: '=siblingShow',//兄弟的提示是否显示出来============= verify: '=verify',//verify是函数,但按照变量传进来更好xxxxxxxxxxx }, replace: true, controller: function ($scope, serviceRegExp, servicePicture) { $scope.servicePicture = servicePicture; $scope.inputChange = function (params, message) { var params = params; var config = { init: [false, false, false], focus: [false, false, true], empty: [true, false, false], formatError: [true, false, false], different: [true, false, false], wrong: [true, false, false], success: [true, true, false], }; $scope.selfShow.isShowOneTip = config[params][0]; $scope.selfShow.isGreenOneTip = config[params][1]; $scope.selfShow.isShowManyTips = config[params][2]; $scope.singleTipText = ($scope.oneTip && $scope.oneTip[params]) || message; }; $scope.focus = function () { if($scope.isPastInput){ $scope.inputChange('init'); return } if($scope.isFirstInput){ $scope.siblingData=''; $scope.siblingShow = { isShowOneTip:false, isGreenOneTip:false, isShowManyTips:false, }; } $scope.inputChange('focus'); }; $scope.changeOrBlur = function () { if ($scope.isMustFill && $scope.selfData=="") {//判断该项是否可以为空 $scope.inputChange('empty', '该项不能为空!'); return } if ($scope.keyOfRegExp && !(serviceRegExp[$scope.keyOfRegExp].test($scope.selfData))) { $scope.inputChange('formatError', '格式错误!'); return } if (!$scope.isPastInput && !$scope.isFirstInput && $scope.selfData != $scope.siblingData) { $scope.inputChange('different', '两次密码输入不一致!'); return } if(angular.isFunction($scope.verify) && !$scope.verify()){//验证原密码是否正确 $scope.inputChange('wrong', '原密码输入错误!'); return }; $scope.inputChange('success', '输入符合要求!'); }; }, link: function (scope, ele, attrs) { } }; }); app.factory('serviceRegExp', function () { return { simplePassword: /^[a-zA-Z][a-zA-Z0-9]{4,24}$/,//简单密码验证,以大小写字母开头,由大小写字母和数字构成的,共5-25位的密码 complexPassword: /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&*?_|~=+\-<>[{}/\'\":;,.\]*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&*?_|~=+\-<>[{}/\'\":;,.\]*]+$)(?![\d!@#$%^&*?_|~=+\-<>[{}/\'\":;,.\]*]+$)[a-zA-Z\d!@#$%^&*?_|~=+\-<>[{}/\'\":;,.\]*]{6,25}$/,//复杂密码验证,同时包含字母、数字、特殊符号如!@#$%^&*?_|~=+\-<>[{}/\'\":;,.\] ipFrom0To255: /^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$/,//ip地址验证,从0.0.0.0~255.255.255.255 portFrom0To65535:/^([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/, mac:/^[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}$/, mobilePhone: /^1[3456789]\d{9}$/,//手机号码验证,中国大陆 numberNotLimit: /^(0|[1-9][0-9]*)$/,//0或非0开头的数字 numberWithLimit: /^(0|[1-9][0-9]{3,4})$/,//0或非0开头的数字4-5位 emailNotLimit: /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,//邮箱验证 emailWithLimit: /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/,//邮箱验证 chineseCharacters: /^[\u4e00-\u9fa5]{0,}$/,//汉字验证 blankSpace: /^\s*$/ //零到多个空格 }; }); app.factory('servicePicture', function () { return { eyeClose: 'data:image/png;base64,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', eyeOpen: 'data:image/png;base64,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', correct: 'data:image/png;base64,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', error: 'data:image/png;base64,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', triangle: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAAAAXNSR0IArs4c6QAAAvpJREFUSA2tl7uLE1EUxmcmk6cJ2WTzQDBBBGEVbBR8oY3IglraiCCWVlaipa1iLVYW7oKVIuL6YC0s7f0T8jQmhAghD/L0+wZnPJPE3TvJXpi9Z07u+c059373zqyuHVCbTqd6qVR6diA4wMxisbhVKBSm+qrEarUaGQ6Hb8C5TtZKQJSYnEwmH8G5QBjb0sByuXxkPB7vgnHSIv39Y8gbVRtlnkBm3zHeBQsEAt4zRGbnAfuEhUjKBEKhkJZOp70BMWfXAHqLKyJhkUhES6VSlku5ZGR2B6APs7BYLObASFQCQmMPUOYWYKbMLB6Pa4lEQrr2LhkAHbCniHgko3Rdt0DRaFS6Ldv1RPkrs8GcvYTvrvQTxvkKh8PS7dgLdQhQGCVS/TeckTAMw7BWMhgMSrfLngNiP3JSqP6LcqTP59MymYzm9/ule872SQ/Vj/tvuM5Iv2maWjab3RfGGCdDqH9jNBrtYu7yEkb1MzOWq9IsIObsHEBU/7oMctSPhVBtOubsGFbuB2AuDUj1q8I4zgTsySyM6p8VrCrUBOyyHEz181q2caZdsw39Lcuy4gj7KgntdltrNpvS5ck2sJIPEfFTRnU6Ha1er2uYDulWsg0I9he0dhWLU5YR/X7fgnqdAkdglUolB2Gz/A0J5lajsLn1VJoD5GBsvXVk9BmlnpXB3Ho83vfbx4xxAemo1WqHBoPBO0A3eW83bj1myq24V5sDcjBgfmzHbfS3ZDChPAu5Jf/XXBq0B2GBhrlc7jb657aPPReo0Who3W5Xul32QiBHADbN5/P30T+WEcja0in1uqgtLHl2IA6QewC/AMyVwKJtqgTkAzCnN1Hya5iu858vqmTy3ztfGUgoZHUF3zPvYcZ4bzd51HkCEoCT/TQ+377AzNhA9s5hLJ2qNso/jvK5q47KmKU+lmwA5HO41+vxHXTK9rH3XLIMbrVaa5DPDqCXbL9LBrZTtcdr4jfktIlrRzVGaRwy5Ef7K+h19Y92+4mAWv9W/AHAizQaNQfgsAAAAABJRU5ErkJggg==' }; }); </script> 二、single-select和both-select <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>onlySelect</title> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> </head> <body> <div ng-controller="thisCtrl"> <div>一、此消彼长式</div> <div>使用场景:</div> <div>有上下两行数据,上行有很多备选项</div> <div>每点击上行的某项,则该项“会”消失并在下行出现</div> <div>每点击下行的某项,则该项“会”消失并在上行出现</div> <div style="margin-bottom:20px;"> <span ng-click="getDataUp()" style="border-radius:5px;padding:2px;background:gray;">点击获取选中数据</span> <span ng-bind="selectedDatasUp"></span> </div> <div style="width:1000px;"> <single-select form-datas-up="formDatasUp" current-datas-up="currentDatasUp" two-label-up="twoLabelUp"></single-select> </div> <div style="width:1000px;border:1px solid gray;margin:100px 0"></div> <div>二、此静彼长式</div> <div>使用场景:</div> <div>有上下两行数据,上行有很多备选项</div> <div>每点击上行的某项,则该项“不”消失并在下行出现,重复点击无效</div> <div>每点击下行的某项,则该项“会”消失,但对上行无影响</div> <div style="margin-bottom:20px;"> <span ng-click="getDataDown()" style="border-radius:5px;padding:2px;background:gray;">点击获取选中数据</span> <span ng-bind="selectedDatasDown"></span> </div> <div style="width:1000px;"> <both-select form-datas-down="formDatasDown" current-datas-down="currentDatasDown" two-label-down="twoLabelDown"></both-select> </div> </div> </body> </html> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope) { $scope.formDatasUp = [ { value: '0', label: '选项一' }, { value: '1', label: '选项二' }, { value: '2', label: '选项三' }, { value: '3', label: '选项四' }, { value: '4', label: '选项五' } ]; $scope.currentDatasUp = []; $scope.twoLabelUp = ['上面标签:','下面标签:']; $scope.getDataUp = function(){ $scope.selectedDatasUp=angular.copy($scope.currentDatasUp); }; //////////////////////////////////////////////////////////////////////////////////////// $scope.formDatasDown = [ { value: '0', label: '选项一' }, { value: '1', label: '选项二' }, { value: '2', label: '选项三' }, { value: '3', label: '选项四' }, { value: '4', label: '选项五' } ]; $scope.currentDatasDown = []; $scope.twoLabelDown = ['备选项:','被选项:']; $scope.getDataDown = function(){ $scope.selectedDatasDown=angular.copy($scope.currentDatasDown); }; }); app.directive('singleSelect', function () { var html = ` <div> <div style="width:100%;height:30px;line-height:30px;border:1px solid green;padding:5px;"> <span ng-bind="twoLabelUp[0]"></span> <span ng-repeat="upData in upDatas track by $index" ng-bind="upData.label" ng-click="clickUpUp(upData.value)" style="border-radius:5px;display:inline-block;margin-right:10px;padding:2px;background:gray"></span> </div> <div style="width:100%;height:30px;line-height:30px;border:1px solid red;padding:5px;"> <span ng-bind="twoLabelUp[1]"></span> <span ng-repeat="downData in downDatas track by $index" ng-bind="downData.label" ng-click="clickUPDown(downData.value)" style="border-radius:5px;display:inline-block;margin-right:10px;padding:2px;background:gray"></span> </div> </div> `; return { restrict: 'E', template: html, scope: { formDatasUp: '=formDatasUp', currentDatasUp: '=currentDatasUp', twoLabelUp: '=twoLabelUp', }, replace: true, controller: function ($scope) { $scope.upDatas = angular.copy($scope.formDatasUp); $scope.downDatas = []; $scope.clickUpUp = function (valueOuter) { angular.forEach($scope.upDatas,function(value,index){ if(valueOuter===value.value){ $scope.upDatas.splice(index,1); $scope.downDatas.push(value); $scope.currentDatasUp.push(value.value); console.log($scope.currentDatasUp) } }) }; $scope.clickUPDown = function (valueOuter) { angular.forEach($scope.downDatas,function(value,index){ if(valueOuter===value.value){ $scope.upDatas.push(value); $scope.downDatas.splice(index,1); var thisIndex=$scope.currentDatasUp.indexOf(value.value) if(thisIndex>-1){ $scope.currentDatasUp.splice(thisIndex,1); } console.log($scope.currentDatasUp) } }) }; }, link: function (scope, ele, attrs) { } }; }); app.directive('bothSelect', function () { var html = ` <div> <div style="width:100%;height:30px;line-height:30px;border:1px solid green;padding:5px;"> <span ng-bind="twoLabelDown[0]"></span> <span ng-repeat="upData in upDatasDown track by $index" ng-bind="upData.label" ng-click="clickDownUp(upData)" style="border-radius:5px;display:inline-block;margin-right:10px;padding:2px;background:gray"></span> </div> <div style="width:100%;height:30px;line-height:30px;border:1px solid red;padding:5px;"> <span ng-bind="twoLabelDown[1]"></span> <span ng-repeat="downData in downDatasDown track by $index" ng-bind="downData.label" ng-click="clickDownDown(downData,$index)" style="border-radius:5px;display:inline-block;margin-right:10px;padding:2px;background:gray"></span> </div> </div> `; return { restrict: 'E', template: html, scope: { formDatasDown: '=formDatasDown', currentDatasDown: '=currentDatasDown', twoLabelDown: '=twoLabelDown', }, replace: true, controller: function ($scope) { $scope.upDatasDown = angular.copy($scope.formDatasDown); $scope.downDatasDown = []; $scope.clickDownUp = function (data) { var thisIndex=$scope.currentDatasDown.indexOf(data.value) if(thisIndex>-1){ return }else{ $scope.downDatasDown.push(data); $scope.currentDatasDown.push(data.value); } }; $scope.clickDownDown = function (data,index) { $scope.downDatasDown.splice(index,1); var thisIndex=$scope.currentDatasDown.indexOf(data.value) if(thisIndex>-1){ $scope.currentDatasDown.splice(thisIndex,1); } }; }, link: function (scope, ele, attrs) { } }; }) </script> 三、on-off组件(真实逻辑,点击后,转圈、向后台发送请求、返回数据、给开关赋值、开关展示状态。点击后,立即改成相反状态,是不对的。) <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="https:cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> <style> table{ border-collapse: collapse; width:1000px; } table td,table th { padding: 5px; border: 1px solid #cbcbcb; } table thead { background-color: #e0e0e0; color: #000; text-align: left; } </style> </head> <body> <div ng-controller="thisCtrl"> <on-off this-switch="parentSwitch"> <span>{{parentSwitch.state?"总开关已开启":"总开关已关闭"}}。总开关关闭时,不能操作分开关!</span> </on-off> <table> <thead> <tr> <th>序号</th> <th>数据1</th> <th>数据2</th> <th>数据3</th> <th>开关1</th> <th>开关2</th> </tr> </thead> <tbody> <tr ng-repeat="item in tableDatas track by $index"> <td ng-bind="$index+1"></td> <td ng-bind="item.value1"></td> <td ng-bind="item.value2"></td> <td ng-bind="item.value3"></td> <td> <on-off parent-switch="parentSwitch" this-switch="item.value4"></on-off> </td> <td> <on-off parent-switch="parentSwitch" this-switch="item.value5"></on-off> </td> </tr> </tbody> </table> </div> </body> </html> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope) { //以下通过向后台请求,获取总开关的数据fromServerParent,经加工后为$scope.parentSwitch var fromServerParent={ isOn:true }; $scope.parentSwitch={ state:fromServerParent.isOn, callback:function(){ this.state=!this.state; } }; //以下通过向后台请求,获取分开关的数据fromServerData,经加工后为$scope.tableDatas var fromServerData=[ {value1:"数据1",value2:"数据2",value3:"数据3",isOn:true,isSwitch:true}, {value1:"数据1",value2:"数据2",value3:"数据3",isOn:true,isSwitch:true}, {value1:"数据1",value2:"数据2",value3:"数据3",isOn:true,isSwitch:true}, {value1:"数据1",value2:"数据2",value3:"数据3",isOn:true,isSwitch:true}, {value1:"数据1",value2:"数据2",value3:"数据3",isOn:true,isSwitch:true}, {value1:"数据1",value2:"数据2",value3:"数据3",isOn:true,isSwitch:true}, {value1:"数据1",value2:"数据2",value3:"数据3",isOn:true,isSwitch:true} ]; var tableDatas=[]; angular.forEach(fromServerData,function(value,key){ var item={}; item.value1=value.value1; item.value2=value.value2; item.value3=value.value3; item.value4={ state:value.isOn, callback:function(){ this.state=!this.state; } }; item.value5={ state:value.isSwitch, callback:function(){ this.state=!this.state; } }; tableDatas.push(item) }) $scope.tableDatas=tableDatas; }); app.directive('onOff', function () {//(含插槽) var html = ` <div style="display:flex;align-content:center;padding-bottom:10px;"> <img ng-src="{{thisSwitch.state?onOff.yes:onOff.no}}" width="65px" height="30px" ng-click="clickSwitch()"/> <span ng-transclude style="height:30px;line-height:30px;padding-left:10px;"></span> </div> `; return { restrict: 'E', template: html, scope: { thisSwitch: '=thisSwitch', parentSwitch: '=parentSwitch', }, transclude:true, replace: false, controller: function ($scope,onOff) { $scope.onOff = onOff; $scope.clickSwitch = function(){ //以下总开关关闭时,不能操作分开关! if($scope.parentSwitch && !$scope.parentSwitch.state){ return '弹窗:父级开关阻止本级开关改变' } if($scope.thisSwitch.isForbid ){ return '弹窗:本级数据的其它条件阻止本级开关改变' } $scope.thisSwitch.callback() }; }, link: function (scope, ele, attr) { } }; }); app.factory('onOff', function () { return { yes: 'data:image/png;base64,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', no: 'data:image/png;base64,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', }; }) </script> 四、simple-dialog 1、不可拖拽(含插槽) <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>三层不可拖拽弹窗之angular1.6.2版</title> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> <style> .simpleDialog { position: fixed; width: 100%; height: 100%; top: 0; left: 0; display: flex; justify-content: center; align-items: center; } .simpleDialog .mask { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background: black; opacity: 0.5; } .simpleDialog .content { position: fixed; background: white; opacity: 1; display: flex; flex-direction: column; } .simpleDialog .content .title { display: flex; background: blue; color: white; padding: 10px; cursor: pointer; user-select: none; } .simpleDialog .content .conform { display: flex; justify-content: center; padding: 10px; background: blue; } </style> </head> <body> <div ng-controller="thisCtrl"> <button ng-click="clickButton()" style="margin-top: 30px; z-index: 250"> 点击-出现-弹窗 </button> <simple-dialog required-data="requiredDataOut"> 1111111 <simple-dialog required-data="requiredDataMid"> 2222222 <simple-dialog required-data="requiredDataIn"> 33333333 </simple-dialog> </simple-dialog> </simple-dialog> </div> </body> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope) { $scope.clickButton = function () { $scope.requiredDataOut.isShow = true; }; $scope.requiredDataOut = { isShow : false, width : '900px', height : '600px', openFn : function () { $scope.requiredDataMid.isShow = true; } }; $scope.requiredDataMid = { isShow : false, width : '600px', height : '400px', openFn : function () { $scope.requiredDataIn.isShow = true; }, }; $scope.requiredDataIn = { isShow : false, width : '300px', height : '200px', }; // 一层弹窗可以如下使用 // $scope.requiredDataOut = { // isShow : false, // }; // $scope.clickButton = function () { // $scope.requiredDataOut.isShow = true; // }; }); app.directive('simpleDialog', function () { var html = ` <div class="simpleDialog" ng-show="requiredData.isShow"> <div class="mask" ng-show="requiredData.isShow"></div> <div class="content" ng-show="requiredData.isShow"> <div class="title"> <span>系统消息</span> </div> <div ng-transclude ng-style="{width:requiredData.width||'800px',height:requiredData.height||'400px'}"></div> <div class="conform"> <button ng-click="close()">关闭</button> <button ng-click="open()">打开</button> </div> <div> </div> `; return { restrict: 'E', template: html, transclude: true, scope: { requiredData: '=' }, controller: function ($scope) { $scope.close = function () { $scope.requiredData.isShow = false; if($scope.requiredData.closeFn)$scope.requiredData.closeFn(); }; $scope.open = function () { if($scope.requiredData.openFn)$scope.requiredData.openFn(); }; } }; }); </script> </html> 2、可拖拽(含插槽) <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>三层可拖拽弹窗之angular1.6.2版</title> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> <style> .simpleDialog { position: fixed; width: 100%; height: 100%; top: 0; left: 0; display: flex; justify-content: center; align-items: center; } .simpleDialog .mask { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background: black; opacity: 0.5; } .simpleDialog .content { position: fixed; background: white; opacity: 1; display: flex; flex-direction: column; } .simpleDialog .content .title { display: flex; background: blue; color: white; padding: 10px; cursor: pointer; user-select: none; } .simpleDialog .content .conform { display: flex; justify-content: center; padding: 10px; background: blue; } </style> </head> <body> <div ng-controller="thisCtrl"> <button ng-click="clickButton()" style="margin-top: 30px; z-index: 250"> 点击-出现-弹窗 </button> <simple-dialog required-data="requiredDataOut"> 1111111 <simple-dialog required-data="requiredDataMid"> 2222222 <simple-dialog required-data="requiredDataIn"> </simple-dialog> </simple-dialog> </simple-dialog> </div> </body> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope, drag) { $scope.clickButton = function () { $scope.requiredDataOut.isShow = true; drag($scope.requiredDataOut.titleId,$scope.requiredDataOut.contentId); }; $scope.requiredDataOut = { isShow : false, width : '900px', height : '600px', titleId : "titleId1", contentId : "contentId1", openFn : function () { $scope.requiredDataMid.isShow = true; drag($scope.requiredDataMid.titleId,$scope.requiredDataMid.contentId); } }; $scope.requiredDataMid = { isShow : false, width : '600px', height : '400px', titleId : "titleId2", contentId : "contentId2", openFn : function () { $scope.requiredDataIn.isShow = true; drag($scope.requiredDataIn.titleId,$scope.requiredDataIn.contentId); }, }; $scope.requiredDataIn = { isShow : false, width : '300px', height : '200px', titleId : "titleId3", contentId : "contentId3", }; // 一层弹窗可以如下使用 // $scope.requiredDataOut = { // isShow : false, // }; // $scope.clickButton = function () { // $scope.requiredDataOut.isShow = true, // drag(); // }; }); app.directive('simpleDialog', function () { var html = ` <div class="simpleDialog" ng-show="requiredData.isShow"> <div class="mask" ng-show="requiredData.isShow"></div> <div class="content" ng-show="requiredData.isShow" id="{{requiredData.contentId||'contentId'}}"> <div class="title" id="{{requiredData.titleId||'titleId'}}"> <span>系统消息</span> </div> <div ng-transclude ng-style="{width:requiredData.width||'800px',height:requiredData.height||'400px'}">后备内容</div> <div class="conform"> <button ng-click="close()">关闭</button> <button ng-click="open()">打开</button> </div> <div> </div> `; return { restrict: 'E', template: html, transclude: true, scope: { requiredData: '=' }, controller: function ($scope) { $scope.close = function () { $scope.requiredData.isShow = false; angular .element( document.getElementById($scope.requiredData.contentId||'contentId') ) .css({ top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }); if($scope.requiredData.closeFn)$scope.requiredData.closeFn(); }; $scope.open = function () { if($scope.requiredData.openFn)$scope.requiredData.openFn(); }; } }; }); app.factory('drag', function () { return function (wholeTitleId, wholeContentId) { var wholeTitleId = wholeTitleId||'titleId'; var wholeContentId = wholeContentId||'contentId'; var oDiv = document.getElementById(wholeContentId); oDiv.onmousedown = down; function processThis(fn, nowThis) { return function (event) { fn.call(nowThis, event); }; } function down(event) { event = event || window.event; if (event.target.id != wholeTitleId) return; this.initOffsetLeft = this.offsetLeft; this.initOffsetTop = this.offsetTop; this.initClientX = event.clientX; this.initClientY = event.clientY; this.maxOffsetWidth = (document.documentElement.clientWidth || document.body.clientWidth) - this.offsetWidth; this.maxOffsetHeight = (document.documentElement.clientHeight || document.body.clientHeight) - this.offsetHeight; if (this.setCapture) { this.setCapture(); this.onmousemove = processThis(move, this); this.onmouseup = processThis(up, this); } else { document.onmousemove = processThis(move, this); document.onmouseup = processThis(up, this); } } function move(event) { var nowLeft = this.initOffsetLeft + (event.clientX - this.initClientX); var nowTop = this.initOffsetTop + (event.clientY - this.initClientY); //此处可以根据实际需要,限制拖动边界 this.style.left = nowLeft + 'px'; this.style.top = nowTop + 'px'; } function up() { if (this.releaseCapture) { this.releaseCapture(); this.onmousemove = null; this.onmouseup = null; } else { document.onmousemove = null; document.onmouseup = null; } } }; }); </script> </html> 五、3种searchSelect搜索下拉 1、searchSelectFull <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>searchSelectFull</title> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> <style> .outer-div{ display: flex; flex-direction: column; flex-wrap: wrap; align-items: top; border: 1px solid #7b8ea5; border-radius: 6px; cursor: pointer; width: 70%; user-select: none; } .select-div{ height: 120px; padding: 5px 0 0 5px; display: flex; justify-content: flex-start; flex-wrap: wrap; overflow-y: auto; } .item-summary{ border: 1px solid #dddddd; padding: 5px 10px; background: #fafafa; margin-right: 5px; margin-bottom: 5px; border-radius: 4px; height:24px; color:#000; } .option-all{ position: absolute; border: 2px solid #dddddd; width:444px; background:#ffffff; z-index: 10; padding:6px } .option-div{ padding: 5px 10px; background: #fafafa; margin-right: 5px; margin-bottom: 5px; border-radius: 4px; text-align: left; } .option-div:hover { background: #cccccc !important; } .yes-selected{ background:#237eff; border-radius:4px; color:#ffffff } .no-selected{ background:#ffffff; border-radius:0; border-bottom:1px solid #e7e7e7; color:#000000 } </style> </head> <body> <div ng-controller="thisCtrl"> <button ng-click="getAllSelectedOptions()">点击获取被选中项</button> <duv>{{result}}</duv> <search-select-full protocol-type="protocolType"></search-select-full> </div> </body> </html> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope) { $scope.protocolType = { "yesOptions": [ "c1222", "cnip", "dnp3", "dns", "doip", "egd", "enip", "enip-io", "fins", "ftp", "ftp-data", "ge-srtp", "goose", "hartip", "http", "iec104", "knxip", "mms", "modbus", "opcda", "opcua", "pn-dcp", "pn-mrrt", "pn-ptcp", "pop3", "rdp", "s7", "s7-plus", "sercos", "smb", "smb2", "smtp", "sv", "telnet" ], "allOptions": [ { "server": "c1222", "client": "c1222" }, { "server": "cnip", "client": "cnip" }, { "server": "dnp3", "client": "dnp3" }, { "server": "dns", "client": "dns" }, { "server": "doip", "client": "doip" }, { "server": "egd", "client": "egd" }, { "server": "enip", "client": "enip" }, { "server": "enip-io", "client": "enip-io" }, { "server": "fins", "client": "fins" }, { "server": "ftp", "client": "ftp" }, { "server": "ftp-data", "client": "ftp-data" }, { "server": "ge-srtp", "client": "ge-srtp" }, { "server": "goose", "client": "goose" }, { "server": "hartip", "client": "hartip" }, { "server": "http", "client": "http" }, { "server": "iec104", "client": "iec104" }, { "server": "knxip", "client": "knxip" }, { "server": "mms", "client": "mms" }, { "server": "modbus", "client": "modbus" }, { "server": "opcda", "client": "opcda" }, { "server": "opcua", "client": "opcua" }, { "server": "pn-dcp", "client": "pn-dcp" }, { "server": "pn-mrrt", "client": "pn-mrrt" }, { "server": "pn-ptcp", "client": "pn-ptcp" }, { "server": "pop3", "client": "pop3" }, { "server": "rdp", "client": "rdp" }, { "server": "s7", "client": "s7" }, { "server": "s7-plus", "client": "s7-plus" }, { "server": "sercos", "client": "sercos" }, { "server": "smb", "client": "smb" }, { "server": "smb2", "client": "smb2" }, { "server": "smtp", "client": "smtp" }, { "server": "sv", "client": "sv" }, { "server": "telnet", "client": "telnet" } ], "isFull": true } $scope.getAllSelectedOptions = function(){ var values = $scope.protocolType.getValues(); $scope.result = JSON.stringify(values) }; }); app.directive('searchSelectFull', function () { var html = ` <div class="outer-div" tabindex="-1" ng-blur="getBlurOut()"> <!-- 以下是select --> <div style="display: flex;" ng-click="getOptions($event)"> <div class="select-div"> <div class="item-summary">已选择{{yesOptions.length}}条</div> <div ng-repeat="client in yesOptions track by $index" class="item-summary"> <span ng-bind="client" style="position:relative;top:-7px"></span> <img ng-click="clickFork(yesOptions,client)" class="onlyQC" src="{{checkImg.fork}}" width="24px" height="24px" /> </div> <div class="item-summary" ng-show="yesOptions.length>20">已选择{{yesOptions.length}}条</div> </div> </div> <!-- 以下是option --> <div style="position: relative" ng-show="isShowOptions"> <div class="option-all"> <div> <input type="text" class="form-control" style="width:300px;height:44px;border-radius:4px;margin:4px 0" ng-focus="getFocusInput()" ng-blur="getBlurInput()" ng-change="changeInputValue()" ng-model="selectedOption"/> <span style="padding-bottom: 5px;display:inline-block;width:112px;" ng-style="{'visibility': isShowSelectAll? 'visible':'hidden'}"> <img ng-click="selectAll()" src="{{isSelectAll?checkImg.yes:checkImg.no}}" width="16px" height="16px"/> <span>{{textSelectAll}}</span> </span> </div> <div style="max-height: 360px;overflow: auto;padding-left: 6px;"> <div ng-repeat="option in filterOptions track by $index" class="option-div" ng-click="clickOptions(option)" ng-class="yesOptions.indexOf(option.client)>-1?'yes-selected':'no-selected'"> <span ng-bind="option.client" style="height:34px;line-height:34px;display: inline-block;"></span> </div> </div> </div> </div> </div> `; return { restrict: 'E', template: html, scope: { protocolType: '=protocolType',//所有选项 }, replace: true, controller: function ($scope,$timeout,checkImg) { $scope.checkImg = checkImg; $scope.isSelectAll = true; $scope.isShowSelectAll = true; $scope.isFocusInput = false; $scope.isShowOptions = false; $scope.isClickOptions = false; $scope.isClickSelectAll = false; $scope.yesOptions = $scope.protocolType.yesOptions; $scope.allOptions = $scope.protocolType.allOptions; $scope.filterOptions = angular.copy($scope.allOptions); $scope.textSelectAll = "全部选择"; if($scope.isSelectAll){ $scope.textSelectAll = "已选择"+$scope.yesOptions.length+"条"; } $scope.clickFork = function (yesOptions,client) { angular.forEach(yesOptions,function(option,index){ if(client===option){ yesOptions.splice(index,1); $scope.textSelectAll = "已选择"+$scope.yesOptions.length+"条"; } }) }; $scope.selectAll = function () { $scope.isClickSelectAll = true; $scope.yesOptions = []; $scope.isSelectAll = !$scope.isSelectAll; if($scope.isSelectAll){ angular.forEach($scope.allOptions,function(option){ $scope.yesOptions.push(option.client) }) $scope.textSelectAll = "已选择"+$scope.yesOptions.length+"条"; }else{ $scope.textSelectAll = "全部选择"; } $timeout(function(){ $scope.isClickSelectAll = false; },300) }; $scope.getOptions = function (event) { if(event.target.className === "onlyQC")return; $scope.isShowOptions = !$scope.isShowOptions; }; //组件失焦时,执行下面函数。想一想,什么时候组件聚焦? $scope.getBlurOut = function () { $timeout(function(){ if(!$scope.isFocusInput){ $scope.isShowOptions = false; } }) }; $scope.getFocusInput = function () { $scope.isFocusInput = true; }; //输入框失焦时,执行下面函数 $scope.getBlurInput = function () { $scope.isFocusInput = false; $timeout(function(){ if(!$scope.isClickOptions && !$scope.isClickSelectAll){ $scope.isShowOptions = false; } },200) }; $scope.clickOptions = function (option) { var flag = true; $scope.isClickOptions = true; angular.forEach($scope.yesOptions,function(client){ if(option.client===client){ flag = false; $scope.yesOptions.splice($scope.yesOptions.indexOf(client),1); } }); if(flag){ $scope.yesOptions.push(option.client); } $scope.textSelectAll = "已选择"+$scope.yesOptions.length+"条"; $timeout(function(){ $scope.isClickOptions = false; },300) }; $scope.changeInputValue = function () { if($scope.selectedOption){ $scope.isShowSelectAll = false; $scope.filterOptions = []; angular.forEach($scope.allOptions,function(option){ if(option.client.indexOf($scope.selectedOption) != -1){ $scope.filterOptions.push(option); } }); }else{ $scope.isShowSelectAll = true; $scope.filterOptions = angular.copy($scope.allOptions); } }; $scope.protocolType.getValues = function () { var client = []; var server = []; var clientNo = []; var serverNo = []; var obj; angular.forEach($scope.allOptions,function(option){ if($scope.yesOptions.indexOf(option.client) != -1){ client.push(option.client) server.push(option.server) }else{ clientNo.push(option.client) serverNo.push(option.server) } }); if($scope.isSelectAll){ obj = { is_all:1, client:clientNo, server:serverNo, } }else{ obj = { is_all:0, client:client, server:server, } } return obj }; }, link: function (scope, ele, attrs) { } }; }); app.factory('checkImg', function () { return { fork: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAWCAYAAAArdgcFAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEBSURBVEhL7ZKxSwMxFMb9/5dwS0T0DocbhBbhhg4Hwg0OglC5QpGDDsJZSgsKD26oZPlsaCPpu5dUaF2kw2954f2+kC8Xxhj8FWe5yD+Vr5oxmsW6f0Yt6kkL4nNGUN4+5tBKQSUpHt68AKpxf7mZb870sI4GyPJlhdSKHS7AEzvyJ+rv7wjcnFAP9Z5EJRpXTKyyEjOS9rdE3lwI8DkgtkQLDQbcjA6KLXE5TVFcM7GFlxwgLA+JHb8IkOVdgxEXZwXKO15yimou7O+Q5Z/PyBNP8lMe70CjeGW7HuFnea9wawN6v8IFaAxePrx5n3ihHaH7EuZmDaJjCj0BZ7mAwTfR6GSuYmrILwAAAABJRU5ErkJggg==', yes: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAA+UlEQVQoFZWSMU4DMRBF/584G7QSRcIxuAZKEykNEiUVHVTQRaKh4AIcgAvQpkukVDlBOAYNSGSlXXuwpViyYYFdS9aMZ/6bsezh5HZ3T2KhqkfosEhWqnjkyd1u3xWKdQMsfaEAB0Zilf8swfdU0w0klmpGpz1BvpbHcklbPf8Okts0CfJtWBTz/Yc++Jc8S3PZVQfKGwiuvMD6XYsMzm1dT/1jXKdQ8E0asHRrAzOzbC6UGINWHPQp1UQ/6wjF2LpmJSKfhti4Bi8+lhWP4I+gAqV1uqSi8j9WRuF3m3eMWVUJBeKxzUoYn7bEX7HDyPmB7QEHbRjyL+/+VnuXDUFOAAAAAElFTkSuQmCC', no: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAAbklEQVQoFWM8c+ZMLQMDQxUQcwAxMeAHUFEbC5CoYmNj02ZmZn5FjK6/f/+K/fr16ypIIwdIk7a29hdiNF69ehWkjIOJGMXY1IxqxBYqULEhFDiglPMDlIygKQKPryBSILUgPSCNbaC0B6RJSuQAbowizhJuOsAAAAAASUVORK5CYII=' }; }); </script> 2、searchSelectMid <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>searchSelectMid</title> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> <style> .outer-div{ width:210px; height:36px; cursor: pointer; } .select-div{ border: 1px solid #dddddd; padding: 5px 10px; background: #fafafa; margin-right: 5px; border-radius: 4px; height:24px; color:#000; } .option-both{ position: absolute; border: 2px solid #dddddd; width:444px; background:#ffffff; z-index: 10; padding:6px } .option-filter{ padding: 5px 10px; background: #fafafa; margin-right: 5px; margin-bottom: 5px; border-radius: 4px; text-align: left; } .option-filter:hover { background: #cccccc !important; } .option-summary{ max-height: 360px; padding: 4px; display: flex; justify-content: flex-start; flex-wrap: wrap; overflow-y: auto; } .yes-selected{ background:#237eff; border-radius:4px; color:#ffffff } .no-selected{ background:#ffffff; border-radius:0; border-bottom:1px solid #e7e7e7; color:#000000 } </style> </head> <body> <div ng-controller="thisCtrl"> <button ng-click="getAllSelectedOptions()">点击获取被选中项</button> <duv>{{result}}</duv> <search-select-mid protocol-type="protocolType"></search-select-mid> </div> </body> </html> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope) { $scope.protocolType = { "yesOptions": [ "c1222", "cnip", "dnp3", "dns", "doip", "egd", "enip", "enip-io", "fins", "ftp", "ftp-data", "ge-srtp", "goose", "hartip", "http", "iec104", "knxip", "mms", "modbus", "opcda", "opcua", "pn-dcp", "pn-mrrt", "pn-ptcp", "pop3", "rdp", "s7", "s7-plus", "sercos", "smb", "smb2", "smtp", "sv", "telnet" ], "allOptions": [ { "server": "c1222", "client": "c1222" }, { "server": "cnip", "client": "cnip" }, { "server": "dnp3", "client": "dnp3" }, { "server": "dns", "client": "dns" }, { "server": "doip", "client": "doip" }, { "server": "egd", "client": "egd" }, { "server": "enip", "client": "enip" }, { "server": "enip-io", "client": "enip-io" }, { "server": "fins", "client": "fins" }, { "server": "ftp", "client": "ftp" }, { "server": "ftp-data", "client": "ftp-data" }, { "server": "ge-srtp", "client": "ge-srtp" }, { "server": "goose", "client": "goose" }, { "server": "hartip", "client": "hartip" }, { "server": "http", "client": "http" }, { "server": "iec104", "client": "iec104" }, { "server": "knxip", "client": "knxip" }, { "server": "mms", "client": "mms" }, { "server": "modbus", "client": "modbus" }, { "server": "opcda", "client": "opcda" }, { "server": "opcua", "client": "opcua" }, { "server": "pn-dcp", "client": "pn-dcp" }, { "server": "pn-mrrt", "client": "pn-mrrt" }, { "server": "pn-ptcp", "client": "pn-ptcp" }, { "server": "pop3", "client": "pop3" }, { "server": "rdp", "client": "rdp" }, { "server": "s7", "client": "s7" }, { "server": "s7-plus", "client": "s7-plus" }, { "server": "sercos", "client": "sercos" }, { "server": "smb", "client": "smb" }, { "server": "smb2", "client": "smb2" }, { "server": "smtp", "client": "smtp" }, { "server": "sv", "client": "sv" }, { "server": "telnet", "client": "telnet" } ], "isFull": true } $scope.getAllSelectedOptions = function(){ var values = $scope.protocolType.getValues(); $scope.result = JSON.stringify(values) }; }); app.directive('searchSelectMid', function () { var html = ` <div tabindex="-1" class="outer-div" ng-blur="outerBlur()" ng-click="outerFocus()" ng-mouseleave="outerLeave()" style=" width:210px; height:36px; cursor: pointer;padding:0"> <!-- 以下是select --> <div ng-mouseenter="enterSelect()" ng-click="clickSelect()" style="padding:4px 0 0 10px;height:34px;" class="select-div"> <span ng-show="yesOptions[0]" ng-bind="yesOptions[0]"></span> <span ng-show="yesOptions.length>1" ng-bind="' ......'"></span> <span ng-show="yesOptions.length==0"> <span style="color:#87a6c9;">请单选或多选或滤选</span> </span> </div> <!-- 以下是option --> <div style="position: relative;" ng-show="isShowOptions||isShowSummary"> <div style="position: absolute;border: 1px solid #818080;width:300px;background:#ffffff;z-index: 10;padding:6px" ng-show="!isShowSummary"> <div> <input type="text" style="width:156px;height:32px;border-radius:4px;margin:4px 0;padding-left: 8px;" placeholder="请输入过滤条件" ng-change="changeInputValue()" ng-model="selectedOption" /> <span style="padding-bottom: 5px;display:inline-block;width:112px;" ng-style="{'visibility': isShowSelectAll? 'visible':'hidden'}"> <img ng-click="selectAll()" src="{{isSelectAll?checkImg.yes:checkImg.no}}" width="16px" height="16px"/> <span>{{textSelectAll}}</span> </span> </div> <div style="max-height: 360px;overflow: auto;" class="youSelect-option"> <div ng-repeat="option in filterOptions track by $index" style=" padding: 2px 8px; background: #fafafa; margin-right: 5px; margin-bottom: 1px; text-align: left; " ng-click="clickOptions(option)" ng-style="yesOptions.indexOf(option.client)>-1?{'background':'#237eff','color':'#ffffff'}:{'background':'#ffffff','color':'#495057'}"> <span ng-bind="option.client" style="height:24px;line-height:24px;display: inline-block;"></span> <i class="fa" style="float:right;position: relative;top:5px" ng-show="yesOptions.indexOf(option.client)>-1"></i> </div> </div> </div> <div style="position: absolute;border: 1px solid #818080;width:300px;background:#ffffff;z-index: 10;padding:6px;box-shadow: 10px 10px 5px #888888;" ng-show="isShowSummary && yesOptions.length>0"> <div style=" max-height: 360px; padding: 4px; display: flex; justify-content: flex-start; flex-wrap: wrap; overflow-y: auto; "> <span style="position:relative;top:4px;">{{'已选'+yesOptions.length+'条 '}}</span> <span ng-repeat="client in yesOptions track by $index" style=" padding: 4px; background: #fafafa; margin-right: 5px; " ng-bind="client" style="color: #000" ></span> </div> </div> </div> </div> `; return { restrict: 'E', template: html, scope: { protocolType: '=protocolType',//所有选项 }, replace: true, controller: function ($scope,$timeout,checkImg) { $scope.checkImg = checkImg; $scope.isShowSummary = false; //是否显示已选项汇总 $scope.isShowSelectAll = true; //是否显示全选 $scope.isShowOptions = false; //是否显示下拉项 $scope.isFocus = false; $scope.isSelectAll = $scope.protocolType.isFull||false; //是否全选 $scope.yesOptions = angular.copy($scope.protocolType.yesOptions); $scope.allOptions = angular.copy($scope.protocolType.allOptions); $scope.filterOptions = angular.copy($scope.allOptions); $scope.textSelectAll = "全部选择"; if($scope.isSelectAll){ $scope.textSelectAll = "已选择"+$scope.yesOptions.length+"条"; } $scope.selectAll = function () { $scope.yesOptions = []; $scope.isSelectAll = !$scope.isSelectAll; if($scope.isSelectAll){ angular.forEach($scope.allOptions,function(option){ $scope.yesOptions.push(option.client) }) $scope.textSelectAll = "已选择"+$scope.yesOptions.length+"条"; }else{ $scope.textSelectAll = "全部选择"; } }; $scope.enterSelect = function () { if($scope.isShowOptions){ $scope.isShowSummary = false; }else{ $scope.isShowSummary = true; } }; $scope.clickSelect = function () { $scope.isShowSummary = false; $scope.isShowOptions = !$scope.isShowOptions; }; $scope.outerLeave = function () { $scope.isShowSummary = false; }; $scope.outerFocus = function () { $scope.isFocus = true; $timeout(function(){ $scope.isFocus = false; },200) }; $scope.outerBlur = function () { $timeout(function(){ if(!$scope.isFocus){//域外失焦 $scope.isShowSummary = false; $scope.isShowOptions = false; } },150) }; $scope.clickOptions = function (option) { var flag = true; angular.forEach($scope.yesOptions,function(client){ if(option.client===client){ flag = false; $scope.yesOptions.splice($scope.yesOptions.indexOf(client),1); } }); if(flag){ $scope.yesOptions.push(option.client); } $scope.textSelectAll = "已选择"+$scope.yesOptions.length+"条"; }; $scope.changeInputValue = function () { if($scope.selectedOption){ $scope.isShowSelectAll = false; $scope.filterOptions = []; angular.forEach($scope.allOptions,function(option){ if(option.client.indexOf($scope.selectedOption) != -1){ $scope.filterOptions.push(option); } }); }else{ $scope.isShowSelectAll = true; $scope.filterOptions = angular.copy($scope.allOptions); } }; $scope.protocolType.getValues = function () { var client = []; var server = []; var clientNo = []; var serverNo = []; var obj; angular.forEach($scope.allOptions,function(option){ if($scope.yesOptions.indexOf(option.client) != -1){ client.push(option.client) server.push(option.server) }else{ clientNo.push(option.client) serverNo.push(option.server) } }); if($scope.isSelectAll){ obj = { is_all:1, client:clientNo, server:serverNo, } }else{ obj = { is_all:0, client:client, server:server, } } return obj }; $scope.protocolType.empty = function (array) { $scope.yesOptions = array||[]; $scope.textSelectAll = "全部选择"; $scope.isSelectAll = false; }; $scope.protocolType.full = function () { $scope.isSelectAll = true; $scope.selectedOption = ''; $scope.changeInputValue(); $scope.yesOptions = angular.copy($scope.protocolType.yesOptions); $scope.textSelectAll = "已选择"+$scope.yesOptions.length+"条"; }; }, link: function (scope, ele, attrs) { } }; }); app.factory('checkImg', function () { return { yes: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAA+UlEQVQoFZWSMU4DMRBF/584G7QSRcIxuAZKEykNEiUVHVTQRaKh4AIcgAvQpkukVDlBOAYNSGSlXXuwpViyYYFdS9aMZ/6bsezh5HZ3T2KhqkfosEhWqnjkyd1u3xWKdQMsfaEAB0Zilf8swfdU0w0klmpGpz1BvpbHcklbPf8Okts0CfJtWBTz/Yc++Jc8S3PZVQfKGwiuvMD6XYsMzm1dT/1jXKdQ8E0asHRrAzOzbC6UGINWHPQp1UQ/6wjF2LpmJSKfhti4Bi8+lhWP4I+gAqV1uqSi8j9WRuF3m3eMWVUJBeKxzUoYn7bEX7HDyPmB7QEHbRjyL+/+VnuXDUFOAAAAAElFTkSuQmCC', no: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAAbklEQVQoFWM8c+ZMLQMDQxUQcwAxMeAHUFEbC5CoYmNj02ZmZn5FjK6/f/+K/fr16ypIIwdIk7a29hdiNF69ehWkjIOJGMXY1IxqxBYqULEhFDiglPMDlIygKQKPryBSILUgPSCNbaC0B6RJSuQAbowizhJuOsAAAAAASUVORK5CYII=' }; }); </script> 3、searchSelectEmpty <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>searchSelectEmpty</title> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> <style> .outer-div{ width:300px; height:36px; cursor: pointer; } .select-div{ border:1px solid #cccccc; border-radius: 6px; } .option-outer{ position: absolute; border: 2px solid #dddddd; width:444px; background:#ffffff; z-index: 10; padding:6px } .option-inner{ padding: 5px 10px; background: #fafafa; margin-right: 5px; margin-bottom: 5px; border-radius: 4px; text-align: left; border-bottom: 1px solid #e7e7e7; } .option-inner:hover { background: #cccccc !important; } .form-input{ width:180px; height:34px; border-radius: 16px; border:1px solid #ffffff; font-size: 16px; padding-left: 20px; } .form-input:focus { outline:none } </style> </head> <body> <div ng-controller="thisCtrl"> <button ng-click="getAllSelectedOptions()">点击获取被选中项</button> <duv>{{result}}</duv> <search-select-empty server-value="serverValue" protocol-type="protocolType"></search-select-empty> </div> </body> </html> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope) { $scope.serverValue = ''; $scope.protocolType = [ { "server": "c1222", "client": "c1222" }, { "server": "cnip", "client": "cnip" }, { "server": "dnp3", "client": "dnp3" }, { "server": "dns", "client": "dns" }, { "server": "doip", "client": "doip" }, { "server": "egd", "client": "egd" }, { "server": "enip", "client": "enip" }, { "server": "enip-io", "client": "enip-io" }, { "server": "fins", "client": "fins" }, { "server": "ftp", "client": "ftp" }, { "server": "ftp-data", "client": "ftp-data" }, { "server": "ge-srtp", "client": "ge-srtp" }, { "server": "goose", "client": "goose" }, { "server": "hartip", "client": "hartip" }, { "server": "http", "client": "http" }, { "server": "iec104", "client": "iec104" }, { "server": "knxip", "client": "knxip" }, { "server": "mms", "client": "mms" }, { "server": "modbus", "client": "modbus" }, { "server": "opcda", "client": "opcda" }, { "server": "opcua", "client": "opcua" }, { "server": "pn-dcp", "client": "pn-dcp" }, { "server": "pn-mrrt", "client": "pn-mrrt" }, { "server": "pn-ptcp", "client": "pn-ptcp" }, { "server": "pop3", "client": "pop3" }, { "server": "rdp", "client": "rdp" }, { "server": "s7", "client": "s7" }, { "server": "s7-plus", "client": "s7-plus" }, { "server": "sercos", "client": "sercos" }, { "server": "smb", "client": "smb" }, { "server": "smb2", "client": "smb2" }, { "server": "smtp", "client": "smtp" }, { "server": "sv", "client": "sv" }, { "server": "telnet", "client": "telnet" } ]; $scope.getAllSelectedOptions = function(){ $scope.result = $scope.serverValue }; }); app.directive('searchSelectEmpty', function () { var html = ` <div class="outer-div" > <!-- 以下是select --> <div ng-click="clickSelect()" class="select-div"> <input type="text" class="form-input" ng-blur="getInputBlur()" ng-change="changeInputValue()" ng-model="clientValue" /> <img src="{{checkImg.triangle}}" width="12px" height="6px" style="float:right;position: relative;top:16px;left:-6px" /> </div> <!-- 以下是option --> <div style="position: relative;" ng-show="isShowOptions && filterOptions.length"> <div class="option-outer" > <div style="max-height: 360px;overflow: auto;padding-left: 6px;"> <div ng-repeat="option in filterOptions track by $index" class="option-inner" ng-click="clickOptions(option)"> <span ng-bind="option.client" style="height:34px;line-height:34px;display: inline-block;"></span> </div> </div> </div> </div> </div> `; return { restrict: 'E', template: html, scope: { protocolType: '=protocolType',//所有选项 serverValue: '=serverValue', }, replace: true, controller: function ($scope,$timeout,checkImg) { $scope.checkImg = checkImg; $scope.isShowOptions = false; $scope.filterOptions = angular.copy($scope.protocolType); $scope.copyOptions = angular.copy($scope.protocolType); $scope.clickSelect = function () { $scope.isShowOptions = true; }; $scope.getInputBlur = function () { $timeout(function(){ $scope.isShowOptions = false; },150) }; $scope.clickOptions = function (option) { $scope.serverValue = option.server; $scope.clientValue = option.client; }; $scope.changeInputValue = function () { if($scope.clientValue){ $scope.filterOptions = []; angular.forEach($scope.copyOptions,function(option){ if(option.client.indexOf($scope.clientValue) != -1){ $scope.filterOptions.push(option); } }); }else{ $scope.filterOptions = angular.copy($scope.copyOptions); } }; }, link: function (scope, ele, attrs) { } }; }); app.factory('checkImg', function () { return { triangle: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAYCAYAAAC8/X7cAAAAAXNSR0IArs4c6QAAAM9JREFUWAnV2MsNwyAMgGFKRmKBToPYImQLGKcDsFNKlUp9BIKNnWBLPvnyf1ffrLWzUso/V9xorZcppfQwxuT4uyRBjg8h+ClHS0O843P7CyAJ8Rn/BZCA+I3/A4yM2IvfBYyIKMUXASMhavFVwAiIo/hDwJWIlvgmwBWI1vhmwJkISDwIcAYCGg8GcCIw8SgABwIbjwZQInriuwAUiN74bkAPgiKeBIBBUMWTASAIynhSQAuCOp4cUENwxLMACggfY1zyTdQ45/z2tmHrXgHsbZxC2VHomgAAAABJRU5ErkJggg==', }; }); </script> 六、input-select树结构(嵌套结构,不可以拖拽,左侧没有竖线) 1、主要需求描述 (1)页面初始时,前端自己显示一个空下拉框 (2)点击下拉框,前端向后台发送请求,将返回的数据,既渲染成下拉项,又存为备份 (3)点击其中一个下拉项,该下拉项如果没有下一级或下一级只有输入框,那么直接渲染,如果有下一级且下一级有下拉框,那么带上该下拉项数据,向后台发送请求,将返回的数据分别渲染成多个下拉框和输入框,作为下拉框的子级并缩进 (4)下拉框有的可以多选,有的只能单选,有的0选中时可以输入,叉掉选中项时,对应的子级也一起删掉 (5)以后重复(2)(3)(4),只是要携带自身至树根所有层级的label和value... (6)点击“保存”,将整个树图和层级关系发给后台,后台生成一条数据,前端刷新页面,该条数据出现在表格中 (7)点击该条数据后面的“详情”,前端将该条数据展示成“保存”前的树图,可以继续编辑 (8)另外,点击域内下拉框,下拉框隐现交替,点击域外,域内下拉框隐藏 2、主要需求实现思路 (1)弃用select标签,使用div标签模拟select标签的效果,给div标签添加tabindex属性,使其能够获取和失去焦点 (2)数据和组件都用嵌套结构,见下面“数据示例”和“组件html代码”,以便实现各种展示效果, (3)点击“保存”时,用递归遍历整个嵌套树,提取出后台所需数据 (4)点击“详情”时,用递归遍历本条详情,逐级逐条生成数据,每条数据的生成,都包含:A、本条数据加工getThisChildren(children,detail1),B、本条选项获取并加工getThisOptions(thisChild,detail2),C、下级数据获取getNextChildren(thisChild,detail3);getNextChildren最后将thisChild.children和detail3传给getThisChildren,开启下一级数据的生成;这样纵使后发出的请求先返回数据,也不会出现混乱。 附、数据示例 var childrenTemp = {//下拉框示例 type: type,//决定渲染成下拉框还是输入框 parentOption: formData.selectFront,//本条父级的前端文字, selectLabel: key,//标签内容 selectFront: '',//本条的前端文字,传给本条的子级 selectBack: '',//本条的后台数据,传给本条的子级 selectModel: '',//0选中时输入的数据 type_detail: type_detail,//0选中时,是否禁止输入 protocol_type: parentProtocolType,//是否把本条后代的根定在此处 allOptions: [],//本条所有的下拉项,每项包含前后端数据 yesOptions: [],//本条所有的选中项,每项只包含前端数据 selectChainThis: parentProtocolType? [thisParams] : selectChainThis,//自身父级至树根所有层级的{label:value}对象组成的数组 isFinal: isFinal,//本条是否有子级 isArray: isArray,//本条是否可以多选 isShowOptions: false,//是否显示本条的下拉项 children: [],//本条的所有子级 }; var childrenTemp = {//输入框示例 type: type,//决定渲染成下拉框还是输入框 parentOption: formData.selectFront,//本数据与父级select的哪个选中项关联, inputLabel: key,//标签内容 inputValue: '',//输入的数据 type_detail: type_detail,//是否禁止输入 }; 3、实例1,不可预览,供实用参考 (1)组件html代码 <div style="margin-left: 20px"><!-- 子级缩进 --> <div ng-show="requestParams.which==='addOrEdit'"> <div ng-repeat="formData in formDatas track by $index"> <div ng-if="formData.type==='input'" style="margin: 10px 0"> </div> <div ng-if="formData.type==='select'"> <input-select ng-if="formData.children.length>0" form-datas="formData.children" request-params="requestParams" ></input-select> </div> </div> </div> <div ng-show="requestParams.which==='detail'"> <div ng-repeat="formData in formDatas track by $index"> <div ng-if="formData.type==='input'" style="margin: 10px 0"> </div> <div ng-if="formData.type==='select'"> <input-select ng-if="formData.children.length>0" ></input-select> </div> </div> </div> </div> (2)页面html代码 <input-select ng-if="formDatas.length>0" form-datas="formDatas" request-params="requestParamsAddOrEdit" > </input-select> (3)组件js代码 (function () { angular.module('common-dir').directive('inputSelect', function () { return { restrict: 'E', templateUrl: 'vendor/common-directive/input-select.html', scope: { formDatas: '=formDatas', requestParams: '=requestParams' }, replace: true, controller: function ($scope, auditApi, $timeout) { $scope.getBlur = function (formData, event) { $timeout(function () { formData.isShowOptions = false; }, 200); }; //这个函数用于:向后台发送请求,获取select的option $scope.getOptions = function (formData, event) { //点击select框,获取下拉项 if (event.target.tagName === 'INPUT') return; //当点击select框内部的input框时,阻止 if (event.target.className === 'onlyQC') return; //当点击select框内部的备选项时,阻止 if (formData.allOptions && formData.allOptions.length > 0) { if (event.target.parentElement.className === 'isShowOptions') { formData.isShowOptions = !formData.isShowOptions; } return; //当下拉项已存在时,阻止 } ................................ }; //这个函数用于:叉掉已选中项时,除了删掉自身,还删掉下面对应的所有子项 $scope.optionDelete = function (formData, front) { formData.yesOptions.splice(formData.yesOptions.indexOf(front), 1); for (var i = 0; i < formData.children.length; i++) { if (formData.children[i].parentOption == front) { formData.children.splice( formData.children.indexOf(formData.children[i]), 1 ); i--; } } }; //这个函数用于:点击下拉项中的某项时 $scope.optionClick = function (formData, option) { var isTrue = formData.type_detail === 'protogo' || formData.type_detail === 'object' || formData.isArray; if (!isTrue) { formData.children = []; } ................................ }; }, link: function (scope, ele, attrs) {} }; }).factory('inputSelect', function (auditApi) { return { /*方法说明 *@method getThisChildren 生成本条的全部子级 *@param{Array}selectChainThis 参数链,向后台发送请求前,应当拼接成字符串 *@param{String}label label标签文字 *@param{String}parent 本条来自于父级的被选中项parent *@param{Object}valueOut 所有子级的原始数据 *@param{Array}children 所有子级的最终数据 *@param{Object,Array,String}thisDetail 本条数据的详情,随着递归的深入,越来越短 *@param{Boolean}isLeap 是否需要跳转,有时随着递归的深入,下一级回到了第一级(协议名称),这种情况叫跳转 *@return {undefined} undefined,没有返回值 */ getThisChildren:function ( selectChainThis, label, parent, valueOut, children, thisDetail, isLeap, request ) { if (type === 'input') { thisChild = {}; } else if (type === 'select') { thisChild = {}; if (isFinal === 1) { //没有子级 } else if (isFinal === 0) { //有子级 if (thisDetail.constructor === Object) { var detailKeysArray = Object.keys(thisDetail); for (var i = 0; i < detailKeysArray.length; i++) { var detailKey = detailKeysArray[i]; if (detailKey == label) { var nextDetail = thisDetail[detailKey]; that.getThisOptions(...); } } } else { //点击下拉框时,组件向后台发送请求,然后才出现下拉项,不用在这里获取下拉项。 } } } children.push(thisChild); }, /*方法说明 *@method getThisOptions 生成本子级的所有下拉项 *@param{Object}thisChild 一个子级 *@param{Object,Array,String}nextDetail 本条数据的详情,随着递归的深入,越来越短 *@param{Array}selectChainThis 参数链,向后台发送请求前,应当拼接成字符串 *@param{String}label label标签文字 *@param{Boolean}isLeap 是否需要跳转,有时随着递归的深入,下一级回到了第一级(协议名称),这种情况叫跳转 *@return {undefined} undefined,没有返回值 */ getThisOptions:function ( thisChild, nextDetail, selectChainThis, label, isLeap, request ) { $http({ method: request.method, root: request.root, url: request.url, params: params }) .then(function (result) { allOptions.push(oneOption); for (var j = 0; j < allOptions.length; j++) { var optionOne = allOptions[j]; that.getNextChildren(...); } }); }, /*方法说明 *@method getNextChildren 获取子级的子级 *@param{Object}optionOne 子级的一个下拉项 *@param{Object,Array,String}nextDetail 本条数据的详情,随着递归的深入,越来越短 *@param{Array}selectChainThis 参数链,向后台发送请求前,应当拼接成字符串 *@param{Object}thisChild 一个子级 *@param{Object}params 请求参数 *@param{Boolean}isLeap 是否需要跳转,有时随着递归的深入,下一级回到了第一级(协议名称),这种情况叫跳转 *@return {undefined} undefined,没有返回值 */ getNextChildren:function ( optionOne, nextDetail, selectChainThis, thisChild, params, isLeap, request ) { var that = this; var isFinal = optionOne.isFinal; var parent = optionOne.front; if (that.wholeKeysArray.indexOf(parent) > -1) {//以下是跳转逻辑 $http({ method: request.method, root: request.root, url: request.url, params: { protocol_type: parent } }) .then(function (result) { var selectChainThis = [{ 协议名称: parent }]; //遇到跳转逻辑时,给选择链重新赋值 for (var label in valueOut) { that.getThisChildren(...); } }); } } else {//以下是正常逻辑 if (isFinal === 1) { var resultKey = Object.keys(thisChild.copyOptions)[0]; var valueOut = thisChild.copyOptions[resultKey]; var isObject = nextDetail.constructor === Object; var detailKeysArray; if (isObject) { detailKeysArray = Object.keys(nextDetail); if (detailKeysArray.indexOf(parent) > -1) { for (var label in valueOut) { if (label === parent) { that.getThisChildren(...); thisChild.yesOptions.push(parent); } } } } } else if (isFinal === 0) { var isObject = nextDetail.constructor === Object; if (isObject) { var detailKeysArray = Object.keys(nextDetail); if (detailKeysArray.indexOf(parent) > -1) { $http({ method: request.method, root: request.root, url: request.url, params: thisParams }) .then(function (result) { if (detailKeysArray.indexOf(resultKeysArray[0]) > -1) {//父级的parentOption和子级的label一样 for (var label in valueOut) { that.getThisChildren(...); } } else {//如果出现parentOption和它子级的label不一样,那么根据数据结构,逻辑写在此处。 for (var label in valueOut) { that.getThisChildren(...); } } }); } } } } }, }; }); })(); (4)页面js代码 $scope.formDatas = [ { type: 'select', selectLabel: '协议名称', selectFront: '', selectBack: '', allOptions: [], yesOptions: [], selectChainThis: [], children: [], isFinal: 0 } ]; 4、实例2,可预览,供演示 <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>input和select</title> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> </head> <body> <div ng-controller="thisCtrl"> <div> <p>=====可预览=====</p> <p>1、向后台发送请求,获取返回数据,填充$scope.formDatas</p> <p>2、向后台发送请求,获取返回数据,填充$scope.formDatas</p> </div> <input-select ng-if="formDatas.length>0" form-datas="formDatas"> </input-select> </div> </body> </html> <script> var app = angular.module('myModel', []); app.controller('thisCtrl', function ($scope/*,cyRequest*/) { //此处,向后台发送请求,获取返回数据,填充$scope.formDatas $scope.formDatas = [ { type: 'input', inputLabel: '输入框', inputValue: '', },{ type: 'select', selectLabel: '选择框标签', selectValue: '后台1', selectOptions: [ { back: '后台0', front: '前端0' }, { back: '后台1', front: '前端1' }, { back: '后台2', front: '前端2' }, { back: '后台3', front: '前端3' }, { back: '后台4', front: '前端4' }, { back: '后台5', front: '前端5' }, ], selectChainThis: [],//默认值。 selectChainNext: null, children: [],//子级。 },{ type: 'select', selectLabel: '选择框标签2222', selectValue: '后台1', selectOptions: [ { back: '后台0', front: '前端0' }, { back: '后台1', front: '前端1' }, { back: '后台2', front: '前端2' }, { back: '后台3', front: '前端3' }, { back: '后台4', front: '前端4' }, { back: '后台5', front: '前端5' }, ], selectChainThis: [],//默认值。 selectChainNext: null, children: [],//子级。 } ]; }); app.directive('inputSelect', function () { var html = ` <div style="margin-left:10px;"> <div ng-repeat="formData in formDatas track by $index"> <div ng-if="formData.type==='input'" style="margin:10px 0"> <label ng-bind="formData.inputLabel+':'" style="width:200px;text-align:right;display:inline-block"></label> <input ng-model="formData.inputValue" type="text" style="width:300px;border:1px solid gray;height:26px;border-radius:4px;"/> </div> <div ng-if="formData.type==='select'" style="margin:10px 0"> <label ng-bind="formData.selectLabel+':'" style="width:200px;text-align:right;display:inline-block"></label> <select ng-model="formData.selectValue" ng-options="option.back as option.front for option in formData.selectOptions" ng-change="clickOption(formData)" style="width:306px;border:1px solid gray;height:26px;border-radius:4px;" ></select> <input-select ng-if="formData.children.length>0" form-datas="formData.children"></input-select> </div> </div> </div> `; return { restrict: 'E', template: html, scope: { formDatas: '=formDatas' }, replace: true, controller: function ($scope) {//此处注入“公共请求服务” $scope.clickOption = function (formData) { var objOuter={}; objOuter[formData.selectLabel] = formData.selectValue; formData.selectChainNext = angular.copy(formData.selectChainThis); formData.selectChainNext.push(objOuter); //此处,把formData.selectChainNext作为“公共请求服务”的参数,向后台发送请求,获取返回数据result并加工,然后填充formData.children var result=[ { type:'input', label:'输入框' },{ type:'select', label:'选择框标签' },{ type:'select', label:'选择框标签2' } ]; angular.forEach(result,function(value,index){ var objInner = { }; if(value.type==='input'){ objInner.type = value.type; objInner.inputLabel = value.label; objInner.inputValue = ''; }else if(value.type==='select'){ objInner.type = value.type; objInner.selectLabel = value.label; objInner.selectValue = '后台1'; objInner.selectOptions = [ { back: '后台0', front: '前端0' }, { back: '后台1', front: '前端1' }, { back: '后台2', front: '前端2' }, { back: '后台3', front: '前端3' }, { back: '后台4', front: '前端4' }, { back: '后台5', front: '前端5' }, ]; objInner.selectChainThis = angular.copy(formData.selectChainNext); objInner.selectChainNext = null; objInner.children = []; if(index===1){ console.log(objInner.selectChainThis) } } formData.children.push(objInner) }) }; }, link: function (scope, ele, attrs) { } }; }) </script> 七、dir-tree树结构(平行结构,可以拖拽,左侧有竖线) 1、主要需求描述 (1)页面初始时,初始化组件,向后台发送请求,获取嵌套数据,改成平级数据,计算并添加缩进和左侧线,渲染组件 (2)可以编辑本条数据 (3)可以拖拽本条数据至目标数据上,将自身和目标数据发给后台,最后刷新页面,获取新嵌套数据(被拖拽数据及其后代成为目标数据的后代) 2、主要需求实现思路 (1)拖拽效果用插件 (2)递归实现嵌套数据改成平级数据,非如此则拖拽出错 (3)递归实现左侧竖线 附、数据示例 var obj={ id: item.id,//本级id name: item.name, puuid: item.puuid, uuid: item.uuid, idsArray: item.idsArray,//供判断层级关系时用 isShowSelf: item.collapsed||true,//是否显示自身 //以上为后台返回,以下为前端添加 isShowChild: true,//是否显示后代 leftTime: leftTime,//竖线从左缩进次数 downTime: 0,//竖线向下延伸次数 } (5)给div标签添加tabindex属性,使其能够获取和失去焦点 3、实例1,不可预览,供实用参考 (1)组件html代码 <div> <div style="display:flex; flex-direction: row" ng-repeat="formData in formDatas track by $index" > <div class="dir-tree-line" ng-drag="true" ng-drop="true" ng-show="formData.isShowSelf" ng-style="{'margin-left':formData.leftTime*28+'px'}" ng-drop-success="dropSuccess($index, $data, $event)" ng-drag-data="formData" ng-click="clickItem(formData)" style="position:relative" > <div> <span ng-style="{'background': $index===0? '#ffffff':'rgb(162, 142, 142)'}" style="height:1px;width:16px;display: inline-block;position: relative;top:-4px"></span> <span class="dir-tree-label" ng-class="formData.isHasChild?((selectedItem.showId && selectedItem.showId === formData.uuid)?'dir-tree-haschild-blue':'dir-tree-haschild-black'):((selectedItem.showId && selectedItem.showId === formData.uuid)?'dir-tree-nochild-blue':'dir-tree-nochild-black')" ></span> <span ng-bind="formData.name" ng-class="(selectedItem.showId && selectedItem.showId === formData.uuid)?'dir-tree-label-text-blue':'dir-tree-label-text-black'" ng-drag="true" ng-drag-data="formData" ></span> <span class="dir-tree-label" ng-show="formData.isHasChild && !formData.isShowChild" ng-class="(selectedItem.showId && selectedItem.showId === formData.uuid)?'dir-tree-haschild-tianjia-blue':'dir-tree-haschild-tianjia-black'" ></span> </div> <div style="position:relative;width:1px;background: rgb(162, 142, 142);left:27px;top:-7px;" ng-style="{'height':((formData.downTime+0.5)*36+8)+'px'}" ng-show="formData.isShowChild && formData.isHasChild" ></div> </div> <div style="flex:1;"></div> </div> </div> (2)页面html代码 <dir-tree request-datas='requestDatas' selected-item='selectedAdd' ng-if="decideOther.isIf"></dir-tree> (3)组件js代码 (function () { angular.module('common-dir').directive('dirTree', function () { return { restrict: 'E', templateUrl: 'vendor/common-directive/dir-tree.html', scope: { requestDatas: '=requestDatas',//与前后台交互相关的内容 selectedItem: '=selectedItem',//树图的被选中项 decideOther: '=decideOther'//本树是否决定“其它树”的隐现 }, replace: true, controller: function ($scope, $timeout, auditApi) { if(!$scope.selectedItem.isAbandonDrag){ $scope.selectedItem.callback = function(name,uuid,puuid){ auditApi .query({ method: $scope.requestDatas.method||'put', root: $scope.requestDatas.root||'organization', url: $scope.requestDatas.url||'/info', data: { name:name,//被修改节点的名称 uuid:uuid,//被修改节点的uuid puuid:puuid,//被修改的节点,应移到哪个节点之下 }, pageLoad: true, }) .then(function () { layer.msg('拖拽成功!'); $scope.getDatas(); }).catch(function(){ $scope.getDatas(); }); } }else{ delete $scope.selectedItem.callback } $scope.clickItem = function(formData){ $scope.selectedItem.name = formData.name; $scope.selectedItem.puuid = formData.puuid; $scope.selectedItem.uuid = formData.uuid; $scope.selectedItem.showId = formData.uuid; var id = formData.id; formData.isShowChild = !formData.isShowChild $scope.formDatas.forEach(function(item){ if(item.idsArray.indexOf(id) > -1 && item.idsArray.length > formData.idsArray.length){ item.isShowChild = formData.isShowChild; item.isShowSelf = formData.isShowChild; } }) $scope.addDownTime($scope.formDatas); } $scope.dropSuccess = function(index,data,event){ var toItem = $scope.formDatas[index]; if(angular.isFunction($scope.selectedItem.callback)){ $scope.selectedItem.callback(data.name,data.uuid,toItem.uuid); } } $scope.getDatas = $scope.requestDatas.getDatas = function(isNewAdd){ auditApi .query({ method: $scope.requestDatas.method||'get', root: $scope.requestDatas.root||'organization', url: $scope.requestDatas.url||'/info', pageLoad: true, }) .then(function (res) { var list = angular.copy(res.rows); var lastList = []; //以下给每条数据添加idsArray属性,供判断层级关系时用 function addEachRowIdsArray(list,fatherId){ list.forEach(function(item) { item.idsArray = fatherId.concat(item.id); if(item.children){ addEachRowIdsArray(item.children,item.idsArray) } }); } addEachRowIdsArray(list,[]) //以下把父子嵌套数据改造成兄弟并列数据,不如此则拖拽出错 function getList(list,leftTime){ angular.forEach(list,function(item){ var obj={ id: item.id, name: item.name, puuid: item.puuid, uuid: item.uuid, idsArray: item.idsArray, isShowSelf: item.collapsed||true,//是否显示自身 //以上为后台返回,以下为前端添加 isShowChild: true,//是否显示后代 leftTime: leftTime,//竖线从左缩进次数 downTime: 0,//竖线向下延伸次数 } lastList.push(obj) if(item.children.length){ obj.isHasChild = true; getList(item.children,leftTime+1) } }) } getList(list,1); if(isNewAdd){ $scope.selectedItem.showId = lastList[0].uuid; $scope.selectedItem.uuid = lastList[0].uuid; } $scope.selectedItem.isInit = Boolean(lastList.length); if($scope.decideOther){//不如此,修改后“其它树”不刷新 $scope.decideOther.isIf = false; $timeout(function(){ $scope.decideOther.isIf = true; $scope.formDatas = lastList; $scope.addDownTime($scope.formDatas) }) } $scope.formDatas = lastList;//已经完成加工的树图总数据! $scope.addDownTime($scope.formDatas)//给总数据的每条数据添加向下延伸次数 }); } $scope.addDownTime = function(formDatas){ //以下给formDatas的每一项添加downTime formDatas.forEach(function(item1){ item1.downTime = 0 ; if(!item1.isShowChild)return; //以下添加item1的downTime var id1 = item1.id; var idsArray = []; //以下获取本级的所有子代的id,并存储在idsArray中 formDatas.forEach(function(item2){ if(item2.idsArray.indexOf(id1) > -1 && item2.idsArray.length == item1.idsArray.length+1){ idsArray.push(item2.id) } }) //以下删除idsArray的最后一项 idsArray.pop(); //以下计算本项的downTime for(var i = 0; i<idsArray.length; i++){ var id2 = idsArray[i];//第i个子代的id for(var ii = 0; ii<formDatas.length; ii++){ var item3 = formDatas[ii]; if(item3.idsArray.indexOf(id2)>-1){//从所有数据中,找到第i个子代 item1.downTime++; if(item3.isHasChild && !item3.isShowChild){//第i个子代有子代且没有展开 var id3 = item3.id; formDatas.forEach(function(item4){ if(item4.idsArray.indexOf(id3) > -1 && item4.idsArray.length > item3.idsArray.length){ ii++//跳过没展开的子代 } }) } } } } }) } $scope.getDatas() }, link: function (scope, ele, attrs) {} }; }); })(); (4)页面js代码 //以下是树图属性传参的数据 $scope.decideOther = {//本树是否决定“其它树”的隐现 isIf: true }; $scope.requestDatas = { };//与前后台交互相关的内容 $scope.selectedDatas = {//树图的被选中项 name: '', uuid: '', puuid: '', showId: '', }; 4、实例2,可预览,供演示用 <!DOCTYPE html> <html lang="en" ng-app="myModel"> <head> <meta charset="UTF-8"> <title>passWord</title> <style> .dir-tree-label{ padding-left: 20px; margin-right: 4px; height: 36px; line-height: 36px; background-position: left 4px; background-repeat: no-repeat; cursor:pointer; user-select:none; } .dir-tree-label-text{ cursor: pointer; user-select: none; } .dir-tree-label-black{ color:black } .dir-tree-label-red{ color:red } </style> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.6.2/angular.js"></script> <script src="https://cdn.bootcss.com/ngDraggable/0.1.11/ngDraggable.js"></script> </head> <body> <div ng-controller="thisCtrl"> <dir-tree form-datas="lastList" other-datas='otherDatas'></dir-tree> </div> </body> </html> <script> var app = angular.module('myModel', ['ngDraggable']); app.controller('thisCtrl', function ($scope) { var res = { "rows": [{ "children": [{ "children": [{ "children": [{ "children": [], "isShowSelf": true, "id": "12", "name": "裁员部一", "puuid": "1ec9f238-9aba-484c-b857-d4183200053c", "uuid": "1e8ae51d-8d2f-486e-b1d1-ff1009f8461e" }, { "children": [], "isShowSelf": true, "id": "13", "name": "裁员部二", "puuid": "1ec9f238-9aba-484c-b857-d4183200053c", "uuid": "be521aa9-1e8c-4262-a156-2d02e3cb9c51" } ], "isShowSelf": true, "id": "4", "name": "人力部一", "puuid": "bcb3e97f-d8ef-4e33-a234-09a3fef8533d", "uuid": "1ec9f238-9aba-484c-b857-d4183200053c" }, { "children": [], "isShowSelf": true, "id": "5", "name": "人力部二", "puuid": "bcb3e97f-d8ef-4e33-a234-09a3fef8533d", "uuid": "ba10a598-9af7-47ba-9c34-ee41d85f0525" }, { "children": [], "isShowSelf": true, "id": "6", "name": "人力部三", "puuid": "bcb3e97f-d8ef-4e33-a234-09a3fef8533d", "uuid": "a880024f-e906-4558-833c-4ec7a20927e3" }, { "children": [], "isShowSelf": true, "id": "7", "name": "人力部四", "puuid": "bcb3e97f-d8ef-4e33-a234-09a3fef8533d", "uuid": "25de3bf5-4848-48e5-a161-04d1492a4edb" } ], "isShowSelf": true, "id": "2", "name": "人力部", "puuid": "f2a8c4ea-e270-44d0-a7bd-18c62fb55358", "uuid": "bcb3e97f-d8ef-4e33-a234-09a3fef8533d" }, { "children": [{ "children": [], "isShowSelf": true, "id": "8", "name": "财务部一", "puuid": "44667cbb-a1d5-4187-84aa-c3ee347d873e", "uuid": "c036eab1-0f4d-4732-be3c-8e2da46cb60e" }, { "children": [], "isShowSelf": true, "id": "9", "name": "财务部二", "puuid": "44667cbb-a1d5-4187-84aa-c3ee347d873e", "uuid": "ee22c501-e797-4190-84d7-41c0523ac784" }, { "children": [], "isShowSelf": true, "id": "10", "name": "财务部三", "puuid": "44667cbb-a1d5-4187-84aa-c3ee347d873e", "uuid": "60dd79cf-20b7-4d04-92c0-968eb85620c1" }, { "children": [], "isShowSelf": true, "id": "11", "name": "财务部四", "puuid": "44667cbb-a1d5-4187-84aa-c3ee347d873e", "uuid": "f7a76408-c3ba-4c4b-8f77-d520407beb31" } ], "isShowSelf": true, "id": "3", "name": "财务部", "puuid": "f2a8c4ea-e270-44d0-a7bd-18c62fb55358", "uuid": "44667cbb-a1d5-4187-84aa-c3ee347d873e" } ], "isShowSelf": true, "id": "1", "name": "中国石油", "puuid": "", "uuid": "f2a8c4ea-e270-44d0-a7bd-18c62fb55358" }] }; var list = angular.copy(res.rows); var lastList = []; //以下解决隐藏和显示问题 function addEachRowIds(list,fatherId){ list.forEach(function(item) { item.idsArray = fatherId.concat(item.id); if(item.children){ addEachRowIds(item.children,item.idsArray) } }); } addEachRowIds(list,[]) //以下把父子嵌套数据改造成兄弟并列数据,不如此则拖拽出错,极端重要!!! function getList(list,left){ angular.forEach(list,function(item){ var obj={ id: item.id, name: item.name, puuid: item.puuid, uuid: item.uuid, idsArray: item.idsArray, isShowSelf: item.isShowSelf, left: left, } lastList.push(obj) if(item.children.length){ obj.isHasChild = true; getList(item.children,left+1) } }) } getList(list,1); $scope.lastList = lastList; $scope.otherDatas = {//页面树图的被选中项 name: '',//被选中项的name uuid: '',//被选中项的uuid puuid: '',//被选中项的puuid,即该数据父级的uuid showId: '',//被选中项的showId,取值为“该条数据”的uuid,在<dir-tree>组件中,showId与每条数据的uuid进行比较,相等的展示为红色,为被选中项本身 callback: function(name,uuid,puuid){//拖动结束后,触发的函数 } }; }); app.directive('dirTree', function () { var html = ` <div> <div class="dir-tree-label" ng-drag="true" ng-drop="true" ng-show="formData.isShowSelf" ng-style="{'margin-left':formData.left*20+'px'}" ng-repeat="formData in formDatas track by $index" ng-drop-success="dropSuccess($index, $data, $event)" ng-drag-data="formData" > <img ng-src="{{formData.isHasChild? fileImg.havechild : fileImg.nochild}}" ng-click="clickImg(formData)"> <span class="dir-tree-label-text" ng-bind="formData.name" ng-class="(otherDatas.showId && otherDatas.showId === formData.uuid)?'dir-tree-label-red':'dir-tree-label-black'" ng-click="clickText(formData)" ng-drag="true" ng-drag-data="formData" ></span> </div> </div> `; return { restrict: 'E', template: html, scope: { formDatas: '=formDatas', otherDatas: '=otherDatas'//页面树图的被选中项 }, replace: true, controller: function ($scope,fileImg) { $scope.fileImg = fileImg; $scope.clickImg = function(formData){ $scope.clickText(formData); var thisArrayIds = formData.idsArray; $scope.formDatas.forEach(function(item){ var flag = true; var itemArrayIds = item.idsArray; thisArrayIds.forEach(function(id,index){ if(itemArrayIds.indexOf(id) === -1){ flag = false; } }) if(flag){ item.isShowSelf = !item.isShowSelf } }) formData.isShowSelf = true } $scope.clickText = function(formData){ $scope.otherDatas.name = formData.name; $scope.otherDatas.puuid = formData.puuid; $scope.otherDatas.uuid = formData.uuid; $scope.otherDatas.showId = formData.uuid; } $scope.dropSuccess = function(index,data,event){ /** * 函数说明:拖拽结束时触发的函数 * @index:目标所在位置的序号 * @data:初始位置的数据 * @event:初始位置的鼠标事件 * */ var toItem = $scope.formDatas[index]; var fromIndex = $scope.formDatas.findIndex(function(item){ return Object.is(data.uuid ,item.uuid) }) $scope.formDatas[index] = data; $scope.formDatas[fromIndex] = toItem; $scope.otherDatas.callback(data.name,data.uuid,toItem.uuid); } }, link: function (scope, ele, attrs) {} }; }); app.factory('fileImg', function () { return { nochild: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAOCAYAAADwikbvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAOw4AAA7DAZBCL/sAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAHZJREFUOE9j+P//Pxiv7HH8jw/D1CFjOAOqACu4srcGqwFEa8ZmAEma0Q3A1JxbgIrRAH7NBABezT3bF8JxfU08WCwrJwlMgwD1bYbZguxXkBjIVmSboepwaCYA8GoGRQeyvy07S8AYBmhnMyGAUzOxGKLnPwMABE2jDc+cnOsAAAAASUVORK5CYII=', havechild: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAIhJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX7EoxobRw4U6BszPUAEHHDEYrBbdALC/sCjGhrGGAUgQ7DQsGtAxVgNAGOQ0YjBMPQyjcEjH/xkAhEKsbVNNI1sAAAAASUVORK5CYII=', }; }); </script>