vue结合 element-ui实现强大的后台管理系统表格组件

简介

表格作为后台管理系统的最重要的基础组件之一,功能越强大,后续所做的开发量就越小,这里把常见的提炼出来,分享给大家。基础功能支持loading,分页,按钮操作,数据格式化,数据选择等。

主要依赖说明 (先安装,步骤略)

 {
    "element-ui": "2.11.1",  
    "vue": "^2.6.10",
    "vue-router": "^3.0.1",
    "font-awesome": "4.7.0"
 }

正文

1.组件

src/components/Table.vue

<template>
  <div>
    <!-- 表头 -->
    <div v-if="title" class="table-title">{{ title }}</div>

    <!-- 表格主体 -->
    <el-table
      :ref="ref"
      v-loading="listLoading"
      :data="tableData"
      :border="isBorder"
      :element-loading-text="loadingText"
      :header-cell-style="{ backgroundColor: '#e5e9f2', color: '#333' }"
      highlight-current-row
      stripe
      size="small"
      empty-text="暂无数据"
      @selection-change="handleSelectionChange"
    >
      <!-- 多选配置 -->
      <el-table-column v-if="isSelection" type="selection" width="55"></el-table-column>

      <!-- 表格索引 -->
      <el-table-column v-if="isShowNumber" fixed="left" type="index" :index="tableIndex"></el-table-column>

      <!-- 表格行  item.showOverflowTooltip 设置表格文字过长显示省略号 -->
      <el-table-column
        v-for="(item, index) in config"
        :key="index"
        :prop="item.prop"
        :label="item.label"
        :width="item.width"
        :show-overflow-tooltip="item.showOverflowTooltip"
      >
        <template slot-scope="scope">
          <!-- format = img, 显示图片 -->
          <span v-if="item.format === 'img'">
            <img title="点击查看大图" alt="图片" class="image-size" :src="scope.row[item.prop]" />
          </span>

          <!-- format = timestamp, 把时间戳转换成YYYY-MM-dd HH:mm:ss ,parseTime过滤器后文会提到   -->
          <span v-else-if="item.format === 'timestamp'">{{ scope.row[item.prop] | parseTime }}</span>

          <!-- format = money, 显示金额 -->
          <span v-else-if="item.format === 'money'">{{ '¥'+scope.row[item.prop] }}</span>

          <!-- format = rate, 显示比例 -->
          <span v-else-if="item.format === 'rate'">{{ scope.row[item.prop]+"%" }}</span>

          <!-- format = number, 显示数字-->
          <span v-else-if="item.format === 'number'">{{ Number(scope.row[item.prop]) }}</span>

          <!-- format = a, 网页跳转 -->
          <span v-else-if="item.format === 'a'">
            <u class="link">
              <a :href="scope.row[item.prop]" target="_blank">{{ scope.row[item.prop] }}</a>
            </u>
          </span>

          <!-- 需要添加其他数据处理,继续添加 v-else-if="item.format === ''" 就好 -->

          <!-- 没有format -->
          <span v-else>{{ scope.row[item.prop] }}</span>
        </template>
      </el-table-column>

      <!-- 表格操作按钮栏 -->
      <el-table-column
        v-if="isHasButtons"
        class="clearfix"
        :width="optionColumnWidth+'px'"
        fixed="right"
        label="操作"
      >
        <template slot-scope="scope">
          <!-- 基本操作 -->
          <span
            v-for="(option, index) in getOptionsName(scope.row.buttonKey).slice(0,buttonCount)"
            :key="index"
            class="button-margin-left"
          >
            <el-button
              size="small"
              :type="index == 0 ? 'primary' : ''"
              @click="handleClickOption(scope.$index, scope.row, option,$event)"
            >
              <span v-html="getButtonHtml(option)"></span>
            </el-button>
          </span>

          <!-- 更多操作 -->
          <el-dropdown
            v-if="getOptionsName(scope.row.buttonKey).length > buttonCount"
            trigger="click"
          >
            <span class="el-dropdown-link">
              更多
              <i class="el-icon-arrow-down el-icon--right"></i>
            </span>
            <el-dropdown-menu slot="dropdown" class="custom-dropdown-menu">
              <el-dropdown-item
                v-for="(option, index) in getOptionsName(scope.row.buttonKey).slice(buttonCount)"
                :key="index"
                class="custom-dropdown-menu-item"
              >
                <el-button
                  size="small"
                  @click="handleClickOption(scope.$index, scope.row, option,$event)"
                >
                  <span v-html="getButtonHtml(option)"></span>
                </el-button>
              </el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页组件 -->
    <div v-if="isShowPagination && total > 0" class="pagination-container">
      <el-pagination
        background
        layout="total, sizes, prev, pager, next, jumper"
        :current-page="page"
        :page-sizes="pageSizeList"
        :page-size="limit"
        :total="total"
        @current-change="handleCurrentChange"
        @size-change="handleSizeChange"
      ></el-pagination>
    </div>
  </div>
