3-3 实现一个搜索列表组件

题目要求

查询触发条件

搜索框敲击回车

点击“Search”按钮

查询中,显示“Loading”

查询无结果时,显示“查无结果”

滚动列表时,支持懒加载

<!-- page页面 -->
<template>
  <div class="search-container">
    <input type="text" class="input-inner" />
    <button>Search</button>
  </div>
  <Scroll :data="data" #default="{ result }" @update-loading="updateLoading">
    <li class="p-0 m-0" v-for="item of result" :key="item.title">
      <h3>{{ item.title }}</h3>
      <p>{{ item.content }}</p>
    </li>
  </Scroll>
  <div class="loading-page" v-if="loading">
    <Loading />
  </div>
</template>

<script lang="ts">
import { defineComponent, shallowReactive, ref } from "vue";
import Scroll from "./scroll.vue";
import Loading from "../loading.vue";
export default defineComponent({
  components: {
    Scroll,
    Loading,
  },
  setup() {
    const loading = ref(false);
    const useResponse = () => {
      const data = shallowReactive({
        status: 0,
        total: 60,
        list: [],
      });

      for (let i = 0; i < 60 / 2; i++) {
        data.list.push(
          ...[
            {
              title: "加总价比还",
              content:
                "然矿存条而带除增克众文较。风便物离放布所布导受感建成构题。命调式或则收算几比每思育产业。新结位阶现论通众构规真亲天龙证。青还龙容认商具山主角由带段。运她般系准任面研那保列定候听三深名火。例节山极市身分有次品你观者验。",
            },
            {
              title: "转亲去新算",
              content:
                "白最物两力日达例把老由置按除些接。将理关参将如物等务打场直消事王保参世。音快何论群部照观总门花量工。车不须单号表龙积太面存证。",
            },
          ]
        );
      }
      return data;
    };

    function updateLoading(val: boolean) {
      loading.value = val;
    }
    return {
      data: useResponse(),
      loading,
      updateLoading,
    };
  },
});
</script>

<style scoped>
.p-0 {
  padding: 0;
}
.m-0 {
  margin: 0;
}
.loading-page {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(255, 255, 255, 0.9);
}
</style>
// 获取滚动条当前的位置
function getScrollTop() {
    let scrollTop = 0
    if (document.documentElement && document.documentElement.scrollTop) {
        scrollTop = document.documentElement.scrollTop
    } else if (document.body) {
        scrollTop = document.body.scrollTop
    }
    return scrollTop
}

// 获取当前可视范围的高度
function getClientHeight() {
    let clientHeight = 0
    if (document.body.clientHeight && document.documentElement.clientHeight) {
        clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight)
    } else {
        clientHeight = Math.max(document.body.clientHeight, document.documentElement.clientHeight)
    }
    return clientHeight
}

// 获取文档完整的高度
function getScrollHeight() {
    return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
}

// 分割数组
function splitArray (list, size) {
    let length = list.length;
    if (length < 1 || size < 1) {
      return [];
    }
    let index = 0;
    let lindex = 0;
    let result = new Array(Math.ceil(length / size));
    while (index < length) {
      result[lindex] = list.slice(index, (index += size));
      lindex++;
    }
    return result;
};

// 节流
export {
    getScrollTop,
    getClientHeight,
    getScrollHeight,
    splitArray
}
<!-- 分页组件 -->
<script lang="ts">
import {
  defineComponent,
  h,
  onMounted,
  onUnmounted,
  nextTick,
  computed,
  ref,
  reactive,
  VNode,
  Ref,
} from "vue";
import {
  getClientHeight,
  getScrollHeight,
  getScrollTop,
  splitArray,
} from "./utlis";
export default defineComponent({
  props: {
    /**
     * 数据源
     */
    data: {
      type: Object,
      default: () => {},
    },
    /**
     * 页面默认渲染
     */
    pageSize: {
      type: Number,
      default: 10,
    },
    loading: {
      type: Boolean,
      default: false,
    },
    delay: {
      type: Number,
      default: 3000,
    },
  },
  emits: ["updateLoading"],
  setup({ data, pageSize, delay }, { slots, emit }) {
    const result = reactive<any[]>([]);
    let pageIndex = ref(0); // 页数
    let slotsElement = ref<Ref<null | VNode[]>>(null);
    let timeId = ref<NodeJS.Timeout | null>(null);
    const list = computed(() => splitArray(data.list, pageSize));
    init();
    slotsElement.value = slots?.default({ result }) || [];

    function handle() {
      if (getScrollHeight() - (getClientHeight() + getScrollTop()) <= 0) {
        ++pageIndex.value;
        if (pageIndex.value >= list.value.length) return;
        list.value[pageIndex.value].forEach((item) => result.push(item));
        emit("updateLoading", true);
        clearTimeout(timeId.value);
        timeId.value = setTimeout(() => {
          emit("updateLoading", false);
          slotsElement.value = slots?.default({ result }) || [];
        }, delay);
      }
    }

    function init() {
      if (Array.isArray(list.value[pageIndex.value])) {
        list.value[pageIndex.value].forEach((item) => result.push(item));
      }
    }

    onMounted(() => {
      nextTick(() => {
        window.addEventListener("scroll", handle, false);
      });
    });

    onUnmounted(() => {
      window.removeEventListener("scroll", handle);
    });
    return () => {
      return h("ul", { class: "list-none p-0 m-0" }, slotsElement.value);
    };
  },
});
</script>

<style scoped>
.list-none {
  list-style: none;
  overflow-anchor: none;
}
</style>

posted @ 2022-03-03 20:22  林见夕  阅读(255)  评论(0)    收藏  举报