vue实现input的实时搜索(防抖版)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Validation</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
      crossorigin="anonymous"
    />
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css"
    />
    <style type="text/css">
      .display-flex {
        display: -webkit-flex;
        display: flex;
      }
      .c-button-secondary {
        background-color: #fff;
        border: 1px solid #3f7cd8;
        padding: 5px 16px;
        color: #3f7cd8;
      }
      .c-button {
        cursor: pointer;
        position: relative;
        display: flex;
        flex-direction: row;
        align-items: center;
        justify-content: center;
        padding: 10px 16px;
        gap: 4px;
        border-radius: 100px;
        font-weight: 700;
        font-size: 1.4rem;
        min-width: 80px;
        word-break: keep-all;
      }
      .add-tag-container {
        flex-wrap: wrap;
      }

      .add-tag-label {
        display: flex;
        align-items: center;
        margin-right: 10px;
        margin-bottom: 10px;
      }

      .add-tag-label-remove {
        font-size: 24px;
        color: #3f7cd8;
        margin-left: 5px;
        cursor: pointer;
      }

      .add-tag-label-remove:hover {
        font-size: 24px;
        color: #77a1e0;
        margin-left: 5px;
        cursor: pointer;
      }
      .inner {
        height: 47px;
        display: -webkit-box;
        word-break: break-all;
        text-overflow: ellipsis;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
      }

      .control-label {
        text-align: left;
        margin-right: 5px;
      }
      .tag-suggestions {
        position: absolute;
        top: 100%;
        left: 0;
        width: 90%;
        max-height: 150px;
        overflow-y: auto;
        border: 1px solid #ccc;
        background-color: #fff;
        z-index: 1;
        overflow-y: hidden;
        max-height: 400px;
      }

      .tag-suggestions li,
      .tag-message {
        list-style: none;
        padding: 8px;
        cursor: pointer;
        border-bottom: 1px solid #eee;
        height: 40px;
      }

      .tag-suggestions li:last-child {
        border-bottom: none;
      }

      .customize-error {
        position: absolute;
        top: 100%;
        left: 0;
        width: 90%;
        max-height: 150px;
        overflow-y: auto;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <h1>Test</h1>
      <div
        class="form-group form-height display-flex"
        style="margin-left: 10px; width: 768px"
      >
        <label class="col-xs-12 control-label inner col-sm-4">Tag</label>
        <div class="input-group col-xs-12 col-sm-7 layout-input-2 display-flex">
          <input
            type="text"
            class="form-control inputTagInfo"
            placeholder="please input tag name"
            v-model="tagInput"
            name="tagInput"
            v-on:focus="focusTag()"
            v-on:blur="blurTag()"
            data-vv-as="Tag"
            data-vv-scope="tag-insert-scope"
            v-validate="'required'"
            autocomplete="off"
          />
          <div style="padding-left: 10px; width: 10%">
            <button type="button" class="btn btn-default" @click="addTags">
              <i class="bi bi-plus-square"></i>
            </button>
          </div>
          <ul class="tag-suggestions" v-show="showTagFlg">
            <div class="" v-show="showTagLoading">
              <div class="tag-message">loading</div>
            </div>
            <div v-show="!showTagLoading">
              <div v-show="showTagList.length">
                <li
                  v-for="(item, index) in showTagList"
                  :value="item.tagName"
                  :key="item.tagId"
                  @mousedown.prevent="chooseTag(item)"
                >
                  {{ item.tagName }}
                </li>
              </div>
              <div class="tag-message" v-show="!showTagList.length">
                no data
              </div>
            </div>
          </ul>
          <span
            v-show="errors.has('tagInput', 'tag-insert-scope')"
            class="error customize-error"
          >
            {{ errors.first('tagInput', 'tag-insert-scope') }}</span
          >
        </div>
      </div>
      <div
        class="form-group form-height display-flex"
        style="margin: 25px 0 0 10px; width: 768px"
      >
        <div class="col-xs-12 control-label inner col-sm-4"></div>
        <div class="display-flex add-tag-container">
          <div v-bind:key="index" v-for="(item, index) in tagList">
            <div class="add-tag-label">
              <button type="button" class="c-button c-button-secondary">
                #{{item.tagName}}
              </button>
              <div class="add-tag-label-remove">
                <i class="bi bi-trash" @click="removeTag(index)"></i>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.13"></script>
    <script src="https://cdn.jsdelivr.net/npm/vee-validate@2.1.0-beta.9/dist/vee-validate.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vee-validate@2.1.0-beta.9/dist/locale/ja.js"></script>
    <script>
      Vue.use(VeeValidate);

      new Vue({
        el: "#app",
        data: {
          tagInput: "",
          showTagFlg: false,
          showTagLoading: false,
          origintagList: [
            {
              tagId: 1,
              tagName: "tag1",
            },
            {
              tagId: 2,
              tagName: "tag2",
            },
            {
              tagId: 3,
              tagName: "tag3",
            },
            {
              tagId: 4,
              tagName: "tag4",
            },
            {
              tagId: 5,
              tagName: "tag5",
            },
          ],
          showTagList: [],
          tagList: [
            {
              tagId: 1,
              tagName: "tag1",
            },
            {
              tagId: 2,
              tagName: "tag2",
            },
            {
              tagId: 3,
              tagName: "tag3",
            },
          ],
        },
        created() {
          //   this.$validator.localize("ja");
          this.$validator.localize("en");
        },
        methods: {
          addTags: function () {
            this.$validator.validate("tag-insert-scope.*").then((result) => {
              if (!result) {
                return;
              }

              const isTagDuplicate = this.checkTagDuplicate();
              if (isTagDuplicate) {
                return;
              }

              if (this.choosedTag && null != this.choosedTag.tagId) {
                this.tagList.push(this.choosedTag);
                this.choosedTag = {};
              } else {
                const newItem = {
                  tagName: this.tagInput,
                };
                this.tagList.push(newItem);
              }
            });
          },

          removeTag: function (index) {
            this.tagList.splice(index, 1);
            this.$validator.errors.clear("tag-insert-scope");
            this.checkTagDuplicate();
          },

          checkTagDuplicate: function () {
            const isTagDuplicate = this.tagList.some(
              (item) => item.tagName === this.tagInput
            );
            if (isTagDuplicate) {
              this.$validator.errors.add({
                field: "tagInput",
                msg: "There are duplicate names among the following tags, please choose again.",
                scope: "tag-insert-scope",
              });
              this.choosedTag = {};
              return true;
            }
            return false;
          },
          focusTag() {
            if (document.activeElement.className.includes("inputTagInfo")) {
              this._debounce(this.getSelectData(this.tagInput), 500);
            }
            this.showTagFlg = true;
          },
          _debounce(func, wait) {
            let timeout;
            return function () {
              let _this = this;
              let args = arguments;
              clearTimeout(timeout);
              timeout = setTimeout(function () {
                func.apply(_this, args);
              }, wait);
            };
          },
          blurTag() {
            this.showTagFlg = false;
          },
          getSelectData(tagInput) {
            this.showTagList = this.origintagList.filter((item) => {
              return item.tagName.match(tagInput);
            });
          },
          chooseTag(choosedTag) {
            this.isSelecting = true;
            this.tagInput = choosedTag.tagName;
            this.choosedTag = choosedTag;
            this.blurTag();
            this.$nextTick(() => {
              this.isSelecting = false;
            });
          },
        },
        watch: {
          tagInput: {
            handler: function () {
              if (!this.isSelecting) {
                this.focusTag();
              }
            },
          },
        },
      });
    </script>
  </body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值