</template>

<script>
export default {
  name: 'AppTable',
  props: {
    // 是否需要序号
    isShowNumber: {
      type: Boolean,
      default: true
    },
    // 是否需要翻页组件
    isShowPagination: {
      type: Boolean,
      default: true
    },
    // 是否需要栅格
    isBorder: {
      type: Boolean,
      default: true
    },
    // 是否是多选表格,默认非多选 :ref="'multipleTable'"
    isSelection: {
      type: Boolean,
      default: false
    },
    // 是否有操作按钮
    isHasButtons: {
      type: Boolean,
      default: true
    },
    // 表头名称
    title: {
      type: String
    },
    // 表格数据
    tableData: {
      type: Array,
      required: true
    },
    // 表格配置信息
    config: {
      type: Array,
      required: true
    },
    // loading提示框
    loadingText: {
      type: String,
      default: '加载中...'
    },
    // loading状态
    loadingStatus: {
      type: Boolean,
      default: false
    },
    // 表格查询列表参数
    listQueryParams: {
      type: Object
    },
    // 根据当前行的状态显示按钮的名称
    buttonsName: {
      type: Object
    },
    // 操作列表宽度
    optionColumnWidth: {
      type: Number,
      default: 100
    },
    // 分页数据
    pageSizeList: {
      type: Array,
      default: function() {
        return [10, 20, 30, 50, 100]
      }
    },
    // 显示的操作栏按钮个数,多余的显示在dropdown里
    buttonCount: {
      type: Number,
      default: 2
    }
  },

  computed: {
    // 看是否是多选表格
    ref: function() {
      return this.isSelection ? 'multipleTable' : undefined
    },
    // 第几页
    page: function() {
      return this.listQueryParams.page || 1
    },
    // 每页数据数
    limit: function() {
      return this.listQueryParams.limit || 10
    },
    // 数据总数
    total: function() {
      return this.listQueryParams.total || 0
    },
    // 获取当前loading的状态
    listLoading: function() {
      return this.loadingStatus
    }
  },
  methods: {
    // 根据按钮的名称,获取按钮的html样式,让按钮有个图标,这里使用的是font-awesome,要使用的话先安装
    getButtonHtml(name) {
      let className
      switch (name) {
        case '查看':
          className = 'eye'
          break
        case '编辑':
          className = 'pencil'
          break
        case '审核':
          className = 'check-circle'
          break
        case '置顶':
          className = 'arrow-circle-o-up'
          break
        case '取消置顶':
          className = 'times-circle'
          break
        case '推荐':
          className = 'thumbs-o-up'
          break
        case '上线':
          className = 'space-shuttle'
          break
        case '下线':
          className = 'space-shuttle'
          break
        case '删除':
          className = 'times-circle-o'
          break
        case '分析':
          className = 'bar-chart'
          break
        case '排序':
          className = 'sort'
          break
      }
      if (className) {
        return `<span><i class="fa fa-${className}"></i> ${name}</span>`
      } else {
        return name
      }
    },

    // 获取当前操作的按钮组
    getOptionsName(key) {
      return this.buttonsName[key] || []
    },

    // 点击按钮传递给父组件
    handleClickOption(index, row, option) {
      this.$emit('subOpitonButton', index, row, option)
    },

    // 表格选择分发事件
    handleSelectionChange(val) {
      this.$emit('subSelected', val)
    },

    // 改变翻页组件中每页数据总数
    handleSizeChange(val) {
      this.listQueryParams.limit = val
      // 改变翻页数目,将页面=1
      this.listQueryParams.page = 1
      this.$emit('update:listQueryParams', this.listQueryParams)
      this.$emit('subClickPagination', this.listQueryParams)
    },

    // 跳到当前是第几页
    handleCurrentChange(val) {
      this.listQueryParams.page = val
      this.$emit('update:listQueryParams', this.listQueryParams)
      this.$emit('subClickPagination', this.listQueryParams)
    },

    // 重写索引生成方法
    tableIndex(index) {
      const i = (this.page - 1) * this.limit + index + 1
      return i
    }
  }
}
</script>

