Skip to content

Vue 中后台表格的增删改查同一解决方案(组件封装版)

仲灏2021-11-29约 1 分钟

<div style="display: none;" hidden="true" aria-hidden="true" data-nosnippet>Are you an LLM? You can read better optimized documentation at /pages/3de341.md for this page in Markdown format</div>

https://www.yuque.com/docs/share/10067c29-d80c-4d39-a624-32881f8fad58?# 《Vue 中后台表格的增删改查统一解决方案(mixin版)》

前言

个人是比较喜欢这一版的, 也是最开始做程序员写的,

使用方法

部分中后台页面都是非常同质化的 CRUD 组成的,很多时候都是一个 Table,然后提供一些操作按钮,并且有一个新增表单。看起来就像这样 image.png

组件的封装

table filter

参数配置:

vue
      tableFilters: [
        {
          is: 'el-input',
          prop: 'title',
          attrs: { placeHolder: '请输入景点名称', style: 'width: 200px;' }
        },
        {
          is: 'el-input',
          prop: 'merchant',
          attrs: { placeHolder: '请选择商户', style: 'width: 200px;' }
        }
      ]
vue
<div class="filter-container d-flex align-items-center justify-content-between pr-20">
  <section>
    <component :is="filter.is" v-for="(filter, index) in filters" :key="index" v-model="filterForm[filter.prop]" v-bind="filter.attrs" class="filter-item mr-10">
      <template v-if="filter.options">
        <el-option v-for="(option, i) in filter.options" :key="`${index}_${i}`" :value="option.value" :label="option.label" />
      </template>
    </component>
  </section>

  <section>
    <el-button class="filter-item" type="warning" icon="el-icon-refresh-left" @click="filterForm={};getList()">重置</el-button>
    <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
    <slot name="action">
      <el-button v-if="crud.includes('c')" class="filter-item" style="margin-left: 10px;" type="success" icon="el-icon-plus" @click="$emit('createItem')">添加</el-button>
    </slot>
  </section>
</div>
----------------
filters: { type: Array, default: () => [] }

table columns

传入参数:

vue
tableColumns: [
  { label: '名称', prop: 'title', width: 130 },
  { label: '联系人', prop: 'name', width: 60, align: 'center' },
  { label: '电话', prop: 'phone', width: 130, align: 'center' },
  { label: '账号', prop: 'acount', width: 140 },
  { label: '人数', prop: 'count', width: 50, align: 'center' },
  { label: '地址', prop: 'address', width: 'auto' },
  {
    label: '状态', prop: 'status', width: 80, align: 'center', render: (h, { row }) => {
      return h('el-tag', { attrs: { type: row.status === '正常' ? 'success' : 'info', size: 'small' }}, row.status)
    }
  },
  { label: '创建时间', prop: 'createTime', width: 160, align: 'center' }
],
vue
<el-table-column v-for="col in columns" :key="col.prop" v-bind="col">
  <template slot-scope="{row}">
    <template v-if="'render' in col">
      <Render :row="row" :render="col.render" />
    </template>
    <span v-else>{{ col.formatter ? col.formatter(row) : row[col.prop] }}</span>
  </template>
</el-table-column>

------------------
columns: { type: Array, default: () => [] },

table item handler

传入参数:

vue
tableHandle: [
        {
          label: '修改',
          type: 'primary',
          isPop: false,
          method: row => {
            this.editItem(row)
          }
        },
        {
          label: '删除',
          type: 'danger',
          isPop: true,
          method: row => {
            this.deleteItem(row)
          }
        }
      ],
vue
  <el-table-column v-if="handle.length" v-bind="handleColumn" :width="(handle.length * 80)+'px'">
    <template slot-scope="scope">
      <template v-for="(btn, index) in handle">
        <el-button v-if="!btn.isPop" :key="index" style="margin: 5px;" size="mini" :type="btn.type" @click.native.prevent="btn.method(scope.row,scope)">{{ btn.label }}</el-button>

        <el-popconfirm v-if="btn.isPop" :key="index" placement="right" confirm-button-text="确定" cancel-button-text="取消" icon="el-icon-info" icon-color="red" title="确定删除吗?" @onConfirm="$message.success('操作成功');getList();btn.method(scope.row, scope)">
          <el-button slot="reference" style="margin: 5px;" size="mini" :type="btn.type">{{ btn.label }}</el-button>
        </el-popconfirm>
      </template>
    </template>
  </el-table-column>

