Vue 通过localStorage使用字符串数组队列实现全局搜索功能(内容有点大,请你忍一下)

606 阅读6分钟

一、需求

1.使用浏览器localStorage,开发网站全局搜索功能。

2.搜索历史不超8条,超过8条依次覆盖时间最久的历史搜索记录

3.新输入的搜索记录,下次input不输入内容focus时应该展示在第一条。

4.新输入的搜索记录回车搜索后,如果之前历史搜索记录中存在,下次input不输入内容focus时这把这条记录放置历史记录展示页面最上面第一条

5.input 在输入内容时不展示搜索记录

  1. 搜索记录不能有重复值

  2. 一键清除

  3. 对特殊字符过滤

大致开发思维:   使用数组队列来存储历史记录,靠近队首的元素为较晚输入的历史,靠近队尾的元素为最新输入的历史,当页面需要显示搜索历史时需要从队尾到队首依次循环显示存储的元素(搜索历史)。

  使用unshift函数向队尾插入元素(搜索历史),pop函数为删除队首元素(搜索历史)。其中如果输入的元素已经在队列里面的,需要使用filter函数把队列里面的元素移除掉,把新输入的元素放入队尾,从而达到了数组去重和最新输入的元素(搜索历史)在下拉框中的最上面显示。

  在向localStorage中读取和写入数组队列时需要JSON.parse()函数把字符串解析为数组队列以供后续读取需要和JSON.stringify()函数把数组转为字符串存入浏览器中,在使用JSON.parse()函数解析字符串数组队列时通过正则表达式和一些限制条件来验证获取到的字符串数组队列是否符合使用条件,如果符合进行JSON.parse()函数解析。注意:不如不判断是否符合使用条件直接使用JSON.parse()函数解析会导致程序报错。本篇博客数组队列的格式如下 ==["元素1","元素2",...]==不符合该格式程序不执行。从而避免了用户手动更改localStorage中的内容。

==功能重难点如下==

①:JSON.stringify()/JSON.parse() ②:正则表达式匹配 ③:进出队列 ④:队列去重 ⑤:搜索历史下拉展示

二、效果

请添加图片描述

三、开发步骤

该全局搜索类似==掘金==的全局搜索。话不多说,来看一下完成该功能需要的开发步骤

①:静态页面和样式编写 ②:input绑定处理函数 ③:下拉框绑定点击事件 ④:一键清除历史点击事件 ⑤:添加localStorage相关操作

四、开发

① 静态页面和样式编写

首先开始静态页面和less样式编写

<template>
  <!-- 全局搜索 -->
  <div class="eo-globalSearch eo-g-df eo-g-pr">
    <!-- 全局搜索->input -->
    <input
      v-model="searchVal"
      class="eogs-search"
      type="text"
      placeholder="请输入"
      maxlength="20"
    />
    <!-- 全局搜索->icon -->
    <span class="iconfont eo-icon-search"></span>
    <!--全局搜索->下拉-->
    <div
      ref="searchDropdown"
      class="eogs-dropdown eo-g-pa"
    >
      <ul ref="searchDropdownRecord" class="eogs-dropdown-history">
        <li>
          这里是显示历史搜索记录的地方
        </li>
        <li
          class="eogs-dropdown-history-clear"
        >
          清空浏览记录
        </li>
      </ul>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';

@Component({})
export default class EoGlobalSearch extends Vue {
	searchVal = ''; //搜索的值
}
</script>
<style scoped lang="less">
//全局搜索
.eo-globalSearch {
  align-items: center;
  justify-content: space-between;
  width: 200px !important;
  height: 30px;
  margin-right: 48px;
  color: #bababa;
  background: #63a9dd;
  border-radius: 4px;
}

//全局搜索->input
.eogs-search {
  width: 70%;
  height: 80%;
  margin-left: 15px;
  font-family: @font-family;
  font-size: 14px;
  color: #fff;
  background: #63a9dd;
  border: none;
  outline: none;
}

//全局搜索->placeholder
.eogs-search::-webkit-input-placeholder {
  color: rgba(255, 255, 255, 60);
}