<style lang="scss" scoped>
.image-size {
  width: 30px;
  height: 30px;
  cursor: pointer;
}
.table-title {
  margin-top: 10px;
  font-size: 18px;
}
.el-dropdown {
  cursor: pointer;
  margin-left: 20px;

  .el-dropdown-link {
    user-select: none;
    cursor: pointer;
  }
}

.button-margin-left {
  margin-left: 8px;
}

.link {
  cursor: pointer;
  color: #4876ff;
}
</style>

 <style lang="scss" >
.el-table {
  .el-table__empty-block {
    height: unset;
  }
}
.custom-dropdown-menu {
  cursor: pointer;
  .custom-dropdown-menu-item {
    margin-top: 10px;
    text-align: center;
  }
  .el-dropdown-menu__item:focus,
  .el-dropdown-menu__item:not(.is-disabled):hover {
    background-color: #fff;
    color: #fff;
  }
}
</style>

2.使用

<template>
  <div>
    <el-row class="margin-top-10">
      <el-card>
        <app-table :list-query-params.sync="listQueryParams" v-bind="tableAttrs" v-on="tableEvent" />
      </el-card>
    </el-row>
  </div>
</template>

<script>
import AppTable from '@/components/Table'
// 定义的接口根据自己项目更换
import TalentServe from '@/api/talent'
//  表格查询参数
const DefaultTableQuery = {
  page: 1,
  limit: 10,
  total: 0
}

