missgentle / Q-A

Web前端技术分享与疑问解答
0 stars 1 forks source link

如何实现@某人的功能? #28

Open missgentle opened 4 years ago

missgentle commented 4 years ago

@功能描述:

  • 1 输入区以“@”结束时弹出人员选择框,否则关闭弹框
  • 2 选中人员后,弹框关闭,删除用户原本输入的"@",添加span标签包裹的 "@某人"
  • 3 删除 "@某人" 要整体删除
  • 4 弹框中已被@过的人员不再显示,删除@后恢复显示
missgentle commented 4 years ago

参考链接:https://segmentfault.com/a/1190000007846897?utm_source=tag-newest

missgentle commented 4 years ago

首先,输入区需要使用contenteditable="true"的div标签。

<div id="inputs" class="textarea fl" style="width:85%;height: 70px" contenteditable="true"></div>
<div id="atCheckDiv" class="atMenubox">
    <select id="atMemberList" multiple="multiple" class="atMenu"> 
    </select>
</div>

然后,代码中动态填充@弹框的人员列表。

let roomOnlineMember = [];
var frag = document.createDocumentFragment();
 for(let y = 0; y<groupData.length; y++){
       if(groupData[y].roomId == this.MRoomId){
             for(let m = 0; m < onlineList.length; m++){
                  for(let n = 0; n < groupData[y].roomGroup.length; n++){
                    if(onlineList[m].id === groupData[y].roomGroup[n].id && onlineList[m].id !== UserInfo.userId){
                      roomOnlineMember.push({id:onlineList[m].id,name:onlineList[m].name});
                      if(!this.MAtPersonList.includes(onlineList[m].id)){
                        frag.appendChild(new Option(onlineList[m].name,onlineList[m].id));
                      }
                    }
                  }
                }
            this.MRoomOnlineMember = roomOnlineMember;
            if(frag.children.length > 1){
                 frag.insertBefore(new Option("所有人","everyone"),frag.firstChild);
            }
           $("#atMemberList").empty().append(frag);
           for(let i = 0; i < this.MAtPersonList.length; i++){
               $("#atMemberList option[value='"+ this.MAtPersonList[i] +"']").hide();
          }
    }
}

然后,写一个input事件监听来弹框。

$('#inputs').on('input', (e) => { this.onInputChangeHandler(e); });
onInputChangeHandler(e:Event){
    if($(e.target).text().endsWith('@') ){

      if(!$("#atMemberList option").length) return;

      for(let i = 0; i < this.MAtPersonList.length; i++){
        if(this.MAtPersonList[i]=="everyone") return;
        $("#atMemberList option[value='"+ this.MAtPersonList[i] +"']").hide();
      }

      // 保存选区以及光标信息,用于获取在光标焦点离开前,光标的位置
      this.MSelection = window.getSelection();
      this.MRange = document.createRange();
      this.MRange.setStart(this.MSelection.anchorNode, this.MSelection.anchorOffset);
      this.MRange.setEnd(this.MSelection.focusNode, this.MSelection.focusOffset);

      // 设置弹出框位置
      var offset = this.MRange.getBoundingClientRect();

      $('#atCheckDiv').css('display','block')
      .css('left',offset.left + 'px')
      .css('top',offset.top-100 + 'px');

    }else{
      $('#atCheckDiv').hide();
    }
  }

再然后是选中@人员的逻辑,向输入区的div中插span标签。

$('#atMemberList').on('change', (e) => { this.onAtCheckListChangeHandler(e);});
onAtCheckListChangeHandler(e:Event){
    this.MAtPersonList.push($('#atMemberList option:selected').val());

    // 删除 @ 符号。
    var textNode = this.MRange.startContainer;
    this.MRange.setStart(textNode, this.MRange.startOffset - 1);
    this.MRange.setEnd(textNode, this.MRange.endOffset);
    this.MRange.deleteContents();

    // 生成需要显示的内容,包括一个 span 和一个空格。
    var spanNode1 = document.createElement('span');
    var spanNode2 = document.createElement('span');
    spanNode1.className = 'at-text';
    spanNode1.setAttribute('id', $('#atMemberList option:selected').val());
    spanNode1.innerHTML = '@' + $('#atMemberList option:selected').text();
    spanNode2.innerHTML = ',';

    // 将生成内容打包放在 Fragment 中,并获取生成内容的最后一个节点,也就是空格。
    var frag = document.createDocumentFragment(),
        node, lastNode;
    frag.appendChild(spanNode1);
    while ((node = spanNode2.firstChild)) {
        lastNode = frag.appendChild(node);
    }

    // 将 Fragment 中的内容放入 range 中,并将光标放在空格之后。
    this.MRange.insertNode(frag);
    this.MSelection.extend(lastNode, 1);
    this.MSelection.collapseToEnd();

    $('#atMemberList').val('');
    $('#atCheckDiv').hide();
  }

最后是删除@某人的逻辑代码,我写了一个keydown事件监听。

$(document).on('keydown',(e) => { this.onKeydownHandler(e);});
onKeydownHandler(e){
    if ($('#inputs').is(':focus') && e.keyCode == 8) {
        var selection = window.getSelection();
        var range = document.createRange();
        range.setStart(selection.anchorNode, selection.anchorOffset);
        range.setEnd(selection.focusNode, selection.focusOffset);

        // 删除逻辑 
        // 1 :由于在创建时默认会在 @xxx 后添加一个空格,
        // 所以当得知光标位于 @xxx 之后的一个第一个字符后并按下删除按钮时,
        // 应该将光标前的 @xxx 给删除
        // 2 :当光标位于 @xxx 中间时,按下删除按钮时应该将整个 @xxx 给删除。

        var removeNode = null;
        if (range.startOffset <= 1 && range.startContainer.parentElement.className != "at-text")
          removeNode = range.startContainer.previousSibling;
        if (range.startContainer.parentElement.className == "at-text")
          removeNode = range.startContainer.parentElement;
        if(removeNode && removeNode.id){
          for(let i = 0; i < this.MAtPersonList.length; i++){
            if(this.MAtPersonList[i] == removeNode.id){
              this.MAtPersonList.splice(i,1);
              $("#atMemberList option[value='" + removeNode.id +"']").show();
            }
          }
          removeNode.remove();
        }
      }
  }