在Angular中实现一个级联效果的下拉框的示例代码

(编辑:jimmy 日期: 2024/12/31 浏览:2)

实现一个具有级联效果的下拉搜索框,实现的结果如下图所示

在Angular中实现一个级联效果的下拉框的示例代码

我们主要通过这个组件,来学习一些细微的逻辑,比如: 如何计算input框内文字的长度; 如何获取光标的位置;如何实现滚动条随着上下键盘的按动进行移动......

具体需求如下

  1. 级联搜索最多不超过三级,以”.“作为级联搜索的连接符
  2. 搜索框跟着文本框中的”.“进行向后移动,向右移动的最大距离不能超过文本框的宽度
  3. 当用户修改之前的级联内容,则不进行搜索,并隐藏搜索框;若用户在之前输入的是”.“, 则将此”.“之后的内容全部删除并搜索当前的相关内容

接下来我们根据需求,来写我们的逻辑

首先我们搭建html页面

   <input
    #targetInput
    autocomplete="off"
    nz-input
    [(ngModel)]="searchValue"
    (keydown)="handlePress($event)"
    (input)="handleSearchList()"/>
   
   <div #searchList class="search-popup" [hidden]="!visible" (keyDown)="onKeydown($event)">
     <nz-spin [nzSpinning]="searchLoading" [class.spinning-height]="searchLoading">
      <div class="data-box" *ngIf="searchData && searchData.length !== 0">
       <ul>
       // 这里在上篇文章中已经讲解过,如何实现让匹配的文字高亮显示~
        <li
         id="item"
         *ngFor="let item of searchData;let i = index;"
         [class.item-selected]="curIndex === i"
         (mouseover)='hoverDataItem(i)'
         (click)="onSelectClick(item)">
         <span [innerHTML]="item | highlightSearchResult:searchValue | safe: 'html'"></span>
        </li>
       </ul>
      </div>
     </nz-spin>
   </div>
