El-table de encapsulación secundaria

En muchos sistemas empresariales mid-end y back-end, las tablas son uno de los componentes más utilizados, que generalmente incluyen condiciones de búsqueda, visualización de tablas, columnas de operación de tablas, paginación, etc. Luego, el componente de tabla que encapsulamos dos veces debe incluir los siguientes puntos funcionales:

1. Adquisición y actualización automática de datos

2. Configuración de columna personalizada

3. Función de paginación

4. Función de búsqueda basada en condiciones de búsqueda.

5. Estado de carga y estado de datos vacíos

1. Vaya primero a la página final.

2. Cree el directorio yxt-table como se muestra a continuación.

index.vue es la página principal

yxt-table.vue es el componente de la tabla

2. Adquisición y actualización automática de datos

Debido a que los datos en la tabla son generalmente relativamente simples, significa llamar a la interfaz para solicitar una lista según las condiciones de búsqueda, luego mostrar los datos de la lista en la tabla uno por uno y luego realizar algunas operaciones personalizadas, como el filtrado y la transformación. columnas específicas. Sin embargo, el mismo principio sigue siendo válido: este paso debe realizarse básicamente para todas las tablas, por lo que, considerando la versatilidad (en realidad por pereza), se implementa en el componente el paso de solicitar la interfaz para obtener datos.

    created () {
        this.getData()
    },
    methods: {
        getData () {
            const fun = this.apiUrl
            fun().then(res => {
                this.tableData = res[this.otherConfig.list] || []
                this.tableTotal = res.pageInfo?.total || 0
            })
        }
    }

3. Configuración de columna personalizada

El componente recibe una matriz como columna personalizada.

tableColumn: [
    { prop: 'name', label: '名称' },
    { prop: 'code', label: '编码' },
    { prop: 'status', label: '状态' }
]

 index.vue

<!-- index.vue -->
<template>
  <div>
    <yxt-table :apiUrl="yxtTableList" 
               :tableColumn="tableColumn"></yxt-table>
  </div>
</template>

<!-- index.vue -->
<script>
import yxtTable from './yxt-table.vue'
import { yxtTableList } from 'https/yxtDemo.js'
export default {
    name: 'yxtDemoTable',
    components: {
        yxtTable
    },
    data () {
        return {
            yxtTableList,
            tableColumn: [
                { prop: 'name', label: '名称' },
                { prop: 'code', label: '编码' },
                { prop: 'status', label: '状态' }
            ]
        }
    }
}
</script>

 yxt-table.vue

<!-- yxt-table.vue -->
<template>
  <div>
    <el-table :data="tableData">
      <el-table-column v-for="item in tableColumn"
                       :key="item.prop"
                       :prop="item.prop"
                       :label="item.label"></el-table-column>
    </el-table>
  </div>
</template>

<!-- yxt-table.vue -->
<script>
export default {
    name: 'yxtTable',
    props: {
        apiUrl: { // 列表接口(必填)
            type: Function,
            required: true
        },
        tableColumn: { // 自定义列配置
            type: Array,
            default: () => []
        },
        otherConfig: { //
            type: Object,
            default: () => {
                return {
                    list: 'list'
                }
            }
        }
    },
    data () {
        return {
            tableData: []
        }
    },
    created () {
        this.getData()
    },
    methods: {
        getData () {
            const fun = this.apiUrl
            fun().then(res => {
                this.tableData = res[this.otherConfig.list] || []
                this.tableTotal = res.pageInfo?.total || 0
            })
        }
    }
}
</script>

En este punto, se puede implementar un formulario.

1. descripción de otra configuración

Dado que nuestra solicitud de interfaz se coloca en el componente, la interfaz que conectamos puede tener diferentes nombres de campo en la lista devuelta por la interfaz debido a diferentes desarrolladores en diferentes grupos de proyectos del negocio. Aquí hacemos compatibilidad pasando parámetros.

2. Lo anterior solo puede lograr una función de visualización simple. También hay algunos datos como el estado 1 que debe convertirse en un registro exitoso y el estado 0 que debe convertirse en un registro fallido para su visualización. Estos requisitos se pueden convertir mediante filtros.