export default {
  name: 'UserList',
  components: {
    AppTable
  },
  data() {
    return {
      // 表格加载loading
      loadingStatus: false,
      //  操作栏宽度
      optionWidth: 280,
      // 表头配置  prop字段和服务端数据给的字段一致
      tableConfig: [
        {
          label: '昵称',
          width: '120px',
          prop: 'nickname'
        },
        {
          label: '头像',
          width: '120px',
          prop: 'headImgUrl',
          // 显示图片
          format: 'img'
        },
        {
          label: '手机号',
          width: '120px',
          prop: 'mobile'
        },
        {
          label: '注册时间',
          width: '140px',
          prop: 'registrationDate',
          // 显示时间
          format: 'timestamp'
        },
        {
          label: '邀请人',
          width: '80px',
          prop: 'inviterUser'
        },
        {
          label: '收益金额',
          width: '120px',
          prop: 'integrals',
          format: 'money'
        },
        // 最后一个不给宽度让表格自适应
        {
          label: '类型',
          prop: 'customIsExpressive'
        }
      ],
      // 参数
      listQueryParams: { ...DefaultTableQuery },
      // 列表数据
      tableData: [],
      // url参数
      params: {
        pageInfo: {
          pageSize: 10,
          pageNo: 1
        }
      },
      // 操作栏按钮
      buttonsName: {
        normal: ['查看', '编辑', '设为可提现'],
        special: ['查看', '编辑', '取消可提现']
      },
      // 选择数据
      selectData: [],
      // 操作数据
      operationalData: {}
    }
  },
  computed: {
    // 表格属性
    tableAttrs() {
      return {
        // 表头配置
        config: this.tableConfig,
        // 表格数据
        tableData: this.tableData,
        // loading
        loadingStatus: this.loadingStatus,
        // 按钮名称
        buttonsName: this.buttonsName,
        // 操作栏宽度
        optionColumnWidth: this.optionWidth,
        // 是否需要选择
        isSelection: true
      }
    },
    // 表格事件
    tableEvent() {
      return {
        // 按钮操作
        subOpitonButton: this.handleTableOption,
        // 分页
        subClickPagination: this.handleRefreshList,
        // 表格数据选择
        subSelected: this.handleSelectionChange
      }
    }
  },
  created() {
    this.getList()
  },
  methods: {
    // 获取列表
    async getList() {
      try {
        // 表格加载loading
        this.loadingStatus = true
        // 分页数据作为参数给服务端
        this.params.pageInfo.pageSize = this.listQueryParams.limit
        this.params.pageInfo.pageNo = this.listQueryParams.page
        // 发送请求,请求到的数据格式见下文,
        const { data, cntData } = await TalentServe.getTalentList(this.params)
        const tableData = data || []
        tableData.forEach(item => {
          if (item.isExpressive === '1') {
            // 给每一行设置buttonKey对应于data里定义的buttonsName的key值
            // buttonsName: {
            //   normal: ['查看', '编辑', '设为可提现'],
            //   special: ['查看', '编辑', '取消可提现']
            // },
            //
            item.buttonKey = 'special'
          } else {
            item.buttonKey = 'normal'
          }
          // 修改显示的数据
          item.customIsExpressive =
            item.isExpressive === '1' ? '可提现' : '不可提现'
        })
        // 分页组件显示  this.listQueryParams.total 值大于0才会出现
        this.listQueryParams.total = cntData
        // 数据给表格
        this.tableData = data
        this.loadingStatus = false
      } catch (error) {
        console.log(error)
      }
    },

    // 表格操作功能 index:表格索引, row:表格行数据, option:按钮名称
    handleTableOption(index, row, option) {
      this.operationalData = { ...row }
      if (option === '查看') {
        console.log(index, row, option)
      } else if (option === '编辑') {
        console.log(index, row, option)
      } else if (option === '设为可提现') {
        console.log(index, row, option)
      } else if (option === '取消可提现') {
        console.log(index, row, option)
      }
    },

    // 选择的数据回调
    handleSelectionChange(data) {
      console.log(data)
    },

    // 分页操作
    handleRefreshList() {
      this.getList()
    }
  }
}
</script>


3.请求到的服务端数据


