仲灏小栈 仲灏小栈
首页
大前端
后端&运维
其他技术
生活
关于我
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

仲灏

诚意, 正心, 格物, 致知
首页
大前端
后端&运维
其他技术
生活
关于我
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 《前端项目基础建设》
  • HTML&CSS

  • JavaScript&TypeScript

  • Node

  • 构建

  • Vue

    • 持久化存储实时读取
    • vue自适应布局
    • Vue 中后台表格的增删改查统一解决方案(mixin版)
    • Vue 中后台表格的增删改查同一解决方案(组件封装版)
      • 前言
      • 使用方法
      • 组件的封装
        • table filter
        • table columns
        • table item handler
        • table pagination
        • all page
    • Vue Render自定义table单元格内容
    • 项目初始化编码规范(eslint,prettier等)
    • vue element-ui 换肤功能开发
    • vue element 定义多种主题实现换肤
    • 大屏自适应容器
    • el-upload 自定义上传进度
    • vue3初学注意点
    • vue3 hooks 自适应可视化大屏
    • vue3 element-plus ant- design 自定义主题
    • 博客开发
    • vue2中使用jsx
    • Vue 放弃繁琐的render函数,拥抱JSX
    • 组件设计步骤
    • 倒计时验证
    • Vue中computed和watch的区别
    • vue3面试聚焦
    • Vue3 原理 Proxy实现响应式
  • React

  • 小程序

  • 跨端

  • Electron

  • WebGL&GIS

  • 浏览器

  • 面经

  • 其他

  • 大前端
  • Vue
仲灏
2021-11-29
目录

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

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

# 前言

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

# 使用方法

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

# 组件的封装

# table filter

参数配置:

      tableFilters: [
        {
          is: 'el-input',
          prop: 'title',
          attrs: { placeHolder: '请输入景点名称', style: 'width: 200px;' }
        },
        {
          is: 'el-input',
          prop: 'merchant',
          attrs: { placeHolder: '请选择商户', style: 'width: 200px;' }
        }
      ]
1
2
3
4
5
6
7
8
9
10
11
12
<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: () => [] }
		

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# table columns

传入参数:

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' }
],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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: () => [] },
1
2
3
4
5
6
7
8
9
10
11

# table item handler

传入参数:

tableHandle: [
        {
          label: '修改',
          type: 'primary',
          isPop: false,
          method: row => {
            this.editItem(row)
          }
        },
        {
          label: '删除',
          type: 'danger',
          isPop: true,
          method: row => {
            this.deleteItem(row)
          }
        }
      ],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  <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: () => [] },
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# table pagination

   <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
      }
1
2
3
4
5
6
7
8

# all page

使用页面(参数传入):

<!--
 * @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>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

组件页面

<!--
 * @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>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
上次更新: 2022/06/05, 20:31:36
Vue 中后台表格的增删改查统一解决方案(mixin版)
Vue Render自定义table单元格内容

← Vue 中后台表格的增删改查统一解决方案(mixin版) Vue Render自定义table单元格内容→

最近更新
01
vim日常使用记录
04-02
02
滑动窗口最大值
04-02
03
有效的字母异位词
04-02
更多文章>
Theme by Vdoing | Copyright © 2021-2025 izhaong | github | 蜀ICP备2021031194号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式