<!-- yxt-table.vue -->
      <el-table-column v-for="item in tableColumn"
                       :key="item.prop"
                       :prop="item.prop"
                       :label="item.label">
        <template v-slot:default="scope">
          <div v-if="item.dictCode">
            {
   
   { scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
          </div>
          <div v-else>
            {
   
   { scope.row[item.prop] }}
          </div>
        </template>
      </el-table-column>



    props: {
        dict: { // 全部字典
            type: Object,
            default: () => {}
        }
    },
    filters: {
        filterStatus (value, array, code = 'code', name = 'name') {
            if (!value && value !== 0) { // 要把0摘出来,一般0都是正常的数据,所以不能只用  !value
                return ''
            }
            const find = array.find(e => (e[code] === value.toString()) || (e[code] === +value)) // 字符型数值型都得匹配
            if (find) {
                return find[name]
            } else { // 没有匹配的就原样返回
                return value
            }
        }
    },
<!-- index.vue -->
    <yxt-table :apiUrl="yxtTableList" 
               :tableColumn="tableColumn"
               :dict="dict"></yxt-table>



    data () {
        return {
           tableColumn: [
                { prop: 'name', label: '名称' },
                { prop: 'code', label: '编码' },
                { prop: 'status', label: '状态', dictCode: 'status' }
            ],
            dict: {
                status: [
                    { code: 0, name: '打卡失败' },
                    { code: 1, name: '打卡成功' }
                ]
            }
        }
    }

La razón por la que dict se establece como objeto aquí es para cargar más diccionarios.

3. Piénselo, ¿qué sucede si desea mostrar un ícono personalizado en la tabla? 

Utilice slot, establezca el slot de un determinado atributo de fila en tableColumn en verdadero y transforme el-table-column de la siguiente manera:

<!-- yxt-table.vue -->     
     <el-table-column v-for="(item, index) in tableColumn"
                       :key="index"
                       :prop="item.prop"
                       :label="item.label">
        <template v-if="item.slot"
                  v-slot:default="scope">
          <slot :name="item.prop"
                :row="scope.row"
                :index="scope.$index"></slot>
        </template>
        <template v-else v-slot:default="scope">
          <div v-if="item.dictCode">
            {
   
   { scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
          </div>
          <div v-else>
            {
   
   { scope.row[item.prop] }}
          </div>
        </template>
      </el-table-column>
<!-- index.vue -->    
    <yxt-table :apiUrl="yxtTableList"
               :tableColumn="tableColumn"
               :otherConfig="otherConfig"
               :dict="dict">
      <template v-slot:icon="{row, index}">
        <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
      </template>
    </yxt-table>



    data () {
        return {
            tableColumn: [
                { prop: 'name', label: '名称' },
                { prop: 'code', label: '编码' },
                { prop: 'status', label: '状态', dictCode: 'status' },
                { prop: 'icon', label: '图标', slot: true }
            ]
        }
    }

 4. En proyectos reales, además de la conversión de diccionario, también existen algunos requisitos de visualización más personalizados, que se pueden calcular pasando un formato de función y luego devolviendo el resultado final del cálculo en este método.

<!-- yxt-table.vue -->
      <el-table-column v-for="(item, index) in tableColumn"
                       :key="index"
                       :prop="item.prop"
                       :label="item.label">
        <template v-if="item.slot"
                  v-slot:default="scope">
          <slot :name="item.prop"
                :row="scope.row"
                :index="scope.$index"></slot>
        </template>
        <template v-else v-slot:default="scope">
          <div v-if="item.dictCode">
            {
   
   { scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
          </div>
          <div v-else-if="item.format">
            {
   
   { item.format(scope.row) }}
          </div>
          <div v-else>
            {
   
   { scope.row[item.prop] }}
          </div>
        </template>
      </el-table-column>
<!-- index.vue -->
    data () {
        return {
            tableColumn: [
                { prop: 'name', label: '名称' },
                { prop: 'code', label: '编码' },
                { prop: 'status', label: '状态', dictCode: 'status' },
                { prop: 'icon', label: '图标', slot: true },
                { prop: 'phone',
                    label: '电话号码',
                    format: (row) => {
                        return `${row.name}-${row.code}(${row.phone})`
                    } }
            ]
        }
    }

5. Las tablas generalmente tienen operaciones por lotes, por lo que se requieren selecciones múltiples y selecciones únicas, así como prohibir selecciones para escenarios específicos.

 yxt-table.vue

<!-- yxt-table.vue -->
<template>
  <div class="yxt-table">
    <!-- 批量操作按钮,因为每个需求不同,批量操作的功能也不同,所以这里只放一个插槽,不设置默认内容,所有按钮均在父级设置 -->
    <div class="multiple-operation">
      <slot name="multiple-operation"
            :selectionData="selectionData"></slot>
    </div>
    <!-- 页面主表格 -->
    <el-table :data="tableData"
              :row-key="rowKey"
              @selection-change="selectionChange">
      <!-- 可选框(多选) -->
      <el-table-column v-if="selection === 'multiple'"
                       type="selection"
                       align="center"
                       width="55"
                       :reserve-selection="rowKey ? true : false"
                       :selectable="selectable"/>
      <!-- 可选框(单选) -->
      <el-table-column v-else-if="selection === 'single'"
                       align="center"
                       width="30">
        <template v-slot:default="scope">
          <el-radio v-model="selectionRadio"
                    :label="scope.$index"
                    :disabled="selectable ? !selectable(scope.row) : false"
                    @change="selectionChangeSingle(scope.row)">
            {
   
   { '' }}
          </el-radio>
        </template>
      </el-table-column>
      <el-table-column v-for="(item, index) in tableColumn"
                       :key="index"
                       :prop="item.prop"
                       :label="item.label">
        <template v-if="item.slot"
                  v-slot:default="scope">
          <slot :name="item.prop"
                :row="scope.row"
                :index="scope.$index"></slot>
        </template>
        <template v-else v-slot:default="scope">
          <div v-if="item.dictCode">
            {
   
   { scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
          </div>
          <div v-else-if="item.format">
            {
   
   { item.format(scope.row) }}
          </div>
          <div v-else>
            {
   
   { scope.row[item.prop] }}
          </div>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<!-- yxt-table.vue -->
<script>
export default {
    name: 'yxtTable',
    props: {
        apiUrl: { // 列表接口(必填)
            type: Function,
            required: true
        },
        tableColumn: { // 自定义列配置
            type: Array,
            default: () => []
        },
        otherConfig: { // 其他配置
            type: Object,
            default: () => {
                return {
                    list: 'list' // 接口返回数据的列表字段的字段名(因为在组件里面调接口,可能不同业务不同项目组不同一个开发者返回给前端的参数名不一致,这里进行兼容)
                }
            }
        },
        dict: { // 全部字典
            type: [Array, Object],
            default: () => []
        },
        selection: { // 是否显示可选框(多选-multiple 、单选-single )
            type: String
        },
        selectable: { // 当前行是否可选择
            type: Function
        },
        rowKey: { // 表格唯一key(适用于分页多选表格,保留之前的选择,不传则为单页选择)
            type: [Number, String, Function],
            default: ''
        }
    },
    filters: {
        filterStatus (value, array, code = 'code', name = 'name') {
            if (!value && value !== 0) { // 要把0摘出来,一般0都是正常的数据,所以不能只用  !value
                return ''
            }
            const find = array.find(e => (e[code] === value.toString()) || (e[code] === +value)) // 字符型数值型都得匹配
            if (find) {
                return find[name]
            } else { // 没有匹配的就原样返回
                return value
            }
        }
    },
    data () {
        return {
            tableData: [],
            tableTotal: 0,
            selectionRadio: '',
            selectionData: []
        }
    },
    created () {
        this.getData()
    },
    methods: {
        getData () {
            const fun = this.apiUrl
            fun().then(res => {
                this.tableData = res[this.otherConfig.list] || []
                this.tableTotal = res.pageInfo?.total || 0
            })
        },

        // 多选,选择行数据change
        selectionChange (selection) {
            this.selectionData = selection
        },

        // 单选,选择行数据change
        selectionChangeSingle (selection) {
            this.selectionData = [selection]
        }
    }
}
</script>
<style scoped lang="scss">
.yxt-table {
  margin: 30px;
  .multiple-operation {
    margin-bottom: 10px;
  }
}
</style>

 index.vue

<!-- index.vue -->
<template>
  <div>
    <yxt-table :apiUrl="yxtTableList"
               :tableColumn="tableColumn"
               :otherConfig="otherConfig"
               :dict="dict"
               selection="multiple"
               :selectable="isSelectable">
      <!-- 图标插槽 -->
      <template v-slot:icon="{row, index}">
        <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
      </template>
      <!-- 批量操作按钮插槽 -->
      <template v-slot:multiple-operation="{selectionData}">
        <el-button type="primary"
                   size="small"
                   @click="handleClick1(selectionData)">批量操作1</el-button>
        <el-button type="success"
                   size="small"
                   @click="handleClick2(selectionData)">批量操作2</el-button>
      </template>
    </yxt-table>
    <yxt-table :apiUrl="yxtTableList"
               :tableColumn="tableColumn"
               :otherConfig="otherConfig"
               :dict="dict"
               selection="single"
               :selectable="isSelectable">
      <!-- 图标插槽 -->
      <template v-slot:icon="{row, index}">
        <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
      </template>
      <!-- 批量操作按钮插槽 -->
      <template v-slot:multiple-operation="{selectionData}">
        <el-button type="primary"
                   size="small"
                   @click="handleClick1(selectionData)">单选操作</el-button>
      </template>
    </yxt-table>
  </div>
</template>

<!-- index.vue -->
<script>
import yxtTable from './yxt-table.vue'
import { yxtTableList } from 'https/yxtDemo.js'
export default {
    name: 'yxtDemoTable',
    components: {
        yxtTable
    },
    data () {
        return {
            yxtTableList,
            tableColumn: [
                { prop: 'name', label: '名称' },
                { prop: 'code', label: '编码' },
                { prop: 'status', label: '状态', dictCode: 'status' },
                { prop: 'icon', label: '图标', slot: true },
                { prop: 'phone',
                    label: '电话号码',
                    format: (row) => {
                        return `${row.name}-${row.code}(${row.phone})`
                    } }
            ],
            tableConfig: {
                stripe: 'stripe',
                border: 'border',
                height: '200',
                maxHeight: '200',
                showHeader: true
            },
            otherConfig: {
                list: 'tasks'
            },
            dict: {
                status: [
                    { code: 0, name: '打卡失败' },
                    { code: 1, name: '打卡成功' }
                ]
            }
        }
    },
    methods: {
        handleClick1 (selectionData) {
            console.log('1', selectionData)
        },
        handleClick2 (selectionData) {
            console.log('2', selectionData)
        },
        isSelectable (row) {
            return row.selectable !== 0
        }
    }
}
</script>
<style scoped lang="scss">
.el-icon-circle-check {
  font-size: 28px;
  color: #67C23A;
}
.el-icon-circle-close {
  font-size: 28px;
  color: #F00;
}
</style>

6. Columna de operación

Según las necesidades comerciales, puede configurar varios botones predeterminados en la columna de operación y configurar el interruptor a través de setupConfig. Si existen requisitos de operación distintos a los botones predeterminados, insértelos a través de la ranura.

<!-- yxt-table.vue -->
      <!-- 操作列 -->
      <el-table-column v-if="setupConfig.width !== 0"
                       :fixed="setupConfig.fixed"
                       :width="setupConfig.width"
                       label="操作">
        <template v-slot:default="scope">
          <slot name="setup"
                :row="scope.row"
                :index="scope.$index"></slot>
          <!-- 查看 -->
          <el-button v-if="setupConfig.view"
                     type="text"
                     @click="setupEvents('view', scope.row)">查看</el-button>
          <!-- 编辑 -->
          <el-button v-if="setupConfig.edit"
                     type="text"
                     @click="setupEvents('edit', scope.row)">编辑</el-button>
          <!-- 删除 -->
          <el-button v-if="setupConfig.del"
                     type="text"
                     @click="setupEvents('del', scope.row)">删除</el-button>
          <!-- 操作日志 -->
          <el-button v-if="setupConfig.log"
                     type="text"
                     @click="setupEvents('log', scope.row)">操作日志</el-button>
        </template>
      </el-table-column>



    props: {
        setupConfig: {
            type: Object,
            default: () => {
                return {
                    width: 'auto'
                }
            }
        }
    },
    methods: {
        setupEvents (setupType, row) { // 操作列方法 查看/编辑/删除/操作日志
            this.$emit(setupType, row)
        }
    }

 index.vue realiza el procesamiento correspondiente, el código no se publicará aquí.

7. Paginación 

La paginación controla si se requieren componentes de paginación. Si no se requiere paginación, se establece en falso. Según las necesidades comerciales, puede pasar tamaños de página para controlar la cantidad de elementos en el cuadro desplegable.

<!-- yxt-table.vue -->
    <!-- 分页 -->
    <el-pagination v-if="pagination"
                   class="pagination tablePage"
                   :pager-count="5"
                   :page-sizes="pageSizes || [10, 20, 50, 100]"
                   :total="tableTotal || 0"
                   :page-size="pageInfo.pageSize || 10"
                   :current-page="pageInfo.startPage || 1"
                   layout="total, sizes, prev, pager, next, jumper"
                   @size-change="sizeChange"
                   @current-change="pageChange"></el-pagination>



    props: {
        pagination: { // 是否需要分页,默认需要
            type: Boolean,
            default: true
        },
        pageSizes: {
            type: Array
        }
    },
    methods: {
        getData () {
            const fun = this.apiUrl
            const pageInfo = { // 分页信息
                pageSize: this.pageInfo.pageSize,
                startPage: this.pageInfo.startPage
            }
            let param = { // 其他的搜素条件

            }
            if (this.pagination) { // 如果需要分页,则传分页信息
                param = { ...param, ...pageInfo }
            }
            fun(param).then(res => {
                this.tableData = res[this.otherConfig.list] || []
                this.tableTotal = res.pageInfo?.total || 0
            })
        },

        // 条数变化
        sizeChange (size) {
            this.pageInfo.startPage = 1
            this.pageInfo.pageSize = size
            this.getData()
        },

        // 页码变化
        pageChange (page) {
            this.pageInfo.startPage = page
            this.getData()
        }
    }

 8. el-table también tiene una función de expansión de fila, que también se puede agregar al componente según las necesidades comerciales.

<!-- yxt-table.vue -->
      <!-- 展开行 -->
      <el-table-column v-if="expand"
                       type="expand">
        <template v-slot:default="scope">
          <slot name="expand"
                :row="scope.row"
                :index="scope.$index"></slot>
        </template>
      </el-table-column>



    props: {
        expand: { // 是否展开行
            type: Boolean,
            default: false
        }
    }

<!-- index.vue -->
    <yxt-table :apiUrl="yxtTableList"
               :tableColumn="tableColumn"
               :expand="true">
      <template v-slot:expand="{row, index}">
        <div>
          <p>序号:{
   
   {index}}</p>
          <p>内容:{
   
   {row}}</p>
        </div>
      </template>
    </yxt-table>

4. Busque y actualice los datos de la tabla según las condiciones de búsqueda.

Agregar un nuevo yxt-search.vue

<!-- yxt-search.vue -->
<template>
  <div class="yxt-search">
    <div v-for="(item,index) in searchConfig"
         :key="index"
         class="yxt-search-item">
      <el-input v-if="item.type==='input'"
                v-model="searchModel[item.key]"
                size="medium"
                :clearable="item.clearable || true"
                :placeholder="item.placeholder || '请输入'"
                :maxlength="item.maxlength"></el-input>
      <el-select v-if="item.type==='select'"
                 v-model="searchModel[item.key]"
                 size="medium"
                 style="width: 100%"
                 :clearable="item.clearable || true"
                 :filterable="item.filterable || true"
                 :disabled="item.disabled || false"
                 :multiple="item.multiple || false"
                 :allow-create="item.allowCreate"
                 :placeholder="item.placeholder || '请选择'">
        <el-option v-for="(selectItem, selectIndex) in item.selectList"
                   :key="selectIndex"
                   :label="selectItem[item.listLabel]"
                   :value="selectItem[item.listValue]"></el-option>
      </el-select>
    </div>
    <div v-if="searchConfig.length" class="yxt-search-button">
      <el-button size="medium" type="primary" @click="search">搜索</el-button>
      <el-button size="medium" type="primary" plain @click="reset">重置</el-button>
      <!-- 其他的按钮需求通过插槽传入 -->
      <slot name="searchBtn" :searchData="searchModel"></slot>
    </div>
  </div>
</template>

<!-- yxt-search.vue -->
<script>
export default {
    name: 'yxtSearch',
    props: {
        searchConfig: { // 搜索条件配置项
            type: Array,
            required: true,
            default () {
                return []
            }
        },
        searchModel: { // 搜索条件绑定值
            type: Object,
            required: true,
            default () {
                return {}
            }
        },
        searchReset: { // 搜索条件默认值重置值
            type: Object
        }
    },
    data () {
        return {
        }
    },
    methods: {
        search () {
            this.$emit('search', this.searchModel)
        },
        reset () {
            if (this.searchReset) { // 如果传入有默认值,则重置后为默认值
                Object.keys(this.searchModel).forEach((item) => {
                    this.searchModel[item] = this.searchReset[item]
                })
            } else {
                Object.keys(this.searchModel).forEach((item) => {
                    this.searchModel[item] = ''
                })
            }
        }
    }
}
</script>
<style scoped lang="scss">
.yxt-search {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: flex-start;
  .yxt-search-item {
    flex: 1;
    margin: 0 10px 10px 0;
    width: calc((100% - 30px) / 4);  // 这里的30px = (分布个数4-1)*间隙1px, 可以根据实际的分布个数和间隙区调整
    min-width: calc((100% - 30px) / 4);
    max-width: calc((100% - 30px) / 4);
    &:nth-child(4n) { // 去除每行最后一个(第4n个)的margin-right
      margin-right: 0;
    }
  }
  .yxt-search-button {
    margin: 0 0 10px 0;
    width: 100%;
    text-align: right;
  }
}
</style>

<!-- yxt-table.vue -->
    <yxt-search :searchConfig="searchConfig"
                :searchModel="searchModel"
                :searchReset="searchReset"
                @search="getData(1)">
      <template v-slot:searchBtn="{searchData}">
        <!-- 其他的按钮需求通过插槽传入 -->
        <slot name="searchBtn" :searchData="searchData"></slot>
      </template>
    </yxt-search>




    props: {
        searchConfig: { // 搜索条件配置项
            type: Array,
            default () {
                return []
            }
        },
        searchReset: { // 搜索条件默认值重置值
            type: Object
        }
    },
    data () {
        return {
            searchModel: this.searchReset ? JSON.parse(JSON.stringify(this.searchReset)) : {}
        }
    },
    methods: {
        getData (startPage) {
            if (startPage) { // 如果传入值,则从改值的页码数开始
                this.pageInfo.startPage = startPage
            }
            let param = { // 其他的搜素条件
                ...this.searchModel
            }
            ...
        }
    }



<!-- index.vue -->
    <yxt-table :apiUrl="yxtTableList"
               :tableColumn="tableColumn"
               :searchConfig="searchConfig"
               :searchReset="searchReset">
      <template v-slot:searchBtn="{searchData}">
        <el-button size="medium" type="success" @click="handleClickExport(searchData)">导出</el-button>
      </template>
    </yxt-table>



     data () {
        return {
            searchConfig: [
                { type: 'input', key: 'name' },
                { type: 'input', key: 'code' },
                { type: 'select',
                    key: 'status',
                    selectList: [
                        { code: 0, name: '打卡失败' },
                        { code: 1, name: '打卡成功' }
                    ],
                    listLabel: 'name',
                    listValue: 'code' }
            ],
            searchReset: {
                name: '张三',
                code: '',
                status: 1
            }
        }
    },
    methods: {
        handleClickExport (data) {
            console.log(data)
        }
    }

5. Estado de carga y estado de datos vacíos

Cargando: el-table Agregue v-loading="loading", en getData, configúrelo en verdadero antes de enviar la solicitud y configúrelo en falso después de obtener los datos.

Datos vacíos: configurado mediante ranura vacía

 6. Código completo:

index.vue
<!-- index.vue -->
<template>
  <div>
    <yxt-table :apiUrl="yxtTableList"
               :tableColumn="tableColumn"
               :otherConfig="otherConfig"
               :dict="dict"
               selection="multiple"
               :selectable="isSelectable"
               :setupConfig="setupConfig"
               :searchConfig="searchConfig"
               :searchReset="searchReset"
               @view="view"
               @log="log">
      <!-- 图标插槽 -->
      <template v-slot:icon="{row, index}">
        <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
      </template>
      <!-- 批量操作按钮插槽 -->
      <template v-slot:multiple-operation="{selectionData}">
        <el-button type="primary"
                   size="small"
                   @click="handleClick1(selectionData)">批量操作1</el-button>
        <el-button type="success"
                   size="small"
                   @click="handleClick2(selectionData)">批量操作2</el-button>
      </template>
      <template v-slot:searchBtn="{searchData}">
        <el-button size="medium" type="success" @click="handleClickExport(searchData)">导出</el-button>
      </template>
    </yxt-table>
    <yxt-table :apiUrl="yxtTableList"
               :tableColumn="tableColumn"
               :otherConfig="otherConfig"
               :dict="dict"
               selection="single"
               :selectable="isSelectable"
               :setupConfig="setupConfig2"
               :pagination="false"
               :expand="true"
               :emptyText="'没有数据的展示文字'">
      <!-- 图标插槽 -->
      <template v-slot:icon="{row, index}">
        <i :class="row.status ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
      </template>
      <!-- 批量操作按钮插槽 -->
      <template v-slot:multiple-operation="{selectionData}">
        <el-button type="primary"
                   size="small"
                   @click="handleClick1(selectionData)">单选操作</el-button>
      </template>
      <template v-slot:expand="{row, index}">
        <div>
          <p>序号:{
   
   {index}}</p>
          <p>内容:{
   
   {row}}</p>
        </div>
      </template>
    </yxt-table>
  </div>
</template>

<!-- index.vue -->
<script>
import yxtTable from './yxt-table.vue'
import { yxtTableList } from 'https/yxtDemo.js'
export default {
    name: 'yxtDemoTable',
    components: {
        yxtTable
    },
    data () {
        return {
            yxtTableList,
            tableColumn: [
                { prop: 'name', label: '名称' },
                { prop: 'code', label: '编码' },
                { prop: 'status', label: '状态', dictCode: 'status' },
                { prop: 'icon', label: '图标', slot: true },
                { prop: 'phone',
                    label: '电话号码',
                    format: (row) => {
                        return `${row.name}-${row.code}(${row.phone})`
                    } }
            ],
            tableConfig: {
                stripe: 'stripe',
                border: 'border',
                height: '200',
                maxHeight: '200',
                showHeader: true
            },
            otherConfig: {
                list: 'tasks'
            },
            setupConfig: {
                width: 100,
                view: true,
                log: true
            },
            setupConfig2: {
                edit: true,
                del: true,
                log: true
            },
            dict: {
                status: [
                    { code: 0, name: '打卡失败' },
                    { code: 1, name: '打卡成功' }
                ]
            },
            searchConfig: [
                { type: 'input', key: 'name' },
                { type: 'input', key: 'code' },
                { type: 'select',
                    key: 'status',
                    selectList: [
                        { code: 0, name: '打卡失败' },
                        { code: 1, name: '打卡成功' }
                    ],
                    listLabel: 'name',
                    listValue: 'code' }
            ],
            searchReset: {
                name: '张三',
                code: '',
                status: 1
            }
        }
    },
    methods: {
        handleClick1 (selectionData) {
            console.log('1', selectionData)
        },
        handleClick2 (selectionData) {
            console.log('2', selectionData)
        },
        handleClickExport (data) {
            console.log(data)
        },
        isSelectable (row) {
            return row.selectable !== 0
        },
        view (row) {
            console.log('view', row)
        },
        log (row) {
            console.log('log', row)
        }
    }
}
</script>
<style scoped lang="scss">
.el-icon-circle-check {
  font-size: 28px;
  color: #67C23A;
}
.el-icon-circle-close {
  font-size: 28px;
  color: #F00;
}
</style>
yxt-table.vue
<!-- yxt-table.vue -->
<template>
  <div class="yxt-table">
    <yxt-search :searchConfig="searchConfig"
                :searchModel="searchModel"
                :searchReset="searchReset"
                @search="getData(1)">
      <template v-slot:searchBtn="{searchData}">
        <!-- 其他的按钮需求通过插槽传入 -->
        <slot name="searchBtn" :searchData="searchData"></slot>
      </template>
    </yxt-search>
    <!-- 批量操作按钮,因为每个需求不同,批量操作的功能也不同,所以这里只放一个插槽,不设置默认内容,所有按钮均在父级设置 -->
    <div class="multiple-operation">
      <slot name="multiple-operation"
            :selectionData="selectionData"></slot>
    </div>
    <!-- 页面主表格 -->
    <el-table :data="tableData"
              :row-key="rowKey"
              v-loading="loading"
              @selection-change="selectionChange">
      <!-- 可选框(多选) -->
      <el-table-column v-if="selection === 'multiple'"
                       type="selection"
                       align="center"
                       width="55"
                       :reserve-selection="rowKey ? true : false"
                       :selectable="selectable"/>
      <!-- 可选框(单选) -->
      <el-table-column v-else-if="selection === 'single'"
                       align="center"
                       width="30">
        <template v-slot:default="scope">
          <el-radio v-model="selectionRadio"
                    :label="scope.$index"
                    :disabled="selectable ? !selectable(scope.row) : false"
                    @change="selectionChangeSingle(scope.row)">
            {
   
   { '' }}
          </el-radio>
        </template>
      </el-table-column>
      <!-- 展开行 -->
      <el-table-column v-if="expand"
                       type="expand">
        <template v-slot:default="scope">
          <slot name="expand"
                :row="scope.row"
                :index="scope.$index"></slot>
        </template>
      </el-table-column>
      <el-table-column v-for="(item, index) in tableColumn"
                       :key="index"
                       :prop="item.prop"
                       :label="item.label">
        <template v-if="item.slot"
                  v-slot:default="scope">
          <slot :name="item.prop"
                :row="scope.row"
                :index="scope.$index"></slot>
        </template>
        <template v-else v-slot:default="scope">
          <div v-if="item.dictCode">
            {
   
   { scope.row[item.prop] | filterStatus(dict[item.dictCode]) }}
          </div>
          <div v-else-if="item.format">
            {
   
   { item.format(scope.row) }}
          </div>
          <div v-else>
            {
   
   { scope.row[item.prop] }}
          </div>
        </template>
      </el-table-column>
      <!-- 操作列 -->
      <el-table-column v-if="setupConfig.width !== 0"
                       :fixed="setupConfig.fixed"
                       :width="setupConfig.width"
                       label="操作">
        <template v-slot:default="scope">
          <slot name="setup"
                :row="scope.row"
                :index="scope.$index"></slot>
          <!-- 查看 -->
          <el-button v-if="setupConfig.view"
                     type="text"
                     @click="setupEvents('view', scope.row)">查看</el-button>
          <!-- 编辑 -->
          <el-button v-if="setupConfig.edit"
                     type="text"
                     @click="setupEvents('edit', scope.row)">编辑</el-button>
          <!-- 删除 -->
          <el-button v-if="setupConfig.del"
                     type="text"
                     @click="setupEvents('del', scope.row)">删除</el-button>
          <!-- 操作日志 -->
          <el-button v-if="setupConfig.log"
                     type="text"
                     @click="setupEvents('log', scope.row)">操作日志</el-button>
        </template>
      </el-table-column>
      <!-- 空状态 -->
      <template slot="empty">
        <p>{
   
   { emptyText }}</p>
      </template>
    </el-table>
    <!-- 分页 -->
    <el-pagination v-if="pagination"
                   class="pagination tablePage"
                   :pager-count="5"
                   :page-sizes="pageSizes || [10, 20, 50, 100]"
                   :total="tableTotal || 0"
                   :page-size="pageInfo.pageSize || 10"
                   :current-page="pageInfo.startPage || 1"
                   layout="total, sizes, prev, pager, next, jumper"
                   @size-change="sizeChange"
                   @current-change="pageChange"></el-pagination>
  </div>
</template>

<!-- yxt-table.vue -->
<script>
import yxtSearch from './yxt-search'
export default {
    name: 'yxtTable',
    components: {
        yxtSearch
    },
    props: {
        apiUrl: { // 列表接口(必填)
            type: Function,
            required: true
        },
        tableColumn: { // 自定义列配置
            type: Array,
            default: () => []
        },
        otherConfig: { // 其他配置
            type: Object,
            default: () => {
                return {
                    list: 'list' // 接口返回数据的列表字段的字段名(因为在组件里面调接口,可能不同业务不同项目组不同一个开发者返回给前端的参数名不一致,这里进行兼容)
                }
            }
        },
        dict: { // 全部字典
            type: [Array, Object],
            default: () => []
        },
        selection: { // 是否显示可选框(多选-multiple 、单选-single )
            type: String
        },
        selectable: { // 当前行是否可选择
            type: Function
        },
        rowKey: { // 表格唯一key(适用于分页多选表格,保留之前的选择,不传则为单页选择)
            type: [Number, String, Function],
            default: ''
        },
        setupConfig: {
            type: Object,
            default: () => {
                return {
                    width: 'auto'
                }
            }
        },
        pagination: { // 是否需要分页,默认需要
            type: Boolean,
            default: true
        },
        pageSizes: { // 分页的下拉框选项
            type: Array
        },
        expand: { // 是否展开行
            type: Boolean,
            default: false
        },
        searchConfig: { // 搜索条件配置项
            type: Array,
            default () {
                return []
            }
        },
        searchReset: { // 搜索条件默认值重置值
            type: Object
        },
        emptyText: {
            type: String
        }
    },
    filters: {
        filterStatus (value, array, code = 'code', name = 'name') {
            if (!value && value !== 0) { // 要把0摘出来,一般0都是正常的数据,所以不能只用  !value
                return ''
            }
            const find = array.find(e => (e[code] === value.toString()) || (e[code] === +value)) // 字符型数值型都得匹配
            if (find) {
                return find[name]
            } else { // 没有匹配的就原样返回
                return value
            }
        }
    },
    data () {
        return {
            loading: true,
            tableData: [],
            tableTotal: 0,
            pageInfo: {
                pageSize: 10,
                startPage: 1
            },
            selectionRadio: '',
            selectionData: [],
            searchModel: this.searchReset ? JSON.parse(JSON.stringify(this.searchReset)) : {}
        }
    },
    created () {
        this.getData()
    },
    methods: {
        getData (startPage) {
            if (startPage) { // 如果传入值,则从改值的页码数开始
                this.pageInfo.startPage = startPage
            }
            this.loading = true
            const fun = this.apiUrl
            const pageInfo = { // 分页信息
                pageSize: this.pageInfo.pageSize,
                startPage: this.pageInfo.startPage
            }
            let param = { // 其他的搜素条件
                ...this.searchModel
            }
            if (this.pagination) { // 如果需要分页,则传分页信息
                param = { ...param, ...pageInfo }
            }
            fun(param).then(res => {
                setTimeout(() => {
                    this.tableData = res[this.otherConfig.list] || []
                    this.tableTotal = res.pageInfo?.total || 0
                    this.loading = false
                }, 2000)
            })
        },

        // 多选,选择行数据change
        selectionChange (selection) {
            this.selectionData = selection
        },

        // 单选,选择行数据change
        selectionChangeSingle (selection) {
            this.selectionData = [selection]
        },

        // 操作列方法 查看/编辑/删除/操作日志
        setupEvents (setupType, row) {
            this.$emit(setupType, row)
        },

        // 条数变化
        sizeChange (size) {
            this.pageInfo.startPage = 1
            this.pageInfo.pageSize = size
            this.getData()
        },

        // 页码变化
        pageChange (page) {
            this.pageInfo.startPage = page
            this.getData()
        }
    }
}
</script>
<style scoped lang="scss">
.yxt-table {
  margin: 30px;
  .multiple-operation {
    margin-bottom: 10px;
  }
}
</style>
yxt-search.vue
<!-- yxt-search.vue -->
<template>
  <div class="yxt-search">
    <div v-for="(item,index) in searchConfig"
         :key="index"
         class="yxt-search-item">
      <el-input v-if="item.type==='input'"
                v-model="searchModel[item.key]"
                size="medium"
                :clearable="item.clearable || true"
                :placeholder="item.placeholder || '请输入'"
                :maxlength="item.maxlength"></el-input>
      <el-select v-if="item.type==='select'"
                 v-model="searchModel[item.key]"
                 size="medium"
                 style="width: 100%"
                 :clearable="item.clearable || true"
                 :filterable="item.filterable || true"
                 :disabled="item.disabled || false"
                 :multiple="item.multiple || false"
                 :allow-create="item.allowCreate"
                 :placeholder="item.placeholder || '请选择'">
        <el-option v-for="(selectItem, selectIndex) in item.selectList"
                   :key="selectIndex"
                   :label="selectItem[item.listLabel]"
                   :value="selectItem[item.listValue]"></el-option>
      </el-select>
    </div>
    <div v-if="searchConfig.length" class="yxt-search-button">
      <el-button size="medium" type="primary" @click="search">搜索</el-button>
      <el-button size="medium" type="primary" plain @click="reset">重置</el-button>
      <!-- 其他的按钮需求通过插槽传入 -->
      <slot name="searchBtn" :searchData="searchModel"></slot>
    </div>
  </div>
</template>

<!-- yxt-search.vue -->
<script>
export default {
    name: 'yxtSearch',
    props: {
        searchConfig: { // 搜索条件配置项
            type: Array,
            required: true,
            default () {
                return []
            }
        },
        searchModel: { // 搜索条件绑定值
            type: Object,
            required: true,
            default () {
                return {}
            }
        },
        searchReset: { // 搜索条件默认值重置值
            type: Object
        }
    },
    data () {
        return {
        }
    },
    methods: {
        search () {
            this.$emit('search', this.searchModel)
        },
        reset () {
            if (this.searchReset) { // 如果传入有默认值,则重置后为默认值
                Object.keys(this.searchModel).forEach((item) => {
                    this.searchModel[item] = this.searchReset[item]
                })
            } else {
                Object.keys(this.searchModel).forEach((item) => {
                    this.searchModel[item] = ''
                })
            }
        }
    }
}
</script>
<style scoped lang="scss">
.yxt-search {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: flex-start;
  .yxt-search-item {
    flex: 1;
    margin: 0 10px 10px 0;
    width: calc((100% - 30px) / 4);  // 这里的30px = (分布个数4-1)*间隙1px, 可以根据实际的分布个数和间隙区调整
    min-width: calc((100% - 30px) / 4);
    max-width: calc((100% - 30px) / 4);
    &:nth-child(4n) { // 去除每行最后一个(第4n个)的margin-right
      margin-right: 0;
    }
  }
  .yxt-search-button {
    margin: 0 0 10px 0;
    width: 100%;
    text-align: right;
  }
}
</style>

yxtTable.json

{
  "retCode": "0",
  "retMsg": "success",
  "pageInfo": {
    "total": 300
  },
  "tasks": [
    { "name": "张三",
      "code": "zhangSan",
      "status": 1,
      "icon": true,
      "phone":  "17801010101",
      "selectable": 1
    },
    { "name": "李四",
      "code": "liSi",
      "status": 0,
      "icon": false,
      "phone": "17802020202",
      "selectable": 2
    },
    { "name": "王五",
      "code": "wangWu",
      "status": 2,
      "icon": true,
      "phone": "17803030303",
      "selectable": 0
    },
    { "name": "马六",
      "code": "maLiu",
      "status": 1,
      "icon": false,
      "phone": "17804040404",
      "selectable": 2
    }
  ]
}

efecto final

Supongo que te gusta

Origin blog.csdn.net/yerongtao/article/details/126998230
Recomendado
Clasificación