可操作的无限长虚拟列表组件 - zptime/blog GitHub Wiki

无限长虚拟列表组件,提高渲染性能,有简单的文字提示气泡框,还有可自定义的操作栏,满足个性化需求

前言

之前写过一个列表穿梭框组件,Ant Design Vue Transfer 穿梭框“造轮子”,里面实现过一个虚拟列表。

这次实现的是一个带操作的虚拟列表组件,结合 vue-virtual-scroller 实现滚动加载无限长列表,带有虚拟化(virtualization)功能,能够提高数据量大时候长列表的性能。

实现

主要实现功能点:

  1. RecycleScroller插件使用:实现虚拟滚动
  2. 选中、未选中样式处理
  3. 标题处理(Tooltip):超过长度省略号,文字提示气泡框
  4. 操作栏处理(Dropdown):可自定义操作项

注意点:

  1. color: currentColor;:继承父元素的 color 属性

    DN Web Docs 之 Color

  2. writing-mode: vertical-rl;:定义了文本水平或垂直排布以及在块级元素中文本的行进方向。我主要用来让三个点垂直排列

    MDN Web Docs 之 writing-mode

使用

  1. 模拟数据
const mockList = [
  { id: 11, name: "ant design vue part 1,ant design vue part 1" },
  { id: 12, name: "ant design vue part 2" },
  { id: 13, name: "ant design vue part 3" },
  { id: 14, name: "ant design vue part 4" },
  { id: 15, name: "ant design vue part 5" },
  { id: 16, name: "ant design vue part 6" },
  { id: 17, name: "ant design vue part 7" },
  { id: 18, name: "ant design vue part 8" },
  { id: 19, name: "ant design vue part 9" },
  { id: 20, name: "ant design vue part 10" },
  { id: 21, name: "ant design vue part 11" },
  { id: 22, name: "ant design vue part 12" },
  { id: 23, name: "ant design vue part 13" },
  { id: 24, name: "ant design vue part 14" },
  { id: 25, name: "ant design vue part 15" },
  { id: 26, name: "ant design vue part 16" },
  { id: 27, name: "ant design vue part 17" },
  { id: 28, name: "ant design vue part 18" },
];
  1. 使用:默认操作只有编辑和删除,用户可以自定义处理
参数 说明 类型
dataSource 数据源 Array
activeKey 选中 key string
operation 操作项 slot
<m-rank-list
  :data-source="list"
  :active-key="activeKey"
  @handle-select="handleSelect"
>
  // 自定义操作栏内容
  <template slot="operation">
    <a-menu-item>新增人员</a-menu-item>
    <a-menu-item>编辑</a-menu-item>
    <a-menu-item>删除</a-menu-item>
  </template>
</m-rank-list>

使用效果

完整代码

<template>
  <a-list class="list">
    <RecycleScroller
      class="recycle"
      :items="dataList"
      :item-size="32"
      key-field="name"
    >
      <a-list-item
        slot-scope="{ item }"
        :key="item.id"
        :class="['item', { 'item-selected': activeId === item.id }]"
        @click="handleSelect(item)"
      >
        <rank-icon class="item-icon" />
        <!-- Tooltip 文字提示 -->
        <a-tooltip placement="topLeft" :title="item.name">
          <div class="item-title">{{ item.name }}</div> </a-tooltip
        ><!-- Dropdown 下拉菜单 -->
        <a-dropdown
          placement="bottomRight"
          :trigger="['hover']"
          class="item-dropdown"
        >
          <span>...</span>
          <a-menu slot="overlay">
            <slot name="operation">
              <a-menu-item @click="handleEdit(item)">编辑</a-menu-item>
              <a-menu-item @click="handleDel(item)">删除</a-menu-item>
            </slot>
          </a-menu>
        </a-dropdown>
      </a-list-item>
    </RecycleScroller>
  </a-list>
</template>

<script>
  import * as R from "ramda";
  import { RecycleScroller } from "vue-virtual-scroller";
  import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
  export default {
    props: ["dataSource", "activeKey"],
    data() {
      return {
        activeId: "",
        dataList: [],
      };
    },
    components: {
      RecycleScroller,
    },
    watch: {
      dataSource(val) {
        if (val?.length) {
          this.dataList = R.clone(val);
          this.activeId = this.activeKey
            ? this.activeKey
            : this.dataList?.[0]?.id;
        } else {
          this.dataList = [];
          this.activeId = "";
        }
      },
    },
    methods: {
      handleSelect(item) {
        this.activeId = item.id;
        this.$emit("handle-select", item);
      },
      handleEdit(item) {
        this.$emit("handle-edit", item);
      },
      handleDel(id) {
        this.$emit("handle-delete", item);
      },
    },
  };
</script>

<style lang="scss" scoped>
  .list {
    .recycle {
      max-height: 320px;
      overflow-y: auto;
      overflow-x: hidden;
      margin-bottom: 24px;
    }
    .item {
      display: flex;
      align-items: center;
      height: 32px;
      padding: 0 0 0 4px;
      border-bottom: 0;
      cursor: pointer;
      // 选中样式处理
      &-selected {
        background-color: #e6f7ff;
        color: #1890ff;
        .item-icon,
        .item-dropdown {
          // 继承颜色
          color: currentColor;
        }
      }
      &-icon {
        font-size: 12px;
        margin-right: 6px;
        color: #ebedf0;
      }
      &-title {
        flex: 1;
        display: block;
        // 超过长度省略号
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
      }
      &-dropdown {
        cursor: pointer;
        // 指定块流动方向
        writing-mode: vertical-rl;
        color: #ebedf0;
        font-weight: bold;
      }
    }
  }
</style>
⚠️ **GitHub.com Fallback** ⚠️