index.vue 6.68 KB
<template>
  <div class="table-viewer-container">
    <div class="header-container">
      <TableFilter
        :filters="filters"
        :loading="tableLoading"
        :style="{ maxWidth: filterMaxWidth }"
        :total="total"
        @filter="handleFilter(...arguments)"
      />

      <TableAction
        :actions="actions"
        :style="{ display: disableActionInline ? 'block' : undefined, marginBottom: disableActionInline ? '18px' : undefined }"
        @action="$emit('action', arguments[0])"
      />
    </div>

    <div class="table-container">
      <ElTable
        v-loading="tableLoading"
        v-bind="tableProps"
        :data="tableData"
        :row-key="rowKey"
        stripe
        class="table"
      >
        <template
          v-for="header in headers"
        >
          <ElTableColumn
            v-if="header.component"
            :key="header.key"
            :prop="header.key"
            :label="header.label"
            :width="header.width"
            :min-width="header.minWidth"
            :formatter="header.formatter || defaultFormatter"
          >
            <template slot-scope="scope">
              <Component
                :is="header.component"
                :value="scope.row[header.key]"
              />
            </template>
          </ElTableColumn>
          <ElTableColumn
            v-else
            :key="header.key"
            :prop="header.key"
            :label="header.label"
            :width="header.width"
            :min-width="header.minWidth"
            :formatter="header.formatter || defaultFormatter"
            show-overflow-tooltip
          />
        </template>
        <ElTableColumn
          v-if="rowActions"
          label="操作"
          :width="rowActionsWidth"
          :min-width="rowActionsMinWidth"
        >
          <template slot-scope="scope">
            <TableAction
              :actions="rowActions instanceof Array ? rowActions : rowActions(scope.row)"
              @action="$emit('action', arguments[0], scope.row)"
            />
          </template>
        </ElTableColumn>
      </ElTable>

      <ElPagination
        style="margin-top: 10px"
        background
        layout="total, sizes, prev, pager, next, jumper"
        :current-page.sync="pagination.page"
        :page-size="pagination.size"
        :page-sizes="pageSizes"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script>
import TableFilter from './components/TableFilter'
import TableAction from './components/TableAction'

export default {
  name: 'TableViewer',
  components: {
    TableFilter,
    TableAction
  },
  props: {
    filters: {
      type: Array,
      default: null
    },
    actions: {
      type: Array,
      default: null
    },
    headers: {
      type: Array,
      required: true
    },
    rowKey: {
      type: String,
      default: 'id'
    },
    rowActions: {
      type: [Array, Function],
      default: null
    },
    rowActionsWidth: {
      type: [Number, String],
      default: null
    },
    rowActionsMinWidth: {
      type: [Number, String],
      default: null
    },
    filterMaxWidth: {
      type: String,
      default: undefined
    },
    disableActionDivider: {
      type: Boolean
    },
    disableActionInline: {
      type: Boolean
    },
    fetchDataMethod: {
      type: Function,
      required: true
    },
    pageSizes: {
      type: Array,
      default: () => [
        10,
        15,
        50,
        100
      ]
    },
    defaultFormatter: {
      type: Function,
      default: (row, column, cellValue, index) => {
        return cellValue ?? '--'
      }
    },
    tableProps: {
      type: Object,
      default: () => {
        return {
          cellStyle: {
            color: '#000'
          },
          headerCellStyle: {
            'background-color': '#C2DCF5',
            color: '#000'
          }
        }
      }
    }
  },
  data() {
    return {
      filter: {},
      pagination: {
        page: 1,
        size: 10
      },
      tableData: [],
      total: 0,
      numberOfElements: 0,
      tableLoading: false
    }
  },
  watch: {
    headers: {
      deep: true,
      immediate: true,
      handler(val) {
        console.log('header', val)
      }
    }
  },
  created() {
    this.fetchData()
  },
  methods: {
    async fetchData() {
      this.loading()
      const pagination = {
        pageNo: this.pagination.page,
        pageSize: this.pagination.size
      }
      try {
        const {
          content,
          totalElements,
          numberOfElements
        } = await this.fetchDataMethod(this.filter, pagination)
        this.tableData = content
        this.total = totalElements
        this.numberOfElements = numberOfElements
      } finally {
        this.cancelLoading()
      }
    },
    async refresh() {
      return this.fetchData()
    },
    async refreshAfterDelete() {
      if (this.pagination.page !== 1 && this.numberOfElements === 1) {
        this.pagination.page = 1
      }
      return this.fetchData()
    },
    async handleFilter(filter, filterWithChange = false) {
      this.filter = filter
      if (!filterWithChange) {
        return
      }
      this.pagination = {
        size: this.pagination.size,
        page: 1
      }
      return this.fetchData()
    },
    async handleSizeChange(size) {
      this.pagination.size = size
      return this.fetchData()
    },
    async handleCurrentChange(page) {
      this.pagination.page = page
      return this.fetchData()
    },
    loading() {
      this.tableLoading = true
    },
    cancelLoading() {
      this.tableLoading = false
    }
  }
}
</script>

<style
  scoped
  lang="scss"
>
.table-viewer-container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.el-divider {
  height: 20px;
  margin: 4px 10px;
}

.header-container {
  padding: 18px 20px 0 20px;
  display: flex;
  justify-content: space-between;
}

.table-container {
  height: 80%;
  flex-grow: 1;
  padding: 20px;
  background-color: #F6F7FB;

  .table {
    border-radius: 6px;
  }
}
::v-deep {
  .text-line-2 {
    .cell {
      overflow: hidden;
      height: 3em \9;
      line-height: 1.5em \9;
      text-overflow: ellipsis;
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      text-align: left;
    }
    // ie Edge 标题摘要两行显示
    @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
      .cell {
        height: 3em;
        line-height: 1.5em;
      }
    }
    // firefox 标题摘要两行显示
    @-moz-document url-prefix() {
      .cell {
        height: 3em;
        line-height: 1.5em;
      }
    }
  }
}
</style>