.search-popup {
 height: 376px;
 width: 246px;
 overflow-y: auto;
 box-shadow: 0 2px 8px rgba(0,0,0,.15);
 border-radius: 4px;
 position: absolute;
 background-color: #fff;
 z-index: 999;
 top: 92px;
 right: 61px;

 .data-box {
  margin: 0 10px;

  &:not(:last-child) {
   border-bottom: 1px solid #E4E5E7;
  }

  .no-search-data {
   display: inline-block;
   width: 100%;
   text-align: center;
   color: #C3C9D3;
   line-height: 40px;
  }
 }

 & ul {
  margin: 0 -10px;
  margin-bottom: 0;
  text-align: left;
 }

 & li {
  padding: 3px 10px;
  position: relative;
  list-style: none;
  height: 32px;
  line-height: 26px;
  &:hover {
   cursor: pointer;
   background-color: #e6f7ff;
  }
  &.item-selected {
   background-color: #E6F7FF;
  }
 }

 &.item-selected {
  background-color: #E6F7FF;

 }

.hidden-box {
 display: inline-block;
 border: 1px solid #ddd;
 visibility: hidden;
}

实现相关的逻辑

根据前两个需求,我们需要根据文本框中的”.“进行向后移动,向右移动的最大距离不能超过文本框的宽度。

思路: 我们需要将文本框中的字符串根据”.“来转换成数组,并且要想办法获取文本框中文字的长度。
如何获取文本框中文字的长度呢?
我们可以将文字的内容,重新放到一个display: inline-block的div容器中,然后获取容器的宽度,如下代码所示~

// html
 <!-- 用于测量input框的文字宽度 -->
 <div class="hidden-box" #firstLevel></div> // 以”.“转化的数组,下标为0的内容的宽度
 <div class="hidden-box" #secondLevel></div> // 以”.“转化的数组,下标为1的内容的宽度
 <div class="hidden-box" #allLevel></div> // 整个文本框的文字的宽度
 
 // ts
 import { ElementRef, Renderer2 } from '@angular/core';
 
 export class SearchListComponent {
  @ViewChild('searchList', { static: true }) public searchList: ElementRef;
  @ViewChild('firstLevel', { static: true }) public firstLevel: ElementRef;
  @ViewChild('secondLevel', { static: true }) public secondLevel: ElementRef;
  @ViewChild('allLevel', { static: true }) public allLevel: ElementRef;
  constructor(private _renderer: Renderer2) {}
     
  public setSearchPosition(rightValue: string): void {
    this._renderer.setStyle(
     this.searchList.nativeElement,
     'right',
     rightValue);
   } 
   
  public setSearchListPosition(targetValue: string): void {
  const inputWidth = 217;
  const defaultRightPosition = 60;
  const maxRightPosition = -148;
  const firstLevel = this.firstLevel.nativeElement;
  const secondLevel = this.secondLevel.nativeElement;
  const allLevel = this.allLevel.nativeElement;
  const targetValueArr = targetValue "htmlcode">
 // 获取光标的位置
 public getCursorPosition(element: HTMLInputElement): number {
  let cursorPosition = 0;
  if (element.selectionStart || element.selectionStart === 0) {
   cursorPosition = element.selectionStart;
  }
  return cursorPosition;
 }
 
 // 用来获取用户输入的内容是什么
 public handlePress(event: KeyboardEvent): void {
   this.curPressKey = event.key;
  }

 // 用户input的时候调用的核心方法
 public handleSearchList(value: string): void {
  this.curIndex = 0;
  const cursorPosition = this.getCursorPosition(this.targetInput.nativeElement); // 获取光标位置
  let targetValue = value;
  const targetValueArr = targetValue "htmlcode">
 public onKeydown(keyDownInfo: {index: number, code: number, e: KeyboardEvent}): void {
  const { code, e } = keyDownInfo;
  e.stopPropagation();
  if (code === 38) { // 键盘上
   e.preventDefault(); // 防止光标由最后边移动到前边,只是在开发中遇到的一点体验上小问题
   if (this.curIndex > 0) {
    this.curIndex--;
   }
  } else if (code === 40) { // 键盘下
   if (this.curIndex < this.searchData.length - 1) {
    this.curIndex++;
   }
  } else if (code === 13) {  // 回车,即相当于用户点击
   this.ruleModal.showModal();
   const curData = this.searchData[this.curIndex];
   if (curData) {
    this.onSelectClick(curData);
   }
  }
  // 实现下拉框的滚动条随着键盘的上下键按动时一起移动
  const lis = document.querySelectorAll('#item');
  const curLiEle = lis[this.curIndex] as HTMLElement;
  const searchList = this.searchList.nativeElement;
  const liEleHeight = 32;
  //(当前选中li标签的offsetTop + li标签自身的高度 - 下拉框的高度)
  searchList.scrollTop = curLiEle.offsetTop + liEleHeight - searchList.clientHeight;
 }

总结

其实这个级联搜索的组件,他的通用性可能并不是很强,但是在实现的过程中,一些细节逻辑的通用性还是比较强的~ 希望这些细节可以给同在开发中的你带来一些帮助~❤

一句话新闻

一文看懂荣耀MagicBook Pro 16
荣耀猎人回归!七大亮点看懂不只是轻薄本,更是游戏本的MagicBook Pro 16.
人们对于笔记本电脑有一个固有印象:要么轻薄但性能一般,要么性能强劲但笨重臃肿。然而,今年荣耀新推出的MagicBook Pro 16刷新了人们的认知——发布会上,荣耀宣布猎人游戏本正式回归,称其继承了荣耀 HUNTER 基因,并自信地为其打出“轻薄本,更是游戏本”的口号。
众所周知,寻求轻薄本的用户普遍更看重便携性、外观造型、静谧性和打字办公等用机体验,而寻求游戏本的用户则普遍更看重硬件配置、性能释放等硬核指标。把两个看似难以相干的产品融合到一起,我们不禁对它产生了强烈的好奇:作为代表荣耀猎人游戏本的跨界新物种,它究竟做了哪些平衡以兼顾不同人群的各类需求呢?