//搜索icon(项目中已经引入iconfont,所以直接使用了eo-icon-search图标类)
.eo-icon-search {
  margin-right: 23px;
  font-size: 14px;
  color: rgba(255, 255, 255, 60);
  cursor: pointer;
}

//全局搜索->搜索下拉
.eogs-dropdown {
  top: 34px;
  width: 200px;
  overflow: hidden;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  transition: height cubic-bezier(0.645, 0.045, 0.355, 1) 0.3s;
}

//全局搜索->搜索下拉->历史->item
.eogs-dropdown-history-item {
  box-sizing: content-box;
  height: 20px;
  padding: 10px;
  overflow: hidden;//超过一行隐藏
  font-size: 14px;
  color: rgba(102, 102, 102, 100);
  text-overflow: ellipsis;//文字过多...显示
  white-space: nowrap;//一行显示
  cursor: pointer;

  &:hover {
    color: #2888d1;
    background-color: #e9eff5;
  }
}

//全局搜索->搜索下拉->历史->clear
.eogs-dropdown-history-clear {
  box-sizing: content-box;
  height: 20px;
  padding-bottom: 10px;
  padding-left: 10px;
  font-size: 14px;
  color: #2888d1;
  cursor: pointer;
}
</style>

==注意:样式是LESS编写的。因为代码注释比较详细所以就不多加注释了== eo-g-df 类是 display:flex ,eo-g-pr类是position:relative,eo-g-pa类是position:absoute 。因为这些类是通过全局引入的所以样式编写中并没有提到。

效果图

在这里插入图片描述

②:input绑定处理函数

给input框绑定focus、blur和keyup.enter事件。focus、blur和keyup.enter的事件处理函数中因为有队列的相关操作,所以函数体中的代码只是示意性代码,在文章后面会有整体代码,大家先有个大概了解

添加 input :focus事件

添加focus事件所需要的前提准备

<input
      v-model="searchVal"
      class="eogs-search"
      type="text"
      placeholder="请输入"
      maxlength="20"
      @focus="searchFocHandle"
    />
    
<div
  ref="searchDropdown"
  :style="{ height: searchDropdownHeight + 'px' }"
  class="eogs-dropdown eo-g-pa"
>
  <ul ref="searchDropdownRecord" class="eogs-dropdown-history">
    <li>
      这里是显示历史搜索记录的地方
    </li>
    <li
      class="eogs-dropdown-history-clear"
    >
      清空浏览记录
    </li>
  </ul>
</div>

==注:input添加focus事件的同时也在其他元素中添加了两个ref, 分别是searchDropdown和searchDropdownRecord使用它们是为了当input focus时显示历史下拉框高度,所以还声明了searchDropdownHeight 变量用来存储高度值==

添加searchFocHandle 函数用来处理input focus事件

searchDropdownHeight = 0;
/**
   * @description searchFocHandle 全局搜索focus处理函数
   * @returns { void }
   */
  searchFocHandle(): void {
    if (this.searchVal) {
      return; //当搜索框中有值时,后续不执行
    }
    
	//显示下拉框
    this.$nextTick(() => {
      this.$set(
        this,
        'searchDropdownHeight',
        (this.$refs.searchDropdownRecord as HTMLBaseElement).clientHeight
      );
    });
  }

同时还需要注意,当input 处于输入状态时 下拉框不显示

watch: {
    searchVal: function (newVal: string) {
      if (newVal) {
        this.$nextTick(() => {
          this.$set(
            this,
            'searchDropdownHeight',
            0
          );
        });
        return;
      }
    }
  }

添加 input :blur事件

<input
  v-model="searchVal"
  class="eogs-search"
  type="text"
  placeholder="请输入"
  maxlength="20"
  @focus="searchFocHandle"
  @blur="searchDropdownHeight = 0"
/>

<!--全局搜索->下拉-->
<div
   ref="searchDropdown"
   :style="{ height: searchDropdownHeight + 'px' }"
   class="eogs-dropdown eo-g-pa"
 >
 </div>

当input 失去焦点时searchDropdownHeight 为0,表示下拉框隐藏。

