适用于vue2的分页mixin

20 阅读2分钟

实现

  • 适用于后台管理系统,其中使用了elementUI的分页组件与table组件
// list-filter-mixin.js
import { cloneDeep, debounce } from "lodash";
import Vue from "vue";

// 判断对象中的所有属性是否为空
function objectValueAllEmpty(obj = {}) {
  return Object.values(obj).every(
    (it) => it === "" || it === null || it === undefined
  );
}
export function listFilterMixin({
  query = {}, // 查询条件
  page = { current: 1, size: 10 }, // 单独管理分页
  body = {}, // 请求体
  api = () => Promise.resolve({ rows: [], total: 0 }), // 请求接口
  immediate = true, // 是否立即执行
  isScroll = true, // 是否滚动加载
  unique = "id", // 唯一标识
}) {
  return {
    data() {
      return {
        filterQuery: cloneDeep(query),
        filterBody: cloneDeep(body),
        renderList: [],
        total: -1,
        loading: false,
        hasMore: true, // 是否还有更多数据
        uniqueIds: new Set([]),
        initPage: {
          // 双向绑定的页码
          current: isScroll ? 1 : page.current,
          size: isScroll ? 10 : page.size,
        },
        wholeQuery: {}, // 包含分页在内的所有query参数
      };
    },
    created() {
      // 初始化
      this.wholeQuery = Vue.observable({
        ...this.initPage,
        ...this.filterQuery,
      });
      immediate && this.initData();
    },
    computed: {
      filter() {
        // 不包含页码
        return {
          query: { ...this.filterQuery },
          body: { ...this.filterBody },
        };
      },
      allQuery() {
        // 包含页码在内的所有query参数
        return {
          ...this.wholeQuery,
          ...this.filter.query,
          ...this.initPage,
        };
      },
    },
    methods: {
      // 保证每次请求的数据没有重复值
      setUniqueData(list) {
        list.forEach((itm) => {
          if (!this.uniqueIds.has(itm[unique])) {
            this.uniqueIds.add(itm[unique]);
            this.renderList.push(itm);
          }
        });
      },
      // 获取数据
      async fetchData() {
        this.loading = true;
        try {
          const qr = this.allQuery;
          const bd = this.filter.body;
          console.log("查询全部参数-----", qr, bd);
          const { rows, total: _total } = await api(qr, bd);
          this.total = _total;
          if (rows.length) {
            this.setUniqueData(rows);
          }
        } catch (error) {
          console.warn(error);
        } finally {
          this.loading = false;
        }
      },
      // 初始化
      initData() {
        this.hasMore = true;
        this.initPage.current = 1;
        this.renderList = []; // 清空渲染列表
        this.uniqueIds = new Set([]); // 清空特征值列表
        this.fetchData();
        console.log("执行了初始化查找--------------");
      },
      // 加载更多
      loadMoreData() {
        console.log("列表数据----", this.renderList, this.total);
        if (this.renderList.length < this.total) {
          this.initPage.current++; // 下一页
          this.fetchData();
        } else {
          // this.$message.warning("没有更多数据了");
          this.hasMore = false;
        }
      },
      // 翻页
      pageChange() {
        this.renderList = []; // 清空渲染列表
        this.uniqueIds = new Set([]); // 清空特征值列表
        this.fetchData();
      },
      // 重置查询条件
      resetData() {
        // 只有当查询条件都为空时才会在函数中执行一次重置,否则在watch中执行
        const needInit =
          objectValueAllEmpty(this.filter.query) &&
          objectValueAllEmpty(this.filter.body);
        this.hasMore = true;
        this.initPage.current = 1; // 重置分页,不重置分页大小
        Object.assign(this.filterQuery, query);
        Object.assign(this.filterBody, body);
        this.renderList = []; // 清空渲染列表
        this.uniqueIds = new Set([]); // 清空特征值列表
        needInit && this.initData();
      },
    },
    watch: {
      filter: {
        handler: debounce(function () {
          this.initData();
        }, 300),
      },
    },
  };
}

