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,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAACXFJREFUeAHtmXtsVEUUh3eXttgCVUCIxVc0+EDFiKUoAoJURdDyVkEjERFKKSgP0fpCFI2oGCC0UKo8JQpNEFEEgUZAUUCKAlYe1igGoVJQEajQJ35n07vMvTu7vbvd9R/vJJOZOXPOmXN+M3Pm7F2XyykOAg4CDgIOAg4CDgIOAg4CDgIOAv9HBNzRdnrcuHHxZWVlt7LO7dS2bre75dmzZ1vQb0k/kX4Z7Qna47TFtHtpf4iNjd2UnZ19ONr2RQWAkSNHipPDMP5e2hTauHAcAYg9yK9FdkleXt634eioSyaiAIwYMeIWjB6N0Q+wcFhOBzF4t8fjeSc+Pn7e9OnTTwfhC2kqIgCkp6cPZtXxON4+pNXDYAbgUurbgDGbcioMFSaRegHAjl+LtlxqV5PW/2ZwFBCy5syZswBAzoa7ZFgAjBkzpmFFRcVz7HgWC4d81DF4D3LraLfQVqJnLFWCZMilVkfG3Llzd4UsjEDIAHDcxdB3MfiqUBbE0CpkZrNrb+bm5h5SZSV2MN6q0kLsV6B3AnqzQ5RzxYQigPNyz99EpoFG7h9oCRq6kDY0aNAgkzu7VzeP8a1ramp0U3ZpccjPwr7bmjRpMnzatGlldgVtnQB2SBybRx2kUfwPu/si9D4BjvHyVq1aDZo8eXKVKjtq1KiLqqurO0Prj5y8GjpQVRG1f4TBp9ShVJMP2CJP5wCezX2qQKC+SVjHhPNJ0D+j3qiZX8fupUNvyQ5ss85jzOqkpKQ+qvMZGRldcfwNeOXYh13Q3ZO13az7Hg43VxUxd4o6jCuRr9J1fY+OaNBGjx59BYo2M7Y6XwE9HZR7sMgBjBhnyCjtSbK54arzgDkT5zfCUy/na9d4khdgTVxc3E3Y8pWyrgtAGmPTMq7EJJWu6wcEAOHriPSbUXalRfAIyHcn6uYJHQc9GNDDwuOC9oqaypIdvgbPE1a+cMfYlSpXc9asWb9xyrqhR2KT6TmE52V4Xg+2hhYAcR7hTQi2sgh/GxMTk8Ku+xAvLS1Nhrepha8yISFhvkETfezIs8Y4Qm0sG9FBdMkp4zQ+Q6DtC/CnLfqzWH+6heYb+gHAG38Js3LnL/Rx0UHx2kaNGnUmkh9U6VVVVR3VsfSFl3T1T4X+nJCVcUS6AG9amyvxMaD0ZH1ThgjfWE7CbFo/G0wAEKCalpeXfwbjpRYL13HM+gbIwU1AiRwGfG7Iszvn0R9ojCPZYqf8qjQVQNgECHdBPG6acLkyOAk5FprLBwAIxRKgPobhepUJZ9bzjEkkP6PSlb71+AsA3xvzJSUl7TC0oTGOZMs6iTp9gLBV4hRzxyzzGcSip1WaDwCUvc2EvMtq2cDO9w7ivDjrl0wB5G+KkrZKP6JdgPVb21iAOPUdINzBWL2KLmLRVECQvMNbvAAIAWVjDKK0OLafQNY/mPPCh9yv0lpKBnQ38USO6EjLXCSHKtB+egGhiKCdhi9qYHRj22J87iQCHu79NRAky1OLoJY2Y8YM6z1Sebx9lP/iR+S5Y4ETPKMH0d1OMx8REjusA9+km6D9NYTB1GpjApsaUleSjbb2cFznMmhsTNJWUgfyzhcrtGBd331XmUSnLKTSIt0HgO12dOLLSjZqlMqLbc3xPVuugPWOlpDB7VaZg/VRvgfldsEKpiqkOdY8xO7utCvEdXgH3r0W/hsEgKkW4mW87Z/Ix0wLPdjww2CT0ZhjB1eEopcncAr8bSwy73vIoN4CzQ/UCZR35Evu0vz8fFu/0DiK8lXG9GtP1ReFfjVZX8Dszroe8egRaM+rdOzdxQs3yfsK0HkMwhaVgX7vgoICv8TBwuMd8u7uBzQ5Yv9VeZ81f7azGPlNZ56+dy28JxnfLy+cFwDpAIDk0QcsjJyc9BcsNO2QUzCZib+1k5ElHuXL8EQ7KnnhroRProrpsx22jjCCvBcAUUaQkK+t8maekLFR2NkpgDDUGAdqRZ5jaXpuAvHWh84aj8+cOfNIXTrY+fOJ8qvgM6Xq+DcbW5ca8j4AhMBEEegMout7M2sZ8wChT20/YMOxXIP82IAM9ZxA93jWkHQ9aJEAjqPLYTIFPWirUlNTn1CF/YJcYWHhT+3bty+GWRw2AJLf/ANTUlIOMB/0iWT+G+Rlh3pQ/fSri9vts3YNzstHzzoDH9ln4pkzZ1ZzciUNVss2BmlZWVkVKlFr4I4dO4pwtggl/WA2eDyM+yYnJ//FvCgLWJgvRF5+EfaiqklWQJkgE4dxvh/Om14qHX9mZmbzysrK9dhp+pkM74/UO3nx/GKU4ZyfPnZyX4cOHXagbACTxo8ON/2egNAsLS2tYOPGjTV+grUE5A926dJlHjlFDDuYrOgIJGKiI3OamkNSNphjv8c0qRlwReWjSwH2mhI7dBSj424ALNGI1f2Rgje0O4rl3jVSFaD4SwLSA2Rjv6t0XZ+c+1J0DKE+zLzpXmr4d6J7Bbrz7OgWeWwcgOMLqabThp5Cvhn24rPZUc06XpLsaJ2FBTqhXO5VooX5MIs8ypOy3kIPOJQvTpyKtoDRBtl4Wjkh8uOrGGOL5BtfQGHLBLrkH6pXsespy5QMC/gl2A8QT2nmfCRbAAg3u9gGwz+ie7VP+lxnPj+dJ9j59XhOpH49njn5sryA6neiAHQZyd0Q8htTwNOtaBsAEZa3FeVLQPw+jTK5Y5P4erSQhaOWFtf+ofIiNqSznl8Mw74ZOD8BGwLGJ9X2kAAQQRZ2k2FNpH2F2lBVVtv/ifYlgMiPJBCALwmN5BhSTfGIsXzAOUgdTrBbK2O7JWQADMXEhRu4v4sY32zQ1BZjfgegxQSz+UTx/eqc3b6AzY7fzjqy2/IXmg5wUTefOl73zMlksBI2AKKUXYmlkR2RX1rnUwOVfUxson4BMNsJdkcIdidUZnGWkyWf0C6mtsNp+bKbShWatqDrEDnCcMlAtQw2iPUCwNAvxxNjJuGEfP8TUOos8JfDVEr9E7mmtElUW7LwyY+3XH4UvVzfwBsRADDIW8jELuelyMShYRCa1ZIj1uC0nJoF6H+D414SCcURBcAwiBORQP8h6oPUrlS7OwurX5EfZluoixITEz8I5b9/P00aQlQAUNeRHyfk5/dwp7tBlzRVarB4Icd7N7v8He1mkpk1OTk5fyATlRJ1AHRWc0KScOwCQGnCvKSv5Th7jIB2rEWLFn/ZfcN1uh2ag4CDgIOAg4CDgIOAg4CDgIOAPQT+BekJrkwYCCycAAAAAElFTkSuQmCC',
      correct: 'data:image/png;base64,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',
      error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAACz1JREFUeAHdG2l0VNX5u28msyQhGyQhy2SBkAARqAkqHFrPKdWjVMoiBCmyCFIQCrRIQUCPpMoxgKhQK2URQWQphEKlQqG1lpa2lCPkoByQSSIkk4SQfYFkMpOZ9/rdF2b63rzZ3psZSnp/zHv32+73fe8u3/3uHQIhLrfyk8M7GltGsMDmccDlEY4Y8BlLgMTce3KoQisA1wJAWoGACeslhDCX9El9vzacrzaHUkUSCuE3MnXpNpYr4DiuAOXno4UqJe2gcnZ0yJdAyBFGzRQPKjdXK5HjjSdoDuDy88NKG6/OAWBf5DgY5a1RJThUlOMInGcIs2tQxqj95OxZmxI5rjwBO6AsK0trt1XNAw5Wo4pprg2Eok4IuQkcKcqOz91LLl3qDqSNgBxgzNA/Byz7Do7llECUUMxLiIkA9/OcSutxpTIUOaAsKzLe3t29Db/4VKUNB5OPEDgUHqlbYrja3ixXrmwHGDM0k7D77cQJLl5uYyGlJ+Q2AzA/u9JyUk47yON/KU3XrgQWjj1wxlMTOK4/6nWiNE27zH+LAPxyAFdYyBjTte+zHLcJlzTZvUaOQoHQom4MC9xW1HUzOsMvPX06gCsoUBn3FB1BgUsCUe5+8qKuK4wZ2v30w/lq16eXsNtvwy+/yJegBxGPy+V7OZWWl73p5tVD19M0q3qr8dRo7AnLcU5Y6s0BHntAabrmWYzojj7IY96bYQ4cGshiT5jgaXVw64AbmRGJVnv3NRQS5xDUm5/ogPoIvWZo6vU7Ta52uB0C3axt+/+L8dRgHAoJHWbLr12Np3WJA0rT9TOQYZI74t4Mw+E8nQ5rVxtEDqganarHffs7rkS+6kx0LIQ//iQw0TG+SAPC677zCKQe+CMkfXAA1EmpsmVhaLCFy83VCBnVwoq5tmEBjaiEMF/vmkFDwPDbP4G6XwLYGuqgevZ4sFz72hebbLwufxQY9p8CJjyC59UNexgqnhoJnLnTb1m4aTOUdZTPRYYdDiZnD6DbWpaFVQ6Ev8/EN7fyxlN6dXwi7wz6pYJZtMPyIHXvCafxVLYmfSDoRoyU3QzLwRqau3AwOh3Adle9iNNFsgPh75PpEy0iVeFwMGA31T8yRgRXWtHk5OKXPwmqKHE79pYmsHxzRb5Yjksvb7w6y8HodAB2D3SA/FK/fhWwXV0iRiayD6R+8hmEf3esCC63ohmYA4aDp0EVI16N2Y67UD1vMrBtmEZUUOyYtXKw8Q4oH6jNwoxOngMo52k+/zeomTsR2M4OERujD4eU3cchYuw4EdzfSlhaJhgOnXYOLwcf22WG6rmToKvkggMk+0k4GF0+UG+gjLwDWBs3XbYUAUPnv85C9axnwH6nXQBF4TodpOwshshxk0VwXxV1sgGNPwPqRPGIZC0WqJk/FcwXzvkS4RVPo1u7jZ1GiXgHIKDAK4cfSPPF81A942mwu3RLEhYGybhs9Zn4nB9SMH2c0J//8mGp6SJ6rrsbbi2eAZ3nPhfBFVc4jncAKcuKi2Ktd1uDFfNrhwzj12p133iRbhwuMXWrF0Hb4b0iuLCiiusHhiOfgxaXVmHh7HaoXToL7pz8nRAc6LstIiUhigFbJx5YBC/JQWfmqmlPgK2uVqQgYRhI3LgdYua431nTIIoGORLjMYS7/YufBNt4qpu6q7ZpBINhr/zFVGSatGItvw6mgh9Ad41JhMRNCSS+sQViF74sgvesGidBN3S4CE4rdWt/Cu3HDkjgwQCwHDuSweUvPxjCXGV0V37LO8GKT9eSsLYI+i5by4MJXS32fgp6N0FNXeEKaDu425U9aHW0PY8Y07R/x5fvBU2qiyBVQhI/qWmzBrtgAJp2vgu63IchYsz3JbiGDa9B82/elsCDCcDzyT8TzPrQcOqhYAp2laXCCZEGNNrB/jXTuGU9NL33pquYUNQvMuiF2FBIFsq0NzWACSfGrislQrDb9+Yd794v43HmJ7F0Dgi5A6ilNGyt+vFTQOMFT6Vl33ZoeGuNJ3TQ4dR27AHBWwJ9achipGg13fBIxscOatEO3SNtsBDYA4iyHYUCDRJw6xz97PMeOfs8MwVSdhwGohHlLDzSB4rAIdCCPYDezAh9iX91A8TOfslnQ5FPjMdN1DEgWp1P2kAJcAi04l4g9D2g34p1ELdguUTfu389De2fScPbCEyvpe77A5B72R8JY5AAfA/AJEhNkOS5FRO3eJUz6BESdPzjC7i1cBrG+DOh7egnQhT/Hj7qcT6xwrgkQiSEAQG4GpwDwPfapLCR2HlLIf4V6Xre+eU/cVs7BTjc3uIFC7i9Yj60HtglaUWf9xjGD2eAcUmISAgVArAHlDBAmIsK+b2yRc+YDwnrNktozF9dhJoXJkqSmXVrl0Dz7l9J6GnyMw13iCpMuga74H2ji4yW6EpwKcSOELwSNWUmJL4lPYfowmwxTZywd++4bazhjZXQ9MFGCU6LecG04r9IEiQSQhkAtNke3i/2MpNZ0Yp39OCaDF6vpH3GT4X+b+/Em23YhKBYSq9B9fPjMCCizXkujZteh4bNhRICzYBsMBz9AtQuiRIJoZ8A/OKXky/d6uQzQshT7CefV7LIJ38ESVs/BqJSieisFeVQhcbbmxtFcE+V5veLoH79KxK0BvOEaeiEsIwsCU4BgLe5xwEMHFYgQMRCT4aStx0E4hLJdVdXQtX0p8Bef1tE76vSsmsL1L22jJ7riUjD8ESIDgd6IBNQIcwRyu/sp8Z0zWVsa4QSoXpcslI/xoMLnV7Ebqu7BaapY6HbdFMEl1OJKpgN/TftwLna0Vl7uG3Ym2jmyVr2jRxxPC2Ozgt4tW4UrTil4pjdI1sSMtAzutSPjkuNb6zHzc/TARlP9Wkv3ge1P5sDnE18MVSN+cP41euVqEznJ6etTgdE9u27CxH1ciXqHx0DTESkiM3e2gxVmCG2fmsUwZVW7pw4wmeEOatVJMK1LkJ6qODaX0PUhr0OtNMBdEbE8SA7BWP+9zmwt7c55PHvVTN/CFbjVScsGC93z3wKNQsK8BSq5/I4HV4NRT1pNVnyCWwYVF6OEVhPcc4BtHp7eGJEW1vrTZx4xDnte8SeHtrcEZDwOgY9KjXU/3IFWPxIfHiS5QuuTkkD3fB8oIcxso/GCKnVkqQBmRUVzrM8kQNo42Xpunl2jg1dJtKXhSHEM4xqRnaF+ZCwCYkDKBIvGp7CXjBOSNjb33F++z1emZvsaodzDhAimDBmAS4V/x3YQmTvfG8KY9RukxFuHUD/mUFAtbh32irWGrs4h1mfhQNudtSJMT01tw6gqOxK80FkLuwh672/GEeuya60SrMu90xyOwcIzTWmafagkBeEsN7zTrYPNlkWedPXYw9wMGXHD8P5gJxy1HvLE3U+nvPYhCW+9PXpAPqfnOzM0RNx1+AMH30J/d/jyfbsRycUkOJiuy9dfA4BoQBjhq4Qz/nXCWEP0jud8HB382pOhbXIX71kOYAK5W+Sgn0b7hyj/W3kPtE1YzsvDTZZZeU2ZDuAGmPM1qeAhd314ARL5ISWUS/MrOiQl3RAWxQ5gDqBFho24/8JNuIBQ78eyP39xYmuDv/AtTLH1CXNq/upis9J0JucQZVdH0XHxGQwhKzklfFGHEwcbmqAIcvDk+MzAzGeqhRQDxDaRC9ad9Q2LASWv3D5kBAXrHcMz79ClT/UkKQPhTu6QOQHzQFCJfBa+hCcJKdh6mUazhNDhTgF71fwMxWHqcjhgTcspQr4vbKExAHCFqtyo+LMnZZ81o73cQDy8M9sBszK4F/nAf9Cz99NQF+RFsThVT2OPk30xAavtpfoQFuSbnK5eCgUHoT3/wBN3qXN5DteXQAAAABJRU5ErkJggg==',
      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+'条&nbsp'}}</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>
 
  
  

  

posted @ 2020-10-27 00:23  WEB前端工程师_钱成  阅读(9160)  评论(0编辑  收藏  举报