效果图

请添加图片描述

添加 input :keyup.enter事件

<input
v-model="searchVal"
class="eogs-search"
type="text"
placeholder="请输入"
maxlength="20"
@focus="searchFocHandle"
@blur="searchDropdownHeight = 0"
@keyup.enter="searchHandle"
/>
<!-- 全局搜索->icon -->
<span class="iconfont eo-icon-search" @click="searchHandle"></span>

因为搜索icon 点击也能搜索所以也绑定点击事件

/**
 * @description searchHandle 回车或点击icon搜索处理函数
 * @returns { void }
 */
searchHandle(): void {
  if (this.searchVal === '' || this.searchVal.trim() === '') {
    return;
  }

  location.href = `xxxxx?query=${(encodeURIComponent(this.searchVal.trim().substring(0,30)))}`;//url跳转
}

③:下拉框绑定点击事件

为下拉框添加点击事件(除一键清空搜索记录外) 在这里插入图片描述

<li
class="eogs-dropdown-history-item"
@mousedown="historyItemHandle(item)"
>
这里是显示历史搜索记录的地方
</li>
/**
   * @description historyItemHandle 搜索历史点击事件处理函数
   * @param { record } 要搜索的值
   * @returns { void }
   */
  historyItemHandle(record: string): void {
    this.$set(this, 'searchVal', record);//input 回显

    if (!this.searchVal.trim()) {
      return; //searchVal 无值时 return
    }
    location.href = ``xxxxx?query=${(encodeURIComponent(this.searchVal.trim().substring(0,30)))}`;
  }

==注:这里是添加的mousedown事件,而不是click事件==,这是为什么呐?这是因为如果在这里添加了click事件,当input 失焦时blur事件执行要先于click事件,从而导致点击事件还没有生效下拉框就隐藏掉了。所以使用mousedown事件,mousedown先于input的blur事件执行

效果图

请添加图片描述

④:一键清除历史点击事件

<li
   class="eogs-dropdown-history-clear"
   @mousedown="clearHistoryHandle"
 >
   清空浏览记录
 </li>
/**
   * @description clearHistoryHandle 一键清除搜索历史记录
   * @returns { void }
   */
  clearHistoryHandle(): void {
    this.$nextTick(() => {
      this.$set(this, 'searchDropdownHeight', 0); //收起历史下拉
    });
  }

⑤:添加localStorage相关操作

声明 searchHistory: string[] = []; //搜索历史 变量用于下拉框回显

searchHistory: string[] = []; //搜索历史

完整版html

<!-- 全局搜索 -->
  <div class="eo-globalSearch eo-g-df eo-g-pr">
    <!-- 全局搜索->input -->
    <input
      v-model="searchVal"
      class="eogs-search"
      type="text"
      placeholder="请输入"
      maxlength="20"
      @focus="searchFocHandle"
      @blur="searchDropdownHeight = 0"
      @keyup.enter="searchHandle"
    />
    <!-- 全局搜索->icon -->
    <span class="iconfont eo-icon-search" @click="searchHandle"></span>
    <!--全局搜索->下拉-->
    <div
      ref="searchDropdown"
      :style="{ height: searchDropdownHeight + 'px' }"
      class="eogs-dropdown eo-g-pa"
    >
      <ul ref="searchDropdownRecord" class="eogs-dropdown-history">
        <!-- mousedown事件早于blur触发 防止下拉隐藏导致事件未触发-->
        <li
          v-for="(item, key) in searchHistory"
          :key="key"
          class="eogs-dropdown-history-item"
          @mousedown="historyItemHandle(item)"
        >
          {{ item }}
        </li>
        <li
          v-show="searchHistory.length > 0"
          class="eogs-dropdown-history-clear"
          @mousedown="clearHistoryHandle"
        >
          清空浏览记录
        </li>
      </ul>
    </div>
  </div>

补充searchFocHandle 函数

/**
   * @description searchFocHandle 全局搜索focus处理函数
   * @returns { void }
   */
  searchFocHandle(): void {
    if (this.searchVal) {
      return; //当搜索框中有值时,后续不执行
    }

    let getRecords = localStorage.getItem('searchHistory')?.trim() || JSON.stringify([]);

    if (this.checkHistory(getRecords) === false) {
      return; //核验不通过
    }

    let records: any = JSON.parse(getRecords);
    this.$set(this, 'searchHistory', records);
    this.$nextTick(() => {
      this.$set(
        this,
        'searchDropdownHeight',
        (this.$refs.searchDropdownRecord as HTMLBaseElement).clientHeight
      );
    });
  }


补充searchHandle函数

/**
   * @description searchHandle 回车或点击icon搜索处理函数
   * @returns { void }
   */
  searchHandle(): void {
    if (this.searchVal === '' || this.searchVal.trim() === '') {
      return;
    }

    let getRecords = localStorage.getItem('searchHistory')?.trim() || JSON.stringify([]);
    if (this.checkHistory(getRecords) === false) {
      return; //核验不通过
    }
    let records: any = JSON.parse(getRecords);

    if (records.includes(this.searchVal.trim())) {
      records = records.filter((item: string) => {
        return item !== this.searchVal.trim(); //队列去重
      });
    }

    records.unshift(this.searchVal.trim()); //向队尾添加元素

    if (records.length > 10) {
      records.pop(); //超过10条,移除
    }

    localStorage.setItem('searchHistory', JSON.stringify(records));
    location.href = `xxxxx?query=${(encodeURIComponent(this.searchVal.trim().substring(0,30)))}`;
  }

补充historyItemHandle函数

historyItemHandle(record: string): void {
    this.$set(this, 'searchVal', record);
    let records = [...this.searchHistory]; //获取记录

    if (!this.searchVal.trim()) {
      return; //searchVal 无值时 return
    }

    if (records.includes(this.searchVal.trim())) {
      records = records.filter((item: string) => {
        return item !== this.searchVal.trim(); //队列去重
      });
    }
    records.unshift(this.searchVal.trim()); //向队尾添加元素
    localStorage.setItem('searchHistory', JSON.stringify(records));
    location.href = `xxxxx?query=${(encodeURIComponent(this.searchVal.trim().substring(0,30)))}`;
  }

补充clearHistoryHandle函数

clearHistoryHandle(): void {
    if (this.searchHistory.length === 0) {
      return;
    }
    this.$nextTick(() => {
      this.$set(this, 'searchDropdownHeight', 0); //收起历史下拉
    });
    localStorage.removeItem('searchHistory'); //删除searchHistory
    this.$set(this, 'searchHistory', []); //清空存储历史变量
  }

补充watch监听

watch: {
    searchVal: function (newVal: string) {
      if (newVal) {
        this.$set(this, 'searchHistory', []);
        this.$nextTick(() => {
          this.$set(
            this,
            'searchDropdownHeight',
           0
          );
        });
        return;
      }
      (this as any).searchFocHandle(); //调用searchFocHandle函数
    }
  }

checkHistory 函数

/**
   * @description checkHistory 核实searchHistory正确性
   * @param { string } records
   * @returns { boolean } true:无误
   */
  checkHistory(records: string): boolean {
    if (records === '') {
      return false;
    }
    if (typeof records !== 'string') {
      return false;
    }

    records = records.trim(); //除去首尾空格

    if (records.charAt(0) !== '[') {
      return false; //不是以'['开头的字符串数组
    }
    if (records.charAt(records.length - 1) !== ']') {
      return false; //不是以'['结尾的字符串数组
    }

    let regResult;
    let resultArr = '[';
    let reg = /\"(.*?)\"/g;
    while ((regResult = reg.exec(records)) !== null) {
      resultArr+=`"${regResult[1]}",`;
    }
    if(resultArr.length>1&&resultArr.charAt(0)==='['){
      resultArr=resultArr.substr(0,resultArr.length-1);
    }
    resultArr+=']';
    return resultArr.length === records.length ? true : false;
  }

分析

通过localStorage.getItem('searchHistory')获取的历史记录有四种情形 1:无值 2:有值,但不是‘["1","2",...]’这种符合要求的格式 3.有值,符合‘["1","2",...]’要求,但是有空格例如‘["1","2",...]   ’或者‘   ["1","2",...]’ 4:有值,完全符合要求

1:无值时:执行JSON.stringify([]),得到字符串数组

let getRecords = localStorage.getItem('searchHistory')?.trim() || JSON.stringify([]);

2:有值,但不是‘["1","2",...]’这种符合要求的格式

let getRecords = localStorage.getItem('searchHistory')?.trim() || JSON.stringify([]);

if (this.checkHistory(getRecords) === false) {
 return; //核验不通过
}

通过 checkHistory函数检测 如果不符合格式要求就return,后续内容不执行

这里说一下checkHistory函数 checkHistory函数是用来检测通过getItem获取的值是否符合要求 函数体中 首先就是检测 是否为空和不是字符串的清空

if (records === '') {
  return false;
}
if (typeof records !== 'string') {
  return false;
}

接着就是去掉字符串首尾空格,后续检测字符串是否以'['开头和']'结尾

records = records.trim(); //除去首尾空格

if (records.charAt(0) !== '[') {
 return false; //不是以'['开头的字符串数组
}
if (records.charAt(records.length - 1) !== ']') {
  return false; //不是以'['结尾的字符串数组
}

然后就是使用正则表达式循环匹配出符合要求的元素(搜索记录),放入数组中,最后通过JSON.stringify字符串化和原始的内容长度比较是否相等,相等了说明是符合要求的字符串数组队列。

let regResult;
let resultArr = [];
let reg = /\"(.*?)\"/g;
while ((regResult = reg.exec(records)) !== null) {
   resultArr+=`"${regResult[1]}",`;//这样的写法是为了处理特殊字符
 }
 if(resultArr.length>1&&resultArr.charAt(0)==='['){//这样的写法是为了处理特殊字符
   resultArr=resultArr.substr(0,resultArr.length-1);//这样的写法是为了处理特殊字符
 }//这样的写法是为了处理特殊字符
 resultArr+=']';//这样的写法是为了处理特殊字符
 return resultArr.length === records.length ? true : false;

==注意:/"(.*?)"/g 需要存入一个变量内,不能写成下面的样子,这样会造成死循环==

while ((regResult = /"(.*?)"/g .exec(records)) !== null) {}

3.有值,符合‘["1","2",...]’要求,但是有空格例如‘["1","2",...]   ’或者‘   ["1","2",...]’

let getRecords = localStorage.getItem('searchHistory')?.trim() || JSON.stringify([]);

if (this.checkHistory(getRecords) === false) {
 return; //核验不通过
}

因为在获取值的时候通过trim()函数除去首尾空格,只要checkHistory检测通过就可以执行后续内容

4:有值,完全符合要求。这个不用多说了

接着使用JSON.parse 把字符串数组解析成为数组队列

let records: any = JSON.parse(getRecords);

当有新的元素(搜索历史)入队时,需要检测队列中是否有该元素(搜索记录)如果有就去重,如果没有就直接入队

if (records.includes(this.searchVal.trim())) {
  records = records.filter((item: string) => {
    return item !== this.searchVal.trim(); //队列去重
  });
}

使用includes 函数检测数组中是否有相同元素(搜索记录),有的话通过filter函数把和输入值(搜索记录)不同的元素(搜索记录)放到数组中并赋值给原来的数组中(records)

使用unshift函数添加元素(搜索记录)到队尾

records.unshift(this.searchVal.trim()); //向队尾添加元素

最后判断队列元素搜索记录)中是否超过10个,超过就用pop函数把队首元素删除掉。然后就可以把值通过JSON.stringify存入localStorage

if (records.length > 10) {
   records.pop(); //超过10条,移除
 }
localStorage.setItem('searchHistory', JSON.stringify(records));
location.href = `xxxxx?query=${(encodeURIComponent(this.searchVal.trim().substring(0,30)))}`;

四、完整代码

<template>
  <!-- 全局搜索 -->
  <div class="eo-globalSearch eo-g-df eo-g-pr">
    <!-- 全局搜索->input -->
    <input
      v-model="searchVal"
      class="eogs-search"
      type="text"
      placeholder="请输入"
      maxlength="30"
      @focus="searchFocHandle"
      @blur="searchDropdownHeight = 0"
      @keyup.enter="searchHandle"
    />
    <!-- 全局搜索->icon -->
    <span class="iconfont eo-icon-search" @click="searchHandle"></span>
    <!--全局搜索->下拉-->
    <div
      ref="searchDropdown"
      :style="{ height: searchDropdownHeight + 'px' }"
      class="eogs-dropdown eo-g-pa"
    >
      <ul ref="searchDropdownRecord" class="eogs-dropdown-history">
        <!-- mousedown事件早于blur触发 防止下拉隐藏导致事件未触发-->
        <li
          v-for="(item, key) in searchHistory"
          :key="key"
          class="eogs-dropdown-history-item"
          @mousedown="historyItemHandle(item)"
        >
          {{ item }}
        </li>
        <li
          v-show="searchHistory.length > 0"
          class="eogs-dropdown-history-clear"
          @mousedown="clearHistoryHandle"
        >
          清空浏览记录
        </li>
      </ul>
    </div>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
import { namespace } from 'vuex-class';

const userModule = namespace('userModule');

@Component({
  watch: {
    searchVal: function (newVal: string) {
      if (newVal) {
        this.$set(this, 'searchHistory', []);
        this.$nextTick(() => {
          this.$set(
            this,
            'searchDropdownHeight',
            0
            // (this.$refs.searchDropdownRecord as HTMLBaseElement).clientHeight
          );
        });
        return;
      }
      (this as any).searchFocHandle(); //调用searchFocHandle函数
    }
  }
})
export default class EoGlobalSearch extends Vue {
  @userModule.State token: any;
  @userModule.State userData: any;

  searchDropdownHeight = 0;
  searchVal = ''; //搜索的值
  searchHistory: string[] = []; //搜索历史

  /**
   * @description searchFocHandle 全局搜索focus处理函数
   * @returns { void }
   */
  searchFocHandle(): void {
    if (this.userData.level === 0 && this.token.length === 0) {
      location.href = '/login';
      return; //权限判断
    }

    if (this.searchVal) {
      return; //当搜索框中有值时,后续不执行
    }

    let getRecords = localStorage.getItem('searchHistory')?.trim() || JSON.stringify([]);

    if (this.checkHistory(getRecords) === false) {
      return; //核验不通过
    }

    let records: any = JSON.parse(getRecords);
    this.$set(this, 'searchHistory', records);
    this.$nextTick(() => {
      this.$set(
        this,
        'searchDropdownHeight',
        (this.$refs.searchDropdownRecord as HTMLBaseElement).clientHeight
      );
    });
  }

  /**
   * @description searchHandle 回车或点击icon搜索处理函数
   * @returns { void }
   */
  searchHandle(): void {
    if (this.searchVal === '' || this.searchVal.trim() === '') {
      return;
    }

    let getRecords = localStorage.getItem('searchHistory')?.trim() || JSON.stringify([]);
    if (this.checkHistory(getRecords) === false) {
      return; //核验不通过
    }
    let records: any = JSON.parse(getRecords);

    if (records.includes(this.searchVal.trim())) {
      records = records.filter((item: string) => {
        return item !== this.searchVal.trim(); //队列去重
      });
    }

    records.unshift(this.searchVal.trim()); //向队尾添加元素

    if (records.length > 8) {
      records.pop(); //超过8条,移除
    }

    localStorage.setItem('searchHistory', JSON.stringify(records));
    location.href = `xxxxx?query=${(encodeURIComponent(this.searchVal.trim().substring(0,30)))}`;
  }

  /**
   * @description historyItemHandle 搜索历史点击事件处理函数
   * @param { record } 要搜索的值
   * @returns { void }
   */
  historyItemHandle(record: string): void {
    this.$set(this, 'searchVal', record);
    let records = [...this.searchHistory]; //获取记录

    if (!this.searchVal.trim()) {
      return; //searchVal 无值时 return
    }

    if (records.includes(this.searchVal.trim())) {
      records = records.filter((item: string) => {
        return item !== this.searchVal.trim(); //队列去重
      });
    }
    records.unshift(this.searchVal.trim()); //向队尾添加元素
    localStorage.setItem('searchHistory', JSON.stringify(records));
    location.href = `xxxxx?query=${encodeURIComponent(this.searchVal.trim().substring(0,30))}`;
  }

  /**
   * @description clearHistoryHandle 一键清除搜索历史记录
   * @returns { void }
   */
  clearHistoryHandle(): void {
    if (this.searchHistory.length === 0) {
      return;
    }
    this.$nextTick(() => {
      this.$set(this, 'searchDropdownHeight', 0); //收起历史下拉
    });
    localStorage.removeItem('searchHistory'); //删除searchHistory
    this.$set(this, 'searchHistory', []); //清空存储历史变量
  }

  /**
   * @description checkHistory 核实searchHistory正确性
   * @param { string } records
   * @returns { boolean } true:无误
   */
  checkHistory(records: string): boolean {
    if (records === '') {
      return false;
    }
    if (typeof records !== 'string') {
      return false;
    }

    records = records.trim(); //除去首尾空格

    if (records.charAt(0) !== '[') {
      return false; //不是以'['开头的字符串数组
    }
    if (records.charAt(records.length - 1) !== ']') {
      return false; //不是以'['结尾的字符串数组
    }

    let regResult;
    let resultArr = '[';
    let reg = /\"(.*?)\"/g;
    while ((regResult = reg.exec(records)) !== null) {
      resultArr+=`"${regResult[1]}",`;
    }
    if(resultArr.length>1&&resultArr.charAt(0)==='['){
      resultArr=resultArr.substr(0,resultArr.length-1);
    }
    resultArr+=']';
    return resultArr.length === records.length ? true : false;
  }
  mounted(){
    //input回显搜索值
    if(this.$route.path==='/search/comlist'&&this.$route.query.query!==''){
      this.$set(this,'searchVal',((this.$route.query.query as string).trim()).substring(0,30));
    }
  }
}
</script>
<style scoped lang="less">
//全局搜索
.eo-globalSearch {
  align-items: center;
  justify-content: space-between;
  width: 200px !important;
  height: 30px;
  margin-right: 48px;
  color: #bababa;
  background: #63a9dd;
  border-radius: 4px;
}

//全局搜索->input
.eogs-search {
  width: 75%;
  height: 80%;
  margin-left: 12px;
  font-family: @font-family;
  font-size: 14px;
  color: #fff;
  background: #63a9dd;
  border: none;
  outline: none;
}

//全局搜索->placeholder
.eogs-search::-webkit-input-placeholder {
  color: rgba(255, 255, 255, 60);
}

//搜索icon
.eo-icon-search {
  margin-right: 12px;
  font-size: 14px;
  color: rgba(255, 255, 255, 60);
  cursor: pointer;
}

//全局搜索->搜索下拉
.eogs-dropdown {
  top: 34px;
  width: 200px;
  overflow: hidden;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  transition: height cubic-bezier(0.645, 0.045, 0.355, 1) 0.3s;
}

//全局搜索->搜索下拉->历史->item
.eogs-dropdown-history-item {
  box-sizing: content-box;
  height: 20px;
  padding: 10px 13px;
  overflow: hidden;//超过一行隐藏
  font-size: 14px;
  color: rgba(102, 102, 102, 100);
  text-overflow: ellipsis;//文字过多...显示
  white-space: nowrap;//一行显示
  cursor: pointer;

  &:hover {
    color: #2888d1;
    background-color: #e9eff5;
  }
}

//全局搜索->搜索下拉->历史->clear
.eogs-dropdown-history-clear {
  box-sizing: content-box;
  height: 20px;
  padding-bottom: 10px;
  padding-left: 10px;
  font-size: 14px;
  color: #2888d1;
  cursor: pointer;
}
</style>


如有错误欢迎指出