使用

<template>
  <div>
    <FilterBar
      :options="options"
      v-model="searchData"
      @on-search="handleSearch"
      @on-reset="handleReset"
    />
    <ListTable
      :tableConfig="tableConfig"
      :operations="operations"
      :tableData="tableData"
      v-model="initPage"
      :total="100"
    />
  </div>
</template>

<script>
import FilterBar from "@/components/FilterBar/index.vue";
import ListTable from "@/components/ListTable/index.vue";
import { listFilterMixin } from "@/mixins/list-filter-mixin.js";
const listFilter = listFilterMixin({
  page: { current: 1, size: 10 },
  body: {
    createTime: "",
  },
  isScroll: false,
});

export default {
  name: "BusniessCapability",
  mixins: [listFilter],
  components: {
    FilterBar,
    ListTable,
  },
  data() {
    return {
      searchData: {
        createTime: "",
      },
      options: [
        {
          type: "date",
          label: "创建时间",
          key: "createTime",
          placeholder: "请选择创建时间",
        },
      ],
      tableConfig: [
        {
          prop: "workOrderType",
          label: "业务能力支撑类型",
        },
        {
          prop: "workOrderType",
          label: "创建时间",
        },
      ],
      operations: [
        {
          label: "修改",
          color: "#5bacff",
          prop: "edit",
          icon: "el-icon-edit",
          fun: (row) => {
            console.log("修改", this, row);
          },
        },
        {
          label: "详情",
          color: "#5bacff",
          prop: "detail",
          icon: "el-icon-search",
          fun: (row) => {
            console.log("详情", this, row);
          },
        },
        {
          label: "删除",
          color: "#f56c6d",
          prop: "delete",
          icon: "el-icon-delete",
          fun: (row) => {
            console.log("删除", this, row);
          },
        },
      ],
      tableData: Array.from({ length: 100 }).map((_, idx) => ({
        workOrderType: "工单标题1",
        workOrderType: "问题描述1",
      })),
    };
  },
  methods: {
    // 查询
    handleSearch(args) {
      Object.assign(this.filterBody, args);
      console.log("查询参数-----", args);
    },
    // 重置
    handleReset() {
      this.resetData();
    },
  },
  watch: {
    initPage(nv) {
      console.log("页码变化了", this.initPage);
      Object.assign(this.wholeQuery, nv);
      this.pageChange();
    },
  },
};
</script>

<style scoped lang="scss"></style>

自定义组件

  • FilterBar是一个永远筛选的区域,包含selectinputtime-picker,可以自行增加
<template>
  <div class="m-3">
    <el-form
      :model="searchData"
      ref="queryForm"
      size="small"
      :inline="true"
      label-width="68px"
    >
      <el-form-item
        v-for="item in options"
        :key="item.key"
        :label="item.label"
        :prop="item.key"
      >
        <el-input
          v-if="item.type === 'input'"
          v-model="searchData[item.key]"
          :placeholder="item.placeholder"
          clearable
          v-bind="item.attributes"
        />
        <el-select
          v-if="item.type === 'select'"
          v-model="searchData[item.key]"
          :placeholder="item.placeholder"
          clearable
          v-bind="item.attributes"
        >
          <el-option
            v-for="dict in item.options"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
        <el-date-picker
          v-if="item.type === 'date'"
          value-format="yyyy-MM-dd"
          v-model="searchData[item.key]"
          :placeholder="item.placeholder"
          clearable
          v-bind="item.attributes"
        />
      </el-form-item>
      <el-form-item>
        <el-button
          type="primary"
          icon="el-icon-search"
          size="mini"
          @click="handleQuery"
          >搜索</el-button
        >
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
          >重置</el-button
        >
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: "FilterBar",
  model: {
    prop: "searchData",
  },
  props: {
    searchData: Object,
    options: {
      type: Array,
      default: () => [],
    },
  },
  methods: {
    handleQuery() {
      this.$emit("on-search", this.searchData);
    },
    resetQuery() {
      this.$refs.queryForm.resetFields();
      this.$emit("on-reset");
    },
  },
  data() {
    return {};
  },
};
</script>

