vue项目,两种UI框架扩展方式

469 阅读1分钟

前言

对 UI 框架进行扩展,是进行组件化开发的第一步。即使现在有许多优秀的 UI 框架为我们提供了便利,为了满足项目的整体风格,基于 UI 框架所提供的组件进行二次扩展也是必不可少的一步。这样做不仅可以满足系统整体 UI 风格保持统一,也提供了统一的修改入口,避免以后需求变更导致需要修改多处地方。

扩展 UI 组件

第一种

一种是直接使用 mixins 混入新配置。其优点是,在现有 UI 组件上直接修改默认配置,其余功能都会得到保留。

<script>
import { Form } from 'element-ui';

export default {
  name: 'vForm',
  mixins: [Form],
  props: {
    'label-position': {
      default: 'right',
    },
  },
};
</script>

第二种

这一种主要适用于单一组件功能的封装,缺点是属性、事件以及插槽等功能需要再次传递给 UI 组件。

<template>
  <el-dialog
    v-bind="$attrs"
    v-on="$listeners"
    :close-on-click-modal="false"
  >
    <span slot="title" v-if="!$attrs.title">
      <slot name="title"></slot>
    </span>
    <slot></slot>
    <span slot="footer">
      <slot name="footer"></slot>
    </span>
  </el-dialog>
</template>
<script>
export default {
  name: 'vDialogDemo',
};
</script>
<style lang="scss" scoped>
/deep/ .el-dialog__footer {
  text-align: center;
}
</style>

表格组件

这里贴一个以前项目里封装的表格组件,主要是将分页组件和表格组件进行封装,以及统一表格样式。保证了切换分页时的逻辑统一,以及支持前端分页、根据 cols 参数渲染表格。

<template>
  <div>
    <el-pagination
      v-if="(showTopPage || pageable) && $attrs.data && $attrs.data.length"
      @size-change="changeSize"
      @current-change="changePage"
      :current-page="pageParams.pageNumber"
      :page-sizes="pageParams.pageSizes || [10, 20, 50, 100]"
      :page-size="pageParams.pageSize"
      :layout="layout"
      :total="pageParams.totalCount"
      style="text-align: right; margin-bottom: 20px"
    ></el-pagination>
    <div style="height: 100%">
      <el-table
        ref="vTableDemoRef"
        v-if="!independentPageable"
        :header-cell-style="headerCellStyle"
        :cell-style="cellStyle"
        :border="border"
        style="width: 99.9%"
        @select="selectInfo"
        @select-all="selectAllInfo"
        v-bind="$attrs"
        v-on="$listeners"
        @sort-change="handleSortChange"
      >
        <!-- 自定义列 -->
        <slot></slot>
        <!-- 根据参数渲染列 -->
        <template v-for="(col, index) in cols">
          <!-- 自定义组件。使用时在cols参数中增加component字段 -->
          <component
            v-if="col.component"
            :key="index"
            :is="col.component"
            :colConfig="col"
          ></component>
          <el-table-column
            v-else
            v-bind="col"
            :key="index"
            :align="col.align || 'center'"
          ></el-table-column>
        </template>
        <slot name="after"></slot>
      </el-table>
    </div>
    <div style="height: 100%">
      <el-table
        ref="vTableDemoRef"
        v-if="independentPageable"
        :header-cell-style="headerCellStyle"
        :cell-style="cellStyle"
        :border="border"
        :data="independentTableData"
        style="height: 100%; width: 99.9%"
        v-bind="$attrs"
        v-on="$listeners"
        @sort-change="handleSortChange"
      >
        <slot></slot>
      </el-table>
    </div>
    <el-pagination
      v-if="showBottomPage || pageable"
      background
      @size-change="changeSize"
      @current-change="changePage"
      :current-page="pageParams.pageNumber"
      :page-sizes="pageParams.pageSizes || [10, 20, 50, 100]"
      :page-size="pageParams.pageSize"
      :layout="layout"
      :total="pageParams.totalCount"
      style="text-align: right; margin-top: 1.4vh"
    ></el-pagination>
  </div>
</template>

<script>

