• 自动补全多标签输入, 适合新闻标签/商品标签


    2015年5月12日 20:16:48 星期二

    效果: 

    原理:

    1. 建立"单字"索引(倒排索引): 将汉字拆分成单个字, 作为redis hash 的一个键, 将所有包含该字的id作为hash的值

    2. 每次输入一个字, 就去redis里将该字的所有id取出来, 输入第二个字的时候, 取出第二个字的所有id与第一个字的id求交集

      求得同时包含这两个字的所有id, 再进一步获取id对应的信息并返回

    另外:

    1. 文中还建立了两个全词索引(redis hash), 一个是 {"完整汉字词语" : "id", .....} 另一个是 {"id" : "完整汉字词语" ,.......} 方便后续程序使用, 与这个自动补全关系不大

    2. 小细节, 每次点选词语的时候, js自动补上"," 而且ajax请求数据的时候, 只把最后一个","后边的用户输入词语发送给服务端

    话不多说, 上代码:

    js+css

      1 <script type="text/javascript">
      2 var ac_domain = 'http://'+document.domain+'/';
      3 initAutoComplete();
      4 function initAutoComplete()
      5 {
      6     var ac_input = document.getElementById('auto_complete_input');
      7     if (!ac_input) {return false;}
      8     var ac_rebuildindex = document.getElementById('rebuild_autocomplete_index');
      9 
     10     if (document.attachEvent) {
     11         ac_input.attachEvent('oninput', input_autocomplete); //输入时进行自动补全
     12         // ac_input.attachEvent('onblur', hide_autocomplete); //鼠标点击其它地方隐藏div, 屏蔽 因为onblur 和 onclick 冲突
     13         ac_input.attachEvent('onfocus', input_autocomplete); //输入框重新获取焦点时显示自动补全结果
     14         ac_input.attachEvent('onkeydown', input_autocomplete_keydown); //处理按键信息
     15         ac_rebuildindex.attachEvent('onclick', rebuild_autocomplete_index);
     16     } else {
     17         ac_input.addEventListener('input', input_autocomplete); //输入时进行自动补全
     18         // ac_input.addEventListener('blur', hide_autocomplete); //鼠标点击其它地方隐藏div, 屏蔽 因为onblur 和 onclick 冲突
     19         ac_input.addEventListener('focus', input_autocomplete); //输入框重新获取焦点时显示自动补全结果
     20         ac_input.addEventListener('keydown', input_autocomplete_keydown); //处理按键信息
     21         ac_rebuildindex.addEventListener('click', rebuild_autocomplete_index);
     22     }
     23 
     24     $(document).bind("click",function(e){
     25         var target  = $(e.target);//表示当前对象,切记,如果没有e这个参数,即表示整个BODY对象
     26         if(target.closest(".form-input-autocomplete,#auto_complete_input").length == 0){
     27            hide_autocomplete();
     28         }
     29      })
     30 }
     31 
     32 function input_autocomplete()
     33 {
     34     var ac_input = document.getElementById('auto_complete_input');
     35     var userinput = ac_input.value;
     36     if (!userinput) {return false;}
     37     var arr_new_word = userinput.split(/,s*/);
     38     var new_word = arr_new_word.pop();
     39 
     40     var url = ac_domain+'getAutoComplete?word='+new_word;
     41     $.get(url, 
     42         function(data) {
     43             if (!data) {return false;};
     44             var objGame = eval('('+data+')');
     45             html = '<ul>';
     46             for (var i in objGame) {
     47                 html += '<li class="auto_complete_li" data-id="'+i+'" data-name="'+objGame[i]+'">'+objGame[i]+'</li>';
     48             }
     49             html += '</ul>';
     50             var ac_div = $("#auto_complete_div");
     51             ac_div.show();
     52             ac_div.html(html);
     53             click_autocomplete_li(); //注册点击事件
     54         }
     55     );
     56 }
     57 
     58 function hide_autocomplete()
     59 {
     60     var ac_div = document.getElementById('auto_complete_div');
     61     ac_div.style.display = 'none';
     62 }
     63 
     64 function click_autocomplete_li()
     65 {
     66     var arr_ac_li = getElementsByClassName('auto_complete_li', 'ul');
     67     for (var i = 0; i < arr_ac_li.length; i++) {
     68         arr_ac_li[i].addEventListener('click', function () {
     69             //获取列表li上的数据, 并组装
     70             var data_id = this.getAttribute('data-id');
     71             var data_name = this.getAttribute('data-name');
     72             var data_info = data_id+'|'+data_name;
     73 
     74             ///////向输入框写数据
     75             var already_input = document.getElementById('auto_complete_input');
     76 
     77             //去掉用户的输入, 换做用户点击的li里的name
     78             var arr_already_input = already_input.value.split(/,s*/);
     79             arr_already_input.pop(); 
     80             arr_already_input.push(data_name);
     81 
     82             //去重
     83             var hash_already_input = {};
     84             for (var i in arr_already_input) {
     85                 hash_already_input[arr_already_input[i]] = arr_already_input[i];
     86             }
     87             arr_already_input = [];
     88             for (key in hash_already_input) {
     89                 arr_already_input.push(key);
     90             }
     91 
     92             //回写入表单
     93             var str_already_input = arr_already_input.join(", ");
     94             document.getElementById('auto_complete_input').value = str_already_input+", ";
     95 
     96             //如果只有一条匹配, 点击后就消失, 多条的话点击后不消失, 可以多次点选
     97             if (arr_ac_li.length == 1) {
     98                 hide_autocomplete();
     99             };
    100         })
    101     };
    102 }
    103 
    104 function rebuild_autocomplete_index()
    105 {
    106     var nodeParent = this.parentNode;
    107     var url = ac_domain+'buildTeamAutoComplete';
    108     $.get(url, 
    109         function(data) {
    110             nodeParent.innerHTML = '已经重建索引,再点击输入框试试';
    111         }
    112     );
    113 }
    114 
    115 function input_autocomplete_keydown(e)
    116 {
    117     switch(e.keyCode){
    118         case '8':
    119         //按下删除键
    120         input_autocomplete();
    121         break;
    122     };
    123 }
    124 
    125 function getElementsByClassName(className, tagName)
    126 {
    127     var t = typeof(document.getElementsByClassName);
    128 
    129     if (t == 'function') {
    130         return document.getElementsByClassName(className);
    131     }
    132 
    133     var tags, matchedTags;
    134     if (tagName) {
    135         tags = document.getElementsByTagName(tagName);
    136     } else {
    137         tags = document.getElementsByTagName('*');
    138     }
    139 
    140     matchedTags = [];
    141     for (var i = 0; i < tags.length; i++) {
    142         if (tags[i].className.indexOf(className) != -1) {
    143             matchedTags.push(tags[i]);
    144         }
    145     }
    146 
    147     return matchedTags;
    148 }
    149 </script>
    150 
    151 <style type="text/css">
    152     /*自动补全*/
    153     .form-input-autocomplete {padding: 0px;  200px; border: 1px solid #aaa; display: none; z-index: 99; position: absolute; background-color: #fff; filter: alpha(opacity=100); opacity: 1;}
    154     .form-input-autocomplete ul {margin: 0px;padding: 0px; text-align: left; list-style: none; font:15px;}
    155     .form-input-autocomplete ul li{margin: 3px;}
    156     .form-input-autocomplete ul li:hover{background-color: #eee; cursor:hand;}
    157 </style>
    View Code

    html (只用在原有的表单上加一个id, 并在下方添加一个隐藏的div, id/class如下)

    1 <input type="text" id="auto_complete_input" autocomplete="off" placeholder="新闻标签" value=""> <a href="javascript:;" id="rebuild_autocomplete_index">没找到? 点这里试试</a>
    2 <div class="form-input-autocomplete" id="auto_complete_div"></div>

    php (php+redis:hash)

     1     //创建索引
     2     public function dobuildTeamAutoComplete()
     3     {
     4         $oneWordIndex = 'autocomplete_zi'; //单字索引
     5         $allWordIndexByName = 'autocomplete_ci_name'; //全词索引, 键为全名(火箭:team_1_2)
     6         $allWordIndexById = 'autocomplete_ci_id'; //全词索引, 键名为id(team_1_2:火箭)
     7 
     8         $type = 'team_';
     9 
    10         //删除之前所有的旧值
    11         $this->redis->del($oneWordIndex);
    12 
    13         //重新构建
    14         $team = $this->redis->hgetall('LeagueTeamsNames');
    15 
    16         $oneWordInfo = array();
    17         $allWordInfoName = array();
    18         foreach ($team as $lid => $jsonTeam) {
    19             if ($lid != 8 && $lid != 12) {
    20                 $arrTeam = json_decode($jsonTeam, true);
    21                 foreach ($arrTeam as $name => $tid) {
    22                     $value = $type.$tid.'_'.$lid;
    23                     $allWordInfo[$name] = $value;
    24                     $length = mb_strlen($name, 'UTF-8');
    25                     for ($i=0; $i < $length; $i++) {
    26                         $strOne = mb_substr($name, $i, 1, 'UTF-8');
    27                         $oneWordInfo[$strOne][$value] = $value; //懒得去重了
    28                     }
    29                 }
    30             }
    31         }
    32         $oneWordInfoRedis = array();
    33         foreach ($oneWordInfo as $word => $ids) {
    34             $a = array_values($ids);
    35             $oneWordInfoRedis[$word] = json_encode($a);
    36         }
    37 
    38         //索引写入redis       
    39         $this->redis->hmset($oneWordIndex, $oneWordInfoRedis);
    40         $this->redis->hmset($allWordIndexByName, $allWordInfo);
    41         $this->redis->hmset($allWordIndexById, array_flip($allWordInfo));
    42 
    43         exit('over');
    44     }
    45 
    46     //自动补全请求处理函数
    47     public function dogetAutoComplete()
    48     {
    49         $oneWordIndex = 'autocomplete_zi'; //单字索引
    50         $allWordIndexById = 'autocomplete_ci_id'; //全词索引, 键名为id(team_1_2:火箭)
    51 
    52         $word = common::request('word', 'G');
    53 
    54         if (empty($word)) {
    55             exit('0');
    56         }
    57         $intWordLength = mb_strlen($word, 'UTF-8');
    58 
    59         $arrId = array();
    60         if (1 == $intWordLength) {
    61             $jsonId = $this->redisSlave->hget($oneWordIndex, $word);
    62             $arrId = json_decode($jsonId, true);
    63         } else {
    64             for ($i=0; $i < $intWordLength; $i++) {
    65                 $strOne = mb_substr($word, $i, 1, 'UTF-8');
    66                 $jsonIdTmp = $this->redisSlave->hget($oneWordIndex, $strOne);
    67                 $arrIdTmp = json_decode($jsonIdTmp, true);
    68 
    69                 $arrIdTmp = $arrIdTmp ? $arrIdTmp : array();
    70                 $arrId = empty($arrId) ? $arrIdTmp : $arrId;
    71                 $b = array_intersect($arrId, $arrIdTmp);
    72                 $arrId = empty($b) ? $arrId : $b; // 新输入的词如果跟之前的没有交集, 就维持原来的交集不变
    73             }
    74         }
    75         $arrId = empty($arrId) ? array() : $arrId;
    76 
    77         //获取球队信息
    78         $arrInfo = array();
    79         foreach ($arrId as $v) {
    80             $arrInfo[$v] = $this->redisSlave->hget($allWordIndexById, $v);
    81         }
    82 
    83         $jsonInfo = json_encode($arrInfo);
    84         exit($jsonInfo);
    85     }
  • 相关阅读:
    RESTful API 设计指南
    SpringBoot系列
    spring boot RESTFul
    mark--唧唧歪歪--2021.2.8
    接口踩坑:Status (blocked:other)
    获取layer.open弹出层的返回值
    VUE_CLI4.5 VUE+Cesium
    Android Studio AVD 模拟器 自带虚拟机 联网 连接网络
    一文搞懂TCP与UDP的区别
    springboot打包成war,部署到tomcat无法访问的问题
  • 原文地址:https://www.cnblogs.com/iLoveMyD/p/4498486.html
Copyright © 2020-2023  润新知