<style scoped lang="scss"></style>

  • ListTable是一个展示数据的表格组件,包含了一个分页器
  • 表格通过配置实现,也可以通过插槽自定义某个表格内容的显示
<template>
  <div class="m-3">
    <el-row>
      <el-col :span="24">
        <el-table
          :data="tableData"
          :max-height="itemHeight * visibleLine"
          style="width: 100%"
          stripe
          :header-row-style="{ backgroundColor: '#ddd' }"
          :header-cell-style="{
            backgroundColor: '#ddd',
            fontWeight: '900',
            color: '#000',
          }"
        >
          <el-table-column type="index" label="排序" />
          <el-table-column
            v-for="item in tableConfig"
            :prop="item.prop"
            :label="item.label"
            :width="item.width"
            class-name="content"
          >
            <template slot-scope="scope">
              <!-- 定制内容 -->
              <slot
                v-if="item.isCustom"
                :name="item.prop"
                v-bind:scope="scope.row"
              />
              <span v-else>{{ scope.row[item.prop] }}</span>
            </template>
          </el-table-column>
          <el-table-column fixed="right" label="操作" class-name="actions">
            <template slot-scope="scope">
              <span class="flex items-center gap-2">
                <span v-for="(btn, idx) in operations" :key="idx">
                  <el-popconfirm
                    v-if="btn.prop === 'delete'"
                    title="确定删除吗?"
                    @confirm="btn.fun(scope.row)"
                  >
                    <el-button
                      slot="reference"
                      :icon="btn.icon"
                      type="text"
                      size="small"
                      :style="{ color: btn.color }"
                      >{{ btn.label }}</el-button
                    >
                  </el-popconfirm>
                  <el-button
                    v-else
                    @click="btn.fun(scope.row)"
                    :icon="btn.icon"
                    type="text"
                    size="small"
                    :style="{ color: btn.color }"
                    >{{ btn.label }}</el-button
                  >
                </span>
              </span>
            </template>
          </el-table-column>
        </el-table>
      </el-col>
    </el-row>
    <el-row>
      <div class="pagination-container">
        <el-pagination
          background
          :current-page="page.current"
          :page-size="page.size"
          layout="total, sizes, prev, pager, next, jumper"
          :page-sizes="[5, 10, 20, 30, 50]"
          :pager-count="pageCounter"
          :total="total"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </el-row>
  </div>
</template>

<script>
export default {
  name: "ListTable",
  model: {
    prop: "page",
    event: "update:page",
  },
  props: {
    tableData: {
      type: Array,
      default: () => [],
    },
    tableConfig: {
      type: Array,
      default: () => [],
    },
    operations: {
      type: Array,
      default: () => [],
    },
    total: {
      type: Number,
      default: 0,
    },
    visibleLine: {
      type: Number,
      default: 13,
    },
    page: {
      type: Object,
      default: () => ({ current: 1, size: 10 }),
    },
  },
  computed: {
    computedPage() {
      return this.page;
    },
  },
  data() {
    return {
      itemHeight: 0,
      pageCounter: document.clientWidth < 992 ? 5 : 7,
    };
  },
  methods: {
    handleSizeChange(size) {
      this.$emit("update:page", { current: 1, size });
    },
    handleCurrentChange(current) {
      this.$emit("update:page", {
        current,
        size: this.computedPage.size,
      });
    },
    initItemHeight() {
      const oneVhHeight = Math.floor(window.innerHeight * 0.01);
      this.itemHeight = oneVhHeight * 6;
      console.log("表格项的高度-----", this.itemHeight);
    },
  },
  created() {
    this.initItemHeight();
  },
};
</script>

<style scoped lang="scss">
.content {
  .cell {
    span {
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      overflow: hidden;
    }
  }
}
.pagination-container {
  padding: 32px 16px;
}
</style>

  • 使用mixin可以大大提高代码的简洁性