Skip to content

Vue Render自定义table单元格内容

仲灏2021-12-26约 1 分钟

解决问题

只举一个例子(我正好需要用到的)

在写中后台时, 如果对 表格组件 再度封装了, 比如这样的

以element-ui 为例:

plain
  <template>
    <el-table
      :data="tableData"
      style="width: 100%">
      <el-table-column
        prop="date"
        label="日期"
        width="180">
      </el-table-column>
      <el-table-column
        prop="name"
        label="姓名"
        width="180">
      </el-table-column>
      <el-table-column
        prop="address"
        label="地址">
      </el-table-column>
    </el-table>
  </template>

对html数据化, 封装成一个组件.

vue
// complex-table.vue
<template>
  <el-table
      :data="list"
      style="width: 100%;"
    >
      <el-table-column v-for="col in columns" :key="col.prop" v-bind="col"></el-table-column>
  </el-table>
</template>
<script>
	export default {
  	props: {
    	columns: Array
    }
  }
</script>

--------------------------
// index.vue
<template>
  <div class="box_container">
    <complex-table     
      :columns="tableColumns"
    />    
  </div>
</template>
<script>
  export default {
    data() {
    	return {
      	tableColumns: [
          { label: '日期', prop: 'sn', align: 'center' },
          { label: '姓名', prop: 'phone', align: 'center' },
          { label: '地址', prop: 'address', align: 'center' }
        ]
      }
    }
  }
</script>

那么问题来了, 如果我要在table中加入头像列, 但是后端返回的是url地址, 你又怎么办呢,你可以这样

vue
// complex-table.vue
...
  <el-table>
    <el-table-column v-for="col in columns" :key="col.prop" v-bind="col">
      <template slot-scope="scope">
        <template v-if="col.prop==='avatar'">
      		<el-avatar :src="row.avatar" />
      	</template>
				<template v-else>
          <span>{{ scope.row[col.prop] }}</span>
				</template>
			</template>
    </el-table-column>
  </el-table>
...

// index.vue
...
data() {
  return {
    tableColumns: [
			{ label: '头像', prop: 'avatar', align: 'center' },
      { label: '日期', prop: 'date', align: 'center' },
      { label: '姓名', prop: 'name', align: 'center' },
      { label: '地址', prop: 'address', align: 'center' }
    ]
  }
}
...

万一又是 **<el-tag>** 标签, **el-button** 不能一直v-if吧

或许也可以使用 <component :is="**" v-bind="**" >这种形式, 如果遇到方法传值又感觉差点什么,差scope传值了

解决方案

使用函数式组件, 可灵活使用, 原理跟 valueFormat 类似, 不废话直接上代码

新建文件 columnRender.js

vue
/*
 * @Description: 函数式组件渲染单元表格
 * @Author: 仲灏<izhaong 164165005@qq.com>
 * @Date: 2020-09-16 15:33:25
 * @LastEditors: 仲灏<izhaong 164165005@qq.com>
 * @LastEditTime: 2020-09-16 15:47:23
 */
export default {
  functional: true,
  props: {
    row: Object,
    render: Function
  },
  render(h, ctx) {
    const params = {
      row: ctx.props.row
    }

    return ctx.props.render(h, params)
  }
}
// complexTable
...
<template v-else-if="'render' in col">
  <!-- <component :is="col.tag" v-bind="col.attrs">{{ col.tagValue }}</component> -->
  <Render :row="row" :render="col.render" />
</template>
...


// index.js
tableColumns: [
    { label: 'name', prop: 'name', align: 'center' },
    { label: '是否有效', prop: 'isValid', align: 'center', render: (h, { row }) => {
      return h('el-tag', { attrs: { type: row.isValid ? 'success' : 'info' }}, row.isValid ? '有效' : '无效')
    } }
]

结语

第一次bb这个多话, 之前直接就啪~ 贴代码了.

当然如果封装的组件足够强大, 能够兼顾涵盖该项目大部分业务, 就可以把组件当做页面使用, 全部使用纯数据驱动, 当然你的业务相似度要高

最终代码

complexTable.vue

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-20 10:22:20
-->
<template>
  <div class="complex-table_container app-container">
    <div class="filter-container d-flex align-items-center">
      <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>

      <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="primary"

          icon="el-icon-edit"
          @click="$emit('createItem')"
        >添加</el-button>
      </slot>
    </div>
    <el-table
      :key="tableKey"
      v-loading="listLoading"
      :height="tableHeight"
      :data="list"
      size="small"
      border
      style="width: 100%;"
    >
      <el-table-column
        type="index"
        width="50"
      />
      <el-table-column v-for="col in columns" :key="col.prop" v-bind="col">
        <template v-if="'index' in col" 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="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>