{
  result: true,
  message: null,
  data: [
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/RYIWG1XDAOVbAXHWONRiab639VRyr7cKQcCibXz8s2m4xhuDmDYo8XxGde0NiaAJ5vy8SHznDIoQ7mBtLibg9sicia0w/132',
      nickname: '万马奔腾',
      mobile: '13892188637',
      registrationDate: 1556666683000,
      userId: '0172cf8bbda841dca100aa8b30b1d58e',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83eptKEXTYEu3Ne3pF7OcUvibUBY0hSibibH7SRTNxN9x7522mYQZ63ZEBF8nbjty1NN60ScPicsuGGrrtQ/132',
      nickname: '    水目菊  ',
      mobile: '15991695887',
      registrationDate: 1560258459000,
      userId: '021c02454ea64fab8640f1fa25e18637',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKLvV2y1XiaJJDfibB7zlqaTudCEsiapR8iaShFykZ2SgVD24zxYve5kPLvQooFy4ibePbpcribtZJVXlEQ/132',
      nickname: '老要',
      mobile: '',
      registrationDate: 1563934025000,
      userId: '024bc4c4f5db418499d3633621b2b834',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmhead/2yfPLY1YiaibeyxYwultf0Z7VNea1H1icXrV9pQcldEHTc/132',
      nickname: '林群梦',
      mobile: '',
      registrationDate: 1557438206000,
      userId: '035826db900d4b7a912664119189b6f1',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83eqF8zPxScdIGo0nK7so6bzFibRF1sEXrT404XUN2gcfFRTB1pcCaZPBUr9lGKGibbiaUWVTOw7HpaNNg/132',
      nickname: '贺励',
      mobile: '',
      registrationDate: 1563490853000,
      userId: '035b59b061454336953d47822c7b0406',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLsymhiblxYck4YOUxdtpZOib12KgJj6ciaJxV2ibtia92YODBhC05YL8rCNKQQjc09tZTP6xBxkT0qK5Q/132',
      nickname: '乾坤',
      mobile: '15091593977',
      registrationDate: 1557153760000,
      userId: '03a91cb5a60f4f02bf22b1e9e179258d',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJpTy32D4vRETGtricKwfhx3kiaTJcibRyMFg5KvSENibeWKE0Xq4gZ9ZyicZLvkUQ4RSVBpwwiaOhW5lLg/132',
      nickname: '鸿涛',
      mobile: '13909256676',
      registrationDate: 1556680061000,
      userId: '03d7811ea5cb4e1e851c5245128674c2',
      inviterUser: '三百千.刘震',
      inviterId: 'b33b3879531d41e4bc19b629cb7c7cb8',
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 408.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKLuf2cK1w7sN3qD0cM4XvbYWnJJa5hibyUKGv0oPzG0LT1UjFvTTRuibOCTvxDibQdU9icFxK1TZOSHQ/132',
      nickname: '格格',
      mobile: '',
      registrationDate: 1560569395000,
      userId: '04346cf9a9284d058f71a59de4b37e49',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJ9PZJVYXlalZyggdlVpCoPJcvIHrh1eN3SRRoTkdBgGjhQwrBQMibYma0xkdf6BlaKHLdv5YzM1AA/132',
      nickname: '水木清华',
      mobile: '13359265165',
      registrationDate: 1557146507000,
      userId: '05f3c11d67044e30a61abd79fc542c36',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    },
    {
      headImgUrl:
        'https://wx.qlogo.cn/mmopen/vi_32/PiajxSqBRaEJiaXZyZ6Tnv4z7YgvVXRvuONlTc9DNQVS0d52HnScISc77XGKawodayVib3fnLwV9trSDlUkRKwHRw/132',
      nickname: '王鸣@ODIN',
      mobile: '',
      registrationDate: 1560861936000,
      userId: '07087c0478ae43b18765272fbb4aa5e1',
      inviterUser: null,
      inviterId: null,
      friendsNum: 0,
      friendsNumSum: 0,
      integrals: 0.0,
      isExpressive: ''
    }
  ],
  cntPage: 33,
  cntData: 323
}

3.补充表格组件用的过滤器(自行注册,步骤略)

export function parseTime(time, cFormat) {
  if (arguments.length === 0) {
    return null
  }

  if ((time + '').length === 10) {
    time = +time * 1000
  }

  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    date = new Date(parseInt(time))
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }
  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
    let value = formatObj[key]
    if (key === 'a') {
      return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
    }
    if (result.length > 0 && value < 10) {
      value = '0' + value
    }
    return value || 0
  })
  return time_str
}

4.效果展示
在这里插入图片描述

GitHub 加速计划 / eleme / element
54.06 K
14.63 K
下载
A Vue.js 2.0 UI Toolkit for Web
最近提交(Master分支:3 个月前 )
c345bb45 7 个月前
a07f3a59 * Update transition.md * Update table.md * Update transition.md * Update table.md * Update transition.md * Update table.md * Update table.md * Update transition.md * Update popover.md 7 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