-------------------
handle: { type: Array, default: () => [] },

table pagination

vue
   <pagination v-show="total>listQuery.size" style="padding: 8px 16px; margin-top: 10px;" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.size" @pagination="getList" />

---------
      total: 0,
      listQuery: {
        page: 1,
        size: 20
      }

all page

使用页面(参数传入):

vue
<!--
 * @Description:
 * @Author: 仲灏<izhaong 164165005@qq.com>
 * @Date: 2020-11-17 17:49:51
 * @LastEditors: 仲灏<izhaong 164165005@qq.com>
 * @LastEditTime: 2020-11-20 14:11:38
-->
<template>
  <div class="app-container">
    <complex-table ref="table" :columns="tableColumns" :handle="tableHandle" :filters="tableFilters" :api="api" @createItem="$router.push('/merchant/create')" />
  </div>
</template>

<script>
import ComplexTable from '../../components/ComplexTable'
import { fetchList } from '@/api/merchant'

export default {
  name: 'MerchantList',

  components: { ComplexTable },
  filters: {
    statusFilter(status) {
      const statusMap = {
        published: 'success',
        draft: 'info',
        deleted: 'danger'
      }
      return statusMap[status]
    }
  },
  data() {
    return {
      api: fetchList,
      tableColumns: [
        { label: '名称', prop: 'title', width: 130 },
        { label: '联系人', prop: 'name', width: 60, align: 'center' },
        { label: '电话', prop: 'phone', width: 130, align: 'center' },
        { label: '账号', prop: 'acount', width: 140 },
        { label: '人数', prop: 'count', width: 50, align: 'center' },
        { label: '地址', prop: 'address', width: 'auto' },
        {
          label: '状态', prop: 'status', width: 80, align: 'center', render: (h, { row }) => {
            return h('el-tag', { attrs: { type: row.status === '正常' ? 'success' : 'info', size: 'small' }}, row.status)
          }
        },
        { label: '创建时间', prop: 'createTime', width: 160, align: 'center' }
      ],
      tableHandle: [
        {
          label: '修改',
          type: 'primary',
          isPop: false,
          method: row => {
            this.editItem(row)
          }
        },
        {
          label: '删除',
          type: 'danger',
          isPop: true,
          method: row => {
            this.deleteItem(row)
          }
        }
      ],

      tableFilters: [
        {
          is: 'el-input',
          prop: 'title',
          attrs: { placeHolder: '请输入商户名称', style: 'width: 200px;' }
        },
        {
          is: 'el-input',
          prop: 'name',
          attrs: { placeHolder: '请输入联系人姓名', style: 'width: 200px;' }
        },
        {
          is: 'el-input',
          prop: 'phone',
          attrs: { placeHolder: '请输入联系人电话', style: 'width: 200px;' }
        }
      ]
    }
  },
  methods: {
    editItem(row) {
      console.log(row)
      this.$router.push(`/merchant/edit/123456789`)
    },
    deleteItem(row) {
      console.log(row)
    }
  }
}
</script>

组件页面

vue
<!--
 * @Descripttion: 数据化表格
 * @version: 1.0.0
 * @Author: 仲灏 Izhaong<164165005@qq.com>
 * @Date: 2020-06-27 15:13:00
 * @LastEditors: 仲灏<izhaong 164165005@qq.com>
 * @LastEditTime: 2020-11-23 15:39:49