export default {
  props: {
    sortCustom: {
      type: Boolean,
      default: false,
    },
    // 表格头样式
    headerCellStyle: {
      default: () => ({
        background: '#F6F6F6',
        color: '#333333',
      }),
    },
    // 单元格样式
    cellStyle: {
      default: () => ({
        'font-weight': 'blod',
        color: '#333333',
        'font-size': '14px',
      }),
    },
    border: {
      default: () => true,
    },
    // 表格列参数
    cols: {
      default: () => [],
    },
    // 是否显示分页
    pageable: {
      type: Boolean,
      default: false,
    },
    // 是否前端自己分页
    independentPageable: {
      type: Boolean,
      default: false,
    },
    tableData: {
      type: Array,
      default: () => [],
    },
    // 是否显示表格上方的分页
    showTopPage: {
      type: Boolean,
      default: false,
    },
    // 是否显示表格下方的分页
    showBottomPage: {
      type: Boolean,
      default: false,
    },
    // 更新分页参数后的回调方法,用于重新请求数据
    getData: {
      type: Function,
      default: () => { },
    },
    // 用于自定义分页参数
    pageData: {
      type: Object,
      default: () => ({}),
    },
    layout: {
      type: String,
      default: 'prev, pager, next, total, sizes, jumper',
    },
    appendData: {
      type: Function,
      default: () => { },
    },
  },
  computed: {
    independentTableData() {
      let startIndex = (this.pageParams.pageNumber - 1) * this.pageParams.pageSize;
      let endIndex = this.pageParams.pageNumber * this.pageParams.pageSize;
      // 如果数据源减少,可能需要获取前页数据
      if (startIndex >= this.tableData.length) {
        const pageNumber = Math.ceil(this.tableData.length / this.pageParams.pageSize);
        startIndex = (pageNumber - 1) * this.pageParams.pageSize;
        endIndex = pageNumber * this.pageParams.pageSize;
      }
      const slicedTableData = this.tableData.slice(startIndex, endIndex);
      this.appendData(slicedTableData);
      return slicedTableData;
    },
  },
  data() {
    return {
      pageParams: {
        // 默认分页参数
        pageNumber: 1,
        pageSize: 10,
        totalCount: 0,
        totalPage: 0,
        searchCount: true,
        optimizeCountSql: true,
        // 自定义分页参数,可覆盖上面的值
        ...this.pageData,
      },
      sort: {
        ascs: '',
        descs: '',
      },
    };
  },
  methods: {
    expandAll() {
      const els = this.$el.getElementsByClassName('el-table__expand-icon');
      for (let i = 0; i < els.length; i++) {
        els[i].click();
      }
    },
    changeSize(val) {
      this.pageParams.pageSize = val;
      this.pageParams.pageNumber = 1;
      if (!this.independentPageable) {
        this.getData();
      }
      this.$emit('changePageSize');
    },
    changePage(val) {
      this.pageParams.pageNumber = val;
      if (!this.independentPageable) {
        this.getData();
      }
      this.$emit('changePageSize');
    },
    // 用于获取当前的分页参数
    getPageData(needSortParams = false, { ascs = '', descs = '' } = { ascs: '', descs: '' }) {
      if (needSortParams) {
        const ascsArr = [];
        const descsArr = [];

        if (this.sort.ascs) ascsArr.push(this.sort.ascs);
        if (this.sort.descs) descsArr.push(this.sort.descs);

        if (ascs) ascsArr.push(ascs);
        if (descs) descsArr.push(descs);

        return {
          ascs: ascsArr.join(','),
          descs: descsArr.join(','),
          ...this.pageParams,
        };
      }


      return this.pageParams;
    },
    search(url, params, callback) {
      const cb = params ? callback : params;
      this.$axios.get(url, {
        params: {
          ...this.pageParams,
          ...params,
        },
      }).then((res) => {
        this.updateByResponse(res);
        cb(res);
      });
    },
    // 用于清空分页信息
    resetSearchParams() {
      this.pageParams.pageNumber = 1;
      this.pageParams.pageSize = 10;
      this.pageParams.totalCount = 0;
      this.pageParams.totalPage = 0;
      return this.pageParams;
    },
    // 用于搜索时,设置分页为第一页
    onSearch() {
      this.pageParams.pageNumber = 1;
      return this.pageParams;
    },
    resetIndependentPageable() {
      this.pageParams.pageNumber = 1;
      this.pageParams.pageSize = 10;
      return this.pageParams;
    },
    // 用于查询后,更新分页参数中的总数
    updateByResponse(res) {
      this.$set(this.pageParams, 'totalCount', res.totalCount);
      this.$set(this.pageParams, 'pageSize', res.pageSize);
      this.$set(this.pageParams, 'pageNumber', res.pageNumber);
    },
    updateByLength(num) {
      this.$set(this.pageParams, 'totalCount', num);
    },
    // 单个选择
    selectInfo(selectInfo) {
      this.$emit('selectInfo', selectInfo);
    },
    // 选择全部
    selectAllInfo(selectAllInfo) {
      this.$emit('selectAllInfo', selectAllInfo);
    },
    // 变更选中状态
    toggleRowSelection(row, status) {
      if (this.$refs.vTableDemoRef) {
        this.$refs.vTableDemoRef.toggleRowSelection(row, status);
      }
    },
    toggleAllSelection() {
      if (this.$refs.vTableDemoRef) {
        this.$refs.vTableDemoRef.toggleAllSelection();
      }
    },
    clearSelection() {
      this.$refs.vTableDemoRef.clearSelection();
    },
    /**
     * 根据关键字切换行状态
     * @param {Array} data  表格数据
     * @param {String} key  关键字code
     * @param {Array} selectedKeys 指定选中关键字
     * @example
     *
     *  this.$refs.vTableDemoRef.selectRows(tableData, 'id', ['12222','1555677']);
     */
    toggleRowsSelectionByKey(data, key, selectedKeys, status = true) {
      if (!data || data.length === 0) return;
      if (!selectedKeys || selectedKeys.length === 0) return;

      // 筛选需要选中行
      const selectedRows = data.filter(row => selectedKeys.findIndex(selectedKey => selectedKey === row[key]) > -1);

      // 选中
      if (selectedRows.length) {
        selectedRows.forEach((row) => {
          this.toggleRowSelection(row, status);
        });
      }
    },
    handleSortChange({ prop, order }) {
      if (!order) {
        this.sort = {
          ascs: '',
          descs: '',
        };
      } else if (order === 'ascending') {
        this.sort = {
          ascs: prop,
          descs: '',
        };
      } else {
        this.sort = {
          ascs: '',
          descs: prop,
        };
      }
      // TODO: better
      // const isAsc = order === 'ascending';
      // const isDesc = !isAsc;
      // this.sort = {
      //   ascs: order && isAsc ? prop : '',
      //   descs: order && !isDesc ? prop : '',
      // };
      if (!this.sortCustom) {
        this.getData();
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.el-input-number {
  width: 100%;
  /deep/ input {
    text-align: left;
  }
}
/deep/ .el-table {
  font-size: 14px;
  color: #333333;
  th {
    padding: 0;
  }
  thead {
    tr {
      height: 30px;
    }
  }
  tbody {
    tr {
      height: 32px;
      td {
        padding: 0;
      }
    }
  }
}
</style>