-->
<template>
  <div class="complex-table_container app-container">
    <div class="filter-container d-flex align-items-center justify-content-between pr-20">
      <section>
        <component :is="filter.is" v-for="(filter, index) in filters" :key="index" v-model="filterForm[filter.prop]" v-bind="filter.attrs" class="filter-item mr-10">
          <template v-if="filter.options">
            <el-option v-for="(option, i) in filter.options" :key="`${index}_${i}`" :value="option.value" :label="option.label" />
          </template>
        </component>
      </section>

      <section>
        <el-button class="filter-item" type="warning" icon="el-icon-refresh-left" @click="filterForm={};getList()">重置</el-button>
        <el-button class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
        <slot name="action">
          <el-button v-if="crud.includes('c')" class="filter-item" style="margin-left: 10px;" type="success" icon="el-icon-plus" @click="$emit('createItem')">添加</el-button>
        </slot>
      </section>
    </div>
    <el-table :key="tableKey" v-loading="listLoading" :height="tableHeight" :data="list" size="small" style="width: 100%;">
      <el-table-column
        type="index"
        width="50"
        label="序列"
        align="center"
      />
      <el-table-column v-for="col in columns" :key="col.prop" v-bind="col">
        <template slot-scope="{row}">
          <template v-if="'render' in col">
            <Render :row="row" :render="col.render" />
          </template>
          <span v-else>{{ col.formatter ? col.formatter(row) : row[col.prop] }}</span>
        </template>

      </el-table-column>

      <el-table-column v-if="handle.length" v-bind="handleColumn" :width="(handle.length * 80)+'px'">
        <template slot-scope="scope">
          <template v-for="(btn, index) in handle">
            <el-button v-if="!btn.isPop" :key="index" style="margin: 5px;" size="mini" :type="btn.type" @click.native.prevent="btn.method(scope.row,scope)">{{ btn.label }}</el-button>

            <el-popconfirm v-if="btn.isPop" :key="index" placement="right" confirm-button-text="确定" cancel-button-text="取消" icon="el-icon-info" icon-color="red" title="确定删除吗?" @onConfirm="$message.success('操作成功');getList();btn.method(scope.row, scope)">
              <el-button slot="reference" style="margin: 5px;" size="mini" :type="btn.type">{{ btn.label }}</el-button>
            </el-popconfirm>
          </template>
        </template>
      </el-table-column>
    </el-table>

    <pagination v-show="total>listQuery.size" style="padding: 8px 16px; margin-top: 10px;" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.size" @pagination="getList" />
  </div>
</template>

<script>
import Pagination from '@/components/Pagination'
import Render from './render'
export default {
  name: 'ComplexTable',
  components: { Pagination, Render },

  props: {
    columns: { type: Array, default: () => [] },
    operates: { type: Array, default: () => [] },
    handle: { type: Array, default: () => [] },
    filters: { type: Array, default: () => [] },
    crud: { type: String, default: 'crud' },
    actionsColumn: {
      type: Object,
      default: () => ({
        label: '操作',
        align: 'center'
      })
    },
    handleColumn: {
      type: Object,
      default: () => ({
        label: '操作',
        align: 'center'
      })
    },
    // eslint-disable-next-line vue/require-default-prop
    api: [Function, Object]
  },
  data() {
    return {
      filterForm: {},
      tableKey: 0,
      list: null,
      total: 0,
      listLoading: true,
      listQuery: {
        page: 1,
        size: 20
      }
    }
  },
  computed: {
    tableHeight() {
      if (this.total > this.listQuery.size) {
        return window.document.body.clientHeight - 242
      } else {
        return window.document.body.clientHeight - 180
      }
    }
  },
  created() {
    this.initFilters()
    this.getList()
  },
  methods: {
    inpubr($event) {
      event.target.blur()
    },
    initFilters() {
      const props = this.filters.map((item) => item.prop)
      props.forEach((key) => {
        this.$set(this.filterForm, key, '')
      })
    },
    getList() {
      this.listLoading = true
      this.api({ ...this.listQuery, ...this.filterForm }).then((response) => {
        this.list = response.data.items
        this.total = response.data.total
        this.listLoading = false
      })
    },
    handleFilter() {
      this.listQuery.page = 1
      this.getList()
    }

  }
}
</script>

<style lang="scss">
.complex-table_container {
  .el-table__body-wrapper {
    &::-webkit-scrollbar {
      /*滚动条整体样式*/
      width: 6px !important;
      /*高宽分别对应横竖滚动条的尺寸*/
      height: 6px !important;
      background: #ffffff !important;
      cursor: pointer !important;
    }

    &::-webkit-scrollbar-thumb {
      /*滚动条里面小方块*/
      border-radius: 5px !important;
      -webkit-box-shadow: inset 0 0 5px rgba(240, 240, 240, 0.5) !important;
      background: rgba(63, 98, 131, 0.8) !important;
      cursor: pointer !important;
    }

    &::-webkit-scrollbar-track {
      /*滚动条里面轨道*/
      -webkit-box-shadow: inset 0 0 5px rgba(240, 240, 240, 0.5) !important;
      border-radius: 0 !important;
      background: rgba(240, 240, 240, 0.5) !important;
      cursor: pointer !important;
    }
  }
}
</style>